/-
Copyright (c) 2018 Andreas Swerdlow. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Andreas Swerdlow, Kexing Ying

! This file was ported from Lean 3 source module linear_algebra.bilinear_form
! leanprover-community/mathlib commit d11893b411025250c8e61ff2f12ccbd7ee35ab15
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.LinearAlgebra.Dual
import Mathbin.LinearAlgebra.Matrix.ToLin

/-!
# Bilinear form

This file defines a bilinear form over a module. Basic ideas
such as orthogonality are also introduced, as well as reflexivive,
symmetric, non-degenerate and alternating bilinear forms. Adjoints of
linear maps with respect to a bilinear form are also introduced.

A bilinear form on an R-(semi)module M, is a function from M x M to R,
that is linear in both arguments. Comments will typically abbreviate
"(semi)module" as just "module", but the definitions should be as general as
possible.

The result that there exists an orthogonal basis with respect to a symmetric,
nondegenerate bilinear form can be found in `quadratic_form.lean` with
`exists_orthogonal_basis`.

## Notations

Given any term B of type bilin_form, due to a coercion, can use
the notation B x y to refer to the function field, ie. B x y = B.bilin x y.

In this file we use the following type variables:
 - `M`, `M'`, ... are modules over the semiring `R`,
 - `M₁`, `M₁'`, ... are modules over the ring `R₁`,
 - `M₂`, `M₂'`, ... are modules over the commutative semiring `R₂`,
 - `M₃`, `M₃'`, ... are modules over the commutative ring `R₃`,
 - `V`, ... is a vector space over the field `K`.

## References

* <https://en.wikipedia.org/wiki/Bilinear_form>

## Tags

Bilinear form,
-/

open BigOperators

universe u v w

/-- `bilin_form R M` is the type of `R`-bilinear functions `M → M → R`. -/
structure BilinForm (R : Type _) (M : Type _) [Semiring R] [AddCommMonoid M] [Module R M] where
  bilin : M → M → R
  bilin_add_left : ∀ x y z : M, bilin (x + y) z = bilin x z + bilin y z
  bilin_smul_left : ∀ (a : R) (x y : M), bilin (a • x) y = a * bilin x y
  bilin_add_right : ∀ x y z : M, bilin x (y + z) = bilin x y + bilin x z
  bilin_smul_right : ∀ (a : R) (x y : M), bilin x (a • y) = a * bilin x y
#align bilin_form BilinForm

variable {R : Type _} {M : Type _} [Semiring R] [AddCommMonoid M] [Module R M]

variable {R₁ : Type _} {M₁ : Type _} [Ring R₁] [AddCommGroup M₁] [Module R₁ M₁]

variable {R₂ : Type _} {M₂ : Type _} [CommSemiring R₂] [AddCommMonoid M₂] [Module R₂ M₂]

variable {R₃ : Type _} {M₃ : Type _} [CommRing R₃] [AddCommGroup M₃] [Module R₃ M₃]

variable {V : Type _} {K : Type _} [Field K] [AddCommGroup V] [Module K V]

variable {B : BilinForm R M} {B₁ : BilinForm R₁ M₁} {B₂ : BilinForm R₂ M₂}

namespace BilinForm

instance : CoeFun (BilinForm R M) fun _ => M → M → R :=
  ⟨bilin⟩

initialize_simps_projections BilinForm (bilin → apply)

@[simp]
theorem coeFn_mk (f : M → M → R) (h₁ h₂ h₃ h₄) : (BilinForm.mk f h₁ h₂ h₃ h₄ : M → M → R) = f :=
  rfl
#align bilin_form.coe_fn_mk BilinForm.coeFn_mk

theorem coeFn_congr : ∀ {x x' y y' : M}, x = x' → y = y' → B x y = B x' y'
  | _, _, _, _, rfl, rfl => rfl
#align bilin_form.coe_fn_congr BilinForm.coeFn_congr

@[simp]
theorem add_left (x y z : M) : B (x + y) z = B x z + B y z :=
  bilin_add_left B x y z
#align bilin_form.add_left BilinForm.add_left

@[simp]
theorem smul_left (a : R) (x y : M) : B (a • x) y = a * B x y :=
  bilin_smul_left B a x y
#align bilin_form.smul_left BilinForm.smul_left

@[simp]
theorem add_right (x y z : M) : B x (y + z) = B x y + B x z :=
  bilin_add_right B x y z
#align bilin_form.add_right BilinForm.add_right

@[simp]
theorem smul_right (a : R) (x y : M) : B x (a • y) = a * B x y :=
  bilin_smul_right B a x y
#align bilin_form.smul_right BilinForm.smul_right

@[simp]
theorem zero_left (x : M) : B 0 x = 0 := by
  rw [← @zero_smul R _ _ _ _ (0 : M), smul_left, MulZeroClass.zero_mul]
#align bilin_form.zero_left BilinForm.zero_left

@[simp]
theorem zero_right (x : M) : B x 0 = 0 := by
  rw [← @zero_smul _ _ _ _ _ (0 : M), smul_right, MulZeroClass.zero_mul]
#align bilin_form.zero_right BilinForm.zero_right

@[simp]
theorem neg_left (x y : M₁) : B₁ (-x) y = -B₁ x y := by
  rw [← @neg_one_smul R₁ _ _, smul_left, neg_one_mul]
#align bilin_form.neg_left BilinForm.neg_left

@[simp]
theorem neg_right (x y : M₁) : B₁ x (-y) = -B₁ x y := by
  rw [← @neg_one_smul R₁ _ _, smul_right, neg_one_mul]
#align bilin_form.neg_right BilinForm.neg_right

@[simp]
theorem sub_left (x y z : M₁) : B₁ (x - y) z = B₁ x z - B₁ y z := by
  rw [sub_eq_add_neg, sub_eq_add_neg, add_left, neg_left]
#align bilin_form.sub_left BilinForm.sub_left

@[simp]
theorem sub_right (x y z : M₁) : B₁ x (y - z) = B₁ x y - B₁ x z := by
  rw [sub_eq_add_neg, sub_eq_add_neg, add_right, neg_right]
#align bilin_form.sub_right BilinForm.sub_right

variable {D : BilinForm R M} {D₁ : BilinForm R₁ M₁}

-- TODO: instantiate `fun_like`
theorem coe_injective : Function.Injective (coeFn : BilinForm R M → M → M → R) := fun B D h =>
  by
  cases B
  cases D
  congr
#align bilin_form.coe_injective BilinForm.coe_injective

@[ext]
theorem ext (H : ∀ x y : M, B x y = D x y) : B = D :=
  coe_injective <| by
    funext
    exact H _ _
#align bilin_form.ext BilinForm.ext

theorem congr_fun (h : B = D) (x y : M) : B x y = D x y :=
  h ▸ rfl
#align bilin_form.congr_fun BilinForm.congr_fun

theorem ext_iff : B = D ↔ ∀ x y, B x y = D x y :=
  ⟨congr_fun, ext⟩
#align bilin_form.ext_iff BilinForm.ext_iff

instance : Zero (BilinForm R M)
    where zero :=
    { bilin := fun x y => 0
      bilin_add_left := fun x y z => (add_zero 0).symm
      bilin_smul_left := fun a x y => (MulZeroClass.mul_zero a).symm
      bilin_add_right := fun x y z => (zero_add 0).symm
      bilin_smul_right := fun a x y => (MulZeroClass.mul_zero a).symm }

@[simp]
theorem coe_zero : ⇑(0 : BilinForm R M) = 0 :=
  rfl
#align bilin_form.coe_zero BilinForm.coe_zero

@[simp]
theorem zero_apply (x y : M) : (0 : BilinForm R M) x y = 0 :=
  rfl
#align bilin_form.zero_apply BilinForm.zero_apply

variable (B D B₁ D₁)

instance : Add (BilinForm R M)
    where add B D :=
    { bilin := fun x y => B x y + D x y
      bilin_add_left := fun x y z => by rw [add_left, add_left, add_add_add_comm]
      bilin_smul_left := fun a x y => by rw [smul_left, smul_left, mul_add]
      bilin_add_right := fun x y z => by rw [add_right, add_right, add_add_add_comm]
      bilin_smul_right := fun a x y => by rw [smul_right, smul_right, mul_add] }

@[simp]
theorem coe_add : ⇑(B + D) = B + D :=
  rfl
#align bilin_form.coe_add BilinForm.coe_add

@[simp]
theorem add_apply (x y : M) : (B + D) x y = B x y + D x y :=
  rfl
#align bilin_form.add_apply BilinForm.add_apply

/-- `bilin_form R M` inherits the scalar action by `α` on `R` if this is compatible with
multiplication.

When `R` itself is commutative, this provides an `R`-action via `algebra.id`. -/
instance {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] : SMul α (BilinForm R M)
    where smul c B :=
    { bilin := fun x y => c • B x y
      bilin_add_left := fun x y z => by rw [add_left, smul_add]
      bilin_smul_left := fun a x y => by rw [smul_left, ← mul_smul_comm]
      bilin_add_right := fun x y z => by rw [add_right, smul_add]
      bilin_smul_right := fun a x y => by rw [smul_right, ← mul_smul_comm] }

@[simp]
theorem coe_smul {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] (a : α)
    (B : BilinForm R M) : ⇑(a • B) = a • B :=
  rfl
#align bilin_form.coe_smul BilinForm.coe_smul

@[simp]
theorem smul_apply {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] (a : α)
    (B : BilinForm R M) (x y : M) : (a • B) x y = a • B x y :=
  rfl
#align bilin_form.smul_apply BilinForm.smul_apply

instance : AddCommMonoid (BilinForm R M) :=
  Function.Injective.addCommMonoid _ coe_injective coe_zero coe_add fun n x => coe_smul _ _

instance : Neg (BilinForm R₁ M₁)
    where neg B :=
    { bilin := fun x y => -B x y
      bilin_add_left := fun x y z => by rw [add_left, neg_add]
      bilin_smul_left := fun a x y => by rw [smul_left, mul_neg]
      bilin_add_right := fun x y z => by rw [add_right, neg_add]
      bilin_smul_right := fun a x y => by rw [smul_right, mul_neg] }

@[simp]
theorem coe_neg : ⇑(-B₁) = -B₁ :=
  rfl
#align bilin_form.coe_neg BilinForm.coe_neg

@[simp]
theorem neg_apply (x y : M₁) : (-B₁) x y = -B₁ x y :=
  rfl
#align bilin_form.neg_apply BilinForm.neg_apply

instance : Sub (BilinForm R₁ M₁)
    where sub B D :=
    { bilin := fun x y => B x y - D x y
      bilin_add_left := fun x y z => by rw [add_left, add_left, add_sub_add_comm]
      bilin_smul_left := fun a x y => by rw [smul_left, smul_left, mul_sub]
      bilin_add_right := fun x y z => by rw [add_right, add_right, add_sub_add_comm]
      bilin_smul_right := fun a x y => by rw [smul_right, smul_right, mul_sub] }

@[simp]
theorem coe_sub : ⇑(B₁ - D₁) = B₁ - D₁ :=
  rfl
#align bilin_form.coe_sub BilinForm.coe_sub

@[simp]
theorem sub_apply (x y : M₁) : (B₁ - D₁) x y = B₁ x y - D₁ x y :=
  rfl
#align bilin_form.sub_apply BilinForm.sub_apply

instance : AddCommGroup (BilinForm R₁ M₁) :=
  Function.Injective.addCommGroup _ coe_injective coe_zero coe_add coe_neg coe_sub
    (fun n x => coe_smul _ _) fun n x => coe_smul _ _

instance : Inhabited (BilinForm R M) :=
  ⟨0⟩

/-- `coe_fn` as an `add_monoid_hom` -/
def coeFnAddMonoidHom : BilinForm R M →+ M → M → R
    where
  toFun := coeFn
  map_zero' := coe_zero
  map_add' := coe_add
#align bilin_form.coe_fn_add_monoid_hom BilinForm.coeFnAddMonoidHom

instance {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] :
    DistribMulAction α (BilinForm R M) :=
  Function.Injective.distribMulAction coeFnAddMonoidHom coe_injective coe_smul

instance {α} [Semiring α] [Module α R] [SMulCommClass α R R] : Module α (BilinForm R M) :=
  Function.Injective.module _ coeFnAddMonoidHom coe_injective coe_smul

section flip

variable (R₂)

/-- Auxiliary construction for the flip of a bilinear form, obtained by exchanging the left and
right arguments. This version is a `linear_map`; it is later upgraded to a `linear_equiv`
in `flip_hom`. -/
def flipHomAux [Algebra R₂ R] : BilinForm R M →ₗ[R₂] BilinForm R M
    where
  toFun A :=
    { bilin := fun i j => A j i
      bilin_add_left := fun x y z => A.bilin_add_right z x y
      bilin_smul_left := fun a x y => A.bilin_smul_right a y x
      bilin_add_right := fun x y z => A.bilin_add_left y z x
      bilin_smul_right := fun a x y => A.bilin_smul_left a y x }
  map_add' A₁ A₂ := by
    ext
    simp
  map_smul' c A := by
    ext
    simp
#align bilin_form.flip_hom_aux BilinForm.flipHomAux

variable {R₂}

theorem flip_flip_aux [Algebra R₂ R] (A : BilinForm R M) : (flipHomAux R₂) (flipHomAux R₂ A) = A :=
  by
  ext (A x y)
  simp [flip_hom_aux]
#align bilin_form.flip_flip_aux BilinForm.flip_flip_aux

variable (R₂)

/-- The flip of a bilinear form, obtained by exchanging the left and right arguments. This is a
less structured version of the equiv which applies to general (noncommutative) rings `R` with a
distinguished commutative subring `R₂`; over a commutative ring use `flip`. -/
def flipHom [Algebra R₂ R] : BilinForm R M ≃ₗ[R₂] BilinForm R M :=
  { flipHomAux R₂ with
    invFun := flipHomAux R₂
    left_inv := flip_flip_aux
    right_inv := flip_flip_aux }
#align bilin_form.flip_hom BilinForm.flipHom

variable {R₂}

@[simp]
theorem flip_apply [Algebra R₂ R] (A : BilinForm R M) (x y : M) : flipHom R₂ A x y = A y x :=
  rfl
#align bilin_form.flip_apply BilinForm.flip_apply

theorem flip_flip [Algebra R₂ R] :
    (flipHom R₂).trans (flipHom R₂) = LinearEquiv.refl R₂ (BilinForm R M) :=
  by
  ext (A x y)
  simp
#align bilin_form.flip_flip BilinForm.flip_flip

/-- The flip of a bilinear form over a ring, obtained by exchanging the left and right arguments,
here considered as an `ℕ`-linear equivalence, i.e. an additive equivalence. -/
abbrev flip' : BilinForm R M ≃ₗ[ℕ] BilinForm R M :=
  flipHom ℕ
#align bilin_form.flip' BilinForm.flip'

/-- The `flip` of a bilinear form over a commutative ring, obtained by exchanging the left and
right arguments. -/
abbrev flip : BilinForm R₂ M₂ ≃ₗ[R₂] BilinForm R₂ M₂ :=
  flipHom R₂
#align bilin_form.flip BilinForm.flip

