/-
Copyright (c) 2014 Floris van Doorn (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn, Leonardo de Moura, Jeremy Avigad, Mario Carneiro
-/
import data.nat.basic
import algebra.group.units

/-! # The units of the natural numbers as a `monoid` and `add_monoid`

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

namespace nat

theorem units_eq_one (u : ℕˣ) : u = 1 :=
units.ext $ nat.eq_one_of_dvd_one ⟨u.inv, u.val_inv.symm⟩

theorem add_units_eq_zero (u : add_units ℕ) : u = 0 :=
add_units.ext $ (nat.eq_zero_of_add_eq_zero u.val_neg).1

@[simp] protected theorem is_unit_iff {n : ℕ} : is_unit n ↔ n = 1 :=
iff.intro
  (λ ⟨u, hu⟩, match n, u, hu, nat.units_eq_one u with _, _, rfl, rfl := rfl end)
  (λ h, h.symm ▸ ⟨1, rfl⟩)

instance unique_units : unique ℕˣ :=
{ default := 1, uniq := nat.units_eq_one }

instance unique_add_units : unique (add_units ℕ) :=
{ default := 0, uniq := nat.add_units_eq_zero }

end nat


/-
Copyright (c) 2021 Lu-Ming Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Lu-Ming Zhang
-/
import data.matrix.basic

/-!
# Orthogonal

This file contains definitions and properties concerning orthogonality of rows and columns.

## Main results

- `matrix.has_orthogonal_rows`:
  `A.has_orthogonal_rows` means `A` has orthogonal (with respect to `dot_product`) rows.
- `matrix.has_orthogonal_cols`:
  `A.has_orthogonal_cols` means `A` has orthogonal (with respect to `dot_product`) columns.

## Tags

orthogonal
-/

namespace matrix

variables {α n m : Type*}
variables [has_mul α] [add_comm_monoid α]
variables (A : matrix m n α)

open_locale matrix

/-- `A.has_orthogonal_rows` means matrix `A` has orthogonal rows (with respect to
`matrix.dot_product`). -/
def has_orthogonal_rows [fintype n] : Prop :=
∀ ⦃i₁ i₂⦄, i₁ ≠ i₂ → dot_product (A i₁) (A i₂) = 0

/-- `A.has_orthogonal_rows` means matrix `A` has orthogonal columns (with respect to
`matrix.dot_product`). -/
def has_orthogonal_cols [fintype m] : Prop :=
has_orthogonal_rows Aᵀ

/-- `Aᵀ` has orthogonal rows iff `A` has orthogonal columns. -/
@[simp] lemma transpose_has_orthogonal_rows_iff_has_orthogonal_cols [fintype m] :
  Aᵀ.has_orthogonal_rows ↔ A.has_orthogonal_cols :=
iff.rfl

/-- `Aᵀ` has orthogonal columns iff `A` has orthogonal rows. -/
@[simp] lemma transpose_has_orthogonal_cols_iff_has_orthogonal_rows [fintype n] :
  Aᵀ.has_orthogonal_cols ↔ A.has_orthogonal_rows :=
iff.rfl

variables {A}

lemma has_orthogonal_rows.has_orthogonal_cols
  [fintype m] (h : Aᵀ.has_orthogonal_rows) :
  A.has_orthogonal_cols := h

lemma has_orthogonal_cols.transpose_has_orthogonal_rows
  [fintype m] (h : A.has_orthogonal_cols) :
  Aᵀ.has_orthogonal_rows := h

lemma has_orthogonal_cols.has_orthogonal_rows
  [fintype n] (h : Aᵀ.has_orthogonal_cols) :
  A.has_orthogonal_rows := h

lemma has_orthogonal_rows.transpose_has_orthogonal_cols
  [fintype n] (h : A.has_orthogonal_rows) :
  Aᵀ.has_orthogonal_cols := h

end matrix


/-
Copyright (c) 2014 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Floris van Doorn, Amelia Livingston, Yury Kudryashov,
Neil Strickland, Aaron Anderson

! This file was ported from Lean 3 source module algebra.divisibility.units
! leanprover-community/mathlib commit 448144f7ae193a8990cb7473c9e9a01990f64ac7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Divisibility.Basic
import Mathbin.Algebra.Group.Units

/-!
# Lemmas about divisibility and units

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

variable {α : Type _}

namespace Units

section Monoid

variable [Monoid α] {a b : α} {u : αˣ}

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

/- warning: units.dvd_mul_right -> Units.dvd_mul_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α _inst_1}, Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) b ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} α _inst_1) α (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} α _inst_1) α (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} α _inst_1) α (coeBase.{succ u1, succ u1} (Units.{u1} α _inst_1) α (Units.hasCoe.{u1} α _inst_1)))) u))) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α _inst_1}, Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) b (Units.val.{u1} α _inst_1 u))) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b)
Case conversion may be inaccurate. Consider using '#align units.dvd_mul_right Units.dvd_mul_rightₓ'. -/
/-- In a monoid, an element `a` divides an element `b` iff `a` divides all
    associates of `b`. -/
theorem dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
  Iff.intro (fun ⟨c, Eq⟩ => ⟨c * ↑u⁻¹, by rw [← mul_assoc, ← Eq, Units.mul_inv_cancel_right]⟩)
    fun ⟨c, Eq⟩ => Eq.symm ▸ (dvd_mul_right _ _).mul_right _
#align units.dvd_mul_right Units.dvd_mul_right

/- warning: units.mul_right_dvd -> Units.mul_right_dvd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α _inst_1}, Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) a ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} α _inst_1) α (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} α _inst_1) α (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} α _inst_1) α (coeBase.{succ u1, succ u1} (Units.{u1} α _inst_1) α (Units.hasCoe.{u1} α _inst_1)))) u)) b) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α _inst_1}, Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) a (Units.val.{u1} α _inst_1 u)) b) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b)
Case conversion may be inaccurate. Consider using '#align units.mul_right_dvd Units.mul_right_dvdₓ'. -/
/-- In a monoid, an element `a` divides an element `b` iff all associates of `a` divide `b`. -/
theorem mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
  Iff.intro (fun ⟨c, Eq⟩ => ⟨↑u * c, Eq.trans (mul_assoc _ _ _)⟩) fun h =>
    dvd_trans (Dvd.intro (↑u⁻¹) (by rw [mul_assoc, u.mul_inv, mul_one])) h
#align units.mul_right_dvd Units.mul_right_dvd

end Monoid

section CommMonoid

variable [CommMonoid α] {a b : α} {u : αˣ}

/- warning: units.dvd_mul_left -> Units.dvd_mul_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)}, Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (coeBase.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (Units.hasCoe.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))))) u) b)) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)}, Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) (Units.val.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) b)) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b)
Case conversion may be inaccurate. Consider using '#align units.dvd_mul_left Units.dvd_mul_leftₓ'. -/
/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
theorem dvd_mul_left : a ∣ u * b ↔ a ∣ b :=
  by
  rw [mul_comm]
  apply dvd_mul_right
#align units.dvd_mul_left Units.dvd_mul_left

