/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes

! This file was ported from Lean 3 source module data.int.modeq
! leanprover-community/mathlib commit c0dbeca1a7a7f4959cdf6b2817629bafbf1547a0
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Nat.Modeq
import Mathbin.Tactic.Ring

/-!

# Congruences modulo an integer

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

This file defines the equivalence relation `a ≡ b [ZMOD n]` on the integers, similarly to how
`data.nat.modeq` defines them for the natural numbers. The notation is short for `n.modeq a b`,
which is defined to be `a % n = b % n` for integers `a b n`.

## Tags

modeq, congruence, mod, MOD, modulo, integers

-/

namespace Int

#print Int.ModEq /-
/-- `a ≡ b [ZMOD n]` when `a % n = b % n`. -/
def ModEq (n a b : ℤ) :=
  a % n = b % n deriving Decidable
#align int.modeq Int.ModEq
-/

-- mathport name: «expr ≡ [ZMOD ]»
notation:50 a " ≡ " b " [ZMOD " n "]" => ModEq n a b

variable {m n a b c d : ℤ}

namespace Modeq

#print Int.ModEq.refl /-
@[refl]
protected theorem refl (a : ℤ) : a ≡ a [ZMOD n] :=
  @rfl _ _
#align int.modeq.refl Int.ModEq.refl
-/

#print Int.ModEq.rfl /-
protected theorem rfl : a ≡ a [ZMOD n] :=
  ModEq.refl _
#align int.modeq.rfl Int.ModEq.rfl
-/

instance : IsRefl _ (ModEq n) :=
  ⟨ModEq.refl⟩

#print Int.ModEq.symm /-
@[symm]
protected theorem symm : a ≡ b [ZMOD n] → b ≡ a [ZMOD n] :=
  Eq.symm
#align int.modeq.symm Int.ModEq.symm
-/

#print Int.ModEq.trans /-
@[trans]
protected theorem trans : a ≡ b [ZMOD n] → b ≡ c [ZMOD n] → a ≡ c [ZMOD n] :=
  Eq.trans
#align int.modeq.trans Int.ModEq.trans
-/

end Modeq

#print Int.coe_nat_modEq_iff /-
theorem coe_nat_modEq_iff {a b n : ℕ} : a ≡ b [ZMOD n] ↔ a ≡ b [MOD n] := by
  unfold modeq Nat.ModEq <;> rw [← Int.ofNat_inj] <;> simp [coe_nat_mod]
#align int.coe_nat_modeq_iff Int.coe_nat_modEq_iff
-/

/- warning: int.modeq_zero_iff_dvd -> Int.modEq_zero_iff_dvd is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int}, Iff (Int.ModEq n a (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero)))) (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n a)
but is expected to have type
  forall {n : Int} {a : Int}, Iff (Int.ModEq n a (OfNat.ofNat.{0} Int 0 (instOfNatInt 0))) (Dvd.dvd.{0} Int Int.instDvdInt n a)
Case conversion may be inaccurate. Consider using '#align int.modeq_zero_iff_dvd Int.modEq_zero_iff_dvdₓ'. -/
theorem modEq_zero_iff_dvd : a ≡ 0 [ZMOD n] ↔ n ∣ a := by rw [modeq, zero_mod, dvd_iff_mod_eq_zero]
#align int.modeq_zero_iff_dvd Int.modEq_zero_iff_dvd

/- warning: has_dvd.dvd.modeq_zero_int -> Dvd.dvd.modEq_zero_int is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n a) -> (Int.ModEq n a (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))))
but is expected to have type
  forall {n : Int} {a : Int}, (Dvd.dvd.{0} Int Int.instDvdInt n a) -> (Int.ModEq n a (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)))
Case conversion may be inaccurate. Consider using '#align has_dvd.dvd.modeq_zero_int Dvd.dvd.modEq_zero_intₓ'. -/
theorem Dvd.dvd.modEq_zero_int (h : n ∣ a) : a ≡ 0 [ZMOD n] :=
  modEq_zero_iff_dvd.2 h
#align has_dvd.dvd.modeq_zero_int Dvd.dvd.modEq_zero_int

/- warning: has_dvd.dvd.zero_modeq_int -> Dvd.dvd.zero_modEq_int is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n a) -> (Int.ModEq n (OfNat.ofNat.{0} Int 0 (OfNat.mk.{0} Int 0 (Zero.zero.{0} Int Int.hasZero))) a)
but is expected to have type
  forall {n : Int} {a : Int}, (Dvd.dvd.{0} Int Int.instDvdInt n a) -> (Int.ModEq n (OfNat.ofNat.{0} Int 0 (instOfNatInt 0)) a)
Case conversion may be inaccurate. Consider using '#align has_dvd.dvd.zero_modeq_int Dvd.dvd.zero_modEq_intₓ'. -/
theorem Dvd.dvd.zero_modEq_int (h : n ∣ a) : 0 ≡ a [ZMOD n] :=
  h.modEq_zero_int.symm
#align has_dvd.dvd.zero_modeq_int Dvd.dvd.zero_modEq_int

/- warning: int.modeq_iff_dvd -> Int.modEq_iff_dvd is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int} {b : Int}, Iff (Int.ModEq n a b) (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.hasSub) b a))
but is expected to have type
  forall {n : Int} {a : Int} {b : Int}, Iff (Int.ModEq n a b) (Dvd.dvd.{0} Int Int.instDvdInt n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.instSubInt) b a))
Case conversion may be inaccurate. Consider using '#align int.modeq_iff_dvd Int.modEq_iff_dvdₓ'. -/
theorem modEq_iff_dvd : a ≡ b [ZMOD n] ↔ n ∣ b - a := by
  rw [modeq, eq_comm] <;> simp [mod_eq_mod_iff_mod_sub_eq_zero, dvd_iff_mod_eq_zero]
#align int.modeq_iff_dvd Int.modEq_iff_dvd

#print Int.modEq_iff_add_fac /-
theorem modEq_iff_add_fac {a b n : ℤ} : a ≡ b [ZMOD n] ↔ ∃ t, b = a + n * t :=
  by
  rw [modeq_iff_dvd]
  exact exists_congr fun t => sub_eq_iff_eq_add'
#align int.modeq_iff_add_fac Int.modEq_iff_add_fac
-/

/- warning: int.modeq.dvd -> Int.ModEq.dvd is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int} {b : Int}, (Int.ModEq n a b) -> (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.hasSub) b a))
but is expected to have type
  forall {n : Int} {a : Int} {b : Int}, (Int.ModEq n a b) -> (Dvd.dvd.{0} Int Int.instDvdInt n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.instSubInt) b a))
Case conversion may be inaccurate. Consider using '#align int.modeq.dvd Int.ModEq.dvdₓ'. -/
/- warning: int.modeq_of_dvd -> Int.modEq_of_dvd is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int} {b : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.hasSub) b a)) -> (Int.ModEq n a b)
but is expected to have type
  forall {n : Int} {a : Int} {b : Int}, (Dvd.dvd.{0} Int Int.instDvdInt n (HSub.hSub.{0, 0, 0} Int Int Int (instHSub.{0} Int Int.instSubInt) b a)) -> (Int.ModEq n a b)
Case conversion may be inaccurate. Consider using '#align int.modeq_of_dvd Int.modEq_of_dvdₓ'. -/
alias modeq_iff_dvd ↔ modeq.dvd modeq_of_dvd
#align int.modeq.dvd Int.ModEq.dvd
#align int.modeq_of_dvd Int.modEq_of_dvd

#print Int.mod_modEq /-
theorem mod_modEq (a n) : a % n ≡ a [ZMOD n] :=
  emod_emod _ _
#align int.mod_modeq Int.mod_modEq
-/

namespace Modeq

/- warning: int.modeq.of_dvd -> Int.ModEq.of_dvd is a dubious translation:
lean 3 declaration is
  forall {m : Int} {n : Int} {a : Int} {b : Int}, (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) m n) -> (Int.ModEq n a b) -> (Int.ModEq m a b)
but is expected to have type
  forall {m : Int} {n : Int} {a : Int} {b : Int}, (Dvd.dvd.{0} Int Int.instDvdInt m n) -> (Int.ModEq n a b) -> (Int.ModEq m a b)
Case conversion may be inaccurate. Consider using '#align int.modeq.of_dvd Int.ModEq.of_dvdₓ'. -/
protected theorem of_dvd (d : m ∣ n) (h : a ≡ b [ZMOD n]) : a ≡ b [ZMOD m] :=
  modEq_of_dvd <| d.trans h.Dvd
#align int.modeq.of_dvd Int.ModEq.of_dvd

#print Int.ModEq.mul_left' /-
protected theorem mul_left' (hc : 0 ≤ c) (h : a ≡ b [ZMOD n]) : c * a ≡ c * b [ZMOD c * n] :=
  Or.cases_on hc.lt_or_eq
    (fun hc => by unfold modeq <;> simp [mul_mod_mul_of_pos hc, show _ = _ from h]) fun hc => by
    simp [hc.symm]
#align int.modeq.mul_left' Int.ModEq.mul_left'
-/

#print Int.ModEq.mul_right' /-
protected theorem mul_right' (hc : 0 ≤ c) (h : a ≡ b [ZMOD n]) : a * c ≡ b * c [ZMOD n * c] := by
  rw [mul_comm a, mul_comm b, mul_comm n] <;> exact h.mul_left' hc
#align int.modeq.mul_right' Int.ModEq.mul_right'
-/