end flip

section ToLin'

variable [Algebra R₂ R] [Module R₂ M] [IsScalarTower R₂ R M]

/-- Auxiliary definition to define `to_lin_hom`; see below. -/
def toLinHomAux₁ (A : BilinForm R M) (x : M) : M →ₗ[R] R
    where
  toFun y := A x y
  map_add' := A.bilin_add_right x
  map_smul' c := A.bilin_smul_right c x
#align bilin_form.to_lin_hom_aux₁ BilinForm.toLinHomAux₁

/-- Auxiliary definition to define `to_lin_hom`; see below. -/
def toLinHomAux₂ (A : BilinForm R M) : M →ₗ[R₂] M →ₗ[R] R
    where
  toFun := toLinHomAux₁ A
  map_add' x₁ x₂ :=
    LinearMap.ext fun x => by
      simp only [to_lin_hom_aux₁, LinearMap.coe_mk, LinearMap.add_apply, add_left]
  map_smul' c x :=
    LinearMap.ext <| by
      dsimp [to_lin_hom_aux₁]
      intros
      simp only [← algebraMap_smul R c x, Algebra.smul_def, LinearMap.coe_mk, LinearMap.smul_apply,
        smul_left]
#align bilin_form.to_lin_hom_aux₂ BilinForm.toLinHomAux₂

variable (R₂)

/-- The linear map obtained from a `bilin_form` by fixing the left co-ordinate and evaluating in
the right.
This is the most general version of the construction; it is `R₂`-linear for some distinguished
commutative subsemiring `R₂` of the scalar ring.  Over a semiring with no particular distinguished
such subsemiring, use `to_lin'`, which is `ℕ`-linear.  Over a commutative semiring, use `to_lin`,
which is linear. -/
def toLinHom : BilinForm R M →ₗ[R₂] M →ₗ[R₂] M →ₗ[R] R
    where
  toFun := toLinHomAux₂
  map_add' A₁ A₂ :=
    LinearMap.ext fun x => by
      dsimp only [to_lin_hom_aux₁, to_lin_hom_aux₂]
      apply LinearMap.ext
      intro y
      simp only [to_lin_hom_aux₂, to_lin_hom_aux₁, LinearMap.coe_mk, LinearMap.add_apply, add_apply]
  map_smul' c A := by
    dsimp [to_lin_hom_aux₁, to_lin_hom_aux₂]
    apply LinearMap.ext
    intro x
    apply LinearMap.ext
    intro y
    simp only [to_lin_hom_aux₂, to_lin_hom_aux₁, LinearMap.coe_mk, LinearMap.smul_apply, smul_apply]
#align bilin_form.to_lin_hom BilinForm.toLinHom

variable {R₂}

@[simp]
theorem to_lin'_apply (A : BilinForm R M) (x : M) : ⇑(toLinHom R₂ A x) = A x :=
  rfl
#align bilin_form.to_lin'_apply BilinForm.to_lin'_apply

/-- The linear map obtained from a `bilin_form` by fixing the left co-ordinate and evaluating in
the right.
Over a commutative semiring, use `to_lin`, which is linear rather than `ℕ`-linear. -/
abbrev toLin' : BilinForm R M →ₗ[ℕ] M →ₗ[ℕ] M →ₗ[R] R :=
  toLinHom ℕ
#align bilin_form.to_lin' BilinForm.toLin'