/- warning: units.mul_left_dvd -> Units.mul_left_dvd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)}, Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (HasLiftT.mk.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (CoeTCₓ.coe.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (coeBase.{succ u1, succ u1} (Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)) α (Units.hasCoe.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))))) u) a) b) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {a : α} {b : α} {u : Units.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)}, Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) (Units.val.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) a) b) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b)
Case conversion may be inaccurate. Consider using '#align units.mul_left_dvd Units.mul_left_dvdₓ'. -/
/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
theorem mul_left_dvd : ↑u * a ∣ b ↔ a ∣ b :=
  by
  rw [mul_comm]
  apply mul_right_dvd
#align units.mul_left_dvd Units.mul_left_dvd

end CommMonoid

end Units

namespace IsUnit

section Monoid

variable [Monoid α] {a b u : α} (hu : IsUnit u)

include hu

#print IsUnit.dvd /-
/-- Units of a monoid divide any element of the monoid. -/
@[simp]
theorem dvd : u ∣ a := by
  rcases hu with ⟨u, rfl⟩
  apply Units.coe_dvd
#align is_unit.dvd IsUnit.dvd
-/

/- warning: is_unit.dvd_mul_right -> IsUnit.dvd_mul_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : α}, (IsUnit.{u1} α _inst_1 u) -> (Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) b u)) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : α}, (IsUnit.{u1} α _inst_1 u) -> (Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) b u)) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b))
Case conversion may be inaccurate. Consider using '#align is_unit.dvd_mul_right IsUnit.dvd_mul_rightₓ'. -/
@[simp]
theorem dvd_mul_right : a ∣ b * u ↔ a ∣ b :=
  by
  rcases hu with ⟨u, rfl⟩
  apply Units.dvd_mul_right
#align is_unit.dvd_mul_right IsUnit.dvd_mul_right

/- warning: is_unit.mul_right_dvd -> IsUnit.mul_right_dvd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : α}, (IsUnit.{u1} α _inst_1 u) -> (Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) a u) b) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α] {a : α} {b : α} {u : α}, (IsUnit.{u1} α _inst_1 u) -> (Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))) a u) b) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α _inst_1)) a b))
Case conversion may be inaccurate. Consider using '#align is_unit.mul_right_dvd IsUnit.mul_right_dvdₓ'. -/
/-- In a monoid, an element a divides an element b iff all associates of `a` divide `b`.-/
@[simp]
theorem mul_right_dvd : a * u ∣ b ↔ a ∣ b :=
  by
  rcases hu with ⟨u, rfl⟩
  apply Units.mul_right_dvd
#align is_unit.mul_right_dvd IsUnit.mul_right_dvd

end Monoid

section CommMonoid

variable [CommMonoid α] (a b u : α) (hu : IsUnit u)

include hu

/- warning: is_unit.dvd_mul_left -> IsUnit.dvd_mul_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (a : α) (b : α) (u : α), (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) -> (Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) u b)) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (a : α) (b : α) (u : α), (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) -> (Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) u b)) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b))
Case conversion may be inaccurate. Consider using '#align is_unit.dvd_mul_left IsUnit.dvd_mul_leftₓ'. -/
/-- In a commutative monoid, an element `a` divides an element `b` iff `a` divides all left
    associates of `b`. -/
@[simp]
theorem dvd_mul_left : a ∣ u * b ↔ a ∣ b :=
  by
  rcases hu with ⟨u, rfl⟩
  apply Units.dvd_mul_left
#align is_unit.dvd_mul_left IsUnit.dvd_mul_left

/- warning: is_unit.mul_left_dvd -> IsUnit.mul_left_dvd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (a : α) (b : α) (u : α), (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) -> (Iff (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) u a) b) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (a : α) (b : α) (u : α), (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) u) -> (Iff (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))) u a) b) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a b))
Case conversion may be inaccurate. Consider using '#align is_unit.mul_left_dvd IsUnit.mul_left_dvdₓ'. -/
/-- In a commutative monoid, an element `a` divides an element `b` iff all
  left associates of `a` divide `b`.-/
@[simp]
theorem mul_left_dvd : u * a ∣ b ↔ a ∣ b :=
  by
  rcases hu with ⟨u, rfl⟩
  apply Units.mul_left_dvd
#align is_unit.mul_left_dvd IsUnit.mul_left_dvd

end CommMonoid

end IsUnit

section CommMonoid

variable [CommMonoid α]

/- warning: is_unit_iff_dvd_one -> isUnit_iff_dvd_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {x : α}, Iff (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) x) (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) x (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {x : α}, Iff (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) x) (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) x (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (Monoid.toOne.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1)))))
Case conversion may be inaccurate. Consider using '#align is_unit_iff_dvd_one isUnit_iff_dvd_oneₓ'. -/
theorem isUnit_iff_dvd_one {x : α} : IsUnit x ↔ x ∣ 1 :=
  ⟨IsUnit.dvd, fun ⟨y, h⟩ => ⟨⟨x, y, h.symm, by rw [h, mul_comm]⟩, rfl⟩⟩
#align is_unit_iff_dvd_one isUnit_iff_dvd_one

#print isUnit_iff_forall_dvd /-
theorem isUnit_iff_forall_dvd {x : α} : IsUnit x ↔ ∀ y, x ∣ y :=
  isUnit_iff_dvd_one.trans ⟨fun h y => h.trans (one_dvd _), fun h => h _⟩
#align is_unit_iff_forall_dvd isUnit_iff_forall_dvd
-/

#print isUnit_of_dvd_unit /-
theorem isUnit_of_dvd_unit {x y : α} (xy : x ∣ y) (hu : IsUnit y) : IsUnit x :=
  isUnit_iff_dvd_one.2 <| xy.trans <| isUnit_iff_dvd_one.1 hu
#align is_unit_of_dvd_unit isUnit_of_dvd_unit
-/

/- warning: is_unit_of_dvd_one -> isUnit_of_dvd_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] (a : α), (Dvd.Dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))))))) -> (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) a)
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CommMonoid.{u1} α] {a : α}, (Dvd.dvd.{u1} α (semigroupDvd.{u1} α (Monoid.toSemigroup.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))) a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (Monoid.toOne.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1))))) -> (IsUnit.{u1} α (CommMonoid.toMonoid.{u1} α _inst_1) a)
Case conversion may be inaccurate. Consider using '#align is_unit_of_dvd_one isUnit_of_dvd_oneₓ'. -/
/- ./././Mathport/Syntax/Translate/Basic.lean:635:2: warning: expanding binder collection (a «expr ∣ » 1) -/
theorem isUnit_of_dvd_one : ∀ (a) (_ : a ∣ 1), IsUnit (a : α)
  | a, ⟨b, Eq⟩ => ⟨Units.mkOfMulEqOne a b Eq.symm, rfl⟩
#align is_unit_of_dvd_one isUnit_of_dvd_one

#print not_isUnit_of_not_isUnit_dvd /-
theorem not_isUnit_of_not_isUnit_dvd {a b : α} (ha : ¬IsUnit a) (hb : a ∣ b) : ¬IsUnit b :=
  mt (isUnit_of_dvd_unit hb) ha
#align not_is_unit_of_not_is_unit_dvd not_isUnit_of_not_isUnit_dvd
-/

end CommMonoid


import week_8.ideas.Part_A_G_modules

/-
-- move this?
# Coercions