#print Int.ModEq.add /-
protected theorem add (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a + c ≡ b + d [ZMOD n] :=
  modEq_iff_dvd.2 <| by
    convert dvd_add h₁.dvd h₂.dvd
    ring
#align int.modeq.add Int.ModEq.add
-/

#print Int.ModEq.add_left /-
protected theorem add_left (c : ℤ) (h : a ≡ b [ZMOD n]) : c + a ≡ c + b [ZMOD n] :=
  ModEq.rfl.add h
#align int.modeq.add_left Int.ModEq.add_left
-/

#print Int.ModEq.add_right /-
protected theorem add_right (c : ℤ) (h : a ≡ b [ZMOD n]) : a + c ≡ b + c [ZMOD n] :=
  h.add ModEq.rfl
#align int.modeq.add_right Int.ModEq.add_right
-/

#print Int.ModEq.add_left_cancel /-
protected theorem add_left_cancel (h₁ : a ≡ b [ZMOD n]) (h₂ : a + c ≡ b + d [ZMOD n]) :
    c ≡ d [ZMOD n] :=
  have : d - c = b + d - (a + c) - (b - a) := by ring
  modEq_iff_dvd.2 <| by
    rw [this]
    exact dvd_sub h₂.dvd h₁.dvd
#align int.modeq.add_left_cancel Int.ModEq.add_left_cancel
-/

#print Int.ModEq.add_left_cancel' /-
protected theorem add_left_cancel' (c : ℤ) (h : c + a ≡ c + b [ZMOD n]) : a ≡ b [ZMOD n] :=
  ModEq.rfl.add_left_cancel h
#align int.modeq.add_left_cancel' Int.ModEq.add_left_cancel'
-/

#print Int.ModEq.add_right_cancel /-
protected theorem add_right_cancel (h₁ : c ≡ d [ZMOD n]) (h₂ : a + c ≡ b + d [ZMOD n]) :
    a ≡ b [ZMOD n] := by
  rw [add_comm a, add_comm b] at h₂
  exact h₁.add_left_cancel h₂
#align int.modeq.add_right_cancel Int.ModEq.add_right_cancel
-/

#print Int.ModEq.add_right_cancel' /-
protected theorem add_right_cancel' (c : ℤ) (h : a + c ≡ b + c [ZMOD n]) : a ≡ b [ZMOD n] :=
  ModEq.rfl.add_right_cancel h
#align int.modeq.add_right_cancel' Int.ModEq.add_right_cancel'
-/

#print Int.ModEq.neg /-
protected theorem neg (h : a ≡ b [ZMOD n]) : -a ≡ -b [ZMOD n] :=
  h.add_left_cancel (by simp_rw [← sub_eq_add_neg, sub_self])
#align int.modeq.neg Int.ModEq.neg
-/

#print Int.ModEq.sub /-
protected theorem sub (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a - c ≡ b - d [ZMOD n] :=
  by
  rw [sub_eq_add_neg, sub_eq_add_neg]
  exact h₁.add h₂.neg
#align int.modeq.sub Int.ModEq.sub
-/

#print Int.ModEq.sub_left /-
protected theorem sub_left (c : ℤ) (h : a ≡ b [ZMOD n]) : c - a ≡ c - b [ZMOD n] :=
  ModEq.rfl.sub h
#align int.modeq.sub_left Int.ModEq.sub_left
-/

#print Int.ModEq.sub_right /-
protected theorem sub_right (c : ℤ) (h : a ≡ b [ZMOD n]) : a - c ≡ b - c [ZMOD n] :=
  h.sub ModEq.rfl
#align int.modeq.sub_right Int.ModEq.sub_right
-/

#print Int.ModEq.mul_left /-
protected theorem mul_left (c : ℤ) (h : a ≡ b [ZMOD n]) : c * a ≡ c * b [ZMOD n] :=
  Or.cases_on (le_total 0 c) (fun hc => (h.mul_left' hc).of_dvd (dvd_mul_left _ _)) fun hc => by
    rw [← neg_neg c, neg_mul, neg_mul _ b] <;>
      exact ((h.mul_left' <| neg_nonneg.2 hc).of_dvd (dvd_mul_left _ _)).neg
#align int.modeq.mul_left Int.ModEq.mul_left
-/

#print Int.ModEq.mul_right /-
protected theorem mul_right (c : ℤ) (h : a ≡ b [ZMOD n]) : a * c ≡ b * c [ZMOD n] :=
  by
  rw [mul_comm a, mul_comm b]
  exact h.mul_left c
#align int.modeq.mul_right Int.ModEq.mul_right
-/

#print Int.ModEq.mul /-
protected theorem mul (h₁ : a ≡ b [ZMOD n]) (h₂ : c ≡ d [ZMOD n]) : a * c ≡ b * d [ZMOD n] :=
  (h₂.mul_left _).trans (h₁.mul_right _)
#align int.modeq.mul Int.ModEq.mul
-/

/- warning: int.modeq.pow -> Int.ModEq.pow is a dubious translation:
lean 3 declaration is
  forall {n : Int} {a : Int} {b : Int} (m : Nat), (Int.ModEq n a b) -> (Int.ModEq n (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) a m) (HPow.hPow.{0, 0, 0} Int Nat Int (instHPow.{0, 0} Int Nat (Monoid.Pow.{0} Int Int.monoid)) b m))
but is expected to have type
  forall {n : Int} {a : Int} {b : Int} (m : Nat), (Int.ModEq n a b) -> (Int.ModEq n (HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat a m) (HPow.hPow.{0, 0, 0} Int Nat Int Int.instHPowIntNat b m))
Case conversion may be inaccurate. Consider using '#align int.modeq.pow Int.ModEq.powₓ'. -/
protected theorem pow (m : ℕ) (h : a ≡ b [ZMOD n]) : a ^ m ≡ b ^ m [ZMOD n] :=
  by
  induction' m with d hd; · rfl
  rw [pow_succ, pow_succ]
  exact h.mul hd
#align int.modeq.pow Int.ModEq.pow

#print Int.ModEq.of_mul_left /-
theorem of_mul_left (m : ℤ) (h : a ≡ b [ZMOD m * n]) : a ≡ b [ZMOD n] := by
  rw [modeq_iff_dvd] at * <;> exact (dvd_mul_left n m).trans h
#align int.modeq.of_mul_left Int.ModEq.of_mul_left
-/

#print Int.ModEq.of_mul_right /-
theorem of_mul_right (m : ℤ) : a ≡ b [ZMOD n * m] → a ≡ b [ZMOD n] :=
  mul_comm m n ▸ of_mul_left _
#align int.modeq.of_mul_right Int.ModEq.of_mul_right
-/

end Modeq

#print Int.modEq_one /-
theorem modEq_one : a ≡ b [ZMOD 1] :=
  modEq_of_dvd (one_dvd _)
#align int.modeq_one Int.modEq_one
-/

#print Int.modEq_sub /-
theorem modEq_sub (a b : ℤ) : a ≡ b [ZMOD a - b] :=
  (modEq_of_dvd dvd_rfl).symm
#align int.modeq_sub Int.modEq_sub
-/

#print Int.modEq_and_modEq_iff_modEq_mul /-
theorem modEq_and_modEq_iff_modEq_mul {a b m n : ℤ} (hmn : m.natAbs.coprime n.natAbs) :
    a ≡ b [ZMOD m] ∧ a ≡ b [ZMOD n] ↔ a ≡ b [ZMOD m * n] :=
  ⟨fun h => by
    rw [modeq_iff_dvd, modeq_iff_dvd] at h
    rw [modeq_iff_dvd, ← nat_abs_dvd, ← dvd_nat_abs, coe_nat_dvd, nat_abs_mul]
    refine' hmn.mul_dvd_of_dvd_of_dvd _ _ <;> rw [← coe_nat_dvd, nat_abs_dvd, dvd_nat_abs] <;>
      tauto,
    fun h => ⟨h.of_mul_right _, h.of_mul_left _⟩⟩
#align int.modeq_and_modeq_iff_modeq_mul Int.modEq_and_modEq_iff_modEq_mul
-/

#print Int.gcd_a_modEq /-
theorem gcd_a_modEq (a b : ℕ) : (a : ℤ) * Nat.gcdA a b ≡ Nat.gcd a b [ZMOD b] :=
  by
  rw [← add_zero ((a : ℤ) * _), Nat.gcd_eq_gcd_ab]
  exact (dvd_mul_right _ _).zero_modEq_int.add_left _
#align int.gcd_a_modeq Int.gcd_a_modEq
-/

#print Int.modEq_add_fac /-
theorem modEq_add_fac {a b n : ℤ} (c : ℤ) (ha : a ≡ b [ZMOD n]) : a + n * c ≡ b [ZMOD n] :=
  calc
    a + n * c ≡ b + n * c [ZMOD n] := ha.add_right _
    _ ≡ b + 0 [ZMOD n] := ((dvd_mul_right _ _).modEq_zero_int.add_left _)
    _ ≡ b [ZMOD n] := by rw [add_zero]

#align int.modeq_add_fac Int.modEq_add_fac
-/

#print Int.modEq_add_fac_self /-
theorem modEq_add_fac_self {a t n : ℤ} : a + n * t ≡ a [ZMOD n] :=
  modEq_add_fac _ ModEq.rfl
#align int.modeq_add_fac_self Int.modEq_add_fac_self
-/

#print Int.mod_coprime /-
theorem mod_coprime {a b : ℕ} (hab : Nat.coprime a b) : ∃ y : ℤ, a * y ≡ 1 [ZMOD b] :=
  ⟨Nat.gcdA a b,
    have hgcd : Nat.gcd a b = 1 := Nat.coprime.gcd_eq_one hab
    calc
      ↑a * Nat.gcdA a b ≡ ↑a * Nat.gcdA a b + ↑b * Nat.gcdB a b [ZMOD ↑b] :=
        ModEq.symm <| modEq_add_fac _ <| ModEq.refl _
      _ ≡ 1 [ZMOD ↑b] := by rw [← Nat.gcd_eq_gcd_ab, hgcd] <;> rfl
      ⟩
#align int.mod_coprime Int.mod_coprime
-/

#print Int.exists_unique_equiv /-
theorem exists_unique_equiv (a : ℤ) {b : ℤ} (hb : 0 < b) :
    ∃ z : ℤ, 0 ≤ z ∧ z < b ∧ z ≡ a [ZMOD b] :=
  ⟨a % b, emod_nonneg _ (ne_of_gt hb),
    by
    have : a % b < |b| := emod_lt _ (ne_of_gt hb)
    rwa [abs_of_pos hb] at this, by simp [modeq]⟩
#align int.exists_unique_equiv Int.exists_unique_equiv
-/

#print Int.exists_unique_equiv_nat /-
theorem exists_unique_equiv_nat (a : ℤ) {b : ℤ} (hb : 0 < b) : ∃ z : ℕ, ↑z < b ∧ ↑z ≡ a [ZMOD b] :=
  let ⟨z, hz1, hz2, hz3⟩ := exists_unique_equiv a hb
  ⟨z.natAbs, by
    constructor <;> rw [← of_nat_eq_coe, of_nat_nat_abs_eq_of_nonneg hz1] <;> assumption⟩
#align int.exists_unique_equiv_nat Int.exists_unique_equiv_nat
-/

#print Int.mod_mul_right_mod /-
@[simp]
theorem mod_mul_right_mod (a b c : ℤ) : a % (b * c) % b = a % b :=
  (mod_modEq _ _).of_mul_right _
#align int.mod_mul_right_mod Int.mod_mul_right_mod
-/

#print Int.mod_mul_left_mod /-
@[simp]
theorem mod_mul_left_mod (a b c : ℤ) : a % (b * c) % c = a % c :=
  (mod_modEq _ _).of_mul_left _
#align int.mod_mul_left_mod Int.mod_mul_left_mod
-/

end Int


import algebra.big_operators.pi
import algebra.module.pi
import algebra.module.linear_map
import algebra.big_operators.ring
import algebra.star.basic
import data.fintype.card
import data.matrix.dmatrix
import linear_algebra.matrix.symmetric
import linear_algebra.matrix
import linear_algebra.matrix.trace
import linear_algebra.matrix.to_lin
import linear_algebra.matrix.nonsingular_inverse
import data.complex.basic
import linear_algebra.eigenspace
import tactic.find
import analysis.normed.group.basic
import analysis.inner_product_space.pi_L2

run_cmd tactic.skip
set_option trace.simplify true
set_option trace.simplify.rewrite_failure false
set_option trace.linarith true

universe u

variables {α n m R : Type*} [fintype n] [fintype m] [decidable_eq n] [has_neg α]

namespace matrix

open_locale matrix

open_locale complex_order complex_conjugate

instance : has_coe (n → ℝ) (n → ℂ) := ⟨λ x, (λ i, ⟨x i, 0⟩)⟩
instance : has_coe (matrix m n ℝ) (matrix m n ℂ) := ⟨λ M, (λ i j, ⟨M i j, 0⟩)⟩

-- The conjugate of a vector is obtained by taking the conjugate of each element of the vector
def vector_conj (v : n → ℂ) : n → ℂ := λ i : n, conj (v i)

-- The matrix conjugate is obtained by taking the conjugate of each element of the matrix
def matrix_conj (M : matrix n n ℂ) : matrix n n ℂ := λ i : n, vector_conj (M i)

-- An eigenpair of M is a pair (e, v) such that v ≠ 0 and M v = e v
def has_eigenpair (M : matrix n n ℂ) (e : ℂ) (v : n → ℂ ) := v ≠ 0 ∧ mul_vec M v = e • v

-- A vector which is part of an eigenpair of M is called an eigenvector of M
def has_eigenvector (M : matrix n n ℂ) (v : n → ℂ ) := ∃ e : ℂ , has_eigenpair M e v

-- A number which is part of an eigenpair of M is called an eigenvalue of M
def has_eigenvalue (M : matrix n n ℂ) (e : ℂ) := ∃ v : n → ℂ , has_eigenpair M e v

-- A left eignpair of M is a pair (e, v) such that v ≠ 0 and vᵀ M = vᵀ e
def has_left_eigenpair (M : matrix n n ℂ) (e : ℂ) (v : n → ℂ ) := v ≠ 0 ∧ vec_mul v M = e • v

-- A vector which is part of an eigenpair of M is called a left eigenvector of M
def has_left_eigenvector (M : matrix n n ℂ) (v : n → ℂ ) := ∃ e : ℂ , has_left_eigenpair M e v

-- A number which is part of an eigenpair of M is called a left eigenvalue of M
def has_left_eigenvalue (M : matrix n n ℂ) (e : ℂ) := ∃ v : n → ℂ , has_left_eigenpair M e v

-- A matrix is caleed skew-symmetric if its transpose is equal to its negative
def is_skew_symmetric (M : matrix n n α ) := Mᵀ = -M

-- ∑ Re(f (v i)) = Re(∑ f (v i))
lemma sum_re {v : n → ℂ} {f : ℂ → ℂ }: finset.univ.sum (λ i, (f (v i)).re) = (finset.univ.sum (λ i, f(v i))).re:=
begin
  rw ← complex.re_lm_coe,
  rw linear_map.map_sum,
end

-- ∑ Im(f (v i)) = Im(∑ f (v i))
lemma sum_im {v : n → ℂ} {f : ℂ → ℂ }: finset.univ.sum (λ i, (f (v i)).im) = (finset.univ.sum (λ i, f(v i))).im:=
begin
  rw ← complex.im_lm_coe,
  rw linear_map.map_sum,
end

-- ∑ (f (v i))' = (∑ f (v i))'
lemma sum_conj_func {v : n → ℂ } {f : ℂ → ℂ }: conj (finset.univ.sum (λ i, f (v i))) = finset.univ.sum (λ i, conj (f (v i))) :=
begin
  ext,
  rw complex.conj_re,
  rw ← sum_re,
  change finset.univ.sum (λ (i : n), (conj (f (v i))).re) = (finset.univ.sum (λ (i : n), conj (f (v i)))).re,
  rw sum_re,
  rw ← sum_im,
  simp only [complex.conj_im, finset.sum_neg_distrib, neg_inj],
  rw sum_im,
end

-- ∑ (v i)' = (∑ (v i))'
lemma sum_conj {v : n → ℂ} : conj (finset.univ.sum (λ i, (v i))) = finset.univ.sum (λ i, conj ((v i))) :=
begin

  have h : conj (finset.univ.sum (λ i, id (v i))) = finset.univ.sum (λ i, conj (id (v i))),
  {exact sum_conj_func},

  dsimp at h,
  exact h,
end

-- (a * b)' = a' * b'
lemma mul_conj (a : ℂ ) (b : ℂ ) : conj (a * b) = (conj a) * (conj b) :=
begin
  ext,
  simp only [complex.mul_re, complex.conj_re, complex.conj_im, mul_neg, neg_mul, neg_neg],
  simp only [complex.mul_im, neg_add_rev, complex.conj_re, complex.conj_im, mul_neg, neg_mul],
  rw add_comm,
end

-- (vᵀv)' = ((v')ᵀv')
lemma dot_product_conj (v : n → ℂ) (w : n → ℂ) : conj (dot_product v w) = dot_product (vector_conj v) (vector_conj w) :=
begin
  repeat {rw dot_product},
  repeat {rw vector_conj},
  dsimp only,
  rw sum_conj,
  apply finset.sum_congr,
  refl,
  intros _ _,
  rw mul_conj,
end

-- (vᵀM)' = (v')ᵀM'
lemma vec_mul_conj (v : n → ℂ) (M : matrix n n ℂ) : vector_conj (vec_mul v M) = vec_mul (vector_conj v) (matrix_conj M):=
begin

  have vm : vec_mul (vector_conj v) M.matrix_conj = λ i , dot_product (vector_conj v) (λ j, conj (M j i)),
  {
    ext;
    rw vec_mul;
    rw dot_product;
    rw dot_product;
    rw matrix_conj;
    simp only;
    rw vector_conj;
    simp only;
    simp_rw vector_conj
  },

  rw vm,
  ext;
  rw vector_conj;
  rw vector_conj;
  dsimp only;
  rw vec_mul;
  rw dot_product_conj;
  repeat {rw vector_conj},
end

-- (Mv)' = M'v'
lemma mul_vec_conj (v : n → ℂ) (M : matrix n n ℂ) : vector_conj (mul_vec M v) = mul_vec (matrix_conj M) (vector_conj v) :=
begin
  have mv : mul_vec (matrix_conj M) (vector_conj v) = λ i , dot_product (vector_conj v) (λ j, conj (M i j)),
  {
    ext;
    rw mul_vec;
    rw dot_product;
    rw dot_product;
    rw matrix_conj;
    simp only;
    rw vector_conj;
    simp only;
    simp_rw mul_comm
  },

  rw mv,
  ext;
  rw vector_conj;
  rw vector_conj;
  dsimp only;
  rw mul_vec;
  rw dot_product_conj;
  repeat {rw vector_conj};
  simp_rw dot_product;
  simp_rw mul_comm,
end

-- v'' = v
lemma vector_conj_conj (v : n → ℂ) : vector_conj (vector_conj v) = v :=
begin
  apply funext,
  rw vector_conj,
  intro x,
  rw vector_conj,
  change conj (conj (v x)) = v x,
  rw complex.conj_conj,
end

-- M'' = M
lemma matrix_conj_conj (M : matrix n n ℂ) : matrix_conj (matrix_conj M) = M :=
begin
  apply funext,
  rw matrix_conj,
  rw matrix_conj,
  intro x,
  change vector_conj (vector_conj (M x)) = M x,
  rw vector_conj_conj,
end

-- (x • v)' = x' • v'
lemma vec_smul_conj (v : n → ℂ) (x : ℂ) : vector_conj (x • v) = (conj x) • (vector_conj v) :=
begin
  repeat {rw vector_conj},
  simp only [pi.smul_apply, algebra.id.smul_eq_mul, map_mul],
  simp_rw mul_conj,
  refl,
end

-- (x • M)' = x' • M'
lemma mat_smul_conj (M : matrix n n ℂ) (x : ℂ) : matrix_conj (x • M) = (conj x) • (matrix_conj M):=
begin
  repeat {rw matrix_conj},
  funext,
  dsimp,
  rw vec_smul_conj,
  rw pi.smul_apply,
  rw algebra.id.smul_eq_mul,
end

-- (M + N)' = M' + N'
lemma mat_add_conj (M : matrix n n ℂ) (N : matrix n n ℂ ) : matrix_conj (M + N) = matrix_conj M + matrix_conj N :=
begin
  funext,
  repeat {rw matrix_conj},
  dsimp,
  repeat {rw vector_conj},
  dsimp,
  rw map_add,
end

-- (M - N)' = M' - N'
lemma mat_sub_conj (M : matrix n n ℂ) (N : matrix n n ℂ ) : matrix_conj (M - N) = matrix_conj M - matrix_conj N :=
begin
  funext,
  repeat {rw matrix_conj},
  dsimp,
  repeat {rw vector_conj},
  dsimp,
  rw map_sub,
end

-- M' i j = (M i j)'
lemma mat_conj_apply (M : matrix n n ℂ) (i j : n): M.matrix_conj i j = (conj (M i j)) :=
begin
  simp_rw matrix_conj,
  rw vector_conj,
end

-- (M ⬝ N)' = M' ⬝ N'
lemma mat_mul_conj (M N : matrix n n ℂ) : (M ⬝ N).matrix_conj = M.matrix_conj ⬝ N.matrix_conj :=
begin
  funext,
  rw matrix.mul_apply,
  rw mat_conj_apply,
  rw matrix.mul,
  simp_rw dot_product,
  rw sum_conj,
  simp_rw mul_conj,
  simp_rw mat_conj_apply,
end

-- (Mᵀv) = (vM)ᵀ
lemma mul_vec_trans (M : matrix n n ℂ ) (v : n → ℂ ) : (mul_vec Mᵀ v) = (vec_mul v M) :=
begin
  ext;
  rw mul_vec;
  rw vec_mul;
  rw dot_product;
  rw dot_product;
  simp_rw matrix.transpose;
  rw finset.sum_congr,
  refl,
  intros _ _,
  rw mul_comm,
  refl,
  intros _ _,
  rw mul_comm,
end

-- vᵀMᵀ = (Mv)ᵀ
lemma vec_mul_trans (M : matrix n n ℂ) (v : n → ℂ ) :  (vec_mul v Mᵀ ) = mul_vec M v :=
begin
  have h : M.mul_vec v = Mᵀᵀ.mul_vec v,
  rw transpose_transpose,
  rw h,
  rw mul_vec_trans,
end

-- (M - N)v = Mv - Nv
lemma sub_mul_vec (M N : matrix n n ℂ) (v : n → ℂ ) : (M - N).mul_vec v = M.mul_vec v - N.mul_vec v:=
begin
  repeat {rw sub_eq_add_neg},
  rw add_mul_vec,
  rw add_right_inj,
  funext,
  repeat{rw mul_vec},
  simp only [pi.neg_apply],
  rw mul_vec,
  simp_rw dot_product,
  rw ← finset.sum_neg_distrib,
  rw finset.sum_congr,
  refl,
  intros _ _,
  rw neg_mul,
end

-- Mᵀ' = M'ᵀ
lemma matrix_trans_conj (M : matrix n n ℂ) : (matrix_conj Mᵀ) = (matrix_conj M)ᵀ :=
begin
  funext,
  rw matrix_conj,
  rw transpose_apply,
  dsimp,
  rw vector_conj,
  rw matrix_conj,
  dsimp,
  rw vector_conj,
end

-- (M ⬝ N)ᵀ = Nᵀ ⬝ Mᵀ
lemma matrix_mul_trans (M N : matrix n n ℂ) : (M ⬝ N)ᵀ = Nᵀ ⬝ Mᵀ :=
begin
  funext i j,
  rw transpose_apply,
  repeat {rw matrix.mul},
  dsimp,
  rw dot_product_comm,
  refl,
end

-- (M ⬝ Mᵀ) = (M ⬝ Mᵀ)ᵀ
lemma prod_transpose_symm (M : matrix n n ℂ) : (M ⬝ Mᵀ)=(M ⬝ Mᵀ)ᵀ :=
begin
  rw eq_comm,
  rw matrix_mul_trans,
  rw transpose_transpose,
end

noncomputable def vector_sq_mag (v : n → ℂ) : ℂ := (dot_product (vector_conj v) v)

-- (v')ᵀv ≥ 0
lemma vector_sq_mag_geq_zero (v : n → ℂ) : vector_sq_mag v ≥ 0 :=
begin
   rw vector_sq_mag,
   rw dot_product,

   -- ∀ i, (v' i) (v i) ≥ 0
   have h : ∀ (x : n), conj (v x) * (v x) ≥ 0,
   {
     intro x,

     have h_im : (conj (v x) * (v x)).im = (0:ℂ).im,
     {
       rw complex.mul_im,
       rw complex.conj_re,
       rw complex.conj_im,
       rw complex.zero_im,
       nlinarith, -- Re(v x) * Im(v x) + - Im(v x) * Re(v x) = 0
     },

    have h_re : (conj (v x) * (v x)).re ≥ (0:ℂ).re,
    {
      rwa complex.mul_re,
      rwa complex.conj_re,
      rwa complex.conj_im,
      rw neg_mul,
      rw sub_neg_eq_add,
      rw complex.zero_re,
      nlinarith,  -- Re(v x) * Re(v x) + Im(v x) * Im(v x) ≥ 0
    },

    rw [ge,complex.le_def],
    split,
    assumption, -- h_re
    rw eq_comm,
    assumption, -- h_im
   },

  apply finset.sum_nonneg,  -- ∀ i, 0 ≤ (v' i) * (v i)
  intro x,
  intro _,
  specialize h x,
  rw ← ge,
  exact h,
end

-- (v')ᵀv ∈ ℝ
lemma vector_sq_mag_real (v : n → ℂ ) : vector_sq_mag v = (vector_sq_mag v).re :=
begin
  ext,
  rw complex.of_real_re,

  have h := vector_sq_mag_geq_zero v,

  rw [ge,complex.le_def] at h,
  cases h with h_re h_im,
  rw complex.of_real_im,
  rw ← h_im,
  rw complex.zero_im,
end

-- (v')ᵀv = 0 ↔ v = 0
theorem vector_zero_iff_sq_mag_zero (v : n → ℂ) : vector_sq_mag v = 0 ↔ v=0 :=
begin
  rw vector_sq_mag,
  split,
  intro prod,
  rw dot_product at prod,

  -- (v i) * (v' i) ≥ 0
  have gez : ∀ i : n, v i * vector_conj v i ≥ 0,
  {
    intro i,
    rw vector_conj,
    dsimp,
    rw ge_iff_le,
    rw complex.le_def,
    split,
    rw complex.mul_re,
    simp only [complex.zero_re, complex.conj_re, complex.conj_im, mul_neg, sub_neg_eq_add],
    nlinarith,  -- 0 ≤ Re(v i) * Re(v i) + Im(v i) * Im(v i)
    rw complex.mul_im,
    rw complex.conj_im,
    rw complex.conj_re,
    simp only [complex.zero_im, mul_neg],
    rw mul_comm,
    rw add_left_neg,
  },

  have important : finset.univ.sum(λ i , v i * vector_conj v i ) = 0 ↔ ∀ i ∈ finset.univ, v i * vector_conj v i = 0,
  {
    apply finset.sum_eq_zero_iff_of_nonneg, -- ∀ i, 0 ≤ (v i) * (v' i)
    intros i _,
    specialize gez i,
    rw ge_iff_le at gez,
    exact gez
  },

  simp_rw mul_comm at prod,
  rw prod at important,
  simp only [eq_self_iff_true, finset.mem_univ, mul_eq_zero, forall_true_left, true_iff] at important,
  funext i,
  specialize important i,
  cases important with vz vcz,
  rw vz,
  refl,
  rw vector_conj at vcz,
  dsimp at vcz,

  have vczc := congr_arg conj vcz,

  rw [complex.conj_conj] at vczc,
  simp only [map_zero] at vczc,
  rw vczc,
  refl,
  intro ez,
  rw ez,
  rw dot_product_comm,
  rw zero_dot_product,
end

-- |x • v| = x²|v|
lemma smul_sq_mag (x : ℂ) (v: n → ℂ ) (h : x.im = 0): vector_sq_mag (x • v) = (x * x) * vector_sq_mag v :=
begin
  rw vector_sq_mag,
  rw vec_smul_conj,

  simp only [dot_product_smul, smul_dot_product, algebra.id.smul_eq_mul, monoid_with_zero_hom.coe_mk, complex.of_real_add,
  complex.of_real_mul],
  rw ← vector_sq_mag,
  rw ← mul_assoc,

  rw star_ring_end_apply,

  suffices x_star : star x = x,
  {rw x_star},

  unfold star,
  ext,
  refl,
  rw h,
  rw neg_zero,
end

-- A vector is real iff it is equal to its conjugate
lemma vector_eq_conj_iff_real (v : n → ℂ) : (∃ u : n → ℝ, v = u) ↔ v = vector_conj v :=
begin
  split,
  intro h,
  cases h with u veq,
  funext,
  rw vector_conj,
  dsimp,
  rw veq,
  rw eq_comm,
  rw complex.eq_conj_iff_real,
  use u x,
  refl,
  intro veq,
  use (λ i , (v i).re ),
  funext,

  have vx_re : v x = (v x).re,
  {
    ext,
    rw complex.of_real_re,
    rw complex.of_real_im,
    rw ← add_self_eq_zero,
    nth_rewrite 0 veq,
    rw vector_conj,
    dsimp,
    rw add_left_neg
  },

  rw vx_re,
  refl,
end

-- A matrix is real iff it is equal to its conjugate
lemma matrix_eq_conj_iff_real (M : matrix n n ℂ) : (∃ N : matrix n n ℝ, M = N) ↔ M = matrix_conj M :=
begin
  split,
  intro h,
  cases h with N eq,
  rw eq,
  funext,
  rw matrix_conj,
  simp_rw vector_conj,
  rw eq_comm,
  rw complex.eq_conj_iff_real,
  use N i j,
  refl,

  intro meq,
  use (λ (i_1 i_2 : n), (M i_1 i_2).re),
  funext i j,

  have mij_conj : M i j = conj (M i j),
  {
    nth_rewrite 0 meq,
    rw matrix_conj,
    dsimp,
    rw vector_conj,
  },

  have mij_re : M i j = (M i j).re,
  {
    ext,
    rw complex.of_real_re,
    rw complex.of_real_im,
    rw ← add_self_eq_zero,
    nth_rewrite 0 mij_conj,
    rw complex.conj_im,
    rw add_left_neg
  },

  rw eq_comm at mij_conj,
  rw mij_re,
  refl,
end

-- The sum of a matrix and its conjugate is a real matrix
lemma matrix_conj_add_real (M : matrix n n ℂ) : ∃ N : matrix n n ℝ , (matrix_conj M + M) = N :=
begin
  rw matrix_eq_conj_iff_real,
  rw mat_add_conj,
  rw matrix_conj_conj,
  rw add_comm,
end

section hermitian

-- A matrix is called Hermitian if it is equal to the conjugate of its transpose
def is_hermitian (M : matrix n n ℂ) :=  matrix_conj Mᵀ  = M

-- The sum of two Hermitian matrices is Hermitian
lemma herm_sum (M : matrix n n ℂ) (N : matrix n n ℂ) (hm : is_hermitian M) (hn : is_hermitian N) : is_hermitian (M + N) :=
begin
  rw is_hermitian,
  rw is_hermitian at hm,
  rw is_hermitian at hn,
  funext,
  rw matrix_conj,
  dsimp,
  rw vector_conj,
  dsimp,
  nth_rewrite 1 ← hm,
  nth_rewrite 1 ← hn,
  repeat {rw matrix_trans_conj},
  dsimp,
  repeat {rw matrix_conj},
  dsimp,
  repeat {rw vector_conj},
  dsimp,
  rw map_add,
end

-- A Hermitian matrix only has real eigenvalues
theorem hermitian_real_eigenvalues (M : matrix n n ℂ) (h : is_hermitian M) (e : ℂ) (ev : has_eigenvalue M e) : e.im = 0 :=
begin
  rw has_eigenvalue at ev,
  cases ev with v ep,
  rw has_eigenpair at ep,
  cases ep with nz mul,
  rw is_hermitian at h,

  have mul_0 : dot_product (vector_conj v) (M.mul_vec v) = dot_product (vector_conj v) (e • v),
  {
    rw mul,
  },

  have mul_1 : dot_product (vector_conj v) (e • v) = e * (vector_sq_mag v),
  {
    rw vector_sq_mag,
    simp only [dot_product_smul, algebra.id.smul_eq_mul],
  },

  have mul_2 : dot_product v (e • (vector_conj v)) = e * vector_sq_mag v,
  {
    rw dot_product_comm,
    rw vector_sq_mag,
    rw dot_product,
    rw dot_product,
    rw finset.mul_sum,
    rw finset.sum_congr,
    refl,
    intros _ _,
    simp only [pi.smul_apply, algebra.id.smul_eq_mul,mul_assoc],
  },

  have mul_3 : dot_product (vector_conj v) (mul_vec (Mᵀ.matrix_conj) v) = (conj e) * vector_sq_mag v,
  {
    rw dot_product_mul_vec,
    rw matrix_trans_conj,
    rw vec_mul_trans,
    rw ← mul_vec_conj,
    rw mul,
    rw vec_smul_conj,
    rw vector_sq_mag,
    simp only [smul_dot_product, algebra.id.smul_eq_mul],
  },

  rw h at mul_3,
  rw mul at mul_3,
  rw mul_1 at mul_3,
  rw ne at nz,
  rw ← vector_zero_iff_sq_mag_zero at nz,
  rw vector_sq_mag at nz,
  rw ← vector_sq_mag at nz,
  rw mul_eq_mul_right_iff at mul_3,
  simp only [nz] at mul_3,
  rw or_false at mul_3,

  have e_im := congr_arg complex.im mul_3,

  rw complex.conj_im at e_im,
  rw eq_neg_iff_add_eq_zero at e_im,
  rw add_self_eq_zero at e_im,
  exact e_im,
end

-- M is Hermitian iff -M is Hermitian
theorem herm_iff_neg_herm (M : matrix n n ℂ) : is_hermitian M ↔ is_hermitian (-M) :=
begin
  repeat {rw is_hermitian},

  have key : (-M)ᵀ.matrix_conj = -(Mᵀ.matrix_conj),
  {
    funext,
    rw matrix_conj,
    rw matrix_conj,
    dsimp,
    rw vector_conj,
    rw vector_conj,
    dsimp,
    ext,
    simp only [complex.neg_re, complex.conj_re],
    simp only [complex.neg_im, complex.conj_im]
  },

  rw key,
  rw neg_inj,
end

-- A Hermitian matrix multiplied by a non-zero real number is Hermitian
theorem herm_real_smul_herm (M : matrix n n ℂ) (a : ℂ) : a=a.re → (a=0 ∨ is_hermitian M ↔ is_hermitian (a • M)):=
begin
  intro a_re,
  rw is_hermitian,
  rw is_hermitian,
  rw transpose_smul,
  rw mat_smul_conj Mᵀ a,
  nth_rewrite 1 a_re,
  simp only [is_R_or_C.conj_of_real, complex.coe_smul],
  split,
  intro h,
  cases h with az m_herm,
  rw az,
  rw zero_smul,
  rw complex.zero_re,
  rw zero_smul,
  rw m_herm,
  rw a_re,
  simp only [complex.of_real_re, complex.coe_smul],
  intro h,
  rw or_iff_not_and_not,
  by_contra h_1,
  cases h_1 with anz mnh,
  apply mnh,
  rw ← sub_eq_zero at h,
  rw a_re at h,
  simp only [complex.of_real_re, complex.coe_smul] at h,
  rw ← smul_sub at h,
  rw smul_eq_zero at h,
  rw a_re at anz,
  rw complex.of_real_eq_zero at anz,
  simp only [anz, false_or] at h,
  rw sub_eq_zero at h,
  exact h,
end

-- Every matrix can be written in the form A + iB, where A and B are Hermitian matrices
theorem matrix_decomposition_hermitian (M : matrix n n ℂ) : ∃ A : matrix n n ℂ, ∃ B : matrix n n ℂ, is_hermitian A ∧ is_hermitian B ∧ M = A + complex.I • B :=
begin
  have h := herm_real_smul_herm,

  have trivial : (1/2) = ((complex.re(1/2)):ℂ),
  {
    ext,
    rw complex.of_real_re,
    rw complex.of_real_im,
    simp only [one_div, complex.inv_im, complex.bit0_im, complex.one_im, bit0_zero, neg_zero', zero_div],
  },

  have trivial_2 : ((1:ℂ)/2) = 0 = false,
  {
    simp only [eq_iff_iff, iff_false],
    by_contra,
    simp only [one_div, inv_eq_zero, bit0_eq_zero, one_ne_zero] at h,
    exact h,
  },

  have trivial_3 : (1:ℂ)/2 + (1:ℂ)/2 = 1,
  {
    rw ← sub_eq_zero,
    rw ← two_mul,
    rw mul_div_comm,
    rw div_self,
    rw mul_one,
    rw sub_self,
    by_contra,
    rw ← one_add_one_eq_two at h,
    rw add_self_eq_zero at h,

    have h_1 := one_ne_zero,

    rw ne at h_1,
    apply h_1,
    exact h,
    exact infinite.nontrivial ℂ,
  },

  use ((1:ℂ)/2) • (M + (matrix_conj Mᵀ)),
  use (complex.I/2) • ((matrix_conj Mᵀ) - M),

  split,
  rw ← herm_real_smul_herm,
  rw trivial_2,
  rw false_or,
  rw is_hermitian,
  rw transpose_add,
  rw mat_add_conj,
  nth_rewrite 1 add_comm,
  rw add_left_cancel_iff,
  rw matrix_trans_conj,
  rw matrix_conj_conj,
  rw transpose_transpose,
  exact trivial,
  split,

  rw is_hermitian,
  rw smul_sub,
  rw matrix_trans_conj,
  rw mat_sub_conj,
  rw matrix_trans_conj,
  rw mat_smul_conj,
  simp only [transpose_sub, transpose_smul],
  rw matrix_trans_conj,
  rw transpose_transpose,
  rw matrix_conj_conj,

  have conj_i : conj(complex.I/2) = -complex.I/2,
  {
    ext,
    rw complex.conj_re,
    rw neg_div,
    rw complex.neg_re,
    rw eq_neg_iff_add_eq_zero,
    rw add_self_eq_zero,
    rw complex.div_re,
    rw complex.I_re,
    simp only [zero_mul, zero_div, complex.bit0_im, complex.one_im, bit0_zero, mul_zero, add_zero],
    rw complex.conj_im,
    rw neg_div,
    rw complex.neg_im,
  },

  rw conj_i,
  rw neg_div,
  nth_rewrite 1 ← neg_add_eq_sub,
  rw neg_smul,
  rw sub_eq_add_neg,
  rw add_right_inj,
  rw mat_smul_conj,
  rw conj_i,
  rw transpose_smul,
  rw neg_div,
  rw neg_smul,
  rw neg_neg,

  rw smul_add,
  rw ← smul_assoc,
  rw smul_sub,
  rw smul_eq_mul,
  rw mul_div,
  rw complex.I_mul_I,
  rw neg_div,
  rw neg_smul,
  rw neg_smul,
  rw neg_sub_neg,
  rw add_add_sub_cancel,
  rw ← add_smul,
  rw trivial_3,
  rw one_smul,
  exact n,
  exact _inst_1,
  exact _inst_3,
end

-- Every Hermitian matrix can be written as A + iB, where A is a symmetric real matrix and B is a skew-symmetric real matrix
theorem matrix_decomposition_symm_skew_symm (M : matrix n n ℂ) (herm : is_hermitian M) : ∃ A : matrix n n ℝ , ∃ B : matrix n n ℝ, is_symm A ∧ is_skew_symmetric B ∧ M = A + complex.I • B:=
begin

  have mul_2_scal: ∀ (x y : ℂ) , x = y ↔ (2 : ℂ)*x = (2 : ℂ)*y,
  {
    intros x y,
    split,
    intro xey,
    rw xey,

    intro xey_2,

    rw mul_eq_mul_left_iff at xey_2,
    cases xey_2 with xey f,
    exact xey,
    exfalso,
    rw ← one_add_one_eq_two at f,
    rw add_self_eq_zero at f,

    have onz := one_ne_zero,

    rw ne at onz,
    apply onz,
    exact f,
    exact infinite.nontrivial ℂ,
  },

  have mul_2 : ∀ (X Y : matrix n n ℂ) , X = Y ↔ (2 : ℂ) • X = (2 : ℂ) • Y,
  {
    intros X Y,
    split,
    intro xey,
    rw xey,
    intro xey_2,
    funext,
    repeat{rw two_smul at xey_2},

    have mul_2_scalar : (X + X) x x_1 = (Y+Y) x x_1,
    {rw xey_2},

    simp only [dmatrix.add_apply] at mul_2_scalar,
    repeat {rw ← two_mul at mul_2_scalar},
    rw mul_eq_mul_left_iff at mul_2_scalar,
    simp only [bit0_eq_zero, one_ne_zero, or_false] at mul_2_scalar,
    exact mul_2_scalar,
  },

  have complex_two_nez : (2:ℂ) ≠ (0:ℂ),
  {
    rw ← one_add_one_eq_two,
    rw ne.def,
    rw add_self_eq_zero,
    rw ← ne.def,
    exact one_ne_zero,
  },

  have two_inv_real : (2:ℂ)⁻¹.re = (2:ℝ)⁻¹,
  {
    simp only [complex.inv_re, complex.bit0_re, complex.one_re],
    rw eq_comm,
    apply eq_div_of_mul_eq,
    simp only [ne.def, monoid_with_zero_hom.map_eq_zero, bit0_eq_zero, one_ne_zero, not_false_iff],
    rw complex.norm_sq,
    dsimp,
    simp only [complex.bit0_im, complex.one_im, bit0_zero, mul_zero, add_zero, inv_mul_cancel_left₀, ne.def, bit0_eq_zero,
    one_ne_zero, not_false_iff],
  },

  have two_inv_im : (2:ℂ)⁻¹.im = (0:ℝ),
  {
    rw complex.inv_im,
    simp only [complex.bit0_im, complex.one_im, bit0_zero, neg_zero', zero_div],
  },

  have h_1 : ((1:ℂ)/2) • (matrix_conj M + M) = matrix_conj (((1:ℂ)/2) • (matrix_conj M + M)),
  {
    rw mat_smul_conj,
    rw mat_add_conj,
    rw matrix_conj_conj,
    rw add_comm,

    suffices c12 : (1:ℂ)/2 = conj ((1:ℂ)/2),
    {rw ← c12},

    rw one_div,
    rw eq_comm,
    rw complex.eq_conj_iff_im,
    exact two_inv_im,
  },

  have conj_i : conj(complex.I/2) = -complex.I/2,
  {
    ext,
    rw complex.conj_re,
    rw neg_div,
    rw complex.neg_re,
    rw eq_neg_iff_add_eq_zero,
    rw add_self_eq_zero,
    rw complex.div_re,
    rw complex.I_re,
    simp only [zero_mul, zero_div, complex.bit0_im, complex.one_im, bit0_zero, mul_zero, add_zero],
    rw complex.conj_im,
    rw neg_div,
    rw complex.neg_im,
  },

  have g_1 : (complex.I/(2:ℂ)) • (matrix_conj M - M) = matrix_conj ((complex.I/(2:ℂ)) • (matrix_conj M - M)),
  {
    rw mat_smul_conj,
    rw mat_sub_conj,
    rw matrix_conj_conj,
    simp_rw smul_sub,
    rw conj_i,
    repeat {rw neg_div},
    repeat {rw neg_smul},
    rw neg_sub_neg,
  },

  rw ← matrix_eq_conj_iff_real at h_1,
  cases h_1 with N hN,
  rw ← matrix_eq_conj_iff_real at g_1,
  cases g_1 with O hO,

  existsi N,
  existsi O,
  rw is_hermitian at herm,

  split,

  rw is_symm,

  have h_2 : Nᵀ = N,
  {
    ext,
    rw ← complex.of_real_inj,

    have h_3 : ∀ (i_1 i_2 : n ), (N:matrix n n ℂ) i_1 i_2 = ((N i_1 i_2):ℂ),
    intros i_1 i_2,
    refl,

    have n_ji : (N:matrix n n ℂ) j i = ((N j i):ℂ),
    specialize h_3 j i,
    rw h_3,

    have n_ij : (N:matrix n n ℂ) i j = ((N i j):ℂ),
    specialize h_3 i j,
    rw h_3,

    rw ← n_ji,
    rw ← n_ij,

    rw ← hN,
    dsimp,
    nth_rewrite 0 ← herm,
    rw matrix_conj_conj,
    rw transpose,
    nth_rewrite 2 ← herm,
    rw matrix_conj_conj,
    rw transpose,
    rw add_comm,
  },

  have mul_3 := mul_2,

  specialize mul_2 M,
  specialize mul_2 (↑(((1:ℝ) / 2) • N) + -(((1:ℂ) / 2) • M.matrix_conj) + ((1:ℂ) / 2) • M),

  have n_smul : (N:(matrix n n ℂ)) = (2:ℂ) • ( ((1:ℂ)/2) • (N:(matrix n n ℂ))),
  {simp only [one_div, smul_inv_smul₀, ne.def, bit0_eq_zero, one_ne_zero, not_false_iff]},

  rw h_2,

  have g_3 : Oᵀ = -O,
  {
    ext,
    rw transpose_apply,
    repeat{rw pi.neg_apply},
    rw ← complex.of_real_inj,
    rw complex.of_real_neg,

    have h_3 : ∀ (i_1 i_2 : n ), (O:matrix n n ℂ) i_1 i_2 = ((O i_1 i_2):ℂ),
    intros i_1 i_2,
    refl,

    have o_ji : (O:matrix n n ℂ) j i = ((O j i):ℂ),
    specialize h_3 j i,
    rw h_3,

    have o_ij : (O:matrix n n ℂ) i j = ((O i j):ℂ),
    specialize h_3 i j,
    rw h_3,

    rw ← o_ji,
    rw ← o_ij,
    repeat {rw ← hO},

    simp only [pi.smul_apply, pi.sub_apply, algebra.id.smul_eq_mul],
    repeat {rw mul_sub},
    rw neg_sub,
    nth_rewrite 0 ← herm,
    nth_rewrite 3 ← herm,
    repeat {rw matrix_conj_conj},
    repeat {rw transpose_apply},
  },

  split,
  rw is_skew_symmetric,
  rw g_3,
  rw ← hO,
  rw ← hN,
  rw smul_add,
  rw ← smul_assoc,
  rw smul_eq_mul,
  rw ← mul_div_assoc,
  rw complex.I_mul_I,
  rw neg_div,
  rw smul_sub,
  simp only [one_div, neg_smul, neg_sub_neg],
  rw add_sub,
  rw add_assoc,
  rw add_sub_cancel',
  rw mul_2,
  simp only [smul_add, smul_inv_smul₀, ne.def, bit0_eq_zero, one_ne_zero, not_false_iff],
  rw ← one_add_one_eq_two,
  rw add_smul,
  repeat {rw one_smul},
end

-- A Hermitian real matrix is symmetric
lemma hermitian_real_symm  (M : matrix n n ℝ) (herm : is_hermitian (M:matrix n n ℂ)) : M.is_symm:=
begin
  rw is_hermitian at herm,
  rw is_symm,

  have herm_1 : ∀ (i j : n), ((M : matrix n n ℂ)ᵀ.matrix_conj) i j = M i j,
  {
    intros i j,
    rw herm,
    refl
  },

  funext i j,
  specialize herm_1 i j,
  rw matrix_trans_conj at herm_1,
  rw transpose_apply at herm_1,
  rw matrix_conj at herm_1,
  simp_rw vector_conj at herm_1,

  unfold coe at herm_1,
  unfold lift_t at herm_1,
  unfold has_lift_t.lift at herm_1,
  unfold coe_t at herm_1,
  unfold has_coe_t.coe at herm_1,
  unfold coe_b at herm_1,
  unfold has_coe.coe at herm_1,

  have trivial : ((conj (({re := M j i, im := 0}):ℂ)):ℂ) = (({re := M j i, im := 0}):ℂ),
  {
    ext,
    rw complex.conj_re,
    rw complex.conj_im,
    unfold complex.im,
    rw neg_zero'
  },

  rw trivial at herm_1,
  rw transpose_apply,

  have trivial_re := congr_arg complex.re herm_1,

  unfold complex.re at trivial_re,
  exact trivial_re,
end

section definite

-- A Hermitian matrix is called positive definite if the product vᵀMv is greater than 0 for all non-zero vectors v
def is_pos_def (M : matrix n n ℂ) := M.is_hermitian ∧ ∀ (v : n → ℂ), v ≠ 0 → 0 < dot_product (vector_conj v) (M.mul_vec v)

-- A Hermitian matrix is called negative definite if the product vᵀMv is smaller than 0 for all non-zero vectors v
def is_neg_def (M : matrix n n ℂ) := M.is_hermitian ∧ ∀ (v : n → ℂ), v ≠ 0 → 0 > dot_product (vector_conj v) (M.mul_vec v)

-- A Hermitian matrix is called positive semidefinite if the product vᵀMv is greater than or equal to 0 for all non-zero vectors v
def is_pos_semidef (M : matrix n n ℂ) := M.is_hermitian ∧ ∀ (v : n → ℂ), v ≠ 0 → 0 ≤ dot_product (vector_conj v) (M.mul_vec v)

-- -- A Hermitian matrix is called negative semidefinite if the product vᵀMv is greater than 0 for all non-zero vectors v
def is_neg_semidef (M : matrix n n ℂ) := M.is_hermitian ∧ ∀ (v : n → ℂ), v ≠ 0 → 0 ≥ dot_product (vector_conj v) (M.mul_vec v)

-- A positive definite matrix only has positive eigenvalues
theorem pos_def_pos_eigenvalues (M : matrix n n ℂ) (e : ℂ) (h : M.is_pos_def) (ev : has_eigenvalue M e) : e > 0 :=
begin
  rw is_pos_def at h,

  have ev_prop := ev,

  rw has_eigenvalue at ev,
  cases ev with v ep,
  rw has_eigenpair at ep,
  cases h with herm mul,

  have herm_prop := herm,

  rw is_hermitian at herm,
  cases ep with vnz vmul,
  specialize mul v,
  simp only [vnz, ne.def, not_false_iff, forall_true_left] at mul,
  rw vmul at mul,
  rw dot_product_smul at mul,
  rw ← vector_sq_mag at mul,
  rw smul_eq_mul at mul,

  have vmgz : vector_sq_mag v > 0,
  {
    rw gt_iff_lt,
    rw lt_iff_le_and_ne,
    split,
    rw ← ge_iff_le,
    simp only [vector_sq_mag_geq_zero],
    rw ne.def,
    rw eq_comm,
    rw vector_zero_iff_sq_mag_zero,
    rw ← ne.def,
    exact vnz
  },

  rw vector_sq_mag_real at mul,
  rw vector_sq_mag_real at vmgz,

  have e_re : e = e.re,

  ext,
  rw complex.of_real_re,
  rw complex.of_real_im,
  apply hermitian_real_eigenvalues M herm_prop e,
  exact ev_prop,
  rw e_re at mul,
  rw e_re,

  have key : 0 < (e.re) * ((vector_sq_mag v).re) / ((vector_sq_mag v).re),
  {
    apply div_pos,
    cases mul with mul_re mul_im,
    simp only [complex.zero_re, complex.mul_re, complex.of_real_re, complex.of_real_im, mul_zero, sub_zero] at mul_re,
    exact mul_re,
    rw ← gt_iff_lt,
    simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
    rwa gt_iff_lt
  },

  rw mul_div_cancel at key,
  simp only [key, gt_iff_lt, complex.zero_lt_real],
  rw ne.def,
  by_contra vz,
  rw vz at vmgz,
  simp only [complex.of_real_zero, gt_iff_lt, lt_self_iff_false] at vmgz,
  exact vmgz,
end

-- A positive semidefinite matrix only has nonnegative eigenvalues
theorem pos_semidef_nonneg_eigenvalues (M : matrix n n ℂ) (e : ℂ) (h : M.is_pos_semidef) (ev : has_eigenvalue M e) : e ≥ 0 :=
begin
  rw is_pos_semidef at h,

  have ev_prop := ev,

  rw has_eigenvalue at ev,
  cases ev with v ep,
  rw has_eigenpair at ep,
  cases h with herm mul,

  have herm_prop := herm,

  rw is_hermitian at herm,
  cases ep with vnz vmul,
  specialize mul v,
  simp only [vnz, ne.def, not_false_iff, forall_true_left] at mul,
  rw vmul at mul,
  rw dot_product_smul at mul,
  rw ← vector_sq_mag at mul,
  rw smul_eq_mul at mul,

  have vmgz : vector_sq_mag v > 0,
  {
    rw gt_iff_lt,
    rw lt_iff_le_and_ne,
    split,
    rw ← ge_iff_le,
    simp only [vector_sq_mag_geq_zero],
    rw ne.def,
    rw eq_comm,
    rw vector_zero_iff_sq_mag_zero,
    rw ← ne.def,
    exact vnz
  },

  rw vector_sq_mag_real at mul,
  rw vector_sq_mag_real at vmgz,

  have e_re : e = e.re,
  {
    ext,
    rw complex.of_real_re,
    rw complex.of_real_im,
    apply hermitian_real_eigenvalues M herm_prop e,
    exact ev_prop
  },

  rw e_re at mul,
  rw e_re,

  have key : 0 ≤ (e.re) * ((vector_sq_mag v).re) / ((vector_sq_mag v).re),
  {
    apply div_nonneg,
    cases mul with mul_re mul_im,
    simp only [complex.zero_re, complex.mul_re, complex.of_real_re, complex.of_real_im, mul_zero, sub_zero] at mul_re,
    exact mul_re,
    rw ← ge_iff_le,
    simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
    rw ge_iff_le,
    rw lt_iff_le_and_ne at vmgz,
    cases vmgz with vge vne,
    exact vge
  },

  rw mul_div_cancel at key,
  simp only [key, ge_iff_le, complex.zero_le_real],
  rw ne.def,
  by_contra vz,
  rw vz at vmgz,
  simp only [complex.of_real_zero, gt_iff_lt, lt_self_iff_false] at vmgz,
  exact vmgz,
end

-- M is negative definite iff -M is positive definite
theorem neg_def_iff_neg_pos_def (M : matrix n n ℂ) : is_neg_def M ↔ is_pos_def (-M) :=
begin

  have important : ∀ v : n → ℂ ,  dot_product (vector_conj v) ((-M).mul_vec v) = -dot_product (vector_conj v) (M.mul_vec v),
  {
    intro v,
    rw dot_product,
    rw dot_product,
    rw ← finset.sum_neg_distrib,
    rw finset.sum_congr,
    refl,
    intros _ _,
    rw neg_mul_vec,
    rw pi.neg_apply,
    rw mul_neg
  },

  rw is_neg_def,
  rw is_pos_def,
  rw ← herm_iff_neg_herm,
  rw and.congr_right_iff,
  intro herm,

  split;

  intro prop;
  intro v;
  intro vnz;
  specialize prop v;
  simp only [vnz, ne.def, not_false_iff, forall_true_left, gt_iff_lt] at prop;
  specialize important v,
  rw important,
  simp only [prop, right.neg_pos_iff],
  rw important at prop,
  rw right.neg_pos_iff at prop,
  rw gt_iff_lt,
  exact prop,
end

-- M is negative semidefinite iff -M is positive semidefinite
theorem neg_semidef_iff_neg_pos_semidef (M : matrix n n ℂ) : is_neg_semidef M ↔ is_pos_semidef (-M) :=
begin

  have important : ∀ v : n → ℂ ,  dot_product (vector_conj v) ((-M).mul_vec v) = -dot_product (vector_conj v) (M.mul_vec v),
  {
    intro v,
    rw dot_product,
    rw dot_product,
    rw ← finset.sum_neg_distrib,
    rw finset.sum_congr,
    refl,
    intros _ _,
    rw neg_mul_vec,
    rw pi.neg_apply,
    rw mul_neg
  },

  rw is_neg_semidef,
  rw is_pos_semidef,
  rw ← herm_iff_neg_herm,
  rw and.congr_right_iff,
  intro herm,

  split;

  intro prop;
  intro v;
  intro vnz;
  specialize prop v;
  simp only [vnz, ne.def, not_false_iff, forall_true_left, gt_iff_lt] at prop;
  specialize important v,
  rw important,
  simp only [right.nonneg_neg_iff],
  rwa ← ge_iff_le,
  rw important at prop,
  rw right.nonneg_neg_iff at prop,
  exact prop,
end

-- A negative definite matrix only has negative eigenvalues
theorem neg_def_neg_eigenvalues (M : matrix n n ℂ) (e : ℂ) (h : M.is_neg_def) (ev : has_eigenvalue M e) : e < 0 :=
begin
  rw neg_def_iff_neg_pos_def at h,

  have ev_prop := ev,

  rw has_eigenvalue at ev,
  cases ev with v ep,
  rw has_eigenpair at ep,
  cases ep with vnz vmul,
  rw is_pos_def at h,
  cases h with herm_prop mul,
  rw ← herm_iff_neg_herm at herm_prop,
  specialize mul v,
  simp only [vnz, ne.def, not_false_iff, forall_true_left] at mul,

  have neg_vmul : (-M).mul_vec v = -(e • v),

  rw neg_mul_vec,
  rw neg_inj,
  exact vmul,
  rw neg_vmul at mul,
  simp only [dot_product_neg, dot_product_smul, algebra.id.smul_eq_mul, right.neg_pos_iff] at mul,
  rw ← vector_sq_mag at mul,

  have vmgz : vector_sq_mag v > 0,
  {
    rw gt_iff_lt,
    rw lt_iff_le_and_ne,
    split,
    rw ← ge_iff_le,
    simp only [vector_sq_mag_geq_zero],
    rw ne.def,
    rw eq_comm,
    rw vector_zero_iff_sq_mag_zero,
    rw ← ne.def,
    exact vnz
  },

  rw vector_sq_mag_real at mul,
  rw vector_sq_mag_real at vmgz,

  have e_re : e = e.re,

  ext,
  rw complex.of_real_re,
  rw complex.of_real_im,
  apply hermitian_real_eigenvalues M herm_prop e,
  exact ev_prop,
  rw e_re at mul,
  rw e_re,

  have key : (e.re) * ((vector_sq_mag v).re) / ((vector_sq_mag v).re) < 0,
  {
    rw div_neg_iff,
    cases mul with mul_re mul_im,
    simp only [complex.mul_re, complex.of_real_re, complex.of_real_im, mul_zero, sub_zero, complex.zero_re] at mul_re,
    simp only,
    right,
    split,
    exact mul_re,
    simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
    exact vmgz
  },

  rw mul_div_cancel at key,
  rw complex.lt_def,
  split,
  simp only [key, complex.of_real_re, complex.zero_re],
  simp only [complex.of_real_im, complex.zero_im],
  rw ne_iff_lt_or_gt,
  right,
  simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
  rwa gt_iff_lt,
end

-- A negative semidefinite matrix only has nonpositive eigenvalues
theorem neg_semidef_nonpos_eigenvalues (M : matrix n n ℂ) (e : ℂ) (h : M.is_neg_semidef) (ev : has_eigenvalue M e) : e ≤ 0 :=
begin
  rw neg_semidef_iff_neg_pos_semidef at h,

  have ev_prop := ev,

  rw has_eigenvalue at ev,
  cases ev with v ep,
  rw has_eigenpair at ep,
  cases ep with vnz vmul,
  rw is_pos_semidef at h,
  cases h with herm_prop mul,
  rw ← herm_iff_neg_herm at herm_prop,
  specialize mul v,
  simp only [vnz, ne.def, not_false_iff, forall_true_left] at mul,

  have neg_vmul : (-M).mul_vec v = -(e • v),

  rw neg_mul_vec,
  rw neg_inj,
  exact vmul,
  rw neg_vmul at mul,
  simp only [dot_product_neg, dot_product_smul, algebra.id.smul_eq_mul, right.neg_pos_iff] at mul,
  rw ← vector_sq_mag at mul,

  have vmgz : vector_sq_mag v > 0,
  {
    rw gt_iff_lt,
    rw lt_iff_le_and_ne,
    split,
    rw ← ge_iff_le,
    simp only [vector_sq_mag_geq_zero],
    rw ne.def,
    rw eq_comm,
    rw vector_zero_iff_sq_mag_zero,
    rw ← ne.def,
    exact vnz
  },

  rw vector_sq_mag_real at mul,
  rw vector_sq_mag_real at vmgz,

  have e_re : e = e.re,
  {
    ext,
    rw complex.of_real_re,
    rw complex.of_real_im,
    apply hermitian_real_eigenvalues M herm_prop e,
    exact ev_prop
  },

  rw e_re at mul,
  rw e_re,

  have key : (e.re) * ((vector_sq_mag v).re) / ((vector_sq_mag v).re) ≤ 0,
  {
    rw div_nonpos_iff,
    cases mul with mul_re mul_im,
    simp only [complex.mul_re, complex.of_real_re, complex.of_real_im, mul_zero, sub_zero, complex.zero_re] at mul_re,
    simp only,
    right,
    split,
    simp only [complex.neg_re, complex.mul_re, complex.of_real_re, complex.of_real_im, mul_zero, sub_zero, right.nonneg_neg_iff] at mul_re,
    exact mul_re,
    simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
    rw lt_iff_le_and_ne at vmgz,
    cases vmgz with vmge vne,
    exact vmge
  },

  rw mul_div_cancel at key,
  rw complex.le_def,
  split,
  simp only [key, complex.of_real_re, complex.zero_re],
  simp only [complex.of_real_im, complex.zero_im],
  rw ne_iff_lt_or_gt,
  right,
  simp only [gt_iff_lt, complex.zero_lt_real] at vmgz,
  rwa gt_iff_lt,
end

-- The sum of a positive definite matrix and a positive semidefinite matrix is positive definite
theorem pos_def_add_semidef_pos (M N : matrix n n ℂ) (hm : is_pos_def M) (hn : is_pos_semidef N) : is_pos_def (M + N) :=
begin
  rw is_pos_def,
  rw is_pos_def at hm,
  rw is_pos_semidef at hn,
  cases hm with herm_m eq_m,
  cases hn with herm_n eq_n,
  split,
  exact herm_sum M N herm_m herm_n,

  intros v vnz,
  specialize eq_m v vnz,
  specialize eq_n v vnz,
  rw matrix.add_mul_vec,
  rw dot_product_add,

  have trivial : ∀ (x y : ℂ), 0 < x → 0 ≤ y → 0 < x + y,
  {
    intros x y hx hy,
    rw complex.lt_def at hx,
    cases hx with hx_re hx_im,
    rw complex.le_def at hy,
    cases hy with hy_re hy_im,
    rw complex.lt_def,

    split,

    rw complex.add_re,
    rw complex.zero_re,
    rw complex.zero_re at hx_re hy_re,
    nlinarith,

    rw complex.add_im,
    rw complex.zero_im,
    rw complex.zero_im at hx_im hy_im,
    nlinarith,
  },

  exact trivial (vector_conj v ⬝ᵥ M.mul_vec v) (vector_conj v ⬝ᵥ N.mul_vec v) eq_m eq_n,
end

-- The sum of two positive semidefinite matrices is positive semidefinite
theorem pos_semidef_add_semidef_semidef (M N : matrix n n ℂ) (hm : is_pos_semidef M) (hn : is_pos_semidef N) : is_pos_semidef (M + N) :=
begin
  rw is_pos_semidef,
  rw is_pos_semidef at hm hn,
  cases hm with herm_m eq_m,
  cases hn with herm_n eq_n,
  split,
  exact herm_sum M N herm_m herm_n,

  intros v vnz,
  specialize eq_m v vnz,
  specialize eq_n v vnz,
  rw matrix.add_mul_vec,
  rw dot_product_add,

  have add := add_le_add eq_m eq_n,
  rw zero_add at add,

  exact add,
end

-- The sum of a negative definite matrix and a negative semidefinite matrix is negative definite
theorem neg_add_semineg_neg (M N : matrix n n ℂ) (hm : is_neg_def M) (hn : is_neg_semidef N) : is_neg_def (M + N) :=
begin
  rw is_neg_def,
  rw is_neg_def at hm,
  rw is_neg_semidef at hn,
  cases hm with herm_m eq_m,
  cases hn with herm_n eq_n,
  split,
  exact herm_sum M N herm_m herm_n,

  intros v vnz,
  specialize eq_m v vnz,
  specialize eq_n v vnz,
  rw matrix.add_mul_vec,
  rw dot_product_add,

  have trivial : ∀ (x y : ℂ), x < 0 → y ≤ 0 → x + y < 0,
  {
    intros x y hx hy,
    rw complex.lt_def at hx,
    cases hx with hx_re hx_im,
    rw complex.le_def at hy,
    cases hy with hy_re hy_im,
    rw complex.lt_def,

    split,

    rw complex.add_re,
    rw complex.zero_re,
    rw complex.zero_re at hx_re hy_re,
    nlinarith,

    rw complex.add_im,
    rw complex.zero_im,
    rw complex.zero_im at hx_im hy_im,
    nlinarith,
  },

  rw gt_iff_lt at eq_m,
  rw ge_iff_le at eq_n,
  rw gt_iff_lt,

  exact trivial (vector_conj v ⬝ᵥ M.mul_vec v) (vector_conj v ⬝ᵥ N.mul_vec v) eq_m eq_n,
end

-- The sum of two positive semidefinite matrices is positive semidefinite
theorem neg_semidef_add_semidef_semidef (M N : matrix n n ℂ) (hm : is_neg_semidef M) (hn : is_neg_semidef N) : is_neg_semidef (M + N) :=
begin
  rw is_neg_semidef,
  rw is_neg_semidef at hm hn,
  cases hm with herm_m eq_m,
  cases hn with herm_n eq_n,
  split,
  exact herm_sum M N herm_m herm_n,

  intros v vnz,
  specialize eq_m v vnz,
  specialize eq_n v vnz,
  rw matrix.add_mul_vec,
  rw dot_product_add,

  have add := add_le_add eq_m eq_n,
  rw zero_add at add,

  exact add,
end

end definite

end hermitian

-- Any two eigenvectors corresponding to different eigenvalues are linearly independent
lemma eigenvectors_linearly_independent (M : matrix n n ℂ) (u v : n → ℂ) (e r : ℂ)
(ep_1 : has_eigenpair M e u) (ep_2 : has_eigenpair M r v) (neq : e ≠ r) :
∀ (a b : ℂ), a • u + b • v = 0 → a = 0 ∧ b = 0 :=
begin
  intros a b lcz,
  rw has_eigenpair at ep_1 ep_2,
  cases ep_1 with unz ep_1,
  cases ep_2 with vnz ep_2,

  rw ne at neq vnz unz,

  have mlcz := congr_arg M.mul_vec lcz,

  rw mul_vec_add at mlcz,
  repeat {rw mul_vec_smul at mlcz},
  rw ep_1 at mlcz,
  rw ep_2 at mlcz,
  rw mul_vec_zero at mlcz,

  have elcz := congr_arg (λ x, e • x) lcz,

  dsimp at elcz,
  rw smul_add at elcz,
  rw smul_zero at elcz,
  rw smul_comm at elcz,
  rw ← elcz at mlcz,
  rw add_right_inj at mlcz,
  rw smul_comm at mlcz,

  have key : (r - e) • b • v = 0,
  {
    rw sub_smul,
    rw mlcz,
    rw sub_self,
  },

  rw smul_eq_zero at key,
  cases key with emrz key,
  exfalso,
  apply neq,
  rw sub_eq_zero at emrz,
  rw eq_comm,
  exact emrz,
  rw smul_eq_zero at key,
  rw or_comm at key,
  cases key with vz bz,
  exfalso,
  apply vnz,
  exact vz,
  split,
  swap,
  exact bz,
  rw bz at lcz,
  simp only [zero_smul, add_zero, smul_eq_zero] at lcz,
  cases lcz with az uz,
  exact az,
  exfalso,
  apply unz,
  exact uz,
end

-- Any linear combination (with non-zero coefficients) of two eigenvectors corresponding to different eigenvalues is not an eigenvector
theorem independent_eigenvectors_linear_combination_not_eigenvector (M : matrix n n ℂ) (u v : n → ℂ) (e r : ℂ) (neq : e ≠ r)
(ep_1 : has_eigenpair M e u) (ep_2 : has_eigenpair M r v) : ∀ (a b : ℂ), a ≠ 0 → b ≠ 0 → ¬(has_eigenvector M (a•u+b•v)) :=
begin
  have epeu := ep_1,
  have eprv := ep_2,

  intros a b anz bnz,
  rw ne at anz bnz neq,
  by_contra ev,
  rw has_eigenvector at ev,
  cases ev with t ep,
  rw has_eigenpair at ep,
  cases ep with lc mul,
  rw mul_vec_add at mul,
  rw smul_add at mul,
  repeat {rw mul_vec_smul at mul},

  rw has_eigenpair at ep_1 ep_2,
  cases ep_1 with unz mul_1,
  cases ep_2 with vnz mul_2,
  rw ne at unz vnz,

  rw [mul_1,mul_2] at mul,

  have helper_lemma : a • (e - t) • u + b • (r - t) • v = 0,
  {
    repeat {rw sub_smul},
    repeat {rw smul_sub},
    rw sub_add_sub_comm,
    rw sub_eq_zero,
    rw smul_comm a t,
    rw smul_comm b t,
    exact mul,
    apply_instance,
    apply_instance
  },

  have lin_ind := eigenvectors_linearly_independent M u v e r epeu eprv neq,

  specialize lin_ind (a • (e - t)) (b • (r - t)),

  have ent : ¬(e - t)=0,
  {
    by_contra eeqt,
    rw eeqt at helper_lemma,
    rw [zero_smul, smul_zero, zero_add] at helper_lemma,
    rw smul_eq_zero at helper_lemma,
    cases helper_lemma,
    apply bnz,
    exact helper_lemma,
    rw smul_eq_zero at helper_lemma,
    cases helper_lemma,
    rw sub_eq_zero at eeqt helper_lemma,
    apply neq,
    rw [helper_lemma,eeqt],
    apply vnz,
    exact helper_lemma
  },

  have rnt : ¬(r - t) = 0,
  {
    by_contra reqt,
    rw reqt at helper_lemma,
    rw [zero_smul, smul_zero, add_zero] at helper_lemma,
    rw smul_eq_zero at helper_lemma,
    cases helper_lemma,
    apply anz,
    exact helper_lemma,
    rw smul_eq_zero at helper_lemma,
    cases helper_lemma,
    apply ent,
    exact helper_lemma,
    apply unz,
    exact helper_lemma
  },

  have aemtnz : a • (e - t) ≠ 0,
  {
    rw smul_ne_zero,
    split,
    rwa ne,
    rwa ne
  },

  have brmtnz : b • (r - t) ≠ 0,
  {
    rw smul_ne_zero,
    split,
    rwa ne,
    rwa ne
  },

  repeat {rw smul_assoc at lin_ind},
  have lin_ind_2 := lin_ind helper_lemma,

  repeat {rw smul_assoc at lin_ind_2},

  rw ne at aemtnz brmtnz,
  apply aemtnz,
  cases lin_ind_2 with aemtz brmtz,
  exact aemtz,
end

-- If two matrices both have the eigenpair (e,v) then (e * e,v) is an eigenpair of their product
lemma eigenvalue_prod (M : matrix n n ℂ) (N : matrix n n ℂ)
(v : n → ℂ) (e : ℂ)  (hme : has_eigenpair M e v) (hne : has_eigenpair N e v):
has_eigenpair (M ⬝ N) (e * e) v :=
begin
  cases hme with ve hme,
  cases hne with ve hne,
  rw has_eigenpair,
  split,
  exact ve,
  rw ← mul_vec_mul_vec,
  rw hne,
  rw mul_vec_smul,
  rw hme,
  rw ← smul_assoc,
  rw ← smul_eq_mul,
end

-- (∀ v, u ⬝ v = 0) ↔ u = 0
lemma forall_left_dot_prod_zero_zero (u : n → ℂ) : (∀ v : (n → ℂ) , dot_product u v = 0) ↔ u = 0:=
begin
  split,
  intro dpz,
  funext,
  specialize dpz (λ i , if i = x then 1 else 0),
  rw dot_product at dpz,

  have key : finset.univ.sum (λ (i : n), u i * ite (i = x) 1 0) = finset.univ.sum (λ (i : n), ite (i = x) (u i) 0),
  {
    rw finset.sum_congr,
    refl,
    intros _ _,
    rw mul_ite,
    rw mul_one,
    rw mul_zero,
  },

  rw key at dpz,
  rw finset.sum_ite at dpz,
  simp only [finset.sum_const_zero, add_zero] at dpz,

  have eq_x : (λ (x_1 : n), x_1 = x) = eq x,
  {
    funext,
    rw eq_iff_iff,
    rw eq_comm
  },

  simp_rw eq_x at dpz,

  simp only at dpz,
  rw finset.filter_eq at dpz,
  simp only [finset.mem_univ, if_true, finset.sum_singleton] at dpz,
  rw dpz,
  rw pi.zero_apply,

  intro uz,
  intro v,
  rw uz,
  rw zero_dot_product,
end

-- (∀ v, v ⬝ u = 0) ↔ u = 0
lemma forall_right_dot_prod_zero_zero (u : n → ℂ) : (∀ v : (n → ℂ) , dot_product v u = 0) ↔ u = 0:=
begin
  simp_rw dot_product_comm,
  rw forall_left_dot_prod_zero_zero,
end

-- e is an eigenvalue of M iff det(M - eI)=0
lemma eigenvalue_zero_det (M : matrix n n ℂ) (e : ℂ ) :
has_eigenvalue M e  ↔ ( (M - e • (1 : matrix n n ℂ)).det = 0) :=
begin

  have eigenvector_mul_eq_zero : ∀ (v : n → ℂ), M.mul_vec v = e • v ↔  (M - e • 1).mul_vec v = 0,
  {
    intro v,
    rw sub_mul_vec,
    split,
    intro mul,
    rw mul,
    rw smul_mul_vec_assoc,
    nth_rewrite 0 ← one_mul_vec v,
    rw sub_eq_zero,

    intro sub_zero,
    rw sub_eq_zero at sub_zero,
    rw sub_zero,
    rw smul_mul_vec_assoc,
    rw one_mul_vec,
  },

  rw  ← not_iff_not,
  rw ← ne.def,
  split,
  swap,
  intro dnz,
  by_contra ev,
  rw has_eigenvalue at ev,
  cases ev with v ev,
  rw has_eigenpair at ev,
  cases ev with vnz mul,
  specialize eigenvector_mul_eq_zero v,
  rw eigenvector_mul_eq_zero at mul,

  have vec_zero := eq_zero_of_mul_vec_eq_zero dnz mul,

  apply vnz,
  exact vec_zero,

  intro n_ev,
  rw has_eigenvalue at n_ev,

  rw ne,
  by_contra,

  have det_t : ((M - e • 1)ᵀ).det = 0,
  {
    rw det_transpose,
    exact h,
  },

  have nn : ¬¬((M - e • 1).det = 0) ↔ (M - e • 1).det = 0,
  {rw not_not},

  rw ← nn at h,

  apply h,
  rw ← ne,

  rw ← nondegenerate_iff_det_ne_zero,
  rw nondegenerate,

  intro u,
  intro for_all,

  by_contra unz,

  simp_rw dot_product_mul_vec at for_all,
  rw forall_left_dot_prod_zero_zero at for_all,
  rename for_all vmu,

  have dpz := congr_arg (dot_product (vector_conj u)) vmu,

  rw dot_product_zero at dpz,

  have nn_t : ¬¬((M - e • 1)ᵀ.det = 0) ↔ (M - e • 1)ᵀ.det = 0,
  {rw not_not},

  rw ← nn_t at det_t,
  apply det_t,
  rw ← ne,

  rw ← nondegenerate_iff_det_ne_zero,
  rw nondegenerate,

  intro v,
  intro for_all_t,
  simp_rw dot_product_mul_vec at for_all_t,
  rw forall_left_dot_prod_zero_zero at for_all_t,
  rw vec_mul_trans at for_all_t,
  rw ← eigenvector_mul_eq_zero at for_all_t,
  rw not_exists at n_ev,
  specialize n_ev v,
  by_contra,
  apply n_ev,
  rw has_eigenpair,
  split,
  rwa ne,
  exact for_all_t,
end

-- The (right) eigenpairs of a matrix are the left eigenpairs of the transpose
theorem eigenpair_left_trans (M : matrix n n ℂ) (v : n → ℂ) (e : ℂ):
has_eigenpair M e v ↔ has_left_eigenpair Mᵀ e v:=
begin
  rw has_eigenpair,
  rw has_left_eigenpair,
  rw vec_mul_trans,
end

-- The eigenvalues and left eigenvalues of a matrix coincide
theorem left_eigenvalue_right_eigenvalue (M : matrix n n ℂ) (e : ℂ):
has_eigenvalue M e ↔ has_left_eigenvalue M e :=
begin

  have left_eigenvalue_trans : has_left_eigenvalue M e ↔ has_eigenvalue Mᵀ e,
  {
    rw has_eigenvalue,
    rw has_left_eigenvalue,

    split;
    intro x;
    cases x with v ep;
    use v,
    rw ← transpose_transpose M at ep,
    rw ← eigenpair_left_trans at ep,
    exact ep,
    rw eigenpair_left_trans at ep,
    rw transpose_transpose at ep,
    exact ep,
  },

  nth_rewrite 0 left_eigenvalue_trans,
  repeat {rw eigenvalue_zero_det},

  have tr : (Mᵀ - e • 1) = (M - e • 1)ᵀ,
  {simp only [transpose_sub, transpose_smul, transpose_one]},

  rw tr,
  rw det_transpose,
end

end matrix


/-
Copyright (c) 2020 Kevin Kappelmann. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kevin Kappelmann
-/
import algebra.continued_fractions.continuants_recurrence
import algebra.continued_fractions.terminated_stable
import tactic.field_simp
import tactic.ring

/-!
# Equivalence of Recursive and Direct Computations of `gcf` Convergents

## Summary

We show the equivalence of two computations of convergents (recurrence relation (`convergents`) vs.
direct evaluation (`convergents'`)) for `gcf`s on linear ordered fields. We follow the proof from
[hardy2008introduction], Chapter 10. Here's a sketch:

Let `c` be a continued fraction `[h; (a₀, b₀), (a₁, b₁), (a₂, b₂),...]`, visually:
$$
  c = h + \dfrac{a_0}
                {b_0 + \dfrac{a_1}
                             {b_1 + \dfrac{a_2}
                                          {b_2 + \dfrac{a_3}
                                                       {b_3 + \dots}}}}
$$
One can compute the convergents of `c` in two ways:
1. Directly evaluating the fraction described by `c` up to a given `n` (`convergents'`)
2. Using the recurrence (`convergents`):
  - `A₋₁ = 1,  A₀ = h,  Aₙ = bₙ₋₁ * Aₙ₋₁ + aₙ₋₁ * Aₙ₋₂`, and
  - `B₋₁ = 0,  B₀ = 1,  Bₙ = bₙ₋₁ * Bₙ₋₁ + aₙ₋₁ * Bₙ₋₂`.

To show the equivalence of the computations in the main theorem of this file
`convergents_eq_convergents'`, we proceed by induction. The case `n = 0` is trivial.

For `n + 1`, we first "squash" the `n + 1`th position of `c` into the `n`th position to obtain
another continued fraction
  `c' := [h; (a₀, b₀),..., (aₙ-₁, bₙ-₁), (aₙ, bₙ + aₙ₊₁ / bₙ₊₁), (aₙ₊₁, bₙ₊₁),...]`.
This squashing process is formalised in section `squash`. Note that directly evaluating `c` up to
position `n + 1` is equal to evaluating `c'` up to `n`. This is shown in lemma
`succ_nth_convergent'_eq_squash_gcf_nth_convergent'`.

By the inductive hypothesis, the two computations for the `n`th convergent of `c` coincide.
So all that is left to show is that the recurrence relation for `c` at `n + 1` and and `c'` at
`n` coincide. This can be shown by another induction.
The corresponding lemma in this file is `succ_nth_convergent_eq_squash_gcf_nth_convergent`.

## Main Theorems

- `generalized_continued_fraction.convergents_eq_convergents'` shows the equivalence under a strict
positivity restriction on the sequence.
- `continued_fractions.convergents_eq_convergents'` shows the equivalence for (regular) continued
fractions.

## References

- https://en.wikipedia.org/wiki/Generalized_continued_fraction
- [*Hardy, GH and Wright, EM and Heath-Brown, Roger and Silverman, Joseph*][hardy2008introduction]

## Tags

fractions, recurrence, equivalence
-/

variables {K : Type*} {n : ℕ}
namespace generalized_continued_fraction
open stream.seq as seq

variables {g : generalized_continued_fraction K} {s : seq $ pair K}

section squash
/-!
We will show the equivalence of the computations by induction. To make the induction work, we need
to be able to *squash* the nth and (n + 1)th value of a sequence. This squashing itself and the
lemmas about it are not very interesting. As a reader, you hence might want to skip this section.
-/

section with_division_ring
variable [division_ring K]

/--
Given a sequence of gcf.pairs `s = [(a₀, bₒ), (a₁, b₁), ...]`, `squash_seq s n`
combines `⟨aₙ, bₙ⟩` and `⟨aₙ₊₁, bₙ₊₁⟩` at position `n` to `⟨aₙ, bₙ + aₙ₊₁ / bₙ₊₁⟩`. For example,
`squash_seq s 0 = [(a₀, bₒ + a₁ / b₁), (a₁, b₁),...]`.
If `s.terminated_at (n + 1)`, then `squash_seq s n = s`.
-/
def squash_seq (s : seq $ pair K) (n : ℕ) : seq (pair K) :=
match prod.mk (s.nth n) (s.nth (n + 1)) with
| ⟨some gp_n, some gp_succ_n⟩ := seq.nats.zip_with
    -- return the squashed value at position `n`; otherwise, do nothing.
    (λ n' gp, if n' = n then ⟨gp_n.a, gp_n.b + gp_succ_n.a / gp_succ_n.b⟩ else gp) s
| _ := s
end

/-! We now prove some simple lemmas about the squashed sequence -/

/-- If the sequence already terminated at position `n + 1`, nothing gets squashed. -/
lemma squash_seq_eq_self_of_terminated (terminated_at_succ_n : s.terminated_at (n + 1)) :
  squash_seq s n = s :=
begin
  change s.nth (n + 1) = none at terminated_at_succ_n,
  cases s_nth_eq : (s.nth n);
  simp only [*, squash_seq]
end

/-- If the sequence has not terminated before position `n + 1`, the value at `n + 1` gets
squashed into position `n`. -/
lemma squash_seq_nth_of_not_terminated {gp_n gp_succ_n : pair K}
  (s_nth_eq : s.nth n = some gp_n) (s_succ_nth_eq : s.nth (n + 1) = some gp_succ_n) :
  (squash_seq s n).nth n = some ⟨gp_n.a, gp_n.b + gp_succ_n.a / gp_succ_n.b⟩ :=
by simp [*, squash_seq]

/-- The values before the squashed position stay the same. -/
lemma squash_seq_nth_of_lt {m : ℕ} (m_lt_n : m < n) : (squash_seq s n).nth m = s.nth m :=
begin
  cases s_succ_nth_eq : s.nth (n + 1),
  case option.none { rw (squash_seq_eq_self_of_terminated s_succ_nth_eq) },
  case option.some
  { obtain ⟨gp_n, s_nth_eq⟩ : ∃ gp_n, s.nth n = some gp_n, from
      s.ge_stable n.le_succ s_succ_nth_eq,
    obtain ⟨gp_m, s_mth_eq⟩ : ∃ gp_m, s.nth m = some gp_m, from
      s.ge_stable (le_of_lt m_lt_n) s_nth_eq,
    simp [*, squash_seq, m_lt_n.ne] }
end

/-- Squashing at position `n + 1` and taking the tail is the same as squashing the tail of the
sequence at position `n`. -/
lemma squash_seq_succ_n_tail_eq_squash_seq_tail_n :
  (squash_seq s (n + 1)).tail = squash_seq s.tail n :=
begin
  cases s_succ_succ_nth_eq : s.nth (n + 2) with gp_succ_succ_n,
  case option.none
  { have : squash_seq s (n + 1) = s, from squash_seq_eq_self_of_terminated s_succ_succ_nth_eq,
    cases s_succ_nth_eq : (s.nth (n + 1));
    simp only [squash_seq, seq.nth_tail, s_succ_nth_eq, s_succ_succ_nth_eq] },
  case option.some
  { obtain ⟨gp_succ_n, s_succ_nth_eq⟩ : ∃ gp_succ_n, s.nth (n + 1) = some gp_succ_n, from
      s.ge_stable (n + 1).le_succ s_succ_succ_nth_eq,
    -- apply extensionality with `m` and continue by cases `m = n`.
    ext1 m,
    cases decidable.em (m = n) with m_eq_n m_ne_n,
    { have : s.tail.nth n = some gp_succ_n, from (s.nth_tail n).trans s_succ_nth_eq,
      simp [*, squash_seq] },
    { have : s.tail.nth m = s.nth (m + 1), from s.nth_tail m,
      cases s_succ_mth_eq : s.nth (m + 1),
      all_goals { have s_tail_mth_eq, from this.trans s_succ_mth_eq },
      { simp only [*, squash_seq, seq.nth_tail, seq.nth_zip_with, option.map₂_none_right] },
      { simp [*, squash_seq] } } }
end

/-- The auxiliary function `convergents'_aux` returns the same value for a sequence and the
corresponding squashed sequence at the squashed position. -/
lemma succ_succ_nth_convergent'_aux_eq_succ_nth_convergent'_aux_squash_seq :
  convergents'_aux s (n + 2) = convergents'_aux (squash_seq s n) (n + 1) :=
begin
  cases s_succ_nth_eq : (s.nth $ n + 1) with gp_succ_n,
  case option.none
  { rw [(squash_seq_eq_self_of_terminated s_succ_nth_eq),
        (convergents'_aux_stable_step_of_terminated s_succ_nth_eq)] },
  case option.some
  { induction n with m IH generalizing s gp_succ_n,
    case nat.zero
    { obtain ⟨gp_head, s_head_eq⟩ : ∃ gp_head, s.head = some gp_head, from
        s.ge_stable zero_le_one s_succ_nth_eq,
      have : (squash_seq s 0).head = some ⟨gp_head.a, gp_head.b + gp_succ_n.a / gp_succ_n.b⟩,
        from squash_seq_nth_of_not_terminated s_head_eq s_succ_nth_eq,
      simp [*, convergents'_aux, seq.head, seq.nth_tail] },
    case nat.succ
    { obtain ⟨gp_head, s_head_eq⟩ : ∃ gp_head, s.head = some gp_head, from
        s.ge_stable (m + 2).zero_le s_succ_nth_eq,
      suffices : gp_head.a / (gp_head.b + convergents'_aux s.tail (m + 2))
               = convergents'_aux (squash_seq s (m + 1)) (m + 2), by
        simpa only [convergents'_aux, s_head_eq],
      have : convergents'_aux s.tail (m + 2) = convergents'_aux (squash_seq s.tail m) (m + 1), by
      { refine (IH gp_succ_n _),
        simpa [seq.nth_tail] using s_succ_nth_eq },
      have : (squash_seq s (m + 1)).head = some gp_head, from
        (squash_seq_nth_of_lt m.succ_pos).trans s_head_eq,
      simp only [*, convergents'_aux, squash_seq_succ_n_tail_eq_squash_seq_tail_n] } }
end

/-! Let us now lift the squashing operation to gcfs. -/

/--
Given a gcf `g = [h; (a₀, bₒ), (a₁, b₁), ...]`, we have
- `squash_nth.gcf g 0 = [h + a₀ / b₀); (a₀, bₒ), ...]`,
- `squash_nth.gcf g (n + 1) = ⟨g.h, squash_seq g.s n⟩`
-/
def squash_gcf (g : generalized_continued_fraction K) : ℕ → generalized_continued_fraction K
| 0 := match g.s.nth 0 with
  | none := g
  | some gp := ⟨g.h + gp.a / gp.b, g.s⟩
  end
| (n + 1) := ⟨g.h, squash_seq g.s n⟩

/-! Again, we derive some simple lemmas that are not really of interest. This time for the
squashed gcf. -/

/-- If the gcf already terminated at position `n`, nothing gets squashed. -/
lemma squash_gcf_eq_self_of_terminated (terminated_at_n : terminated_at g n) :
  squash_gcf g n = g :=
begin
  cases n,
  case nat.zero
  { change g.s.nth 0 = none at terminated_at_n,
    simp only [convergents', squash_gcf, convergents'_aux, terminated_at_n] },
  case nat.succ
  { cases g, simp [(squash_seq_eq_self_of_terminated terminated_at_n), squash_gcf] }
end

/-- The values before the squashed position stay the same. -/
lemma squash_gcf_nth_of_lt {m : ℕ} (m_lt_n : m < n) :
  (squash_gcf g (n + 1)).s.nth m = g.s.nth m :=
by simp only [squash_gcf, (squash_seq_nth_of_lt m_lt_n)]

/-- `convergents'` returns the same value for a gcf and the corresponding squashed gcf at the
squashed position. -/
lemma succ_nth_convergent'_eq_squash_gcf_nth_convergent' :
  g.convergents' (n + 1) = (squash_gcf g n).convergents' n :=
begin
  cases n,
  case nat.zero
  { cases g_s_head_eq : (g.s.nth 0);
    simp [g_s_head_eq, squash_gcf, convergents', convergents'_aux, seq.head] },
  case nat.succ
  { simp only [succ_succ_nth_convergent'_aux_eq_succ_nth_convergent'_aux_squash_seq,
      convergents', squash_gcf] }
end

/-- The auxiliary continuants before the squashed position stay the same. -/
lemma continuants_aux_eq_continuants_aux_squash_gcf_of_le {m : ℕ} :
  m ≤ n → continuants_aux g m = (squash_gcf g n).continuants_aux m :=
nat.strong_induction_on m
(begin
  clear m,
  assume m IH m_le_n,
  cases m with m',
  { refl },
  { cases n with n',
    { exact (m'.not_succ_le_zero m_le_n).elim }, -- 1 ≰ 0
    { cases m' with m'',
      { refl },
      { -- get some inequalities to instantiate the IH for m'' and m'' + 1
        have m'_lt_n : m'' + 1 < n' + 1 := m_le_n,
        have succ_m''th_conts_aux_eq := IH (m'' + 1) (lt_add_one (m'' + 1)) m'_lt_n.le,
        have : m'' < m'' + 2 := lt_add_of_pos_right m'' zero_lt_two,
        have m''th_conts_aux_eq := IH m'' this (le_trans this.le m_le_n),
        have : (squash_gcf g (n' + 1)).s.nth m'' = g.s.nth m'', from
          squash_gcf_nth_of_lt (nat.succ_lt_succ_iff.mp m'_lt_n),
        simp [continuants_aux, succ_m''th_conts_aux_eq, m''th_conts_aux_eq, this] } } }
end)

end with_division_ring

/-- The convergents coincide in the expected way at the squashed position if the partial denominator
at the squashed position is not zero. -/
lemma succ_nth_convergent_eq_squash_gcf_nth_convergent [field K]
  (nth_part_denom_ne_zero : ∀ {b : K}, g.partial_denominators.nth n = some b → b ≠ 0) :
  g.convergents (n + 1) = (squash_gcf g n).convergents n :=
begin
  cases decidable.em (g.terminated_at n) with terminated_at_n not_terminated_at_n,
  { have : squash_gcf g n = g, from squash_gcf_eq_self_of_terminated terminated_at_n,
    simp only [this, (convergents_stable_of_terminated n.le_succ terminated_at_n)] },
  { obtain ⟨⟨a, b⟩, s_nth_eq⟩ : ∃ gp_n, g.s.nth n = some gp_n, from
      option.ne_none_iff_exists'.mp not_terminated_at_n,
    have b_ne_zero : b ≠ 0, from nth_part_denom_ne_zero (part_denom_eq_s_b s_nth_eq),
    cases n with n',
    case nat.zero
    { suffices : (b * g.h + a) / b = g.h + a / b, by
        simpa [squash_gcf, s_nth_eq, convergent_eq_conts_a_div_conts_b,
          (continuants_recurrence_aux s_nth_eq zeroth_continuant_aux_eq_one_zero
          first_continuant_aux_eq_h_one)],
      calc
        (b * g.h + a) / b = b * g.h / b + a / b  : by ring -- requires `field`, not `division_ring`
                      ... = g.h + a / b          : by rw (mul_div_cancel_left _ b_ne_zero) },
    case nat.succ
    { obtain ⟨⟨pa, pb⟩, s_n'th_eq⟩ : ∃ gp_n', g.s.nth n' = some gp_n' :=
        g.s.ge_stable n'.le_succ s_nth_eq,
      -- Notations
      let g' := squash_gcf g (n' + 1),
      set pred_conts := g.continuants_aux (n' + 1) with succ_n'th_conts_aux_eq,
      set ppred_conts := g.continuants_aux n' with n'th_conts_aux_eq,
      let pA := pred_conts.a, let pB := pred_conts.b,
      let ppA := ppred_conts.a, let ppB := ppred_conts.b,
      set pred_conts' := g'.continuants_aux (n' + 1) with succ_n'th_conts_aux_eq',
      set ppred_conts' := g'.continuants_aux n' with n'th_conts_aux_eq',
      let pA' := pred_conts'.a, let pB' := pred_conts'.b, let ppA' := ppred_conts'.a,
      let ppB' := ppred_conts'.b,
      -- first compute the convergent of the squashed gcf
      have : g'.convergents (n' + 1)
           = ((pb + a / b) * pA' + pa * ppA') / ((pb + a / b) * pB' + pa * ppB'),
      { have : g'.s.nth n' = some ⟨pa, pb + a / b⟩ :=
          squash_seq_nth_of_not_terminated s_n'th_eq s_nth_eq,
        rw [convergent_eq_conts_a_div_conts_b,
          continuants_recurrence_aux this n'th_conts_aux_eq'.symm succ_n'th_conts_aux_eq'.symm], },
      rw this,
      -- then compute the convergent of the original gcf by recursively unfolding the continuants
      -- computation twice
      have : g.convergents (n' + 2)
           = (b * (pb * pA + pa * ppA) + a * pA) / (b * (pb * pB + pa * ppB) + a * pB),
      { -- use the recurrence once
        have : g.continuants_aux (n' + 2) = ⟨pb * pA + pa * ppA, pb * pB + pa * ppB⟩ :=
          continuants_aux_recurrence s_n'th_eq n'th_conts_aux_eq.symm succ_n'th_conts_aux_eq.symm,
        -- and a second time
        rw [convergent_eq_conts_a_div_conts_b,
          continuants_recurrence_aux s_nth_eq succ_n'th_conts_aux_eq.symm this] },
      rw this,
      suffices : ((pb + a / b) * pA + pa * ppA) / ((pb + a / b) * pB + pa * ppB)
               = (b * (pb * pA + pa * ppA) + a * pA) / (b * (pb * pB + pa * ppB) + a * pB),
      { obtain ⟨eq1, eq2, eq3, eq4⟩ : pA' = pA ∧ pB' = pB ∧ ppA' = ppA ∧ ppB' = ppB,
        { simp [*, (continuants_aux_eq_continuants_aux_squash_gcf_of_le $ le_refl $ n' + 1).symm,
            (continuants_aux_eq_continuants_aux_squash_gcf_of_le n'.le_succ).symm] },
        symmetry,
        simpa only [eq1, eq2, eq3, eq4, mul_div_cancel _  b_ne_zero] },
      field_simp,
      congr' 1; ring } }
end

end squash

/-- Shows that the recurrence relation (`convergents`) and direct evaluation (`convergents'`) of the
gcf coincide at position `n` if the sequence of fractions contains strictly positive values only.
Requiring positivity of all values is just one possible condition to obtain this result.
For example, the dual - sequences with strictly negative values only - would also work.

In practice, one most commonly deals with (regular) continued fractions, which satisfy the
positivity criterion required here. The analogous result for them
(see `continued_fractions.convergents_eq_convergents`) hence follows directly from this theorem.
-/
theorem convergents_eq_convergents' [linear_ordered_field K]
  (s_pos : ∀ {gp : pair K} {m : ℕ}, m < n → g.s.nth m = some gp → 0 < gp.a ∧ 0 < gp.b) :
  g.convergents n = g.convergents' n :=
begin
  induction n with n IH generalizing g,
  case nat.zero { simp },
  case nat.succ
  { let g' := squash_gcf g n, -- first replace the rhs with the squashed computation
    suffices : g.convergents (n + 1) = g'.convergents' n, by
      rwa [succ_nth_convergent'_eq_squash_gcf_nth_convergent'],
    cases decidable.em (terminated_at g n) with terminated_at_n not_terminated_at_n,
    { have g'_eq_g : g' = g, from squash_gcf_eq_self_of_terminated terminated_at_n,
      rw [(convergents_stable_of_terminated n.le_succ terminated_at_n), g'_eq_g, (IH _)],
      assume _ _ m_lt_n s_mth_eq, exact (s_pos (nat.lt.step m_lt_n) s_mth_eq) },
    { suffices : g.convergents (n + 1) = g'.convergents n, by -- invoke the IH for the squashed gcf
      { rwa ← IH,
        assume gp' m m_lt_n s_mth_eq',
        -- case distinction on m + 1 = n or m + 1 < n
        cases m_lt_n with n succ_m_lt_n,
        { -- the difficult case at the squashed position: we first obtain the values from
          -- the sequence
          obtain ⟨gp_succ_m, s_succ_mth_eq⟩ : ∃ gp_succ_m, g.s.nth (m + 1) = some gp_succ_m, from
            option.ne_none_iff_exists'.mp not_terminated_at_n,
          obtain ⟨gp_m, mth_s_eq⟩ : ∃ gp_m, g.s.nth m = some gp_m, from
            g.s.ge_stable m.le_succ s_succ_mth_eq,
          -- we then plug them into the recurrence
          suffices : 0 < gp_m.a ∧ 0 < gp_m.b + gp_succ_m.a / gp_succ_m.b, by
          { have ot : g'.s.nth m = some ⟨gp_m.a, gp_m.b + gp_succ_m.a / gp_succ_m.b⟩, from
              squash_seq_nth_of_not_terminated mth_s_eq s_succ_mth_eq,
            have : gp' = ⟨gp_m.a, gp_m.b + gp_succ_m.a / gp_succ_m.b⟩, by cc,
            rwa this },
          refine ⟨(s_pos (nat.lt.step m_lt_n) mth_s_eq).left, _⟩,
          refine add_pos (s_pos (nat.lt.step m_lt_n) mth_s_eq).right _,
          have : 0 < gp_succ_m.a ∧ 0 < gp_succ_m.b := s_pos (lt_add_one $ m + 1) s_succ_mth_eq,
          exact (div_pos this.left this.right) },
        { -- the easy case: before the squashed position, nothing changes
          refine s_pos (nat.lt.step $ nat.lt.step succ_m_lt_n) _,
          exact eq.trans (squash_gcf_nth_of_lt succ_m_lt_n).symm s_mth_eq' } },
      -- now the result follows from the fact that the convergents coincide at the squashed position
      -- as established in `succ_nth_convergent_eq_squash_gcf_nth_convergent`.
      have : ∀ ⦃b⦄, g.partial_denominators.nth n = some b → b ≠ 0, by
      { assume b nth_part_denom_eq,
        obtain ⟨gp, s_nth_eq, ⟨refl⟩⟩ : ∃ gp, g.s.nth n = some gp ∧ gp.b = b, from
          exists_s_b_of_part_denom nth_part_denom_eq,
        exact (ne_of_lt (s_pos (lt_add_one n) s_nth_eq).right).symm },
      exact succ_nth_convergent_eq_squash_gcf_nth_convergent this } }
end

end generalized_continued_fraction

open generalized_continued_fraction

namespace continued_fraction

/-- Shows that the recurrence relation (`convergents`) and direct evaluation (`convergents'`) of a
(regular) continued fraction coincide. -/
theorem convergents_eq_convergents' [linear_ordered_field K] {c : continued_fraction K} :
  (↑c : generalized_continued_fraction K).convergents =
    (↑c : generalized_continued_fraction K).convergents' :=
begin
  ext n,
  apply convergents_eq_convergents',
  assume gp m m_lt_n s_nth_eq,
  exact ⟨zero_lt_one.trans_le ((c : simple_continued_fraction K).property m gp.a
      (part_num_eq_s_a s_nth_eq)).symm.le,
    c.property m gp.b $ part_denom_eq_s_b s_nth_eq⟩
end

end continued_fraction


/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Mario Carneiro, Kevin Buzzard, Yury Kudryashov
-/
import algebra.module.submodule.basic
import algebra.punit_instances

/-!
# The lattice structure on `submodule`s

This file defines the lattice structure on submodules, `submodule.complete_lattice`, with `⊥`
defined as `{0}` and `⊓` defined as intersection of the underlying carrier.
If `p` and `q` are submodules of a module, `p ≤ q` means that `p ⊆ q`.

Many results about operations on this lattice structure are defined in `linear_algebra/basic.lean`,
most notably those which use `span`.

## Implementation notes

This structure should match the `add_submonoid.complete_lattice` structure, and we should try
to unify the APIs where possible.

-/

variables {R S M : Type*}

section add_comm_monoid
variables [semiring R] [semiring S] [add_comm_monoid M] [module R M] [module S M]
variables [has_smul S R] [is_scalar_tower S R M]
variables {p q : submodule R M}

namespace submodule

/-- The set `{0}` is the bottom element of the lattice of submodules. -/
instance : has_bot (submodule R M) :=
⟨{ carrier := {0}, smul_mem' := by simp { contextual := tt }, .. (⊥ : add_submonoid M)}⟩

instance inhabited' : inhabited (submodule R M) := ⟨⊥⟩

@[simp] lemma bot_coe : ((⊥ : submodule R M) : set M) = {0} := rfl
@[simp] lemma bot_to_add_submonoid : (⊥ : submodule R M).to_add_submonoid = ⊥ := rfl

section
variables (R)
@[simp] lemma restrict_scalars_bot : restrict_scalars S (⊥ : submodule R M) = ⊥ := rfl

@[simp] lemma mem_bot {x : M} : x ∈ (⊥ : submodule R M) ↔ x = 0 := set.mem_singleton_iff
end

@[simp] lemma restrict_scalars_eq_bot_iff {p : submodule R M} :
  restrict_scalars S p = ⊥ ↔ p = ⊥ :=
by simp [set_like.ext_iff]

instance unique_bot : unique (⊥ : submodule R M) :=
⟨infer_instance, λ x, subtype.ext $ (mem_bot R).1 x.mem⟩

instance : order_bot (submodule R M) :=
{ bot := ⊥,
  bot_le := λ p x, by simp [zero_mem] {contextual := tt} }

protected lemma eq_bot_iff (p : submodule R M) : p = ⊥ ↔ ∀ x ∈ p, x = (0 : M) :=
⟨ λ h, h.symm ▸ λ x hx, (mem_bot R).mp hx,
  λ h, eq_bot_iff.mpr (λ x hx, (mem_bot R).mpr (h x hx)) ⟩

@[ext] protected lemma bot_ext (x y : (⊥ : submodule R M)) : x = y :=
begin
  rcases x with ⟨x, xm⟩, rcases y with ⟨y, ym⟩, congr,
  rw (submodule.eq_bot_iff _).mp rfl x xm,
  rw (submodule.eq_bot_iff _).mp rfl y ym,
end

protected lemma ne_bot_iff (p : submodule R M) : p ≠ ⊥ ↔ ∃ x ∈ p, x ≠ (0 : M) :=
by { haveI := classical.prop_decidable, simp_rw [ne.def, p.eq_bot_iff, not_forall] }

lemma nonzero_mem_of_bot_lt {p : submodule R M} (bot_lt : ⊥ < p) : ∃ a : p, a ≠ 0 :=
let ⟨b, hb₁, hb₂⟩ := p.ne_bot_iff.mp bot_lt.ne' in ⟨⟨b, hb₁⟩, hb₂ ∘ (congr_arg coe)⟩

lemma exists_mem_ne_zero_of_ne_bot {p : submodule R M} (h : p ≠ ⊥) : ∃ b : M, b ∈ p ∧ b ≠ 0 :=
let ⟨b, hb₁, hb₂⟩ := p.ne_bot_iff.mp h in ⟨b, hb₁, hb₂⟩

/-- The bottom submodule is linearly equivalent to punit as an `R`-module. -/
@[simps] def bot_equiv_punit : (⊥ : submodule R M) ≃ₗ[R] punit :=
{ to_fun := λ x, punit.star,
  inv_fun := λ x, 0,
  map_add' := by { intros, ext, },
  map_smul' := by { intros, ext, },
  left_inv := by { intro x, ext, },
  right_inv := by { intro x, ext, }, }

lemma eq_bot_of_subsingleton (p : submodule R M) [subsingleton p] : p = ⊥ :=
begin
  rw eq_bot_iff,
  intros v hv,
  exact congr_arg coe (subsingleton.elim (⟨v, hv⟩ : p) 0)
end

/-- The universal set is the top element of the lattice of submodules. -/
instance : has_top (submodule R M) :=
⟨{ carrier := set.univ, smul_mem' := λ _ _ _, trivial, .. (⊤ : add_submonoid M)}⟩

@[simp] lemma top_coe : ((⊤ : submodule R M) : set M) = set.univ := rfl

@[simp] lemma top_to_add_submonoid : (⊤ : submodule R M).to_add_submonoid = ⊤ := rfl

@[simp] lemma mem_top {x : M} : x ∈ (⊤ : submodule R M) := trivial

section
variables (R)
@[simp] lemma restrict_scalars_top : restrict_scalars S (⊤ : submodule R M) = ⊤ := rfl
end

@[simp] lemma restrict_scalars_eq_top_iff {p : submodule R M} :
  restrict_scalars S p = ⊤ ↔ p = ⊤ :=
by simp [set_like.ext_iff]

instance : order_top (submodule R M) :=
{ top := ⊤,
  le_top := λ p x _, trivial }

lemma eq_top_iff' {p : submodule R M} : p = ⊤ ↔ ∀ x, x ∈ p :=
eq_top_iff.trans ⟨λ h x, h trivial, λ h x _, h x⟩

/-- The top submodule is linearly equivalent to the module.

This is the module version of `add_submonoid.top_equiv`. -/
@[simps] def top_equiv : (⊤ : submodule R M) ≃ₗ[R] M :=
{ to_fun := λ x, x,
  inv_fun := λ x, ⟨x, by simp⟩,
  map_add' := by { intros, refl, },
  map_smul' := by { intros, refl, },
  left_inv := by { intro x, ext, refl, },
  right_inv := by { intro x, refl, }, }

instance : has_Inf (submodule R M) :=
⟨λ S,
{ carrier   := ⋂ s ∈ S, (s : set M),
  zero_mem' := by simp [zero_mem],
  add_mem'  := by simp [add_mem] {contextual := tt},
  smul_mem' := by simp [smul_mem] {contextual := tt} }⟩

private lemma Inf_le' {S : set (submodule R M)} {p} : p ∈ S → Inf S ≤ p :=
set.bInter_subset_of_mem

private lemma le_Inf' {S : set (submodule R M)} {p} : (∀q ∈ S, p ≤ q) → p ≤ Inf S :=
set.subset_Inter₂

instance : has_inf (submodule R M) :=
⟨λ p q,
{ carrier   := p ∩ q,
  zero_mem' := by simp [zero_mem],
  add_mem'  := by simp [add_mem] {contextual := tt},
  smul_mem' := by simp [smul_mem] {contextual := tt} }⟩

instance : complete_lattice (submodule R M) :=
{ sup          := λ a b, Inf {x | a ≤ x ∧ b ≤ x},
  le_sup_left  := λ a b, le_Inf' $ λ x ⟨ha, hb⟩, ha,
  le_sup_right := λ a b, le_Inf' $ λ x ⟨ha, hb⟩, hb,
  sup_le       := λ a b c h₁ h₂, Inf_le' ⟨h₁, h₂⟩,
  inf          := (⊓),
  le_inf       := λ a b c, set.subset_inter,
  inf_le_left  := λ a b, set.inter_subset_left _ _,
  inf_le_right := λ a b, set.inter_subset_right _ _,
  Sup          := λtt, Inf {t | ∀t'∈tt, t' ≤ t},
  le_Sup       := λ s p hs, le_Inf' $ λ q hq, hq _ hs,
  Sup_le       := λ s p hs, Inf_le' hs,
  Inf          := Inf,
  le_Inf       := λ s a, le_Inf',
  Inf_le       := λ s a, Inf_le',
  ..submodule.order_top,
  ..submodule.order_bot,
  ..set_like.partial_order }

@[simp] theorem inf_coe : ↑(p ⊓ q) = (p ∩ q : set M) := rfl

@[simp] theorem mem_inf {p q : submodule R M} {x : M} :
  x ∈ p ⊓ q ↔ x ∈ p ∧ x ∈ q := iff.rfl

@[simp] theorem Inf_coe (P : set (submodule R M)) : (↑(Inf P) : set M) = ⋂ p ∈ P, ↑p := rfl

@[simp] theorem finset_inf_coe {ι} (s : finset ι) (p : ι → submodule R M) :
  (↑(s.inf p) : set M) = ⋂ i ∈ s, ↑(p i) :=
begin
  letI := classical.dec_eq ι,
  refine s.induction_on _ (λ i s hi ih, _),
  { simp },
  { rw [finset.inf_insert, inf_coe, ih],
    simp },
end

@[simp] theorem infi_coe {ι} (p : ι → submodule R M) :
  (↑⨅ i, p i : set M) = ⋂ i, ↑(p i) :=
by rw [infi, Inf_coe]; ext a; simp; exact
⟨λ h i, h _ i rfl, λ h i x e, e ▸ h _⟩

@[simp] lemma mem_Inf {S : set (submodule R M)} {x : M} : x ∈ Inf S ↔ ∀ p ∈ S, x ∈ p :=
set.mem_Inter₂

@[simp] theorem mem_infi {ι} (p : ι → submodule R M) {x} :
  x ∈ (⨅ i, p i) ↔ ∀ i, x ∈ p i :=
by rw [← set_like.mem_coe, infi_coe, set.mem_Inter]; refl

@[simp] theorem mem_finset_inf {ι} {s : finset ι} {p : ι → submodule R M} {x : M} :
  x ∈ s.inf p ↔ ∀ i ∈ s, x ∈ p i :=
by simp only [← set_like.mem_coe, finset_inf_coe, set.mem_Inter]

lemma mem_sup_left {S T : submodule R M} : ∀ {x : M}, x ∈ S → x ∈ S ⊔ T :=
show S ≤ S ⊔ T, from le_sup_left

lemma mem_sup_right {S T : submodule R M} : ∀ {x : M}, x ∈ T → x ∈ S ⊔ T :=
show T ≤ S ⊔ T, from le_sup_right

lemma add_mem_sup {S T : submodule R M} {s t : M} (hs : s ∈ S) (ht : t ∈ T) : s + t ∈ S ⊔ T :=
add_mem (mem_sup_left hs) (mem_sup_right ht)

lemma mem_supr_of_mem {ι : Sort*} {b : M} {p : ι → submodule R M} (i : ι) (h : b ∈ p i) :
  b ∈ (⨆i, p i) :=
have p i ≤ (⨆i, p i) := le_supr p i,
@this b h

open_locale big_operators

lemma sum_mem_supr {ι : Type*} [fintype ι] {f : ι → M} {p : ι → submodule R M}
  (h : ∀ i, f i ∈ p i) :
  ∑ i, f i ∈ ⨆ i, p i :=
sum_mem $ λ i hi, mem_supr_of_mem i (h i)

lemma sum_mem_bsupr {ι : Type*} {s : finset ι} {f : ι → M} {p : ι → submodule R M}
  (h : ∀ i ∈ s, f i ∈ p i) :
  ∑ i in s, f i ∈ ⨆ i ∈ s, p i :=
sum_mem $ λ i hi, mem_supr_of_mem i $ mem_supr_of_mem hi (h i hi)

/-! Note that `submodule.mem_supr` is provided in `linear_algebra/basic.lean`. -/

lemma mem_Sup_of_mem {S : set (submodule R M)} {s : submodule R M}
  (hs : s ∈ S) : ∀ {x : M}, x ∈ s → x ∈ Sup S :=
show s ≤ Sup S, from le_Sup hs

theorem disjoint_def {p p' : submodule R M} :
  disjoint p p' ↔ ∀ x ∈ p, x ∈ p' → x = (0:M) :=
show (∀ x, x ∈ p ∧ x ∈ p' → x ∈ ({0} : set M)) ↔ _, by simp

theorem disjoint_def' {p p' : submodule R M} :
  disjoint p p' ↔ ∀ (x ∈ p) (y ∈ p'), x = y → x = (0:M) :=
disjoint_def.trans ⟨λ h x hx y hy hxy, h x hx $ hxy.symm ▸ hy,
  λ h x hx hx', h _ hx x hx' rfl⟩

lemma eq_zero_of_coe_mem_of_disjoint (hpq : disjoint p q) {a : p} (ha : (a : M) ∈ q) :
  a = 0 :=
by exact_mod_cast disjoint_def.mp hpq a (coe_mem a) ha

end submodule

section nat_submodule

/-- An additive submonoid is equivalent to a ℕ-submodule. -/
def add_submonoid.to_nat_submodule : add_submonoid M ≃o submodule ℕ M :=
{ to_fun := λ S,
  { smul_mem' := λ r s hs, show r • s ∈ S, from nsmul_mem hs _, ..S },
  inv_fun := submodule.to_add_submonoid,
  left_inv := λ ⟨S, _, _⟩, rfl,
  right_inv := λ ⟨S, _, _, _⟩, rfl,
  map_rel_iff' := λ a b, iff.rfl }

@[simp]
lemma add_submonoid.to_nat_submodule_symm :
  ⇑(add_submonoid.to_nat_submodule.symm : _ ≃o add_submonoid M) = submodule.to_add_submonoid := rfl

@[simp]
lemma add_submonoid.coe_to_nat_submodule (S : add_submonoid M) :
  (S.to_nat_submodule : set M) = S := rfl

@[simp]
lemma add_submonoid.to_nat_submodule_to_add_submonoid (S : add_submonoid M) :
  S.to_nat_submodule.to_add_submonoid = S :=
add_submonoid.to_nat_submodule.symm_apply_apply S

@[simp]
lemma submodule.to_add_submonoid_to_nat_submodule (S : submodule ℕ M) :
  S.to_add_submonoid.to_nat_submodule = S :=
add_submonoid.to_nat_submodule.apply_symm_apply S

end nat_submodule

end add_comm_monoid

section int_submodule

variables [add_comm_group M]

/-- An additive subgroup is equivalent to a ℤ-submodule. -/
def add_subgroup.to_int_submodule : add_subgroup M ≃o submodule ℤ M :=
{ to_fun := λ S,
  { smul_mem' := λ r s hs, S.zsmul_mem hs _, ..S},
  inv_fun := submodule.to_add_subgroup,
  left_inv := λ ⟨S, _, _, _⟩, rfl,
  right_inv := λ ⟨S, _, _, _⟩, rfl,
  map_rel_iff' := λ a b, iff.rfl }

@[simp]
lemma add_subgroup.to_int_submodule_symm :
  ⇑(add_subgroup.to_int_submodule.symm : _ ≃o add_subgroup M) = submodule.to_add_subgroup := rfl

@[simp]
lemma add_subgroup.coe_to_int_submodule (S : add_subgroup M) :
  (S.to_int_submodule : set M) = S := rfl

@[simp]
lemma add_subgroup.to_int_submodule_to_add_subgroup (S : add_subgroup M) :
  S.to_int_submodule.to_add_subgroup = S :=
add_subgroup.to_int_submodule.symm_apply_apply S

@[simp]
lemma submodule.to_add_subgroup_to_int_submodule (S : submodule ℤ M) :
  S.to_add_subgroup.to_int_submodule = S :=
add_subgroup.to_int_submodule.apply_symm_apply S

end int_submodule