@[simp]
theorem sum_left {α} (t : Finset α) (g : α → M) (w : M) :
    B (∑ i in t, g i) w = ∑ i in t, B (g i) w :=
  (BilinForm.toLin' B).map_sum₂ t g w
#align bilin_form.sum_left BilinForm.sum_left

@[simp]
theorem sum_right {α} (t : Finset α) (w : M) (g : α → M) :
    B w (∑ i in t, g i) = ∑ i in t, B w (g i) :=
  (BilinForm.toLin' B w).map_sum
#align bilin_form.sum_right BilinForm.sum_right

variable (R₂)

/-- The linear map obtained from a `bilin_form` by fixing the right co-ordinate and evaluating in
the left.
This is the most general version of the construction; it is `R₂`-linear for some distinguished
commutative subsemiring `R₂` of the scalar ring.  Over semiring with no particular distinguished
such subsemiring, use `to_lin'_flip`, which is `ℕ`-linear.  Over a commutative semiring, use
`to_lin_flip`, which is linear. -/
def toLinHomFlip : BilinForm R M →ₗ[R₂] M →ₗ[R₂] M →ₗ[R] R :=
  (toLinHom R₂).comp (flipHom R₂).toLinearMap
#align bilin_form.to_lin_hom_flip BilinForm.toLinHomFlip

variable {R₂}

@[simp]
theorem to_lin'_flip_apply (A : BilinForm R M) (x : M) : ⇑(toLinHomFlip R₂ A x) = fun y => A y x :=
  rfl
#align bilin_form.to_lin'_flip_apply BilinForm.to_lin'_flip_apply

/-- The linear map obtained from a `bilin_form` by fixing the right co-ordinate and evaluating in
the left.
Over a commutative semiring, use `to_lin_flip`, which is linear rather than `ℕ`-linear. -/
abbrev toLin'Flip : BilinForm R M →ₗ[ℕ] M →ₗ[ℕ] M →ₗ[R] R :=
  toLinHomFlip ℕ
#align bilin_form.to_lin'_flip BilinForm.toLin'Flip

end ToLin'

end BilinForm

section EquivLin

/-- A map with two arguments that is linear in both is a bilinear form.

This is an auxiliary definition for the full linear equivalence `linear_map.to_bilin`.
-/
def LinearMap.toBilinAux (f : M₂ →ₗ[R₂] M₂ →ₗ[R₂] R₂) : BilinForm R₂ M₂
    where
  bilin x y := f x y
  bilin_add_left x y z := (LinearMap.map_add f x y).symm ▸ LinearMap.add_apply (f x) (f y) z
  bilin_smul_left a x y := by rw [LinearMap.map_smul, LinearMap.smul_apply, smul_eq_mul]
  bilin_add_right x y z := LinearMap.map_add (f x) y z
  bilin_smul_right a x y := LinearMap.map_smul (f x) a y
#align linear_map.to_bilin_aux LinearMap.toBilinAux

/-- Bilinear forms are linearly equivalent to maps with two arguments that are linear in both. -/
def BilinForm.toLin : BilinForm R₂ M₂ ≃ₗ[R₂] M₂ →ₗ[R₂] M₂ →ₗ[R₂] R₂ :=
  { BilinForm.toLinHom R₂ with
    invFun := LinearMap.toBilinAux
    left_inv := fun B => by
      ext
      simp [LinearMap.toBilinAux]
    right_inv := fun B => by
      ext
      simp [LinearMap.toBilinAux] }
#align bilin_form.to_lin BilinForm.toLin

/-- A map with two arguments that is linear in both is linearly equivalent to bilinear form. -/
def LinearMap.toBilin : (M₂ →ₗ[R₂] M₂ →ₗ[R₂] R₂) ≃ₗ[R₂] BilinForm R₂ M₂ :=
  BilinForm.toLin.symm
#align linear_map.to_bilin LinearMap.toBilin

@[simp]
theorem LinearMap.toBilinAux_eq (f : M₂ →ₗ[R₂] M₂ →ₗ[R₂] R₂) :
    LinearMap.toBilinAux f = LinearMap.toBilin f :=
  rfl
#align linear_map.to_bilin_aux_eq LinearMap.toBilinAux_eq

@[simp]
theorem LinearMap.toBilin_symm :
    (LinearMap.toBilin.symm : BilinForm R₂ M₂ ≃ₗ[R₂] _) = BilinForm.toLin :=
  rfl
#align linear_map.to_bilin_symm LinearMap.toBilin_symm

@[simp]
theorem BilinForm.toLin_symm :
    (BilinForm.toLin.symm : _ ≃ₗ[R₂] BilinForm R₂ M₂) = LinearMap.toBilin :=
  LinearMap.toBilin.symm_symm
#align bilin_form.to_lin_symm BilinForm.toLin_symm

@[simp, norm_cast]
theorem BilinForm.toLin_apply (x : M₂) : ⇑(BilinForm.toLin B₂ x) = B₂ x :=
  rfl
#align bilin_form.to_lin_apply BilinForm.toLin_apply

end EquivLin

namespace LinearMap

variable {R' : Type _} [CommSemiring R'] [Algebra R' R] [Module R' M] [IsScalarTower R' R M]

/-- Apply a linear map on the output of a bilinear form. -/
@[simps]
def compBilinForm (f : R →ₗ[R'] R') (B : BilinForm R M) : BilinForm R' M
    where
  bilin x y := f (B x y)
  bilin_add_left x y z := by rw [BilinForm.add_left, map_add]
  bilin_smul_left r x y := by
    rw [← smul_one_smul R r (_ : M), BilinForm.smul_left, smul_one_mul r (_ : R), map_smul,
      smul_eq_mul]
  bilin_add_right x y z := by rw [BilinForm.add_right, map_add]
  bilin_smul_right r x y := by
    rw [← smul_one_smul R r (_ : M), BilinForm.smul_right, smul_one_mul r (_ : R), map_smul,
      smul_eq_mul]
#align linear_map.comp_bilin_form LinearMap.compBilinForm

end LinearMap

namespace BilinForm

section Comp

variable {M' : Type w} [AddCommMonoid M'] [Module R M']

/-- Apply a linear map on the left and right argument of a bilinear form. -/
def comp (B : BilinForm R M') (l r : M →ₗ[R] M') : BilinForm R M
    where
  bilin x y := B (l x) (r y)
  bilin_add_left x y z := by rw [LinearMap.map_add, add_left]
  bilin_smul_left x y z := by rw [LinearMap.map_smul, smul_left]
  bilin_add_right x y z := by rw [LinearMap.map_add, add_right]
  bilin_smul_right x y z := by rw [LinearMap.map_smul, smul_right]
#align bilin_form.comp BilinForm.comp

/-- Apply a linear map to the left argument of a bilinear form. -/
def compLeft (B : BilinForm R M) (f : M →ₗ[R] M) : BilinForm R M :=
  B.comp f LinearMap.id
#align bilin_form.comp_left BilinForm.compLeft

/-- Apply a linear map to the right argument of a bilinear form. -/
def compRight (B : BilinForm R M) (f : M →ₗ[R] M) : BilinForm R M :=
  B.comp LinearMap.id f
#align bilin_form.comp_right BilinForm.compRight

theorem comp_comp {M'' : Type _} [AddCommMonoid M''] [Module R M''] (B : BilinForm R M'')
    (l r : M →ₗ[R] M') (l' r' : M' →ₗ[R] M'') :
    (B.comp l' r').comp l r = B.comp (l'.comp l) (r'.comp r) :=
  rfl
#align bilin_form.comp_comp BilinForm.comp_comp

@[simp]
theorem compLeft_compRight (B : BilinForm R M) (l r : M →ₗ[R] M) :
    (B.compLeft l).compRight r = B.comp l r :=
  rfl
#align bilin_form.comp_left_comp_right BilinForm.compLeft_compRight

@[simp]
theorem compRight_compLeft (B : BilinForm R M) (l r : M →ₗ[R] M) :
    (B.compRight r).compLeft l = B.comp l r :=
  rfl
#align bilin_form.comp_right_comp_left BilinForm.compRight_compLeft

@[simp]
theorem comp_apply (B : BilinForm R M') (l r : M →ₗ[R] M') (v w) : B.comp l r v w = B (l v) (r w) :=
  rfl
#align bilin_form.comp_apply BilinForm.comp_apply

@[simp]
theorem compLeft_apply (B : BilinForm R M) (f : M →ₗ[R] M) (v w) : B.compLeft f v w = B (f v) w :=
  rfl
#align bilin_form.comp_left_apply BilinForm.compLeft_apply

@[simp]
theorem compRight_apply (B : BilinForm R M) (f : M →ₗ[R] M) (v w) : B.compRight f v w = B v (f w) :=
  rfl
#align bilin_form.comp_right_apply BilinForm.compRight_apply

@[simp]
theorem comp_id_left (B : BilinForm R M) (r : M →ₗ[R] M) : B.comp LinearMap.id r = B.compRight r :=
  by
  ext
  rfl
#align bilin_form.comp_id_left BilinForm.comp_id_left

@[simp]
theorem comp_id_right (B : BilinForm R M) (l : M →ₗ[R] M) : B.comp l LinearMap.id = B.compLeft l :=
  by
  ext
  rfl
#align bilin_form.comp_id_right BilinForm.comp_id_right

@[simp]
theorem compLeft_id (B : BilinForm R M) : B.compLeft LinearMap.id = B :=
  by
  ext
  rfl
#align bilin_form.comp_left_id BilinForm.compLeft_id

@[simp]
theorem compRight_id (B : BilinForm R M) : B.compRight LinearMap.id = B :=
  by
  ext
  rfl
#align bilin_form.comp_right_id BilinForm.compRight_id

-- Shortcut for `comp_id_{left,right}` followed by `comp_{right,left}_id`,
-- has to be declared after the former two to get the right priority
@[simp]
theorem comp_id_id (B : BilinForm R M) : B.comp LinearMap.id LinearMap.id = B :=
  by
  ext
  rfl
#align bilin_form.comp_id_id BilinForm.comp_id_id

theorem comp_inj (B₁ B₂ : BilinForm R M') {l r : M →ₗ[R] M'} (hₗ : Function.Surjective l)
    (hᵣ : Function.Surjective r) : B₁.comp l r = B₂.comp l r ↔ B₁ = B₂ :=
  by
  constructor <;> intro h
  · -- B₁.comp l r = B₂.comp l r → B₁ = B₂
    ext
    cases' hₗ x with x' hx
    subst hx
    cases' hᵣ y with y' hy
    subst hy
    rw [← comp_apply, ← comp_apply, h]
  ·-- B₁ = B₂ → B₁.comp l r = B₂.comp l r
    subst h
#align bilin_form.comp_inj BilinForm.comp_inj

end Comp

variable {M₂' M₂'' : Type _}

variable [AddCommMonoid M₂'] [AddCommMonoid M₂''] [Module R₂ M₂'] [Module R₂ M₂'']

section congr

/-- Apply a linear equivalence on the arguments of a bilinear form. -/
def congr (e : M₂ ≃ₗ[R₂] M₂') : BilinForm R₂ M₂ ≃ₗ[R₂] BilinForm R₂ M₂'
    where
  toFun B := B.comp e.symm e.symm
  invFun B := B.comp e e
  left_inv B := ext fun x y => by simp only [comp_apply, LinearEquiv.coe_coe, e.symm_apply_apply]
  right_inv B := ext fun x y => by simp only [comp_apply, LinearEquiv.coe_coe, e.apply_symm_apply]
  map_add' B B' := ext fun x y => by simp only [comp_apply, add_apply]
  map_smul' B B' := ext fun x y => by simp [comp_apply, smul_apply]
#align bilin_form.congr BilinForm.congr

@[simp]
theorem congr_apply (e : M₂ ≃ₗ[R₂] M₂') (B : BilinForm R₂ M₂) (x y : M₂') :
    congr e B x y = B (e.symm x) (e.symm y) :=
  rfl
#align bilin_form.congr_apply BilinForm.congr_apply

@[simp]
theorem congr_symm (e : M₂ ≃ₗ[R₂] M₂') : (congr e).symm = congr e.symm :=
  by
  ext (B x y)
  simp only [congr_apply, LinearEquiv.symm_symm]
  rfl
#align bilin_form.congr_symm BilinForm.congr_symm

@[simp]
theorem congr_refl : congr (LinearEquiv.refl R₂ M₂) = LinearEquiv.refl R₂ _ :=
  LinearEquiv.ext fun B => ext fun x y => rfl
#align bilin_form.congr_refl BilinForm.congr_refl

theorem congr_trans (e : M₂ ≃ₗ[R₂] M₂') (f : M₂' ≃ₗ[R₂] M₂'') :
    (congr e).trans (congr f) = congr (e.trans f) :=
  rfl
#align bilin_form.congr_trans BilinForm.congr_trans

theorem congr_congr (e : M₂' ≃ₗ[R₂] M₂'') (f : M₂ ≃ₗ[R₂] M₂') (B : BilinForm R₂ M₂) :
    congr e (congr f B) = congr (f.trans e) B :=
  rfl
#align bilin_form.congr_congr BilinForm.congr_congr

theorem congr_comp (e : M₂ ≃ₗ[R₂] M₂') (B : BilinForm R₂ M₂) (l r : M₂'' →ₗ[R₂] M₂') :
    (congr e B).comp l r =
      B.comp (LinearMap.comp (e.symm : M₂' →ₗ[R₂] M₂) l)
        (LinearMap.comp (e.symm : M₂' →ₗ[R₂] M₂) r) :=
  rfl
#align bilin_form.congr_comp BilinForm.congr_comp

theorem comp_congr (e : M₂' ≃ₗ[R₂] M₂'') (B : BilinForm R₂ M₂) (l r : M₂' →ₗ[R₂] M₂) :
    congr e (B.comp l r) =
      B.comp (l.comp (e.symm : M₂'' →ₗ[R₂] M₂')) (r.comp (e.symm : M₂'' →ₗ[R₂] M₂')) :=
  rfl
#align bilin_form.comp_congr BilinForm.comp_congr

end congr

section LinMulLin

/-- `lin_mul_lin f g` is the bilinear form mapping `x` and `y` to `f x * g y` -/
def linMulLin (f g : M₂ →ₗ[R₂] R₂) : BilinForm R₂ M₂
    where
  bilin x y := f x * g y
  bilin_add_left x y z := by rw [LinearMap.map_add, add_mul]
  bilin_smul_left x y z := by rw [LinearMap.map_smul, smul_eq_mul, mul_assoc]
  bilin_add_right x y z := by rw [LinearMap.map_add, mul_add]
  bilin_smul_right x y z := by rw [LinearMap.map_smul, smul_eq_mul, mul_left_comm]
#align bilin_form.lin_mul_lin BilinForm.linMulLin

variable {f g : M₂ →ₗ[R₂] R₂}

@[simp]
theorem linMulLin_apply (x y) : linMulLin f g x y = f x * g y :=
  rfl
#align bilin_form.lin_mul_lin_apply BilinForm.linMulLin_apply

@[simp]
theorem linMulLin_comp (l r : M₂' →ₗ[R₂] M₂) :
    (linMulLin f g).comp l r = linMulLin (f.comp l) (g.comp r) :=
  rfl
#align bilin_form.lin_mul_lin_comp BilinForm.linMulLin_comp

@[simp]
theorem linMulLin_compLeft (l : M₂ →ₗ[R₂] M₂) :
    (linMulLin f g).compLeft l = linMulLin (f.comp l) g :=
  rfl
#align bilin_form.lin_mul_lin_comp_left BilinForm.linMulLin_compLeft

@[simp]
theorem linMulLin_compRight (r : M₂ →ₗ[R₂] M₂) :
    (linMulLin f g).compRight r = linMulLin f (g.comp r) :=
  rfl
#align bilin_form.lin_mul_lin_comp_right BilinForm.linMulLin_compRight

end LinMulLin

/-- The proposition that two elements of a bilinear form space are orthogonal. For orthogonality
of an indexed set of elements, use `bilin_form.is_Ortho`. -/
def IsOrtho (B : BilinForm R M) (x y : M) : Prop :=
  B x y = 0
#align bilin_form.is_ortho BilinForm.IsOrtho

theorem isOrtho_def {B : BilinForm R M} {x y : M} : B.IsOrtho x y ↔ B x y = 0 :=
  Iff.rfl
#align bilin_form.is_ortho_def BilinForm.isOrtho_def

theorem isOrthoZeroLeft (x : M) : IsOrtho B (0 : M) x :=
  zero_left x
#align bilin_form.is_ortho_zero_left BilinForm.isOrthoZeroLeft

theorem isOrthoZeroRight (x : M) : IsOrtho B x (0 : M) :=
  zero_right x
#align bilin_form.is_ortho_zero_right BilinForm.isOrthoZeroRight

theorem ne_zero_of_not_isOrtho_self {B : BilinForm K V} (x : V) (hx₁ : ¬B.IsOrtho x x) : x ≠ 0 :=
  fun hx₂ => hx₁ (hx₂.symm ▸ isOrthoZeroLeft _)
#align bilin_form.ne_zero_of_not_is_ortho_self BilinForm.ne_zero_of_not_isOrtho_self

/-- A set of vectors `v` is orthogonal with respect to some bilinear form `B` if and only
if for all `i ≠ j`, `B (v i) (v j) = 0`. For orthogonality between two elements, use
`bilin_form.is_ortho` -/
def IsOrthoCat {n : Type w} (B : BilinForm R M) (v : n → M) : Prop :=
  Pairwise (B.IsOrtho on v)
#align bilin_form.is_Ortho BilinForm.IsOrthoCat

theorem isOrthoCat_def {n : Type w} {B : BilinForm R M} {v : n → M} :
    B.IsOrthoᵢ v ↔ ∀ i j : n, i ≠ j → B (v i) (v j) = 0 :=
  Iff.rfl
#align bilin_form.is_Ortho_def BilinForm.isOrthoCat_def

section

variable {R₄ M₄ : Type _} [Ring R₄] [IsDomain R₄]

variable [AddCommGroup M₄] [Module R₄ M₄] {G : BilinForm R₄ M₄}

@[simp]
theorem isOrtho_smul_left {x y : M₄} {a : R₄} (ha : a ≠ 0) : IsOrtho G (a • x) y ↔ IsOrtho G x y :=
  by
  dsimp only [is_ortho]
  constructor <;> intro H
  · rw [smul_left, mul_eq_zero] at H
    cases H
    · trivial
    · exact H
  · rw [smul_left, H, MulZeroClass.mul_zero]
#align bilin_form.is_ortho_smul_left BilinForm.isOrtho_smul_left

@[simp]
theorem isOrtho_smul_right {x y : M₄} {a : R₄} (ha : a ≠ 0) : IsOrtho G x (a • y) ↔ IsOrtho G x y :=
  by
  dsimp only [is_ortho]
  constructor <;> intro H
  · rw [smul_right, mul_eq_zero] at H
    cases H
    · trivial
    · exact H
  · rw [smul_right, H, MulZeroClass.mul_zero]
#align bilin_form.is_ortho_smul_right BilinForm.isOrtho_smul_right

/-- A set of orthogonal vectors `v` with respect to some bilinear form `B` is linearly independent
  if for all `i`, `B (v i) (v i) ≠ 0`. -/
theorem linearIndependent_of_isOrthoCat {n : Type w} {B : BilinForm K V} {v : n → V}
    (hv₁ : B.IsOrthoᵢ v) (hv₂ : ∀ i, ¬B.IsOrtho (v i) (v i)) : LinearIndependent K v := by
  classical
    rw [linearIndependent_iff']
    intro s w hs i hi
    have : B (s.sum fun i : n => w i • v i) (v i) = 0 := by rw [hs, zero_left]
    have hsum : (s.sum fun j : n => w j * B (v j) (v i)) = w i * B (v i) (v i) :=
      by
      apply Finset.sum_eq_single_of_mem i hi
      intro j hj hij
      rw [is_Ortho_def.1 hv₁ _ _ hij, MulZeroClass.mul_zero]
    simp_rw [sum_left, smul_left, hsum] at this
    exact eq_zero_of_ne_zero_of_mul_right_eq_zero (hv₂ i) this
#align bilin_form.linear_independent_of_is_Ortho BilinForm.linearIndependent_of_isOrthoCat

end

section Basis

variable {F₂ : BilinForm R₂ M₂}

variable {ι : Type _} (b : Basis ι R₂ M₂)

/-- Two bilinear forms are equal when they are equal on all basis vectors. -/
theorem ext_basis (h : ∀ i j, B₂ (b i) (b j) = F₂ (b i) (b j)) : B₂ = F₂ :=
  toLin.Injective <| b.ext fun i => b.ext fun j => h i j
#align bilin_form.ext_basis BilinForm.ext_basis

/-- Write out `B x y` as a sum over `B (b i) (b j)` if `b` is a basis. -/
theorem sum_repr_mul_repr_mul (x y : M₂) :
    ((b.repr x).Sum fun i xi => (b.repr y).Sum fun j yj => xi • yj • B₂ (b i) (b j)) = B₂ x y :=
  by
  conv_rhs => rw [← b.total_repr x, ← b.total_repr y]
  simp_rw [Finsupp.total_apply, Finsupp.sum, sum_left, sum_right, smul_left, smul_right,
    smul_eq_mul]
#align bilin_form.sum_repr_mul_repr_mul BilinForm.sum_repr_mul_repr_mul

end Basis

/-! ### Reflexivity, symmetry, and alternativity -/

/-- The proposition that a bilinear form is reflexive -/
def IsRefl (B : BilinForm R M) : Prop :=
  ∀ x y : M, B x y = 0 → B y x = 0
#align bilin_form.is_refl BilinForm.IsRefl

namespace IsRefl

variable (H : B.IsRefl)

theorem eq_zero : ∀ {x y : M}, B x y = 0 → B y x = 0 := fun x y => H x y
#align bilin_form.is_refl.eq_zero BilinForm.IsRefl.eq_zero

theorem ortho_comm {x y : M} : IsOrtho B x y ↔ IsOrtho B y x :=
  ⟨eq_zero H, eq_zero H⟩
#align bilin_form.is_refl.ortho_comm BilinForm.IsRefl.ortho_comm

protected theorem neg {B : BilinForm R₁ M₁} (hB : B.IsRefl) : (-B).IsRefl := fun x y =>
  neg_eq_zero.mpr ∘ hB x y ∘ neg_eq_zero.mp
#align bilin_form.is_refl.neg BilinForm.IsRefl.neg

protected theorem smul {α} [Semiring α] [Module α R] [SMulCommClass α R R] [NoZeroSMulDivisors α R]
    (a : α) {B : BilinForm R M} (hB : B.IsRefl) : (a • B).IsRefl := fun x y h =>
  (smul_eq_zero.mp h).elim (fun ha => smul_eq_zero_of_left ha _) fun hBz =>
    smul_eq_zero_of_right _ (hB _ _ hBz)
#align bilin_form.is_refl.smul BilinForm.IsRefl.smul

protected theorem groupSmul {α} [Group α] [DistribMulAction α R] [SMulCommClass α R R] (a : α)
    {B : BilinForm R M} (hB : B.IsRefl) : (a • B).IsRefl := fun x y =>
  (smul_eq_zero_iff_eq _).mpr ∘ hB x y ∘ (smul_eq_zero_iff_eq _).mp
#align bilin_form.is_refl.group_smul BilinForm.IsRefl.groupSmul

end IsRefl

@[simp]
theorem isReflZero : (0 : BilinForm R M).IsRefl := fun _ _ _ => rfl
#align bilin_form.is_refl_zero BilinForm.isReflZero

@[simp]
theorem isRefl_neg {B : BilinForm R₁ M₁} : (-B).IsRefl ↔ B.IsRefl :=
  ⟨fun h => neg_neg B ▸ h.neg, IsRefl.neg⟩
#align bilin_form.is_refl_neg BilinForm.isRefl_neg

/-- The proposition that a bilinear form is symmetric -/
def IsSymm (B : BilinForm R M) : Prop :=
  ∀ x y : M, B x y = B y x
#align bilin_form.is_symm BilinForm.IsSymm

namespace IsSymm

variable (H : B.IsSymm)

protected theorem eq (x y : M) : B x y = B y x :=
  H x y
#align bilin_form.is_symm.eq BilinForm.IsSymm.eq

theorem isRefl : B.IsRefl := fun x y H1 => H x y ▸ H1
#align bilin_form.is_symm.is_refl BilinForm.IsSymm.isRefl

theorem ortho_comm {x y : M} : IsOrtho B x y ↔ IsOrtho B y x :=
  H.IsRefl.ortho_comm
#align bilin_form.is_symm.ortho_comm BilinForm.IsSymm.ortho_comm

protected theorem add {B₁ B₂ : BilinForm R M} (hB₁ : B₁.IsSymm) (hB₂ : B₂.IsSymm) :
    (B₁ + B₂).IsSymm := fun x y => (congr_arg₂ (· + ·) (hB₁ x y) (hB₂ x y) : _)
#align bilin_form.is_symm.add BilinForm.IsSymm.add

protected theorem sub {B₁ B₂ : BilinForm R₁ M₁} (hB₁ : B₁.IsSymm) (hB₂ : B₂.IsSymm) :
    (B₁ - B₂).IsSymm := fun x y => (congr_arg₂ Sub.sub (hB₁ x y) (hB₂ x y) : _)
#align bilin_form.is_symm.sub BilinForm.IsSymm.sub

protected theorem neg {B : BilinForm R₁ M₁} (hB : B.IsSymm) : (-B).IsSymm := fun x y =>
  congr_arg Neg.neg (hB x y)
#align bilin_form.is_symm.neg BilinForm.IsSymm.neg

protected theorem smul {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] (a : α)
    {B : BilinForm R M} (hB : B.IsSymm) : (a • B).IsSymm := fun x y =>
  congr_arg ((· • ·) a) (hB x y)
#align bilin_form.is_symm.smul BilinForm.IsSymm.smul

end IsSymm

@[simp]
theorem isSymmZero : (0 : BilinForm R M).IsSymm := fun _ _ => rfl
#align bilin_form.is_symm_zero BilinForm.isSymmZero

@[simp]
theorem isSymm_neg {B : BilinForm R₁ M₁} : (-B).IsSymm ↔ B.IsSymm :=
  ⟨fun h => neg_neg B ▸ h.neg, IsSymm.neg⟩
#align bilin_form.is_symm_neg BilinForm.isSymm_neg

theorem isSymm_iff_flip' [Algebra R₂ R] : B.IsSymm ↔ flipHom R₂ B = B :=
  by
  constructor
  · intro h
    ext (x y)
    exact h y x
  · intro h x y
    conv_lhs => rw [← h]
    simp
#align bilin_form.is_symm_iff_flip' BilinForm.isSymm_iff_flip'

/-- The proposition that a bilinear form is alternating -/
def IsAlt (B : BilinForm R M) : Prop :=
  ∀ x : M, B x x = 0
#align bilin_form.is_alt BilinForm.IsAlt

namespace IsAlt

theorem self_eq_zero (H : B.IsAlt) (x : M) : B x x = 0 :=
  H x
#align bilin_form.is_alt.self_eq_zero BilinForm.IsAlt.self_eq_zero

theorem neg_eq (H : B₁.IsAlt) (x y : M₁) : -B₁ x y = B₁ y x :=
  by
  have H1 : B₁ (x + y) (x + y) = 0 := self_eq_zero H (x + y)
  rw [add_left, add_right, add_right, self_eq_zero H, self_eq_zero H, Ring.zero_add, Ring.add_zero,
    add_eq_zero_iff_neg_eq] at H1
  exact H1
#align bilin_form.is_alt.neg_eq BilinForm.IsAlt.neg_eq

theorem isRefl (H : B₁.IsAlt) : B₁.IsRefl :=
  by
  intro x y h
  rw [← neg_eq H, h, neg_zero]
#align bilin_form.is_alt.is_refl BilinForm.IsAlt.isRefl

theorem ortho_comm (H : B₁.IsAlt) {x y : M₁} : IsOrtho B₁ x y ↔ IsOrtho B₁ y x :=
  H.IsRefl.ortho_comm
#align bilin_form.is_alt.ortho_comm BilinForm.IsAlt.ortho_comm

protected theorem add {B₁ B₂ : BilinForm R M} (hB₁ : B₁.IsAlt) (hB₂ : B₂.IsAlt) : (B₁ + B₂).IsAlt :=
  fun x => (congr_arg₂ (· + ·) (hB₁ x) (hB₂ x) : _).trans <| add_zero _
#align bilin_form.is_alt.add BilinForm.IsAlt.add

protected theorem sub {B₁ B₂ : BilinForm R₁ M₁} (hB₁ : B₁.IsAlt) (hB₂ : B₂.IsAlt) :
    (B₁ - B₂).IsAlt := fun x => (congr_arg₂ Sub.sub (hB₁ x) (hB₂ x)).trans <| sub_zero _
#align bilin_form.is_alt.sub BilinForm.IsAlt.sub

protected theorem neg {B : BilinForm R₁ M₁} (hB : B.IsAlt) : (-B).IsAlt := fun x =>
  neg_eq_zero.mpr <| hB x
#align bilin_form.is_alt.neg BilinForm.IsAlt.neg

protected theorem smul {α} [Monoid α] [DistribMulAction α R] [SMulCommClass α R R] (a : α)
    {B : BilinForm R M} (hB : B.IsAlt) : (a • B).IsAlt := fun x =>
  (congr_arg ((· • ·) a) (hB x)).trans <| smul_zero _
#align bilin_form.is_alt.smul BilinForm.IsAlt.smul

end IsAlt

@[simp]
theorem isAltZero : (0 : BilinForm R M).IsAlt := fun _ => rfl
#align bilin_form.is_alt_zero BilinForm.isAltZero

@[simp]
theorem isAlt_neg {B : BilinForm R₁ M₁} : (-B).IsAlt ↔ B.IsAlt :=
  ⟨fun h => neg_neg B ▸ h.neg, IsAlt.neg⟩
#align bilin_form.is_alt_neg BilinForm.isAlt_neg

/-! ### Linear adjoints -/

section LinearAdjoints

variable (B) (F : BilinForm R M)

variable {M' : Type _} [AddCommMonoid M'] [Module R M']

variable (B' : BilinForm R M') (f f' : M →ₗ[R] M') (g g' : M' →ₗ[R] M)

/-- Given a pair of modules equipped with bilinear forms, this is the condition for a pair of
maps between them to be mutually adjoint. -/
def IsAdjointPair :=
  ∀ ⦃x y⦄, B' (f x) y = B x (g y)
#align bilin_form.is_adjoint_pair BilinForm.IsAdjointPair

variable {B B' B₂ f f' g g'}

theorem IsAdjointPair.eq (h : IsAdjointPair B B' f g) : ∀ {x y}, B' (f x) y = B x (g y) :=
  h
#align bilin_form.is_adjoint_pair.eq BilinForm.IsAdjointPair.eq

theorem isAdjointPair_iff_compLeft_eq_compRight (f g : Module.End R M) :
    IsAdjointPair B F f g ↔ F.compLeft f = B.compRight g :=
  by
  constructor <;> intro h
  · ext (x y)
    rw [comp_left_apply, comp_right_apply]
    apply h
  · intro x y
    rw [← comp_left_apply, ← comp_right_apply]
    rw [h]
#align bilin_form.is_adjoint_pair_iff_comp_left_eq_comp_right BilinForm.isAdjointPair_iff_compLeft_eq_compRight

theorem isAdjointPairZero : IsAdjointPair B B' 0 0 := fun x y => by
  simp only [BilinForm.zero_left, BilinForm.zero_right, LinearMap.zero_apply]
#align bilin_form.is_adjoint_pair_zero BilinForm.isAdjointPairZero

theorem isAdjointPairId : IsAdjointPair B B 1 1 := fun x y => rfl
#align bilin_form.is_adjoint_pair_id BilinForm.isAdjointPairId

theorem IsAdjointPair.add (h : IsAdjointPair B B' f g) (h' : IsAdjointPair B B' f' g') :
    IsAdjointPair B B' (f + f') (g + g') := fun x y => by
  rw [LinearMap.add_apply, LinearMap.add_apply, add_left, add_right, h, h']
#align bilin_form.is_adjoint_pair.add BilinForm.IsAdjointPair.add

variable {M₁' : Type _} [AddCommGroup M₁'] [Module R₁ M₁']

variable {B₁' : BilinForm R₁ M₁'} {f₁ f₁' : M₁ →ₗ[R₁] M₁'} {g₁ g₁' : M₁' →ₗ[R₁] M₁}

theorem IsAdjointPair.sub (h : IsAdjointPair B₁ B₁' f₁ g₁) (h' : IsAdjointPair B₁ B₁' f₁' g₁') :
    IsAdjointPair B₁ B₁' (f₁ - f₁') (g₁ - g₁') := fun x y => by
  rw [LinearMap.sub_apply, LinearMap.sub_apply, sub_left, sub_right, h, h']
#align bilin_form.is_adjoint_pair.sub BilinForm.IsAdjointPair.sub

variable {B₂' : BilinForm R₂ M₂'} {f₂ f₂' : M₂ →ₗ[R₂] M₂'} {g₂ g₂' : M₂' →ₗ[R₂] M₂}

theorem IsAdjointPair.smul (c : R₂) (h : IsAdjointPair B₂ B₂' f₂ g₂) :
    IsAdjointPair B₂ B₂' (c • f₂) (c • g₂) := fun x y => by
  rw [LinearMap.smul_apply, LinearMap.smul_apply, smul_left, smul_right, h]
#align bilin_form.is_adjoint_pair.smul BilinForm.IsAdjointPair.smul

variable {M'' : Type _} [AddCommMonoid M''] [Module R M'']

variable (B'' : BilinForm R M'')

theorem IsAdjointPair.comp {f' : M' →ₗ[R] M''} {g' : M'' →ₗ[R] M'} (h : IsAdjointPair B B' f g)
    (h' : IsAdjointPair B' B'' f' g') : IsAdjointPair B B'' (f'.comp f) (g.comp g') := fun x y => by
  rw [LinearMap.comp_apply, LinearMap.comp_apply, h', h]
#align bilin_form.is_adjoint_pair.comp BilinForm.IsAdjointPair.comp

theorem IsAdjointPair.mul {f g f' g' : Module.End R M} (h : IsAdjointPair B B f g)
    (h' : IsAdjointPair B B f' g') : IsAdjointPair B B (f * f') (g' * g) := fun x y => by
  rw [LinearMap.mul_apply, LinearMap.mul_apply, h, h']
#align bilin_form.is_adjoint_pair.mul BilinForm.IsAdjointPair.mul

variable (B B' B₁ B₂) (F₂ : BilinForm R₂ M₂)

/-- The condition for an endomorphism to be "self-adjoint" with respect to a pair of bilinear forms
on the underlying module. In the case that these two forms are identical, this is the usual concept
of self adjointness. In the case that one of the forms is the negation of the other, this is the
usual concept of skew adjointness. -/
def IsPairSelfAdjoint (f : Module.End R M) :=
  IsAdjointPair B F f f
#align bilin_form.is_pair_self_adjoint BilinForm.IsPairSelfAdjoint

/-- The set of pair-self-adjoint endomorphisms are a submodule of the type of all endomorphisms. -/
def isPairSelfAdjointSubmodule : Submodule R₂ (Module.End R₂ M₂)
    where
  carrier := { f | IsPairSelfAdjoint B₂ F₂ f }
  zero_mem' := isAdjointPairZero
  add_mem' f g hf hg := hf.add hg
  smul_mem' c f h := h.smul c
#align bilin_form.is_pair_self_adjoint_submodule BilinForm.isPairSelfAdjointSubmodule

@[simp]
theorem mem_isPairSelfAdjointSubmodule (f : Module.End R₂ M₂) :
    f ∈ isPairSelfAdjointSubmodule B₂ F₂ ↔ IsPairSelfAdjoint B₂ F₂ f := by rfl
#align bilin_form.mem_is_pair_self_adjoint_submodule BilinForm.mem_isPairSelfAdjointSubmodule

theorem isPairSelfAdjoint_equiv (e : M₂' ≃ₗ[R₂] M₂) (f : Module.End R₂ M₂) :
    IsPairSelfAdjoint B₂ F₂ f ↔ IsPairSelfAdjoint (B₂.comp ↑e ↑e) (F₂.comp ↑e ↑e) (e.symm.conj f) :=
  by
  have hₗ : (F₂.comp ↑e ↑e).compLeft (e.symm.conj f) = (F₂.comp_left f).comp ↑e ↑e :=
    by
    ext
    simp [LinearEquiv.symm_conj_apply]
  have hᵣ : (B₂.comp ↑e ↑e).compRight (e.symm.conj f) = (B₂.comp_right f).comp ↑e ↑e :=
    by
    ext
    simp [LinearEquiv.conj_apply]
  have he : Function.Surjective (⇑(↑e : M₂' →ₗ[R₂] M₂) : M₂' → M₂) := e.surjective
  show BilinForm.IsAdjointPair _ _ _ _ ↔ BilinForm.IsAdjointPair _ _ _ _
  rw [is_adjoint_pair_iff_comp_left_eq_comp_right, is_adjoint_pair_iff_comp_left_eq_comp_right, hᵣ,
    hₗ, comp_inj _ _ he he]
#align bilin_form.is_pair_self_adjoint_equiv BilinForm.isPairSelfAdjoint_equiv

/-- An endomorphism of a module is self-adjoint with respect to a bilinear form if it serves as an
adjoint for itself. -/
def IsSelfAdjoint (f : Module.End R M) :=
  IsAdjointPair B B f f
#align bilin_form.is_self_adjoint BilinForm.IsSelfAdjoint

/-- An endomorphism of a module is skew-adjoint with respect to a bilinear form if its negation
serves as an adjoint. -/
def IsSkewAdjoint (f : Module.End R₁ M₁) :=
  IsAdjointPair B₁ B₁ f (-f)
#align bilin_form.is_skew_adjoint BilinForm.IsSkewAdjoint

theorem isSkewAdjoint_iff_neg_self_adjoint (f : Module.End R₁ M₁) :
    B₁.IsSkewAdjoint f ↔ IsAdjointPair (-B₁) B₁ f f :=
  show (∀ x y, B₁ (f x) y = B₁ x ((-f) y)) ↔ ∀ x y, B₁ (f x) y = (-B₁) x (f y) by
    simp only [LinearMap.neg_apply, BilinForm.neg_apply, BilinForm.neg_right]
#align bilin_form.is_skew_adjoint_iff_neg_self_adjoint BilinForm.isSkewAdjoint_iff_neg_self_adjoint

/-- The set of self-adjoint endomorphisms of a module with bilinear form is a submodule. (In fact
it is a Jordan subalgebra.) -/
def selfAdjointSubmodule :=
  isPairSelfAdjointSubmodule B₂ B₂
#align bilin_form.self_adjoint_submodule BilinForm.selfAdjointSubmodule

@[simp]
theorem mem_selfAdjointSubmodule (f : Module.End R₂ M₂) :
    f ∈ B₂.selfAdjointSubmodule ↔ B₂.IsSelfAdjoint f :=
  Iff.rfl
#align bilin_form.mem_self_adjoint_submodule BilinForm.mem_selfAdjointSubmodule

variable (B₃ : BilinForm R₃ M₃)

/-- The set of skew-adjoint endomorphisms of a module with bilinear form is a submodule. (In fact
it is a Lie subalgebra.) -/
def skewAdjointSubmodule :=
  isPairSelfAdjointSubmodule (-B₃) B₃
#align bilin_form.skew_adjoint_submodule BilinForm.skewAdjointSubmodule

@[simp]
theorem mem_skewAdjointSubmodule (f : Module.End R₃ M₃) :
    f ∈ B₃.skewAdjointSubmodule ↔ B₃.IsSkewAdjoint f :=
  by
  rw [is_skew_adjoint_iff_neg_self_adjoint]
  exact Iff.rfl
#align bilin_form.mem_skew_adjoint_submodule BilinForm.mem_skewAdjointSubmodule

end LinearAdjoints

end BilinForm

namespace BilinForm

section Orthogonal

/-- The orthogonal complement of a submodule `N` with respect to some bilinear form is the set of
elements `x` which are orthogonal to all elements of `N`; i.e., for all `y` in `N`, `B x y = 0`.

Note that for general (neither symmetric nor antisymmetric) bilinear forms this definition has a
chirality; in addition to this "left" orthogonal complement one could define a "right" orthogonal
complement for which, for all `y` in `N`, `B y x = 0`.  This variant definition is not currently
provided in mathlib. -/
def orthogonal (B : BilinForm R M) (N : Submodule R M) : Submodule R M
    where
  carrier := { m | ∀ n ∈ N, IsOrtho B n m }
  zero_mem' x _ := isOrthoZeroRight x
  add_mem' x y hx hy n hn := by
    rw [is_ortho, add_right, show B n x = 0 from hx n hn, show B n y = 0 from hy n hn, zero_add]
  smul_mem' c x hx n hn := by
    rw [is_ortho, smul_right, show B n x = 0 from hx n hn, MulZeroClass.mul_zero]
#align bilin_form.orthogonal BilinForm.orthogonal

variable {N L : Submodule R M}

@[simp]
theorem mem_orthogonal_iff {N : Submodule R M} {m : M} :
    m ∈ B.orthogonal N ↔ ∀ n ∈ N, IsOrtho B n m :=
  Iff.rfl
#align bilin_form.mem_orthogonal_iff BilinForm.mem_orthogonal_iff

theorem orthogonal_le (h : N ≤ L) : B.orthogonal L ≤ B.orthogonal N := fun _ hn l hl => hn l (h hl)
#align bilin_form.orthogonal_le BilinForm.orthogonal_le

theorem le_orthogonal_orthogonal (b : B.IsRefl) : N ≤ B.orthogonal (B.orthogonal N) :=
  fun n hn m hm => b _ _ (hm n hn)
#align bilin_form.le_orthogonal_orthogonal BilinForm.le_orthogonal_orthogonal

-- ↓ This lemma only applies in fields as we require `a * b = 0 → a = 0 ∨ b = 0`
theorem span_singleton_inf_orthogonal_eq_bot {B : BilinForm K V} {x : V} (hx : ¬B.IsOrtho x x) :
    (K ∙ x) ⊓ B.orthogonal (K ∙ x) = ⊥ :=
  by
  rw [← Finset.coe_singleton]
  refine' eq_bot_iff.2 fun y h => _
  rcases mem_span_finset.1 h.1 with ⟨μ, rfl⟩
  have := h.2 x _
  · rw [Finset.sum_singleton] at this⊢
    suffices hμzero : μ x = 0
    · rw [hμzero, zero_smul, Submodule.mem_bot]
    change B x (μ x • x) = 0 at this
    rw [smul_right] at this
    exact Or.elim (zero_eq_mul.mp this.symm) id fun hfalse => False.elim <| hx hfalse
  · rw [Submodule.mem_span] <;> exact fun _ hp => hp <| Finset.mem_singleton_self _
#align bilin_form.span_singleton_inf_orthogonal_eq_bot BilinForm.span_singleton_inf_orthogonal_eq_bot

-- ↓ This lemma only applies in fields since we use the `mul_eq_zero`
theorem orthogonal_span_singleton_eq_toLin_ker {B : BilinForm K V} (x : V) :
    B.orthogonal (K ∙ x) = (BilinForm.toLin B x).ker :=
  by
  ext y
  simp_rw [mem_orthogonal_iff, LinearMap.mem_ker, Submodule.mem_span_singleton]
  constructor
  · exact fun h => h x ⟨1, one_smul _ _⟩
  · rintro h _ ⟨z, rfl⟩
    rw [is_ortho, smul_left, mul_eq_zero]
    exact Or.intro_right _ h
#align bilin_form.orthogonal_span_singleton_eq_to_lin_ker BilinForm.orthogonal_span_singleton_eq_toLin_ker

theorem span_singleton_sup_orthogonal_eq_top {B : BilinForm K V} {x : V} (hx : ¬B.IsOrtho x x) :
    (K ∙ x) ⊔ B.orthogonal (K ∙ x) = ⊤ :=
  by
  rw [orthogonal_span_singleton_eq_to_lin_ker]
  exact LinearMap.span_singleton_sup_ker_eq_top _ hx
#align bilin_form.span_singleton_sup_orthogonal_eq_top BilinForm.span_singleton_sup_orthogonal_eq_top

/-- Given a bilinear form `B` and some `x` such that `B x x ≠ 0`, the span of the singleton of `x`
  is complement to its orthogonal complement. -/
theorem isCompl_span_singleton_orthogonal {B : BilinForm K V} {x : V} (hx : ¬B.IsOrtho x x) :
    IsCompl (K ∙ x) (B.orthogonal <| K ∙ x) :=
  { Disjoint := disjoint_iff.2 <| span_singleton_inf_orthogonal_eq_bot hx
    Codisjoint := codisjoint_iff.2 <| span_singleton_sup_orthogonal_eq_top hx }
#align bilin_form.is_compl_span_singleton_orthogonal BilinForm.isCompl_span_singleton_orthogonal

end Orthogonal

/-- The restriction of a bilinear form on a submodule. -/
@[simps apply]
def restrict (B : BilinForm R M) (W : Submodule R M) : BilinForm R W
    where
  bilin a b := B a b
  bilin_add_left _ _ _ := add_left _ _ _
  bilin_smul_left _ _ _ := smul_left _ _ _
  bilin_add_right _ _ _ := add_right _ _ _
  bilin_smul_right _ _ _ := smul_right _ _ _
#align bilin_form.restrict BilinForm.restrict

/-- The restriction of a symmetric bilinear form on a submodule is also symmetric. -/
theorem restrictSymm (B : BilinForm R M) (b : B.IsSymm) (W : Submodule R M) :
    (B.restrict W).IsSymm := fun x y => b x y
#align bilin_form.restrict_symm BilinForm.restrictSymm

/-- A nondegenerate bilinear form is a bilinear form such that the only element that is orthogonal
to every other element is `0`; i.e., for all nonzero `m` in `M`, there exists `n` in `M` with
`B m n ≠ 0`.

Note that for general (neither symmetric nor antisymmetric) bilinear forms this definition has a
chirality; in addition to this "left" nondegeneracy condition one could define a "right"
nondegeneracy condition that in the situation described, `B n m ≠ 0`.  This variant definition is
not currently provided in mathlib. In finite dimension either definition implies the other. -/
def Nondegenerate (B : BilinForm R M) : Prop :=
  ∀ m : M, (∀ n : M, B m n = 0) → m = 0
#align bilin_form.nondegenerate BilinForm.Nondegenerate

section

variable (R M)

/-- In a non-trivial module, zero is not non-degenerate. -/
theorem not_nondegenerate_zero [Nontrivial M] : ¬(0 : BilinForm R M).Nondegenerate :=
  let ⟨m, hm⟩ := exists_ne (0 : M)
  fun h => hm (h m fun n => rfl)
#align bilin_form.not_nondegenerate_zero BilinForm.not_nondegenerate_zero

end

variable {M₂' : Type _}

variable [AddCommMonoid M₂'] [Module R₂ M₂']

theorem Nondegenerate.ne_zero [Nontrivial M] {B : BilinForm R M} (h : B.Nondegenerate) : B ≠ 0 :=
  fun h0 => not_nondegenerate_zero R M <| h0 ▸ h
#align bilin_form.nondegenerate.ne_zero BilinForm.Nondegenerate.ne_zero

theorem Nondegenerate.congr {B : BilinForm R₂ M₂} (e : M₂ ≃ₗ[R₂] M₂') (h : B.Nondegenerate) :
    (congr e B).Nondegenerate := fun m hm =>
  e.symm.map_eq_zero_iff.1 <|
    h (e.symm m) fun n => (congr_arg _ (e.symm_apply_apply n).symm).trans (hm (e n))
#align bilin_form.nondegenerate.congr BilinForm.Nondegenerate.congr

@[simp]
theorem nondegenerate_congr_iff {B : BilinForm R₂ M₂} (e : M₂ ≃ₗ[R₂] M₂') :
    (congr e B).Nondegenerate ↔ B.Nondegenerate :=
  ⟨fun h => by
    convert h.congr e.symm
    rw [congr_congr, e.self_trans_symm, congr_refl, LinearEquiv.refl_apply], Nondegenerate.congr e⟩
#align bilin_form.nondegenerate_congr_iff BilinForm.nondegenerate_congr_iff

/-- A bilinear form is nondegenerate if and only if it has a trivial kernel. -/
theorem nondegenerate_iff_ker_eq_bot {B : BilinForm R₂ M₂} : B.Nondegenerate ↔ B.toLin.ker = ⊥ :=
  by
  rw [LinearMap.ker_eq_bot']
  constructor <;> intro h
  · refine' fun m hm => h _ fun x => _
    rw [← to_lin_apply, hm]
    rfl
  · intro m hm
    apply h
    ext x
    exact hm x
#align bilin_form.nondegenerate_iff_ker_eq_bot BilinForm.nondegenerate_iff_ker_eq_bot

theorem Nondegenerate.ker_eq_bot {B : BilinForm R₂ M₂} (h : B.Nondegenerate) : B.toLin.ker = ⊥ :=
  nondegenerate_iff_ker_eq_bot.mp h
#align bilin_form.nondegenerate.ker_eq_bot BilinForm.Nondegenerate.ker_eq_bot

/-- The restriction of a reflexive bilinear form `B` onto a submodule `W` is
nondegenerate if `disjoint W (B.orthogonal W)`. -/
theorem nondegenerateRestrictOfDisjointOrthogonal (B : BilinForm R₁ M₁) (b : B.IsRefl)
    {W : Submodule R₁ M₁} (hW : Disjoint W (B.orthogonal W)) : (B.restrict W).Nondegenerate :=
  by
  rintro ⟨x, hx⟩ b₁
  rw [Submodule.mk_eq_zero, ← Submodule.mem_bot R₁]
  refine' hW.le_bot ⟨hx, fun y hy => _⟩
  specialize b₁ ⟨y, hy⟩
  rw [restrict_apply, Submodule.coe_mk, Submodule.coe_mk] at b₁
  exact is_ortho_def.mpr (b x y b₁)
#align bilin_form.nondegenerate_restrict_of_disjoint_orthogonal BilinForm.nondegenerateRestrictOfDisjointOrthogonal

/-- An orthogonal basis with respect to a nondegenerate bilinear form has no self-orthogonal
elements. -/
theorem IsOrthoCat.not_isOrtho_basis_self_of_nondegenerate {n : Type w} [Nontrivial R]
    {B : BilinForm R M} {v : Basis n R M} (h : B.IsOrthoᵢ v) (hB : B.Nondegenerate) (i : n) :
    ¬B.IsOrtho (v i) (v i) := by
  intro ho
  refine' v.ne_zero i (hB (v i) fun m => _)
  obtain ⟨vi, rfl⟩ := v.repr.symm.surjective m
  rw [Basis.repr_symm_apply, Finsupp.total_apply, Finsupp.sum, sum_right]
  apply Finset.sum_eq_zero
  rintro j -
  rw [smul_right]
  convert MulZeroClass.mul_zero _ using 2
  obtain rfl | hij := eq_or_ne i j
  · exact ho
  · exact h hij
#align bilin_form.is_Ortho.not_is_ortho_basis_self_of_nondegenerate BilinForm.IsOrthoCat.not_isOrtho_basis_self_of_nondegenerate

/-- Given an orthogonal basis with respect to a bilinear form, the bilinear form is nondegenerate
iff the basis has no elements which are self-orthogonal. -/
theorem IsOrthoCat.nondegenerate_iff_not_isOrtho_basis_self {n : Type w} [Nontrivial R]
    [NoZeroDivisors R] (B : BilinForm R M) (v : Basis n R M) (hO : B.IsOrthoᵢ v) :
    B.Nondegenerate ↔ ∀ i, ¬B.IsOrtho (v i) (v i) :=
  by
  refine' ⟨hO.not_is_ortho_basis_self_of_nondegenerate, fun ho m hB => _⟩
  obtain ⟨vi, rfl⟩ := v.repr.symm.surjective m
  rw [LinearEquiv.map_eq_zero_iff]
  ext i
  rw [Finsupp.zero_apply]
  specialize hB (v i)
  simp_rw [Basis.repr_symm_apply, Finsupp.total_apply, Finsupp.sum, sum_left, smul_left] at hB
  rw [Finset.sum_eq_single i] at hB
  · exact eq_zero_of_ne_zero_of_mul_right_eq_zero (ho i) hB
  · intro j hj hij
    convert MulZeroClass.mul_zero _ using 2
    exact hO hij
  · intro hi
    convert MulZeroClass.zero_mul _ using 2
    exact finsupp.not_mem_support_iff.mp hi
#align bilin_form.is_Ortho.nondegenerate_iff_not_is_ortho_basis_self BilinForm.IsOrthoCat.nondegenerate_iff_not_isOrtho_basis_self

section

theorem toLin_restrict_ker_eq_inf_orthogonal (B : BilinForm K V) (W : Subspace K V) (b : B.IsRefl) :
    (B.toLin.domRestrict W).ker.map W.Subtype = (W ⊓ B.orthogonal ⊤ : Subspace K V) :=
  by
  ext x; constructor <;> intro hx
  · rcases hx with ⟨⟨x, hx⟩, hker, rfl⟩
    erw [LinearMap.mem_ker] at hker
    constructor
    · simp [hx]
    · intro y _
      rw [is_ortho, b]
      change (B.to_lin.dom_restrict W) ⟨x, hx⟩ y = 0
      rw [hker]
      rfl
  · simp_rw [Submodule.mem_map, LinearMap.mem_ker]
    refine' ⟨⟨x, hx.1⟩, _, rfl⟩
    ext y
    change B x y = 0
    rw [b]
    exact hx.2 _ Submodule.mem_top
#align bilin_form.to_lin_restrict_ker_eq_inf_orthogonal BilinForm.toLin_restrict_ker_eq_inf_orthogonal

theorem toLin_restrict_range_dualCoannihilator_eq_orthogonal (B : BilinForm K V)
    (W : Subspace K V) : (B.toLin.domRestrict W).range.dualCoannihilator = B.orthogonal W :=
  by
  ext x; constructor <;> rw [mem_orthogonal_iff] <;> intro hx
  · intro y hy
    rw [Submodule.mem_dualCoannihilator] at hx
    refine' hx (B.to_lin.dom_restrict W ⟨y, hy⟩) ⟨⟨y, hy⟩, rfl⟩
  · rw [Submodule.mem_dualCoannihilator]
    rintro _ ⟨⟨w, hw⟩, rfl⟩
    exact hx w hw
#align bilin_form.to_lin_restrict_range_dual_coannihilator_eq_orthogonal BilinForm.toLin_restrict_range_dualCoannihilator_eq_orthogonal

variable [FiniteDimensional K V]

open FiniteDimensional

theorem finrank_add_finrank_orthogonal {B : BilinForm K V} {W : Subspace K V} (b₁ : B.IsRefl) :
    finrank K W + finrank K (B.orthogonal W) =
      finrank K V + finrank K (W ⊓ B.orthogonal ⊤ : Subspace K V) :=
  by
  rw [← to_lin_restrict_ker_eq_inf_orthogonal _ _ b₁, ←
    to_lin_restrict_range_dual_coannihilator_eq_orthogonal _ _, finrank_map_subtype_eq]
  conv_rhs =>
    rw [←
      @Subspace.finrank_add_finrank_dualCoannihilator_eq K V _ _ _ _
        (B.to_lin.dom_restrict W).range,
      add_comm, ← add_assoc, add_comm (finrank K ↥(B.to_lin.dom_restrict W).ker),
      LinearMap.finrank_range_add_finrank_ker]
#align bilin_form.finrank_add_finrank_orthogonal BilinForm.finrank_add_finrank_orthogonal

/-- A subspace is complement to its orthogonal complement with respect to some
reflexive bilinear form if that bilinear form restricted on to the subspace is nondegenerate. -/
theorem restrict_nondegenerate_of_isCompl_orthogonal {B : BilinForm K V} {W : Subspace K V}
    (b₁ : B.IsRefl) (b₂ : (B.restrict W).Nondegenerate) : IsCompl W (B.orthogonal W) :=
  by
  have : W ⊓ B.orthogonal W = ⊥ := by
    rw [eq_bot_iff]
    intro x hx
    obtain ⟨hx₁, hx₂⟩ := Submodule.mem_inf.1 hx
    refine' Subtype.mk_eq_mk.1 (b₂ ⟨x, hx₁⟩ _)
    rintro ⟨n, hn⟩
    rw [restrict_apply, Submodule.coe_mk, Submodule.coe_mk, b₁]
    exact hx₂ n hn
  refine' IsCompl.of_eq this (eq_top_of_finrank_eq <| (Submodule.finrank_le _).antisymm _)
  conv_rhs => rw [← add_zero (finrank K _)]
  rw [← finrank_bot K V, ← this, Submodule.dim_sup_add_dim_inf_eq,
    finrank_add_finrank_orthogonal b₁]
  exact le_self_add
#align bilin_form.restrict_nondegenerate_of_is_compl_orthogonal BilinForm.restrict_nondegenerate_of_isCompl_orthogonal

/-- A subspace is complement to its orthogonal complement with respect to some reflexive bilinear
form if and only if that bilinear form restricted on to the subspace is nondegenerate. -/
theorem restrict_nondegenerate_iff_isCompl_orthogonal {B : BilinForm K V} {W : Subspace K V}
    (b₁ : B.IsRefl) : (B.restrict W).Nondegenerate ↔ IsCompl W (B.orthogonal W) :=
  ⟨fun b₂ => restrict_nondegenerate_of_isCompl_orthogonal b₁ b₂, fun h =>
    B.nondegenerateRestrictOfDisjointOrthogonal b₁ h.1⟩
#align bilin_form.restrict_nondegenerate_iff_is_compl_orthogonal BilinForm.restrict_nondegenerate_iff_isCompl_orthogonal

/-- Given a nondegenerate bilinear form `B` on a finite-dimensional vector space, `B.to_dual` is
the linear equivalence between a vector space and its dual with the underlying linear map
`B.to_lin`. -/
noncomputable def toDual (B : BilinForm K V) (b : B.Nondegenerate) : V ≃ₗ[K] Module.Dual K V :=
  B.toLin.linearEquivOfInjective (LinearMap.ker_eq_bot.mp <| b.ker_eq_bot)
    Subspace.dual_finrank_eq.symm
#align bilin_form.to_dual BilinForm.toDual

theorem toDual_def {B : BilinForm K V} (b : B.Nondegenerate) {m n : V} : B.toDual b m n = B m n :=
  rfl
#align bilin_form.to_dual_def BilinForm.toDual_def

section DualBasis

variable {ι : Type _} [DecidableEq ι] [Fintype ι]

/-- The `B`-dual basis `B.dual_basis hB b` to a finite basis `b` satisfies
`B (B.dual_basis hB b i) (b j) = B (b i) (B.dual_basis hB b j) = if i = j then 1 else 0`,
where `B` is a nondegenerate (symmetric) bilinear form and `b` is a finite basis. -/
noncomputable def dualBasis (B : BilinForm K V) (hB : B.Nondegenerate) (b : Basis ι K V) :
    Basis ι K V :=
  b.dualBasis.map (B.toDual hB).symm
#align bilin_form.dual_basis BilinForm.dualBasis

@[simp]
theorem dualBasis_repr_apply (B : BilinForm K V) (hB : B.Nondegenerate) (b : Basis ι K V) (x i) :
    (B.dualBasis hB b).repr x i = B x (b i) := by
  rw [dual_basis, Basis.map_repr, LinearEquiv.symm_symm, LinearEquiv.trans_apply,
    Basis.dualBasis_repr, to_dual_def]
#align bilin_form.dual_basis_repr_apply BilinForm.dualBasis_repr_apply

theorem apply_dualBasis_left (B : BilinForm K V) (hB : B.Nondegenerate) (b : Basis ι K V) (i j) :
    B (B.dualBasis hB b i) (b j) = if j = i then 1 else 0 := by
  rw [dual_basis, Basis.map_apply, Basis.coe_dualBasis, ← to_dual_def hB,
    LinearEquiv.apply_symm_apply, Basis.coord_apply, Basis.repr_self, Finsupp.single_apply]
#align bilin_form.apply_dual_basis_left BilinForm.apply_dualBasis_left

theorem apply_dualBasis_right (B : BilinForm K V) (hB : B.Nondegenerate) (sym : B.IsSymm)
    (b : Basis ι K V) (i j) : B (b i) (B.dualBasis hB b j) = if i = j then 1 else 0 := by
  rw [Sym, apply_dual_basis_left]
#align bilin_form.apply_dual_basis_right BilinForm.apply_dualBasis_right

end DualBasis

end

/-! We note that we cannot use `bilin_form.restrict_nondegenerate_iff_is_compl_orthogonal` for the
lemma below since the below lemma does not require `V` to be finite dimensional. However,
`bilin_form.restrict_nondegenerate_iff_is_compl_orthogonal` does not require `B` to be nondegenerate
on the whole space. -/

/-- The restriction of a reflexive, non-degenerate bilinear form on the orthogonal complement of
the span of a singleton is also non-degenerate. -/
theorem restrictOrthogonalSpanSingletonNondegenerate (B : BilinForm K V) (b₁ : B.Nondegenerate)
    (b₂ : B.IsRefl) {x : V} (hx : ¬B.IsOrtho x x) :
    Nondegenerate <| B.restrict <| B.orthogonal (K ∙ x) :=
  by
  refine' fun m hm => Submodule.coe_eq_zero.1 (b₁ m.1 fun n => _)
  have : n ∈ (K ∙ x) ⊔ B.orthogonal (K ∙ x) :=
    (span_singleton_sup_orthogonal_eq_top hx).symm ▸ Submodule.mem_top
  rcases Submodule.mem_sup.1 this with ⟨y, hy, z, hz, rfl⟩
  specialize hm ⟨z, hz⟩
  rw [restrict] at hm
  erw [add_right, show B m.1 y = 0 by rw [b₂] <;> exact m.2 y hy, hm, add_zero]
#align bilin_form.restrict_orthogonal_span_singleton_nondegenerate BilinForm.restrictOrthogonalSpanSingletonNondegenerate

section LinearAdjoints

theorem compLeft_injective (B : BilinForm R₁ M₁) (b : B.Nondegenerate) :
    Function.Injective B.compLeft := fun φ ψ h =>
  by
  ext w
  refine' eq_of_sub_eq_zero (b _ _)
  intro v
  rw [sub_left, ← comp_left_apply, ← comp_left_apply, ← h, sub_self]
#align bilin_form.comp_left_injective BilinForm.compLeft_injective

theorem isAdjointPair_unique_of_nondegenerate (B : BilinForm R₁ M₁) (b : B.Nondegenerate)
    (φ ψ₁ ψ₂ : M₁ →ₗ[R₁] M₁) (hψ₁ : IsAdjointPair B B ψ₁ φ) (hψ₂ : IsAdjointPair B B ψ₂ φ) :
    ψ₁ = ψ₂ :=
  B.compLeft_injective b <| ext fun v w => by rw [comp_left_apply, comp_left_apply, hψ₁, hψ₂]
#align bilin_form.is_adjoint_pair_unique_of_nondegenerate BilinForm.isAdjointPair_unique_of_nondegenerate

variable [FiniteDimensional K V]

/-- Given bilinear forms `B₁, B₂` where `B₂` is nondegenerate, `symm_comp_of_nondegenerate`
is the linear map `B₂.to_lin⁻¹ ∘ B₁.to_lin`. -/
noncomputable def symmCompOfNondegenerate (B₁ B₂ : BilinForm K V) (b₂ : B₂.Nondegenerate) :
    V →ₗ[K] V :=
  (B₂.toDual b₂).symm.toLinearMap.comp B₁.toLin
#align bilin_form.symm_comp_of_nondegenerate BilinForm.symmCompOfNondegenerate

theorem comp_symmCompOfNondegenerate_apply (B₁ : BilinForm K V) {B₂ : BilinForm K V}
    (b₂ : B₂.Nondegenerate) (v : V) : toLin B₂ (B₁.symmCompOfNondegenerate B₂ b₂ v) = toLin B₁ v :=
  by erw [symm_comp_of_nondegenerate, LinearEquiv.apply_symm_apply (B₂.to_dual b₂) _]
#align bilin_form.comp_symm_comp_of_nondegenerate_apply BilinForm.comp_symmCompOfNondegenerate_apply

@[simp]
theorem symmCompOfNondegenerate_left_apply (B₁ : BilinForm K V) {B₂ : BilinForm K V}
    (b₂ : B₂.Nondegenerate) (v w : V) : B₂ (symmCompOfNondegenerate B₁ B₂ b₂ w) v = B₁ w v :=
  by
  conv_lhs => rw [← BilinForm.toLin_apply, comp_symm_comp_of_nondegenerate_apply]
  rfl
#align bilin_form.symm_comp_of_nondegenerate_left_apply BilinForm.symmCompOfNondegenerate_left_apply

/-- Given the nondegenerate bilinear form `B` and the linear map `φ`,
`left_adjoint_of_nondegenerate` provides the left adjoint of `φ` with respect to `B`.
The lemma proving this property is `bilin_form.is_adjoint_pair_left_adjoint_of_nondegenerate`. -/
noncomputable def leftAdjointOfNondegenerate (B : BilinForm K V) (b : B.Nondegenerate)
    (φ : V →ₗ[K] V) : V →ₗ[K] V :=
  symmCompOfNondegenerate (B.compRight φ) B b
#align bilin_form.left_adjoint_of_nondegenerate BilinForm.leftAdjointOfNondegenerate

theorem isAdjointPairLeftAdjointOfNondegenerate (B : BilinForm K V) (b : B.Nondegenerate)
    (φ : V →ₗ[K] V) : IsAdjointPair B B (B.leftAdjointOfNondegenerate b φ) φ := fun x y =>
  (B.compRight φ).symmCompOfNondegenerate_left_apply b y x
#align bilin_form.is_adjoint_pair_left_adjoint_of_nondegenerate BilinForm.isAdjointPairLeftAdjointOfNondegenerate

/-- Given the nondegenerate bilinear form `B`, the linear map `φ` has a unique left adjoint given by
`bilin_form.left_adjoint_of_nondegenerate`. -/
theorem isAdjointPair_iff_eq_of_nondegenerate (B : BilinForm K V) (b : B.Nondegenerate)
    (ψ φ : V →ₗ[K] V) : IsAdjointPair B B ψ φ ↔ ψ = B.leftAdjointOfNondegenerate b φ :=
  ⟨fun h =>
    B.isAdjointPair_unique_of_nondegenerate b φ ψ _ h
      (isAdjointPairLeftAdjointOfNondegenerate _ _ _),
    fun h => h.symm ▸ isAdjointPairLeftAdjointOfNondegenerate _ _ _⟩
#align bilin_form.is_adjoint_pair_iff_eq_of_nondegenerate BilinForm.isAdjointPair_iff_eq_of_nondegenerate

end LinearAdjoints

end BilinForm


import topology.order
import group_theory.quotient_group
import valuation.canonical

/-!
# Valuation Spectrum (Spv)

The API for the valuation spectrum of a commutative ring. Normally defined as
"the equivalence classes of valuations", there are set-theoretic issues.
These issues are easily solved by noting that two valuations are equivalent
if and only if they induce the same preorder on R, where the preorder
attached to a valuation sends (r,s) to v r ≤ v s.

## Implementation details

Our definition of Spv is currently the predicates which come from a
valuation. There is another approach though:
In the proof of Prop 2.2 of “Continuous Valuations” by R. Huber,
the relations that come from a valuations are classified as thoe satisfying some axioms.
See also Wedhorn 4.7.
-/

universes u u₀ u₁ u₂ u₃

local attribute [instance, priority 0] classical.DLO

/-- Valuation spectrum of a ring. -/
-- Note that the valuation takes values in a group in the same universe as R.
-- This is to avoid "set-theoretic issues".
definition Spv (R : Type u₀) [comm_ring R] :=
{ineq : R → R → Prop // ∃ {Γ₀ : Type u₀} [linear_ordered_comm_group_with_zero Γ₀],
 by exactI ∃ (v : valuation R Γ₀), ∀ r s : R, v r ≤ v s ↔ ineq r s}

variables {R : Type u₀} [comm_ring R] {v : Spv R}

local notation r `≤[`v`]` s := v.1 r s

/- Spv R is morally a quotient, so we start by giving it a quotient-like interface -/
namespace Spv
open valuation

variables {Γ₀  : Type u}   [linear_ordered_comm_group_with_zero Γ₀]
variables {Γ'₀ : Type u₁}  [linear_ordered_comm_group_with_zero Γ'₀]
variables {Γ''₀ : Type u₂} [linear_ordered_comm_group_with_zero Γ''₀]

-- The work is embedded here with `canonical_valuation_is_equiv v` etc.
-- The canonical valuation attached to v lives in R's universe.
/-- The equivalence class of a valuation. -/
definition mk (v : valuation R Γ₀) : Spv R :=
⟨λ r s, v r ≤ v s,
  ⟨value_monoid v, by apply_instance, canonical_valuation v, canonical_valuation_is_equiv v⟩⟩

@[simp] lemma mk_val (v : valuation R Γ₀) : (mk v).val = λ r s, v r ≤ v s := rfl

/-- The value group attached to a term of type Spv R -/
definition out_Γ₀ (v : Spv R) : Type u₀ := classical.some v.2

/-- The value group attached to a term of type Spv R
is a linearly ordered commutative group with zero. -/
noncomputable instance (v : Spv R) : linear_ordered_comm_group_with_zero (out_Γ₀ v) :=
classical.some $ classical.some_spec v.2

/-- An explicit representative of an equivalence class of valuations (a term of type Spv R). -/
noncomputable definition out (v : Spv R) : valuation R (out_Γ₀ v) :=
classical.some $ classical.some_spec $ classical.some_spec v.2

@[simp] lemma mk_out {v : Spv R} : mk (out v) = v :=
begin
  rcases v with ⟨ineq, hv⟩,
  rw subtype.ext,
  ext,
  exact classical.some_spec (classical.some_spec (classical.some_spec hv)) _ _,
end

/-- The explicit representative of the equivalence class of a valuation v is equivalent to v. -/
lemma out_mk (v : valuation R Γ₀) : (out (mk v)).is_equiv v :=
classical.some_spec (classical.some_spec (classical.some_spec (mk v).2))

/-- A function defined on all valuations of R descends to Spv R. -/
noncomputable def lift {X}
  (f : Π ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀], valuation R Γ₀ → X) (v : Spv R) : X :=
f (out v)

/-- The computation principle for Spv:
If a function is constant on equivalence classes of valuations,
then it descends to a well-defined function on Spv R.-/
theorem lift_eq {X}
  (f₀ : Π ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀], valuation R Γ₀ → X)
  (f : Π ⦃Γ₀ : Type u⦄ [linear_ordered_comm_group_with_zero Γ₀], valuation R Γ₀ → X)
  (v : valuation R Γ₀)
  (h : ∀ ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀] (v₀ : valuation R Γ₀),
    v₀.is_equiv v → f₀ v₀ = f v) :
  lift f₀ (mk v) = f v :=
h _ (out_mk v)

/-- Prop-valued version of the computation principle for Spv:
If a predicate is constant on equivalence classes of valuations,
then it descends to a well-defined predicate on Spv R.-/
theorem lift_eq'
  (f₀ : Π ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀], valuation R Γ₀ → Prop)
  (f : Π ⦃Γ₀ : Type u⦄ [linear_ordered_comm_group_with_zero Γ₀], valuation R Γ₀ → Prop)
  (v : valuation R Γ₀)
  (h : ∀ ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀] (v₀ : valuation R Γ₀),
    v₀.is_equiv v → (f₀ v₀ ↔ f v)) :
  lift f₀ (mk v) ↔ f v :=
h _ (out_mk v)

/-- For every term of Spv R, there exists a valuation representing it.-/
lemma exists_rep (v : Spv R) :
  ∃ {Γ₀ : Type u₀} [linear_ordered_comm_group_with_zero Γ₀], by exactI ∃ (v₀ : valuation R Γ₀),
  mk v₀ = v :=
⟨out_Γ₀ v, infer_instance, out v, mk_out⟩

/-- The quotient map that sends a valuation to its equivalence class is sound:
it sends equivalent valuations to the same class. -/
lemma sound {v₁ : valuation R Γ'₀} {v₂ : valuation R Γ''₀} (h : v₁.is_equiv v₂) : mk v₁ = mk v₂ :=
begin
  apply subtype.val_injective,
  ext r s,
  apply h,
end

/-- The induction principle for Spv.-/
lemma induction_on (v : Spv R) (P : Spv R → Prop)
  (h : Π ⦃Γ₀ : Type u₀⦄ [linear_ordered_comm_group_with_zero Γ₀] (v : valuation R Γ₀), P (Spv.mk v)) :
  P v :=
begin
  rw ← @mk_out _ _ v,
  apply h
end

/-- If two valuations are mapped to the same term of Spv R, then they are equivalent. -/
lemma is_equiv_of_eq_mk {v₁ : valuation R Γ'₀} {v₂ : valuation R Γ''₀} (h : mk v₁ = mk v₂) :
  v₁.is_equiv v₂ :=
begin
  intros r s,
  have := congr_arg subtype.val h,
  replace := congr this (rfl : r = r),
  replace := congr this (rfl : s = s),
  simp at this,
  simp [this],
end

@[ext]
lemma ext (v₁ v₂ : Spv R) (h : (Spv.out v₁).is_equiv (Spv.out v₂)) :
  v₁ = v₂ :=
by simpa only [Spv.mk_out] using Spv.sound h

lemma ext_iff  {v₁ v₂ : Spv R} :
  v₁ = v₂ ↔ ((Spv.out v₁).is_equiv (Spv.out v₂)) :=
⟨λ h, Spv.is_equiv_of_eq_mk (by simpa only [Spv.mk_out] using h), Spv.ext _ _⟩

/-- A coercion that allows to treat an element of Spv(R) as a function,
by picking a random representative of the equivalence class of valuations. -/
noncomputable instance : has_coe_to_fun (Spv R) :=
{ F := λ v, R → out_Γ₀ v,
  coe := λ v, (out v : R → out_Γ₀ v) }

section

@[simp] lemma map_zero : v 0 = 0 := valuation.map_zero _
@[simp] lemma map_one  : v 1 = 1 := valuation.map_one _
@[simp] lemma map_mul  : ∀ x y, v (x * y) = v x * v y := valuation.map_mul _
@[simp] lemma map_add  : ∀ x y, v (x + y) ≤ max (v x) (v y) := valuation.map_add _

end

section supp

/--The support of an equivalence class of valuations.-/
noncomputable def supp (v : Spv R) := v.out.supp

/--The support of an equivalence class of valuations is equal to the
support of any of its representatives.-/
@[simp] lemma supp_mk (v : valuation R Γ₀) : (mk v).supp = v.supp :=
(out_mk v).supp_eq

end supp

section comap
variables {S : Type*} [comm_ring S] {T : Type*} [comm_ring T]

/--The pullback of an equivalence class of valuations
along a ring homomorphism.-/
noncomputable def comap (f : R → S) [is_ring_hom f] : Spv S → Spv R :=
lift $ λ Γ₀ _ v, by exactI Spv.mk (v.comap f)

/--The pullpack of an equivalence class of valuations
is the equivalence class of the pullback of any of its representatives.-/
lemma comap_mk (f : R → S) [is_ring_hom f] (v : valuation S Γ₀) :
  comap f (mk v) = mk (v.comap f) :=
begin
  delta comap, rw lift_eq,
  intros Γ₀ _ v h, resetI,
  apply sound,
  exact is_equiv.comap f h
end

/--The pullback along the identity is the identity.-/
lemma comap_id_apply (v : Spv R) : comap (id : R → R) v = v :=
begin
  apply induction_on v, clear v,
  intros Γ₀ _ v, resetI,
  rw [comap_mk, valuation.comap_id],
end

/--The pullback along the identity is the identity.-/
@[simp] lemma comap_id : comap (id : R → R) = id :=
funext $ comap_id_apply

/--The pullback along a composition is the composition of the pullback maps.-/
@[simp] lemma comap_comp_apply (g : S → T) (f : R → S) [is_ring_hom g] [is_ring_hom f] (v : Spv T):
  comap (g ∘ f) v = comap f (comap g v) :=
begin
  apply induction_on v, clear v,
  intros Γ₀ _ v, resetI,
  rw [comap_mk, comap_mk, comap_mk, valuation.comap_comp],
end

/--The pullback along a composition is the composition of the pullback maps.-/
@[simp] lemma comap_comp (g : S → T) (f : R → S) [is_ring_hom g] [is_ring_hom f] :
  comap (g ∘ f) = comap f ∘ comap g :=
funext $ comap_comp_apply g f

/--The support of the pullback of an equivalence class of valuations
is the pullback (as ideal) of the support.-/
lemma supp_comap (f : R → S) [is_ring_hom f] (v : Spv S) :
  (v.comap f).supp = v.supp.comap f :=
begin
  apply induction_on v, clear v,
  intros Γ₀ _ v, resetI,
  rw [comap_mk, supp_mk, supp_mk, valuation.comap_supp],
end

/--An element is contained in the support of the pullback of an equivalence
class of valuations if and only if its image is contained in the support.-/
lemma mem_supp_comap (f : R → S) [is_ring_hom f] (v : Spv S) (r : R) :
  r ∈ (v.comap f).supp ↔ f r ∈ v.supp :=
by { rw supp_comap, exact iff.rfl }

end comap

/-- The open sets generating the topology of Spv R. See [Wedhorn, Def 4.1].-/
definition basic_open (r s : R) : set (Spv R) :=
{v | v r ≤ v s ∧ v s ≠ 0}

/-- The topology on the valuation spectrum of a ring. -/
instance : topological_space (Spv R) :=
topological_space.generate_from {U : set (Spv R) | ∃ r s : R, U = basic_open r s}

lemma mk_mem_basic_open {r s : R} (v : valuation R Γ₀) :
  mk v ∈ basic_open r s ↔ v r ≤ v s ∧ v s ≠ 0 :=
begin
  apply and_congr,
  { apply out_mk, },
  { apply (out_mk v).ne_zero, },
end

end Spv


import tactic

open set

lemma useful2
  (Y : set ℕ)
  : (∀ (x : ℕ), ∃ (y : ℕ), x ≤ y ∧ y ∈ Y) → Y.infinite :=
begin
  intros h hY,
  obtain ⟨N, hN⟩ : bdd_above Y := finite.bdd_above hY,
  obtain ⟨y, hNy, hyY⟩ : ∃ (y : ℕ), N + 1 ≤ y ∧ y ∈ Y := h (N + 1),
  specialize hN hyY,
  linarith,
end


import tactic
import data.list.chain
import data.fintype.basic
import data.list.rotate
import data.list.basic
import tactic.omega
import data.nat.modeq
import tactic.zify
import data.stream.basic

open_locale classical
noncomputable theory

variables {X : Type}

def cycle := λ (P : X → X → Prop) (c : list X), ∃ (e : c ≠ list.nil), list.chain P (c.last e) c

def reverse := λ (P : X → X → Prop) (x y : X), P y x

def length_cycle_pos {l : list X} {P : X → X → Prop} (c : cycle P l) : 0 < l.length :=
begin
    cases c with a b,
    exact list.length_pos_of_ne_nil a,
end

-- updated acyclic definition.
def acyclic : (X → X → Prop) → Prop := λ P, ∀ (c : list X), ¬ cycle P c

lemma size_lemma {x : ℕ} (l : list X) (y : x < l.reverse.length) : l.length - 1 - x < l.length :=
begin
    rw list.length_reverse at y,
    omega,
end

lemma nth_reverse {x : ℕ} (l : list X) (y: x < l.reverse.length) : l.reverse.nth_le x y = l.nth_le (l.length - 1 - x) (size_lemma l y) :=
begin
    have to_use := eq.symm (list.nth_le_reverse l.reverse x _ _),
    rw to_use,
    simp,
    simp only [list.length_reverse],
    exact size_lemma l y,
end

lemma nonempty_last (l : list X) (e : l.length > 0) : l.reverse ≠ list.nil :=
begin
    rw eq.symm (list.length_reverse l) at e,
    exact list.ne_nil_of_length_pos e,
end

lemma last_reverse (l : list X) (e : l.length > 0) : l.reverse.last (nonempty_last l e) = l.nth_le 0 e :=
begin
    rw list.last_eq_nth_le,
    rw nth_reverse,
    have ineq : (l.length - 1 - (l.reverse.length - 1)) = 0,
    rw list.length_reverse,
    simp,
    simp,
end

lemma head'_eq_nth_le_zero (l : list X) (y : X) (e : l ≠ list.nil) : y ∈ l.head' → y = (l.nth_le 0 (list.length_pos_of_ne_nil e)) :=
begin
    intro a,
    obviously,
    rw ←list.nth_zero at a,
    rw list.nth_le_nth (list.length_pos_of_ne_nil e) at a,
    obviously,
end

lemma within_rotate_of_within {l : list X} {i : ℕ} (n : ℕ) (e : i < l.length) : i < (l.rotate n).length := by obviously

lemma ne_nil_of_lt_length {a b : ℕ} : a < b → b > 0 := by omega

lemma nth_le_rotate {l : list X} {n i : ℕ} (e : i < l.length) : ((l.rotate n).nth_le i (within_rotate_of_within n e)) = l.nth_le ((i + n) % l.length) (nat.mod_lt (i+n) (ne_nil_of_lt_length e)) :=
begin
    have first := list.nth_rotate e,
    work_on_goal 1 {exact n,},
    rw list.nth_le_nth (within_rotate_of_within n e) at first,
    rw list.nth_le_nth ((i + n).mod_lt (ne_nil_of_lt_length e)) at first,
    obviously,
end

lemma rotate1_cycle_of_cycle {a : X} {l : list X} {P : X → X → Prop} : cycle P (a :: l) → cycle P (l ++ [a]) :=
begin
    intro c,
    unfold cycle at *,
    cases c with _ c,
    obviously,
    change list.chain' P (a :: l ++ [a]),
    apply list.chain'.append,
    change list.chain' P (a :: l) at c_right,
    exact c_right,
    exact list.chain'_singleton a,
    intros x x_mem y y_mem,
    have i := list.mem_last'_eq_last x_mem,
    cases i with _ i,
    rw i,
    obviously,
end

-- proof by induction? cycle (a :: l) iff cycle (l ++ [a]), list.rotate_cons_succ
lemma rotate'_cycle_of_cycle {c : list X} {P : X → X → Prop} {n : ℕ} : cycle P c → cycle P (list.rotate' c n) :=
begin
    intro cy,
    induction n,
    rw list.rotate'_zero,
    exact cy,

    rw nat.succ_eq_add_one,
    rw ←list.rotate'_rotate',
    have i := n_ih,
    cases i with n _,
    have e := list.exists_cons_of_ne_nil n,
    cases e with a e,
    cases e with l e,
    rw e,
    rw list.rotate'_cons_succ,
    rw list.rotate'_zero,
    rw e at n_ih,
    exact rotate1_cycle_of_cycle n_ih,
end

lemma dominate_ineq1 (i n : ℕ) : ¬(i = 0) → (i < n → i - 1 < n - 1) := by omega
lemma dominate_ineq2 (i : ℕ) : ¬(i = 0) → (i - 1 + 1) = i := by omega
lemma ineq4 (i n : ℕ) (e : i < n) : i.pred < n :=
begin
    rw nat.pred_eq_sub_one i,
    omega,
end
lemma ineq5 (i n : ℕ) (e : i < n) (z: ¬i = n - 1) : i < n - 1 := by omega

-- every node in a cycle is defeated by some other node in that cycle.
lemma dominate_of_cycle (l : list X) (P : X → X → Prop) (c : cycle P l) : ∀ x ∈ l, ∃ y ∈ l, P y x :=
begin
    intro x, -- for any element x
    intro mem, -- and a proof that x is in the list
    unfold cycle at c,
    cases c with e c,
    rw list.chain_iff_nth_le at c,
    cases c with c_right c_left,
    let i := l.index_of x,
    have i_bound : i < l.length,
    apply list.index_of_lt_length.mpr,
    exact mem,
    by_cases z : i = 0,
        {use (l.last e),
        use list.last_mem e,
        rw ←(list.index_of_nth_le i_bound),
        change P (l.last e) (l.nth_le i i_bound),
        simp_rw z,
        exact (c_right (eq.rec i_bound z)),},

        {use l.nth_le (i - 1) ((ineq4 i l.length) i_bound),
        use l.nth_le_mem (i - 1) ((ineq4 i l.length) i_bound),
        specialize c_left (i - 1),
        specialize c_left ((dominate_ineq1 i l.length) z i_bound),
        simp_rw (dominate_ineq2 i z) at c_left,
        rw ←(list.index_of_nth_le i_bound),
        change P (l.nth_le (i - 1) _) (l.nth_le i i_bound),
        exact c_left,},
end

lemma succ_pred {i : ℕ} (e : 0 < i) : i - 1 + 1 = i := by omega
lemma dominate_ineq3 {a b : ℕ} (e : 0 < b) : a < b - 1 → a + 1 < b := by omega
-- have defeats := dominates_of_cycle X c (split_cycle_VCCR_function V X P) cy mini mini_mem,
lemma dominates_of_cycle_index (l : list X) (P : X → X → Prop) (c : cycle P l) (i : ℕ) (i_bound : i < l.length)
   : P (l.nth_le i i_bound) (l.nth_le ((i + 1) % l.length) (nat.mod_lt (i + 1) (length_cycle_pos c))) :=
begin
    unfold cycle at c,
    cases c with e c,
    rw list.chain_iff_nth_le at c,
    cases c with c_left c_right,
    by_cases z : i = l.length - 1,
        {simp_rw z, rw ←list.last_eq_nth_le l e,
        specialize c_left (list.length_pos_of_ne_nil e),
        simp_rw succ_pred (list.length_pos_of_ne_nil e),
        simp_rw nat.mod_self,
        exact c_left,},

    specialize c_right i,
    have i_bound2 := ineq5 i l.length i_bound z,
    specialize c_right i_bound2,
    have underflow : (i + 1) % l.length = (i + 1),
    rw nat.mod_eq_of_lt,
    exact dominate_ineq3 (list.length_pos_of_ne_nil e) i_bound2,
    simp_rw underflow,
    exact c_right,
end

lemma dominates_of_cycle (l : list X) (P : X → X → Prop) (c : cycle P l) : ∀ x ∈ l, ∃ y ∈ l, P x y :=
begin
    intro x,
    intro mem,
    let i := l.index_of x,
    have d := dominates_of_cycle_index l P c,
    specialize d i,
    specialize d (list.index_of_lt_length.mpr mem),
    use (l.nth_le ((i + 1) % l.length) ((i + 1).mod_lt (length_cycle_pos c))),
    split,
    apply list.nth_le_mem,
    simp_rw list.index_of_nth_le at d,
    exact d,
end

lemma cycle_reverse_in_reverse_relation (P : X → X → Prop) (l : list X) (c : cycle P l) : cycle (reverse P) (l.reverse) :=
begin
    obviously, -- handles proof of reverse ne nil,
    rw list.chain_iff_nth_le,
    rw list.chain_iff_nth_le at c_h,
    obviously, -- performs the necessary splitting and rewriting
    rw last_reverse l (list.length_pos_of_ne_nil c_w),
    rw nth_reverse,
    simp_rw nat.sub_zero,
    rw ←list.last_eq_nth_le,
    specialize c_h_left (list.length_pos_of_ne_nil c_w),
    exact c_h_left,

    rw nth_reverse,
    rw nth_reverse,
    simp [reverse],
    specialize c_h_right (l.length - 1 - (i + 1)),
    have o : ∀ n, (i < n - 1) → n - 1 - (i + 1) < n - 1 := by omega,
    specialize c_h_right (o l.length h),
    have o : ∀ n, (i < n - 1) → (n - 1 - (i + 1) + 1) = (n - 1 - i) := by omega,
    simp_rw (o l.length h) at c_h_right,
    exact c_h_right,
end

noncomputable def to_path : list X → list X
| list.nil := list.nil
| (list.cons u p) :=
    let p' := to_path p
    in if p'.index_of u < p'.length
        then (p'.drop (p'.index_of u))
        else (list.cons u p')

lemma ite_left_if {α : Type} {p : Prop} [decidable p] {a b : α} : p → ite p a b = a := by obviously
lemma ite_right_if {α : Type} {p : Prop} [decidable p] {a b : α} : ¬ p → ite p a b = b := by obviously

lemma nodup_drop_of_nodup {l : list X} {n : ℕ} : l.nodup → (l.drop n).nodup :=
begin
    by_cases n < l.length,
    rw list.nodup_iff_nth_le_inj,
    rw list.nodup_iff_nth_le_inj,
    intros a i j h₁ h₂,
    rw list.nth_le_drop',
    rw list.nth_le_drop',
    intro eq,
    specialize a (n+i),
    specialize a (n+j),
    rw list.length_drop at h₁,
    rw list.length_drop at h₂,
    have o : ∀ (a b c : ℕ), c < b → a < b - c → c + a < b := by omega,
    specialize a (o i l.length n h h₁),
    specialize a (o j l.length n h h₂),
    specialize a eq,
    exact (add_right_inj n).mp a,

    push_neg at h,
    intro a,
    have sub : (list.drop n l).length = 0,
    rw list.length_drop,
    omega,
    rw (list.eq_nil_of_length_eq_zero sub),
    exact list.nodup_nil,
end

lemma to_path_nodup (l : list X) : (to_path l).nodup :=
begin
    induction l,
    exact list.nodup_nil,
    simp [to_path],

    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    simp_rw (ite_left_if h),
    exact nodup_drop_of_nodup l_ih,

    simp_rw (ite_right_if h),
    have not_mem : l_hd ∉ (to_path l_tl),
    contrapose h,
    push_neg at h, push_neg,
    exact list.index_of_lt_length.mpr h,
    exact list.nodup_cons_of_nodup not_mem l_ih,
end

lemma to_path_eq_nil_iff (l : list X) : (to_path l) = list.nil ↔ l = list.nil :=
begin
    induction l,
    obviously,
    simp [to_path] at ᾰ,
    contrapose ᾰ,
    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    apply list.ne_nil_of_length_pos,
    rw (ite_left_if h),
    rw list.length_drop,
    have ineq : ∀ (a b : ℕ), a < b → 0 < b - a := by omega,
    exact ineq (list.index_of l_hd (to_path l_tl)) ((to_path l_tl).length) h,
    rw (ite_right_if h),
    exact list.cons_ne_nil (l_hd) (to_path l_tl),
end

lemma helper (X : Type) (l : list X) (n : l ≠ list.nil) : 0 < (to_path l).length :=
begin
    apply list.length_pos_of_ne_nil,
    contrapose n,
    push_neg, push_neg at n,
    exact (to_path_eq_nil_iff l).mp n,
end

lemma to_path_first_elem (X : Type) (l : list X) (n : l ≠ list.nil) : (list.nth_le (to_path l) 0 (helper X l n)) = (list.nth_le l 0 (list.length_pos_of_ne_nil n)) :=
begin
    induction l,
    obviously,
    simp [to_path],
    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    simp_rw (ite_left_if h),
    rw list.nth_le_drop',
    simp_rw nat.add_zero,
    rw list.index_of_nth_le,

    simp_rw (ite_right_if h),
    obviously,
end

lemma helper' (X : Type) (l : list X) (n: 0 < (to_path l).length) : l ≠ list.nil :=
begin
    contrapose n,
    push_neg at n,
    have a := (to_path_eq_nil_iff l).mpr n,
    contrapose a,
    push_neg at a,
    exact list.ne_nil_of_length_pos a,
end

lemma to_path_first_elem' (X : Type) (l : list X) (n : (to_path l).length > 0) : (list.nth_le (to_path l) 0 n) = (list.nth_le l 0 (list.length_pos_of_ne_nil (helper' X l n))) :=
begin
    induction l,
    obviously,
    simp [to_path],
    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    simp_rw (ite_left_if h),
    rw list.nth_le_drop',
    simp_rw nat.add_zero,
    rw list.index_of_nth_le,

    simp_rw (ite_right_if h),
    obviously,
end

lemma to_path_ne_nil_iff (l : list X) (n : l ≠ list.nil) : (to_path l) ≠ list.nil :=
begin
    by_contradiction,
    push_neg at h,
    have test := (to_path_eq_nil_iff l).mp h,
    exact n test,
end

-- list.index_of l_hd (to_path l_tl) + ((to_path l_tl).length - list.index_of l_hd (to_path l_tl) - 1) = (to_path l_tl).length - 1
lemma last_elem_lemma {a b : ℕ} : (a < b) → a + (b - a - 1) = b - 1 := by omega

lemma to_path_last_elem (l : list X) (n : l ≠ list.nil) : ((to_path l).last (to_path_ne_nil_iff l n)) = (l.last n) :=
begin
    induction l,
    obviously,
    simp [to_path],
    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    simp_rw (ite_left_if h),
    rw list.last_eq_nth_le,
    simp_rw list.length_drop,
    rw list.nth_le_drop',
    simp_rw last_elem_lemma h,
    have to_path_ne_nil : (to_path l_tl) ≠ list.nil,
    apply list.ne_nil_of_length_pos,
    have pos_of_lt : ∀ (a b : ℕ), a < b → 0 < b := by omega,
    exact pos_of_lt (list.index_of l_hd (to_path l_tl)) (to_path l_tl).length h,
    have to_path_ne_nil := (not_iff_not_of_iff (to_path_eq_nil_iff l_tl)).mp to_path_ne_nil,
    specialize l_ih to_path_ne_nil,
    rw ←list.last_eq_nth_le (to_path l_tl) (to_path_ne_nil_iff l_tl to_path_ne_nil),
    rw l_ih,
    exact (eq.symm (list.last_cons n to_path_ne_nil)),

    simp_rw (ite_right_if h),
    induction l_tl,
    obviously,
    rw list.last_cons,
    exact l_ih,
end

lemma drop_chain'_of_chain' {X : Type} {l : list X} {P : X → X → Prop} {n : ℕ} : l.chain' P → (l.drop n).chain' P :=
begin
    by_cases n < l.length,
    rw list.chain'_iff_nth_le,
    rw list.chain'_iff_nth_le,
    intros a i t,
    rw list.nth_le_drop',
    rw list.nth_le_drop',
    rw list.length_drop at t,
    specialize a (n + i),
    have o : ∀ (n i l : ℕ), n < l → i < l - n - 1 → n + i < l - 1 := by omega,
    specialize a (o n i l.length h t),
    exact a,

    push_neg at h,
    intro a,
    have sub : (list.drop n l).length = 0,
    rw list.length_drop,
    omega,
    rw (list.eq_nil_of_length_eq_zero sub),
    exact list.chain'_nil,
end

lemma nth_le_cons {l : list X} {i : ℕ} {e : (i - 1) < l.length} {a : X} {p : i < (a :: l).length} (n : ¬ i = 0) : ((a :: l).nth_le i p) = (l.nth_le (i - 1) e) :=
begin
  let j := i - 1,
  change (a :: l).nth_le (i) p = l.nth_le j e,
  change (a :: l).nth_le (i) p = (a :: l).nth_le (j + 1) _,
  congr,
  change i = i - 1 + 1,
  omega,
  dsimp at *, simp at *, assumption,
end

lemma to_path_chain'_of_chain' {X : Type} {l : list X} {P : X → X → Prop} : list.chain' P l → list.chain' P (to_path l) :=
begin
    intro a,
    induction l,
    obviously,
    simp [to_path],
    specialize l_ih (and.right (list.chain'_cons'.mp a)),
    by_cases (list.index_of l_hd (to_path l_tl) < (to_path l_tl).length),
    simp_rw (ite_left_if h),
    exact drop_chain'_of_chain' l_ih,

    simp_rw (ite_right_if h),
    by_cases j : l_tl = list.nil,
    rw (to_path_eq_nil_iff l_tl).mpr j,
    exact list.chain'_singleton l_hd,

    rw list.chain'_iff_nth_le,
    rw list.chain'_iff_nth_le at l_ih,
    rw list.chain'_iff_nth_le at a,
    intros i i_bounds,
    by_cases i = 0,
    simp_rw h,
    rw [list.nth_le], rw [list.nth_le],
    specialize a 0,
    have a_proof : 0 < (l_hd :: l_tl).length - 1,
    rw list.length_cons,
    simp only [nat.add_succ_sub_one, add_zero],
    exact (list.length_pos_of_ne_nil j),
    specialize a a_proof,
    rw [list.nth_le] at a, rw [list.nth_le] at a,
    have x := (to_path_first_elem X l_tl j),
    have z : (to_path l_tl).nth_le 0 (helper X l_tl j) = (to_path l_tl).nth_le 0 (list.nth_le._main._proof_1 l_hd (to_path l_tl) 0
  (eq.rec (nat.lt_pred_iff.mp i_bounds)
     ((λ [c : has_add ℕ] (ᾰ ᾰ_1 : ℕ) (e_2 : ᾰ = ᾰ_1) (ᾰ_2 ᾰ_3 : ℕ) (e_3 : ᾰ_2 = ᾰ_3),
         congr (congr_arg has_add.add e_2) e_3) i 0 h 1 1 (eq.refl 1)))),
    refl,
    rw z at x,
    rw x,
    exact a,

    rw [list.nth_le],
    specialize l_ih (i - 1),
    rw list.length_cons at i_bounds,
    simp only [nat.add_succ_sub_one, add_zero] at i_bounds,
    have o : ∀ i n, ¬ i = 0 → i < n → i - 1 < n - 1 := by omega,
    specialize l_ih (o i (to_path l_tl).length h i_bounds),
    rw nth_le_cons h,
    have o : ∀ i, ¬ i = 0 → i - 1 + 1 = i := by omega,
    simp_rw (o i h) at l_ih,
    exact l_ih,
end

lemma rotate'_eq_nil_iff (X : Type) (l : list X) (n : ℕ) : l.rotate' n = list.nil ↔ l = list.nil :=
begin
    have a := list.length_eq_zero,
    work_on_goal 2 {
        exact l,
    },
    rw ←a,
    have a := list.length_eq_zero,
    work_on_goal 2 {
        exact l.rotate' n,
    },
    rw ←a,
    rw list.length_rotate' l n,
end

lemma cycle_of_cycle_imp {X : Type} {l : list X} {p₁ p₂ : X → X → Prop} (e : ∀ x y, p₁ x y → p₂ x y) : cycle p₁ l → cycle p₂ l :=
begin
    obviously,
    exact list.chain.imp e ᾰ_h,
end

lemma chain'_of_chain'_append {x : list X} {y : list X} {P : X → X → Prop} (e : list.chain' P (x ++ y)) : list.chain' P x :=
begin
    rw list.chain'_iff_nth_le, rw list.chain'_iff_nth_le at e,
    intros i h,
    specialize e i,
    have o : ∀ a b c, a < b - 1 → a < b + c - 1 := by omega,
    have t := (o i x.length y.length h),
    rw ←list.length_append at t,
    specialize e t,
    rw list.nth_le_append (nat.lt_of_lt_pred t) (nat.lt_of_lt_pred h) at e,
    rw list.nth_le_append at e,
    exact e,
end

lemma chain'_take_of_chain {l : list X} {P : X → X → Prop} (a : l ≠ list.nil) {n : ℕ} (c : list.chain P (l.last a) l) : list.chain' P (l.take n) :=
begin
    change list.chain' P ((l.last a) :: l) at c,
    rw list.chain'_cons' at c,
    cases c with _ c,
    rw ←(list.take_append_drop n l) at c,
    let y := (list.drop n l),
    change list.chain' P (list.take n l ++ y) at c,
    exact chain'_of_chain'_append c,
end

lemma nth_take_ineq {a b c : ℕ} (d : a < b) (e : c = b) : (a < c) :=
begin
    have e := eq.symm e,
    rw e at d,
    exact d,
end

lemma nth_take {X : Type} {n x : ℕ} (e : x < n) (s : stream X) : ((s.take n).nth_le x (nth_take_ineq e (s.length_take n))) = (s.nth x) :=
begin
    unfold stream.take,
    rw list.nth_le_map,
    rw list.nth_le_range,
    rw list.length_range,
    exact e,
end


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jan-David Salchow, Sébastien Gouëzel, Jean Lo, Yury Kudryashov, Frédéric Dupuis,
  Heather Macbeth
-/
import topology.algebra.module.basic
import linear_algebra.determinant

/-!
# The determinant of a continuous linear map.
-/

namespace continuous_linear_map

/-- The determinant of a continuous linear map, mainly as a convenience device to be able to
write `A.det` instead of `(A : M →ₗ[R] M).det`. -/
@[reducible] noncomputable def det {R : Type*} [comm_ring R]
  {M : Type*} [topological_space M] [add_comm_group M] [module R M] (A : M →L[R] M) : R :=
linear_map.det (A : M →ₗ[R] M)

end continuous_linear_map

namespace continuous_linear_equiv

@[simp] lemma det_coe_symm {R : Type*} [field R]
  {M : Type*} [topological_space M] [add_comm_group M] [module R M] (A : M ≃L[R] M) :
  (A.symm : M →L[R] M).det = (A : M →L[R] M).det ⁻¹ :=
linear_equiv.det_coe_symm A.to_linear_equiv

end continuous_linear_equiv