Something which will come up again and again in this workshop is
the concept of a coercion. We have seen things which computer scientists
call `φ : M →+[G] N` and we call "functions", but to Lean they are
functions with baggage, which in this case is all the axioms and theorems
attached to the theory of G-module homomorphisms (for example
a proof of the theorem that `φ 0 = 0`). This means that `φ` itself is a
pair consisting of a function and a whole bunch of extra stuff, and
in particular `φ` is not a function (it's a function and more).
The actual function `M → N` is called `⇑φ` by Lean, but we can just
call it `φ` most of the time.

The system that makes this happen is called a coercion -- we coercing
`φ` to a function `⇑φ`. We will see other examples of coercions later.
-/

/-

# Making the API for H⁰(G,M)

If `G` is a group and `M` is a G-module then H⁰(G,M), or `H0 G M`, is the abelian
group of G-invariant elements of `M`. We make the definition so we have
to make the interface too. We show that `H0 G M` is an abelian group,
define a coercion to `M` sending `m` to `↑m`, and define `m.spec` to be
the statement that `↑m` is G-invariant.

Let's start by giving a preliminary definition of H⁰ as an additive
subgroup of `M`.

-/

open set

/-- `H0 G M` is the type of G-invariant elements of M. -/
def H0_subgroup (G M : Type)
  [monoid G] [add_comm_group M] [distrib_mul_action G M] : add_subgroup M :=
{ carrier := {m | ∀ g : G, g • m = m },
  -- Need to check it's a subgroup.
  -- Axiom 1: zero in ("closed under `0`")
  zero_mem' := begin
    -- you can start with this
    rw mem_set_of_eq, -- says that `a ∈ { x | p x}` is the same as `p a`.
    -- can you take it from there?
    exact smul_zero,
  end,
  -- Axiom 2 : closed under `+`
  add_mem' := begin
    intros a b ha hb g,
    rw mem_set_of_eq at *, -- that's how I'd start
    rw [smul_add, ha, hb], -- then the sneaky refl closes the goal
  end,
  -- Axiom 3 : closed under `-`
  neg_mem' := begin
    simp *,
  end }

/-

This makes `H0_subgroup G M`, a term (an additive subgroup of `M`, and
hence a term of type `add_subgroup M`). But this is no good -- we want
to consider functions `H⁰(G,M) → H⁰(G,N)` so we need a *type* `H0 G M`.
We need to promote the term to a type. We do this by using Lean's
theory of subtypes, with notation `{ x // P x }` (a type) as oppposed to
the set-theoretic `{ x | P x }` (a term)

-/

/-- Group cohomology `H⁰(G,M)` as a type. -/
def H0 (G M : Type)
  [monoid G] [add_comm_group M] [distrib_mul_action G M] : Type :=
{m : M // ∀ g : G, g • m = m }

-- let's make an API and prove stuff about `H0 G M` in the `H0` namespace.
namespace H0

-- let `G` be a group (or a monoid) and let `M` be a `G`-module.
variables {G M : Type}
  [monoid G] [add_comm_group M] [distrib_mul_action G M]

/-
We have defined `H0 G M` to be a type, a so-called subtype of `M`,
but a type in its own right. It has terms of its own (unlike `S : set M`
or `A : add_subgroup M`)

So how does this work? A term `m` of type `H0 G M` is a *package* consisting
of a term `m.1 : M` and a proof `m.2 : ∀ g, g • m.1 = m.1`. We do not
want to use these internal computer science terms for this package
of information, we want a nice interface. Below we use coercion, to turn
a term `m : H0 G M` into a term `↑m : M`.

-/

/-- set up coercion from `H⁰(G,M) to M`, sending `m` to `↑m` -/
instance : has_coe (H0 G M) M :=
-- this is the last time we see `m.1`
⟨λ m, m.1⟩

-- That's a definition, so we need to make a little API.

/-- If `a : M` then `↑⟨a, ha⟩ = a` -/
@[simp] lemma coe_def (a : M) (ha : ∀(g : G), g • a = a) :
  ((⟨a, ha⟩ : H0 G M) : M) = a := rfl

-- this is our nice interface
lemma spec (m : H0 G M) : ∀ (g : G), g • (m : M) = m :=
-- this is the last time we see `m.2`
m.2

/-

The idea now is that we should avoid `m.1` and `m.2` completely,
and use `m : M` or `↑m` for the element of the module, and `m.spec` for
the proof that it is `G`-invariant.

## Basic Infrastructure

We have made a new definition, `H0`, and now we need to make it easier
to use. Things we do here:

* We want to get (for free) that `H0 G M` is a group (so we need to put
  this fact into the type class mechanism).

* We want to know that two terms of type `H0 G M` are equal if
  and only if the corresponding terms of type `M` are equal (so we want to
  prove an extensionality lemma).

* We want to know that things like 0 and addition coincide in `M`
  and `H0 G M` (the coercion is a group homomorphism)

Let's start by making H⁰(G, M) a.k.a. `H0 G M` into a group. This is easy
because `H0 G M` is the type corresponding to the term `H0_subgroup G M`
which is a subgroup, hence a group.

-/

-- tell type class inference that `H0 G M` is a group
instance : add_comm_group (H0 G M) :=
add_subgroup.to_add_comm_group (H0_subgroup G M)

-- Let's now prove an ext_iff lemma (useful for rewriting)
lemma ext_iff (m₁ m₂ : H0 G M) : m₁ = m₂ ↔ (m₁ : M) = (m₂ : M) :=
begin
  split,
  { rintro rfl, refl },
  { ext }
end

-- Let's tell the simplifier how the group structure (addition, 0, negation
-- and subtraction) works with respect to the coercion. All the proofs
-- are true by definition

@[simp] lemma coe_add (a b : H0 G M) :
  ((a + b : H0 G M) : M) = a + b := rfl

@[simp] lemma coe_zero : ((0 : H0 G M) : M) = 0 := rfl

@[simp] lemma coe_neg (a : H0 G M) :
  ((-a : H0 G M) : M) = -a := rfl

-- we'll never use it but what the heck, others might
@[simp] lemma coe_sub (a b : H0 G M) :
  ((a - b : H0 G M) : M) = a - b := rfl

-- try these
example (m₁ m₂ m₃ : H0 G M) : m₁ + (m₂ - m₁ + m₃) = m₃ + m₂ :=
begin
  -- which tactic?
  abel
end

example (g : G) (m : H0 G M) : g • (m + m : M) = m + m :=
begin
  -- can you help the simplifier?
  simp [m.spec g],
end

end H0

/-

## API for the interaction of G-module maps and our new definition `H⁰`

Now let's prove that a G-module map `φ : M →+[G] N` induces a natural
abelian group hom `φ.H0 : H⁰(G,M) →+ H⁰(G,N)`. I would rather do this in
`φ`'s namespace, which is `distrib_mul_action_hom`, because then
I can write `φ.H0` directly.

-/

namespace distrib_mul_action_hom

variables {G M N : Type}
  [monoid G] [add_comm_group M] [add_comm_group N]
  [distrib_mul_action G M] [distrib_mul_action G N]
  (a : M) (b : N)

-- Let's first define the group homomorphism `H0 G M →+ H0 G N` induced by `φ`.
-- Recall that the constructor of `H0 G N` needs as input a pair consisting
-- of `b : N` and `hb : ∀ g, g • b = b`, and we make the element of `H0 G N`
-- using the `⟨b, hb⟩` notation.

/- The function underlying the group homomorphism `H⁰(G,M) → H⁰(G,N)`
   induced by a `G`-equivariant group homomorphism `φ : M →+[G] N` -/
def H0_underlying_function (φ : M →+[G] N) (m : H0 G M) : H0 G N :=
⟨φ m, begin
  -- use φ.map_smul and a.spec to prove that this map is well-defined.
  -- Remember that `rw` doesn't work under binders, and ∀ is a binder, so start
  -- with `intros`.
  intros,
  rw [←φ.map_smul, m.spec],
end⟩

/-- The group homomorphism  `H⁰(G,M) →+ H⁰(G,N)`
   induced by a `G`-equivariant group homomorphism `φ : M →+[G] N` -/
def H0 (φ : M →+[G] N) : H0 G M →+ H0 G N :=
-- to make a group homomorphism we need apply a constructor
add_monoid_hom.mk'
-- to the function we just made
(H0_underlying_function φ)
-- and then prove that this function preserves addition.
begin
  intros a b,
  simp only [H0_underlying_function],
  ext,
  simp,
end

end distrib_mul_action_hom

-- The API for `φ.H0` starts here

namespace H0

variables {G M N : Type}
  [monoid G] [add_comm_group M] [add_comm_group N]
  [distrib_mul_action G M] [distrib_mul_action G N]
  (a : M) (b : N)

/-

## An API for `φ.H0`

So now if `φ : M →+[G] N` is a G-module homomorphism, we can talk
about `φ.H0 : H0 G M →+ H0 G N`, an abelian group homomorphism
from H⁰(G,M) to H⁰(G,N).

As ever, this is a definition so we need to make a little API.
We start with the following handy fact:

Given a G-module map `φ : M →+[G] N`, The following diagram commutes:

            φ
  M ----------------> N
  /\                  /\
  | coercion ↑        | coercion ↑
  |                   |
  |                   |
H⁰(G,M) ---------> H⁰(G,N)
-/
@[simp] lemma coe_apply (m : H0 G M) (φ : M →+[G] N) :
  ((φ.H0 m) : N) = φ m :=
begin
  -- Look at the goal the way I have written it.
  -- Unfold the definitions. It's true by definition.
  -- Look at the goal the way Lean is displaying it
  -- right now. It's just coercions everywhere. Ignore them.
  refl,
end

open distrib_mul_action_hom

-- If you're in to that sort of thing, you can prove that `φ.H0`
-- is functorial. That's it and comp.
def id_apply (m : H0 G M) :
  (distrib_mul_action_hom.id G).H0 m = m :=
begin
  -- remember extensionality.
  ext,
  refl,
end

variables {P : Type} [add_comm_group P] [distrib_mul_action G P]

def comp (φ : M →+[G] N) (ψ : N →+[G] P) :
  (ψ ∘ᵍ φ).H0 = ψ.H0.comp φ.H0 :=
begin
  refl,
end

end H0

/-

## First exactness result

If 0 → M → N → P → 0 is a short exact sequence, then there
is a long exact sequence

0 → H⁰(G,M) → H⁰(G,N) → H⁰(G,P)

and we can't go any further because we haven't defined H¹! This boils
down to two theorems; let's prove them.

-/
open function

open distrib_mul_action_hom

variables {G M N P : Type}
  [monoid G] [add_comm_group M] [add_comm_group N] [add_comm_group P]
  [distrib_mul_action G M] [distrib_mul_action G N] [distrib_mul_action G P]
  (a : M) (b : N)

-- 0 → H⁰(G,M) → H⁰(G,N) is exact, i.e. φ.H0 is injective
theorem H0_hom.left_exact (φ : M →+[G] N) (hφ : injective φ) :
  injective φ.H0 :=
begin
  intros a b h,
  ext,
  apply hφ,
  rw H0.ext_iff at h,
  simpa using h,
end

-- H⁰(G,M) → H⁰(G,N) → H⁰(G,P) is exact, i.e. an image equals a kernel.
theorem H0_hom.middle_exact' (φ : M →+[G] N) (hφ : injective φ)
  (ψ : N →+[G] P) (he : is_exact φ ψ) :
  φ.H0.range = ψ.H0.ker :=
begin
  ext n,
  rw is_exact.def at he,
  specialize he n,
  rw add_monoid_hom.mem_ker,
  rw [H0.ext_iff, n.coe_apply, H0.coe_zero, ← he],
  split,
  { rintro ⟨y, rfl⟩,
    exact ⟨y, rfl⟩ },
  { rintro ⟨y, hy⟩,
    refine ⟨⟨y, _⟩, _⟩,
    { intro g,
      apply hφ,
      simpa [hy] using n.spec g },
    { ext,
      exact hy } }
end


/-
Copyright (c) 2020 Scott Morrison. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Scott Morrison
-/
import data.finsupp.basic

/-!
# The pointwise product on `finsupp`.

For the convolution product on `finsupp` when the domain has a binary operation,
see the type synonyms `add_monoid_algebra`
(which is in turn used to define `polynomial` and `mv_polynomial`)
and `monoid_algebra`.
-/

noncomputable theory

open finset

universes u₁ u₂ u₃ u₄ u₅
variables {α : Type u₁} {β : Type u₂} {γ : Type u₃} {δ : Type u₄} {ι : Type u₅}

namespace finsupp

/-! ### Declarations about the pointwise product on `finsupp`s -/

section
variables [mul_zero_class β]

/-- The product of `f g : α →₀ β` is the finitely supported function
  whose value at `a` is `f a * g a`. -/
instance : has_mul (α →₀ β) := ⟨zip_with (*) (mul_zero 0)⟩

lemma coe_mul (g₁ g₂ : α →₀ β) : ⇑(g₁ * g₂) = g₁ * g₂ := rfl

@[simp] lemma mul_apply {g₁ g₂ : α →₀ β} {a : α} : (g₁ * g₂) a = g₁ a * g₂ a :=
rfl

lemma support_mul [decidable_eq α] {g₁ g₂ : α →₀ β} : (g₁ * g₂).support ⊆ g₁.support ∩ g₂.support :=
begin
  intros a h,
  simp only [mul_apply, mem_support_iff] at h,
  simp only [mem_support_iff, mem_inter, ne.def],
  rw ←not_or_distrib,
  intro w,
  apply h,
  cases w; { rw w, simp },
end

instance : mul_zero_class (α →₀ β) :=
finsupp.coe_fn_injective.mul_zero_class _ coe_zero coe_mul

end

instance [semigroup_with_zero β] : semigroup_with_zero (α →₀ β) :=
finsupp.coe_fn_injective.semigroup_with_zero _ coe_zero coe_mul

instance [non_unital_non_assoc_semiring β] : non_unital_non_assoc_semiring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_non_assoc_semiring _ coe_zero coe_add coe_mul (λ _ _, rfl)

instance [non_unital_semiring β] : non_unital_semiring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_semiring _ coe_zero coe_add coe_mul (λ _ _, rfl)

instance [non_unital_comm_semiring β] : non_unital_comm_semiring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_comm_semiring _ coe_zero coe_add coe_mul (λ _ _, rfl)

instance [non_unital_non_assoc_ring β] : non_unital_non_assoc_ring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_non_assoc_ring _
  coe_zero coe_add coe_mul coe_neg coe_sub (λ _ _, rfl) (λ _ _, rfl)

instance [non_unital_ring β] : non_unital_ring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_ring _
  coe_zero coe_add coe_mul coe_neg coe_sub (λ _ _, rfl) (λ _ _, rfl)

instance [non_unital_comm_ring β] : non_unital_comm_ring (α →₀ β) :=
finsupp.coe_fn_injective.non_unital_comm_ring _
  coe_zero coe_add coe_mul coe_neg coe_sub (λ _ _, rfl) (λ _ _, rfl)

-- TODO can this be generalized in the direction of `pi.has_scalar'`
-- (i.e. dependent functions and finsupps)
-- TODO in theory this could be generalised, we only really need `smul_zero` for the definition
instance pointwise_scalar [semiring β] : has_scalar (α → β) (α →₀ β) :=
{ smul := λ f g, finsupp.of_support_finite (λ a, f a • g a) begin
    apply set.finite.subset g.finite_support,
    simp only [function.support_subset_iff, finsupp.mem_support_iff, ne.def,
      finsupp.fun_support_eq, finset.mem_coe],
    intros x hx h,
    apply hx,
    rw [h, smul_zero],
  end }

@[simp]
lemma coe_pointwise_smul [semiring β] (f : α → β) (g : α →₀ β) :
  ⇑(f • g) = f • g := rfl

/-- The pointwise multiplicative action of functions on finitely supported functions -/
instance pointwise_module [semiring β] : module (α → β) (α →₀ β) :=
function.injective.module _ coe_fn_add_hom coe_fn_injective coe_pointwise_smul

end finsupp


constant ℝ : Type
@[instance] constant real_field : linear_ordered_field ℝ

axiom lem {P: Prop} : P ∨ ¬P
axiom set.ext {α: Type} {A B:set α} : (∀(x:α), x ∈ A ↔ x ∈ B) → A = B

theorem dne {P: Prop} : ¬¬P → P :=
begin
    intro HnnP,
    have H: P ∨ ¬P,
    apply lem,
    cases H with HP HnP,
    assumption,
    apply false.elim,
    apply HnnP,
    exact HnP
end

theorem diff_equal {α: Type} (A:set α) : A \ ∅ = A :=
    set.ext (λ x, iff.intro
        and.left
        (λ H, and.intro H id)
    )
theorem int_empty {α: Type} (A: set α) : A ∩ ∅ = ∅ :=
    set.ext (λ x, iff.intro
        and.right
        false.elim
    )
theorem empty_int {α: Type} (A: set α) : ∅ ∩ A = ∅ :=
    set.ext (λ x, iff.intro
        and.left
        false.elim
    )
theorem int_univ {α: Type} (A: set α) : A ∩ set.univ = A :=
    set.ext (λ x, iff.intro
        and.left
        (λ H, ⟨H, true.intro⟩)
    )
theorem univ_int {α: Type} (A: set α) : set.univ ∩ A = A :=
    set.ext (λ x, iff.intro
        and.right
        (λ H, ⟨true.intro, H⟩)
    )
theorem empty_union {α: Type} (A: set α) : ∅ ∪ A = A :=
    set.ext (λ x, iff.intro
        (λ H, or.elim H false.elim id)
        or.inr
    )
theorem union_empty {α: Type} (A: set α) : A ∪ ∅ = A :=
    set.ext (λ x, iff.intro
        (λ H, or.elim H id false.elim)
        or.inl
    )
theorem univ_union {α: Type} (A: set α) : set.univ ∪ A = set.univ :=
    set.ext (λ x, iff.intro
        (λ H, true.intro)
        or.inl
    )
theorem union_univ {α: Type} (A: set α) : A ∪ set.univ = set.univ :=
    set.ext (λ x, iff.intro
        (λ H, true.intro)
        or.inr
    )
theorem comp_univ {α: Type} : -(∅:set α) = set.univ :=
    set.ext (λ x, iff.intro
        (λ H, true.intro)
        (λ H, λ H2, H2)
    )
theorem comp_comp {α : Type} {A : set α} : -(-A) = A :=
    set.ext (λ x, iff.intro
        dne
        (λ p q, q p)
    )

-- sigma field
class sigma_field {α : Type} (F : set (set α)) :=
    (empty_sigma : ∅ ∈ F)
    (comp_sigma : ∀ A, A ∈ F → - A ∈ F)
    (union_sigma : ∀ {A B}, A ∈ F → B ∈ F → A ∪ B ∈ F)

export sigma_field

theorem sample_sigma {α : Type} {F : set (set α)} [s : sigma_field F] :
    set.univ ∈ F :=
begin
    rw ←comp_univ,
    exact comp_sigma ∅ s.empty_sigma,
end

theorem diff_sigma {α : Type} {F : set (set α)} [s : sigma_field F] {A B:set α} :
    A ∈ F → B ∈ F → B \ A ∈ F :=
begin
    intros p q,
    have H1: -(A ∪ -B) = B \ A,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xL,
            constructor,
            apply dne,
                intro xnB,
                apply xL,
                right,
                exact xnB,
            intro xA,
            apply xL,
            left, assumption,
            intro xBmA,
            cases xBmA with xB xnA,
                intro xLR,
                cases xLR with xA xnB,
                apply xnA xA,
                apply xnB xB,
    rw ←H1,
    simp [s.comp_sigma,s.union_sigma,p,q]
end

theorem int_sigma {α : Type} {F : set (set α)} [s : sigma_field F] {A B:set α} :
    A ∈ F → B ∈ F → A ∩ B ∈ F :=
begin
    intros p q,
    have H1: -(-A ∪ -B) = A ∩ B,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xL,
            constructor,
            apply dne,
                intro xnA,
                exact xL (or.inl xnA),
            apply dne,
                intro xnB,
                exact xL (or.inr xnB),
            intro xAiB,
            cases xAiB with xA xB,
                intro xnL,
                cases xnL with xmA xmB,
                exact xmA xA,
                exact xmB xB,
    rw ←H1,
    simp [s.comp_sigma,s.union_sigma,p,q]
end

-- probability
class probability {α : Type} {F : set (set α)} (P : Π (x:set α), x ∈ F → ℝ) extends sigma_field F :=
    (prob_nonneg : ∀ {A:set α} {p:A ∈ F}, 0 ≤ (P A p))
    (prob_sample_one : P set.univ sample_sigma = 1)
    (prob_disjoint : ∀ {A B} (p:A ∈ F) (q:B ∈ F),
    A ∩ B = ∅ → P (A ∪ B) (union_sigma p q) = (P A p) + (P B q))

export probability

theorem prob_union {α : Type} {A B C : set α} {F : set (set α)} (P : Π (x:set α), x ∈ F → ℝ) [c : probability P]
(p:A ∈ F) (q:B ∈ F) (r:A ∪ B = C) (s:A ∩ B = ∅) :
P C (r ▸ union_sigma p q) = (P A p) + (P B q) :=
let PA := P A p,
    PB := P B q,
    PAUB := P (A ∪ B) (union_sigma p q)
in
begin
    have H1: PAUB = PA+PB,
    exact prob_disjoint P p q s,
    have H2: C ∈ F,
    rw ←r,
    exact union_sigma p q,
    have H3: ∀ c:C ∈ F, P C c = PAUB,
    rw H1,
    rw ←r,
    rw ←c.prob_disjoint,
    intro c,
    exact rfl,
    exact s,
    rw ←c.prob_disjoint,
    exact H3 H2,
    exact s
end

theorem prob_empty_zero {α : Type} {A B C : set α} {F : set (set α)} {P : Π (x:set α), x ∈ F → ℝ} [c : probability P] :
P ∅ c.empty_sigma = 0 :=
let PE := P ∅ c.empty_sigma,
    PΩ := P set.univ sample_sigma,
    PEUΩ := P (∅ ∪ set.univ) (union_sigma c.empty_sigma sample_sigma)
in
begin
    have H1: PE + PΩ = PΩ,
    apply eq.symm,
    apply prob_union P,
    exact empty_union set.univ,
    exact empty_int set.univ,
    apply add_right_cancel,
    rw zero_add,
    exact H1
end

theorem prob_comp {α : Type} {F : set (set α)} {P : Π (x:set α), x ∈ F → ℝ} [c : probability P]
{A} {p:A ∈ F} :
(P A p) + (P (-A) (comp_sigma A p)) = 1 :=
let PAC := P (-A) (comp_sigma A p)
in
begin
    have H1: A ∪ (-A) = set.univ,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAUAC,
            exact true.intro,
            intro xS,
            cases lem with xA xnA,
            left, assumption,
            right, exact xnA,
    have H2: A ∩ (-A) = ∅,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAiAC,
            cases xAiAC with xA xAC,
            apply xAC,
            exact xA,
            apply false.elim,
    apply eq.trans,
    apply eq.symm,
    apply prob_union,
    exact H1,
    exact H2,
    exact prob_sample_one P
end

theorem prob_subset_le {α : Type} {F : set (set α)} {P : Π (x:set α), x ∈ F → ℝ} [c : probability P]
{A B} {p:A ∈ F} {q:B ∈ F} :
A ⊆ B → (P A p) ≤ (P B q) :=
let BmA := B \ A,
    AiB := A ∩ B,
    BmAUA := B \ A ∪ A,
    PA := P A p,
    PB := P B q,
    PBmA := P BmA (diff_sigma p q),
    PAiB := P AiB (int_sigma p q)
in
begin
    intro HAB,
    have H1: A ∪ BmA = B,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAUBmA,
            cases xAUBmA with xA xBmA,
            exact HAB xA,
            exact xBmA.left,
            intro xB,
            cases lem with xA xnA,
            left, assumption,
            right, exact ⟨xB, xnA⟩,
    have H2: A ∩ BmA = ∅,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAiBmA,
            cases xAiBmA with xA xBmA,
            exact xBmA.right xA,
            apply false.elim,
    rw prob_union P _ _ H1,
    apply le_add_of_nonneg_right,
    apply prob_nonneg,
    exact H2,
    exact diff_sigma p q
end

theorem inclusion_exclusion  {α : Type} {F : set (set α)} {P : Π (x:set α), x ∈ F → ℝ} [c : probability P]
{A B} {p:A ∈ F} {q:B ∈ F} :
P (A ∪ B) (union_sigma p q) = (P A p) + (P B q) - (P (A ∩ B) (int_sigma p q)) :=
let BmA := B \ A,
    AiB := A ∩ B,
    AUB := A ∪ B,
    PA := P A p,
    PB := P B q,
    PBmA := P BmA (diff_sigma p q),
    PAiB := P AiB (int_sigma p q),
    PAUB := P (A ∪ B) (union_sigma p q)
in
begin
    have H1: A ∪ BmA = AUB,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAUBmA,
            cases xAUBmA with xA xBmA,
            left, assumption,
            right, exact xBmA.left,
            intro xAUB,
            cases xAUB with xA xB,
            left, assumption,
            cases lem with _ xnA,
            left, assumption,
            right, exact ⟨xB, xnA⟩,
    have H2: A ∩ BmA = ∅,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xAiBmA,
            cases xAiBmA with xA xBmA,
            exact xBmA.right xA,
            apply false.elim,
    have H3: P (A ∪ B) _ = PA+PBmA,
    exact prob_union P p (diff_sigma p q) H1 H2,
    rw H3,
    rw ←add_sub,
    apply congr_arg,
    apply eq_sub_of_add_eq,
    have H4: BmA ∪ AiB = B,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro h,
            cases h with xBmA xAiB,
            exact xBmA.left,
            exact xAiB.right,
            intro xB,
            cases lem with xA xnA,
            right, exact ⟨xA, xB⟩,
            left, exact ⟨xB, xnA⟩,
    have H5: BmA ∩ AiB = ∅,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro h,
            exact h.left.right h.right.left,
            apply false.elim,
    rw ←prob_union P,
    exact H4,
    exact H5
end

theorem total_prob  {α : Type} {F : set (set α)} {P : Π (x:set α), x ∈ F → ℝ} [c : probability P]
{A B C} {p:A ∈ F} {q:B ∈ F} {r:C ∈ F}
{s:A ∪ B = set.univ} {t:A ∩ B = ∅}:
(P (C ∩ A) (int_sigma r p)) + (P (C ∩ B) (int_sigma r q)) = P C r :=
let PA := P A p,
    PB := P B q,
    PC := P C r,
    CA := C ∩ A,
    CB := C ∩ B,
    PCA := P CA (int_sigma r p),
    PCB := P CB (int_sigma r q)
in
begin
    have H1: CA ∪ CB = C,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xCAUCB,
            cases xCAUCB with xCA xCB,
            exact xCA.left,
            exact xCB.left,
            intro xC,
            have xAB: x ∈ A ∪ B,
            rw s,
            exact true.intro,
            cases xAB with xA xB,
            left, exact ⟨xC, xA⟩,
            right, exact ⟨xC, xB⟩,
    have H2: CA ∩ CB = ∅,
    apply set.ext,
        intro x,
        apply iff.intro,
            intro xCAiCB,
            rw ←t,
            exact ⟨xCAiCB.left.right, xCAiCB.right.right⟩,
            apply false.elim,
    apply eq.symm,
    apply prob_union,
    exact H1,
    exact H2
end


/-
Copyright (c) 2020 Hanting Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Hanting Zhang, Johan Commelin

! This file was ported from Lean 3 source module ring_theory.mv_polynomial.symmetric
! leanprover-community/mathlib commit 70fd9563a21e7b963887c9360bd29b2393e6225a
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.MvPolynomial.Rename
import Mathlib.Data.MvPolynomial.CommRing
import Mathlib.Algebra.Algebra.Subalgebra.Basic

/-!
# Symmetric Polynomials and Elementary Symmetric Polynomials

This file defines symmetric `MvPolynomial`s and elementary symmetric `MvPolynomial`s.
We also prove some basic facts about them.

## Main declarations

* `MvPolynomial.IsSymmetric`

* `MvPolynomial.symmetricSubalgebra`

* `MvPolynomial.esymm`

## Notation

+ `esymm σ R n`, is the `n`th elementary symmetric polynomial in `MvPolynomial σ R`.

As in other polynomial files, we typically use the notation:

+ `σ τ : Type _` (indexing the variables)

+ `R S : Type _` `[CommSemiring R]` `[CommSemiring S]` (the coefficients)

+ `r : R` elements of the coefficient ring

+ `i : σ`, with corresponding monomial `X i`, often denoted `X_i` by mathematicians

+ `φ ψ : MvPolynomial σ R`

-/

open Equiv (Perm)

open BigOperators

noncomputable section

namespace Multiset

variable {R : Type _} [CommSemiring R]

/-- The `n`th elementary symmetric function evaluated at the elements of `s` -/
def esymm (s : Multiset R) (n : ℕ) : R :=
  ((s.powersetLen n).map Multiset.prod).sum
#align multiset.esymm Multiset.esymm

theorem Finset.esymm_map_val {σ} (f : σ → R) (s : Finset σ) (n : ℕ) :
    (s.val.map f).esymm n = (s.powersetLen n).sum fun t => t.prod f := by
  simp only [esymm, powersetLen_map, ← Finset.map_val_val_powersetLen, map_map]
  rfl
#align finset.esymm_map_val Multiset.Finset.esymm_map_val

end Multiset

namespace MvPolynomial

variable {σ : Type _} {R : Type _}

variable {τ : Type _} {S : Type _}

/-- A `MvPolynomial φ` is symmetric if it is invariant under
permutations of its variables by the  `rename` operation -/
def IsSymmetric [CommSemiring R] (φ : MvPolynomial σ R) : Prop :=
  ∀ e : Perm σ, rename e φ = φ
#align mv_polynomial.is_symmetric MvPolynomial.IsSymmetric

variable (σ R)

/-- The subalgebra of symmetric `MvPolynomial`s. -/
def symmetricSubalgebra [CommSemiring R] : Subalgebra R (MvPolynomial σ R) where
  carrier := setOf IsSymmetric
  algebraMap_mem' r e := rename_C e r
  mul_mem' ha hb e := by rw [AlgHom.map_mul, ha, hb]
  add_mem' ha hb e := by rw [AlgHom.map_add, ha, hb]
#align mv_polynomial.symmetric_subalgebra MvPolynomial.symmetricSubalgebra

variable {σ R}

@[simp]
theorem mem_symmetricSubalgebra [CommSemiring R] (p : MvPolynomial σ R) :
    p ∈ symmetricSubalgebra σ R ↔ p.IsSymmetric :=
  Iff.rfl
#align mv_polynomial.mem_symmetric_subalgebra MvPolynomial.mem_symmetricSubalgebra

namespace IsSymmetric

section CommSemiring

variable [CommSemiring R] [CommSemiring S] {φ ψ : MvPolynomial σ R}

@[simp]
theorem C (r : R) : IsSymmetric (C r : MvPolynomial σ R) :=
  (symmetricSubalgebra σ R).algebraMap_mem r
set_option linter.uppercaseLean3 false in
#align mv_polynomial.is_symmetric.C MvPolynomial.IsSymmetric.C

@[simp]
theorem zero : IsSymmetric (0 : MvPolynomial σ R) :=
  (symmetricSubalgebra σ R).zero_mem
#align mv_polynomial.is_symmetric.zero MvPolynomial.IsSymmetric.zero

@[simp]
theorem one : IsSymmetric (1 : MvPolynomial σ R) :=
  (symmetricSubalgebra σ R).one_mem
#align mv_polynomial.is_symmetric.one MvPolynomial.IsSymmetric.one

theorem add (hφ : IsSymmetric φ) (hψ : IsSymmetric ψ) : IsSymmetric (φ + ψ) :=
  (symmetricSubalgebra σ R).add_mem hφ hψ
#align mv_polynomial.is_symmetric.add MvPolynomial.IsSymmetric.add

theorem mul (hφ : IsSymmetric φ) (hψ : IsSymmetric ψ) : IsSymmetric (φ * ψ) :=
  (symmetricSubalgebra σ R).mul_mem hφ hψ
#align mv_polynomial.is_symmetric.mul MvPolynomial.IsSymmetric.mul

theorem smul (r : R) (hφ : IsSymmetric φ) : IsSymmetric (r • φ) :=
  (symmetricSubalgebra σ R).smul_mem hφ r
#align mv_polynomial.is_symmetric.smul MvPolynomial.IsSymmetric.smul

@[simp]
theorem map (hφ : IsSymmetric φ) (f : R →+* S) : IsSymmetric (map f φ) := fun e => by
  rw [← map_rename, hφ]
#align mv_polynomial.is_symmetric.map MvPolynomial.IsSymmetric.map

end CommSemiring

section CommRing

variable [CommRing R] {φ ψ : MvPolynomial σ R}

theorem neg (hφ : IsSymmetric φ) : IsSymmetric (-φ) :=
  (symmetricSubalgebra σ R).neg_mem hφ
#align mv_polynomial.is_symmetric.neg MvPolynomial.IsSymmetric.neg

theorem sub (hφ : IsSymmetric φ) (hψ : IsSymmetric ψ) : IsSymmetric (φ - ψ) :=
  (symmetricSubalgebra σ R).sub_mem hφ hψ
#align mv_polynomial.is_symmetric.sub MvPolynomial.IsSymmetric.sub

end CommRing

end IsSymmetric

section ElementarySymmetric

open Finset

variable (σ R) [CommSemiring R] [CommSemiring S] [Fintype σ] [Fintype τ]

/-- The `n`th elementary symmetric `MvPolynomial σ R`. -/
def esymm (n : ℕ) : MvPolynomial σ R :=
  ∑ t in powersetLen n univ, ∏ i in t, X i
#align mv_polynomial.esymm MvPolynomial.esymm

/-- The `n`th elementary symmetric `MvPolynomial σ R` is obtained by evaluating the
`n`th elementary symmetric at the `Multiset` of the monomials -/
theorem esymm_eq_multiset_esymm : esymm σ R = (Finset.univ.val.map X).esymm := by
  refine' funext fun n => (Multiset.Finset.esymm_map_val X _ n).symm
#align mv_polynomial.esymm_eq_multiset_esymm MvPolynomial.esymm_eq_multiset_esymm

theorem aeval_esymm_eq_multiset_esymm [Algebra R S] (f : σ → S) (n : ℕ) :
    aeval f (esymm σ R n) = (Finset.univ.val.map f).esymm n := by
  simp_rw [esymm, aeval_sum, aeval_prod, aeval_X, Multiset.Finset.esymm_map_val]
#align mv_polynomial.aeval_esymm_eq_multiset_esymm MvPolynomial.aeval_esymm_eq_multiset_esymm

/-- We can define `esymm σ R n` by summing over a subtype instead of over `powerset_len`. -/
theorem esymm_eq_sum_subtype (n : ℕ) :
    esymm σ R n = ∑ t : { s : Finset σ // s.card = n }, ∏ i in (t : Finset σ), X i :=
  sum_subtype _ (fun _ => mem_powerset_len_univ_iff) _
#align mv_polynomial.esymm_eq_sum_subtype MvPolynomial.esymm_eq_sum_subtype

/-- We can define `esymm σ R n` as a sum over explicit monomials -/
theorem esymm_eq_sum_monomial (n : ℕ) :
    esymm σ R n = ∑ t in powersetLen n univ, monomial (∑ i in t, Finsupp.single i 1) 1 := by
  simp_rw [monomial_sum_one]
  rfl
#align mv_polynomial.esymm_eq_sum_monomial MvPolynomial.esymm_eq_sum_monomial

@[simp]
theorem esymm_zero : esymm σ R 0 = 1 := by
  simp only [esymm, powersetLen_zero, sum_singleton, prod_empty]
#align mv_polynomial.esymm_zero MvPolynomial.esymm_zero

theorem map_esymm (n : ℕ) (f : R →+* S) : map f (esymm σ R n) = esymm σ S n := by
  simp_rw [esymm, map_sum, map_prod, map_X]
#align mv_polynomial.map_esymm MvPolynomial.map_esymm

theorem rename_esymm (n : ℕ) (e : σ ≃ τ) : rename e (esymm σ R n) = esymm τ R n :=
  calc
    rename e (esymm σ R n) = ∑ x in powersetLen n univ, ∏ i in x, X (e i) := by
      simp_rw [esymm, map_sum, map_prod, rename_X]
    _ = ∑ t in powersetLen n (univ.map e.toEmbedding), ∏ i in t, X i := by
      simp [Finset.powersetLen_map, -Finset.map_univ_equiv]
    _ = ∑ t in powersetLen n univ, ∏ i in t, X i := by rw [Finset.map_univ_equiv]

#align mv_polynomial.rename_esymm MvPolynomial.rename_esymm

theorem esymm_isSymmetric (n : ℕ) : IsSymmetric (esymm σ R n) := by
  intro
  rw [rename_esymm]
#align mv_polynomial.esymm_is_symmetric MvPolynomial.esymm_isSymmetric

theorem support_esymm'' (n : ℕ) [DecidableEq σ] [Nontrivial R] :
    (esymm σ R n).support =
      (powersetLen n (univ : Finset σ)).bunionᵢ fun t =>
        (Finsupp.single (∑ i : σ in t, Finsupp.single i 1) (1 : R)).support := by
  rw [esymm_eq_sum_monomial]
  simp only [← single_eq_monomial]
  refine' Finsupp.support_sum_eq_bunionᵢ (powersetLen n (univ : Finset σ)) _
  intro s t hst
  rw [Finset.disjoint_left, Finsupp.support_single_ne_zero _ one_ne_zero]
  rw [Finsupp.support_single_ne_zero _ one_ne_zero]
  simp only [one_ne_zero, mem_singleton, Finsupp.mem_support_iff]
  rintro a h rfl
  have := congr_arg Finsupp.support h
  rw [Finsupp.support_sum_eq_bunionᵢ, Finsupp.support_sum_eq_bunionᵢ] at this
  have hsingle : ∀ s : Finset σ, ∀ x : σ, x ∈ s → (Finsupp.single x 1).support = {x} := by
    intros _ x _
    rw [Finsupp.support_single_ne_zero x one_ne_zero]
  have hs := bunionᵢ_congr (of_eq_true (eq_self s)) (hsingle s)
  have ht := bunionᵢ_congr (of_eq_true (eq_self t)) (hsingle t)
  rw [hs, ht] at this
  simp only [Finsupp.support_single_ne_zero _ one_ne_zero] at this
  · simp only [bunionᵢ_singleton_eq_self] at this
    exact absurd this hst.symm
  all_goals intro x y; simp [Finsupp.support_single_disjoint]
#align mv_polynomial.support_esymm'' MvPolynomial.support_esymm''

theorem support_esymm (n : ℕ) [DecidableEq σ] [Nontrivial R] :
    (esymm σ R n).support =
      (powersetLen n (univ : Finset σ)).image fun t => ∑ i : σ in t, Finsupp.single i 1 := by
  rw [support_esymm']
  exact bunionᵢ_singleton
#align mv_polynomial.support_esymm MvPolynomial.support_esymm

theorem degrees_esymm [Nontrivial R] (n : ℕ) (hpos : 0 < n) (hn : n ≤ Fintype.card σ) :
    (esymm σ R n).degrees = (univ : Finset σ).val := by
  classical
    have :
      (Finsupp.toMultiset ∘ fun t : Finset σ => ∑ i : σ in t, Finsupp.single i 1) = Finset.val :=
      by
      funext
      simp [Finsupp.toMultiset_sum_single]
    rw [degrees, support_esymm, sup_finset_image, this]
    have : ((powersetLen n univ).sup (fun (x : Finset σ) => x)).val
        = sup (powersetLen n univ) val := by
      refine' comp_sup_eq_sup_comp _ _ _
      · intros
        simp only [union_val, sup_eq_union]
        congr
      · rfl
    rw [← this]
    obtain ⟨k, rfl⟩ := Nat.exists_eq_succ_of_ne_zero hpos.ne'
    simpa using powerset_len_sup _ _ (Nat.lt_of_succ_le hn)
#align mv_polynomial.degrees_esymm MvPolynomial.degrees_esymm

end ElementarySymmetric

end MvPolynomial


variables p q r s : Prop

theorem allah : ¬p ∨ ¬q → ¬(p ∧ q) :=
assume h₁ : ¬p ∨ ¬q,
h₁.elim
    (assume hnp : ¬p,
    assume hpq : p ∧ q,
    absurd hpq.left hnp)
    (assume hnq : ¬q,
    assume hpq : p ∧ q,
    absurd hpq.right hnq)

theorem yhwh : ¬(p ∧ ¬p) :=
assume hpnp : p ∧ ¬p,
absurd hpnp.left hpnp.right

theorem vaisravana : p ∧ ¬q → ¬(p → q) :=
assume hpnq : p ∧ ¬q,
assume hpiq : p → q,
have hq : q, from hpiq (hpnq.left),
absurd hq hpnq.right

theorem amaterasu : ¬p → (p → q) :=
assume hnp : ¬p,
assume hp : p,
absurd hp hnp

theorem gabriel : (¬p ∨ q) → (p → q) :=
assume hnpq : ¬p ∨ q,
assume hp : p,
hnpq.elim
    (assume hnp : ¬p, absurd hp hnp)
    (assume hq : q, hq)

theorem suwako : (p ∨ false) ↔ p :=
iff.intro
    (assume hpf : p ∨ false,
    hpf.elim
        (assume hp : p, hp)
        (assume hf : false, hf.elim))
    (assume hp : p,
    or.inl hp)

theorem kanako : p ∧ false ↔ false :=
iff.intro
    (assume hpf : p ∧ false,
    (hpf.right).elim)
    (assume hf : false, hf.elim)

theorem athena : ¬(p ↔ ¬p) :=
assume h : p ↔ ¬p,
have hpnp : p → ¬p, from h.mp,
have hnpp : ¬p → p, from h.mpr,
have hnp : p → false, from
    (assume hp : p,
    show false, from hpnp hp hp),
have hp : p, from hnpp hnp,
absurd hp hnp

theorem hermes : (p → q) → (¬q → ¬p) :=
assume hpq : p → q,
assume hnq : ¬q,
assume hp : p,
absurd (hpq hp) hnq