-- CvxLean/Lib/Math/LogDet.lean

import Mathlib.LinearAlgebra.Matrix.LDL

import CvxLean.Lib.Math.SchurComplement

import CvxLean.Lib.Math.Subadditivity

import CvxLean.Lib.Math.LinearAlgebra.Matrix.Triangular

import CvxLean.Lib.Math.LinearAlgebra.Matrix.ToUpperTri

import CvxLean.Lib.Math.LinearAlgebra.Matrix.LDL

namespace Matrix

open Matrix BigOperators

variable {n : Type} [Fintype n] [LinearOrder n] [LocallyFiniteOrderBot n]

variable {𝕜 : Type} [RCLike 𝕜]

variable {A : Matrix n n ℝ} (hA : A.PosDef)

open scoped Matrix ComplexOrder

-- Repository definitions used:
content: def toUpperTri (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float :=
  fun i j => if i ≤ j then A i j else 0

content: noncomputable instance PosDef.Invertible [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) :
    Invertible M :=
  invertibleOfIsUnitDet M (isUnit_iff_ne_zero.2 hM.det_ne_zero)

content: def diag {n} {α} (M : Matrix n n α) : n → α :=
  fun i => M i i

content: def det {n : ℕ} (A : Matrix (Fin n) (Fin n) Float) : Float :=
  if h : 0 < n then
    if n == 1 then A ⟨0, h⟩ ⟨0, h⟩ else
      (List.finRange n).foldl (fun s i =>
        s + (-1) ^ (Float.ofNat i.val) * A i ⟨0, h⟩ * det (minor A i ⟨0, h⟩)) 0
  else 0

content: def minor (A : Matrix (Fin n) (Fin n) Float) (a b : Fin n) :
    Matrix (Fin n.pred) (Fin n.pred) Float :=
  match n with
  | 0 => fun _ => Fin.elim0
  | _ + 1 => minorAux A a b

content: private def minorAux (A : Matrix (Fin n.succ) (Fin n.succ) Float) (a b : Fin n.succ) :
    Matrix (Fin n) (Fin n) Float :=
  fun i j =>
    let i' : Fin n.succ := if i.val < a.val then i else i.succ;
    let j' : Fin n.succ := if j.val < b.val then j else j.succ;
    A i' j'

content: def toUpperTri {m α : Type _} [LinearOrder m] [Zero α] (A : Matrix m m α) :
    Matrix m m α :=
  fun i j => if i ≤ j then A i j else 0

content: noncomputable def IsHermitian.sqrt {A : Matrix n n ℝ} (hA : A.IsHermitian) : Matrix n n ℝ :=
  hA.eigenvectorMatrix * Matrix.diagonal (fun i => (hA.eigenvalues i).sqrt) * hA.eigenvectorMatrixᵀ

content: structure SymmMatrixVariable where
  index  : Nat
  name   : String
  I      : Nat
  J      : Nat
  primal : Option Float
  dual   : Option Float

content: instance : ToString SymmMatrixVariable where
  toString smv :=
    s!"{smv.index} | " ++
    s!"{smv.name} | " ++
    s!"{smv.I} | " ++
    s!"{smv.J} | " ++
    s!"{smv.primal} | " ++
    s!"{smv.dual}"

content: structure Result where
  summary        : Summary
  constraints    : List Constraint
  vars           : List Variable
  symmMatrixVars : List SymmMatrixVariable

content: instance : ToString Result where
  toString res :=
    (toString res.summary) ++ "\n" ++
    "CONSTRAINTS \n" ++
    "INDEX | NAME | AT | ACTIVITY | LOWER LIMIT | UPPER LIMIT | DUAL LOWER | DUAL UPPER \n" ++
    (res.constraints.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++
    "VARIABLES \n" ++
    "INDEX | NAME | AT | ACTIVITY | LOWER LIMIT | UPPER LIMIT | DUAL LOWER | DUAL UPPER | " ++
    "CONIC DUAL \n" ++
    (res.vars.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++
    "SYMMETRIC MATRIX VARIABLES \n" ++
    "INDEX | NAME | I | J | PRIMAL | DUAL \n" ++
    (res.symmMatrixVars.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n"

content: private def symmMatrixVarsTitle : Parsec Unit :=
  skipString "SYMMETRIC MATRIX VARIABLES" <* endOfLine

content: private def symmMatrixVarsHeaders : Parsec Unit :=
  skipString "INDEX"  <* ws' <*
  skipString "NAME"   <* ws' <*
  skipString "I"      <* ws' <*
  skipString "J"      <* ws' <*
  skipString "PRIMAL" <* ws' <*
  skipString "DUAL"   <* ws' <* endOfLine

content: private def symmMatrixVar : Parsec SymmMatrixVariable :=
  SymmMatrixVariable.mk <$>
  constraintElem nat         <*>
  constraintElem string      <*>
  constraintElem nat         <*>
  constraintElem nat         <*>
  constraintElem optionFloat <*>
  constraintElem optionFloat <* ws' <* endOfLine

content: private def symmMatrixVars : Parsec (List SymmMatrixVariable) :=
  (symmMatrixVarsTitle  *>
  symmMatrixVarsHeaders *>
  Array.data <$> many symmMatrixVar) <|> pure []

content: def result : Parsec Result :=
  Result.mk <$>
  summary        <* ws <*>
  constraints    <* ws <*>
  vars           <* ws <*>
  symmMatrixVars <* ws

content: unsafe def inferDimension (ty : Expr) : MetaM (List (Nat × Nat)) :=
  match ty.consumeMData with
  | .const ``Real _ =>
      return [(1, 1)]
  | .forallE _ (.app (.const ``Fin _) nExpr) e _ =>
    match e with
    | .const ``Real _  => do
      let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr
      return [(n, 1)]
    | .forallE _ (.app (.const ``Fin _) mExpr) (.const ``Real _) _ => do
      let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr
      let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr
      return [(n, m)]
    | _ => throwSolveError "could not infer dimension of {ty}."
  | .app (.app (.app M FinN) FinM) R => do
    match (M, FinN, FinM, R) with
    | (.const ``Matrix _, .app (.const ``Fin _) nExpr, .app (.const ``Fin _) mExpr,
        .const ``Real _) =>
      let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr
      let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr
      return [(n, m)]
    | _ => throwSolveError "could not infer dimension of {ty}."
  | .app (.app (.const ``Prod _) tyl) tyr => do
    let l ← inferDimension tyl
    let r ← inferDimension tyr
    return (l ++ r)
  | _ => throwSolveError "could not infer dimension of {ty}."

content: unsafe def ofProblemData (minExpr : MinimizationExpr) (data : ProblemData)
    (sections : ScalarAffineSections) : MetaM CBF.Problem := do
  let totalDim ← getTotalDim minExpr

  let mut cbf := CBF.Problem.empty
  cbf := cbf.addScalarVariable (CBF.Cone.mk CBF.ConeType.F totalDim)

  if h : data.objective.isSome then
    let sa := data.objective.get h
    let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A]
    let aEnc := CBF.EncodedVector.fromArray sa.a
    let bEnc := CBF.EncodedValue.mk (some sa.b)
    cbf := cbf.setObjectivePSDVariablesCoord AEnc
    cbf := cbf.setObjectiveScalarVariablesCoord aEnc
    cbf := cbf.setObjectiveShiftCoord bEnc

  for (sa, sct) in data.scalarAffineConstraints do
    let coneType := translateCone sct
    let cone := CBF.Cone.mk coneType 1
    let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A]
    let aEnc := CBF.EncodedVector.fromArray sa.a
    let bEnc := sa.b
    cbf := cbf.addScalarValuedAffineConstraint cone AEnc aEnc bEnc

  for ma in data.matrixAffineConstraints do
    let HEnc := CBF.EncodedMatrixList.fromArray ma.H
    let DEnc := CBF.EncodedMatrix.fromArray ma.D
    cbf := cbf.addMatrixValuedAffineConstraint ma.n HEnc DEnc

  
  let n := cbf.scalarConstraints.n
  let cones := cbf.scalarConstraints.cones
  let groupedCones ← groupCones sections cones
  cbf := cbf.setScalarConstraints (CBF.ConeProduct.mk n groupedCones.length groupedCones)

  return cbf

content: structure SimpleMatrixVarSol where
  name  : String
  I     : Nat
  J     : Nat
  value : Option Float

content: instance : ToString SimpleMatrixVarSol where
  toString v :=
    match v.value with
    | some x => v.name ++ "[" ++ toString v.I ++ ", " ++ toString v.J ++ "]* = " ++ toString x
    | none   => "none"

content: structure SolutionData where
  status         : String
  varsSols       : List SimpleVarSol
  matrixVarsSols : List SimpleMatrixVarSol

content: instance : ToString SolutionData where
  toString s :=
    "Status: " ++ s.status ++ "\n" ++
    "Variables: (" ++ ", ".intercalate (s.varsSols.map toString) ++ ")\n" ++
    "Matrix variables: (" ++ ", ".intercalate (s.matrixVarsSols.map toString) ++ ")"

content: def p :=
  optimization (x y : ℝ)
    minimize -2 * x
    subject to
      c₁ : 0 ≤ x
      c₂ : 1 < y
      c₃ : log (y - 1) ≤ 2 * sqrt x + 1
      c₄ : 3 * x + 5 * y ≤ 10

content: -- Transform (proving `p ≡ q`).

equivalence* eqv/q : p :=

content: def const (k : α) : Matrix m n α :=
  fun _ _ => k

content: instance [Preorder α] : Preorder (Matrix m n α) where
  le := fun A B => ∀ i j, A i j ≤ B i j
  le_refl := fun _ _ _ => le_refl _
  le_trans := fun _ _ _ hAB hBC i j => le_trans (hAB i j) (hBC i j)
  lt_iff_le_not_le := fun _ _ => refl _

content: def abs (A : Matrix m n ℝ) : Matrix m n ℝ :=
  fun i j => |A i j|

content: def sum [Fintype m] [AddCommMonoid α] (X : Matrix m m α) : α :=
  ∑ i, (∑ j, X i j)

content: def toArray (A : Matrix (Fin n) (Fin n) Float) : Array (Array Float) :=
  (Array.range n).map <| fun i =>
    if h : i < n then Vec.Computable.toArray (A ⟨i, h⟩) else Array.mk <| List.replicate n 0

content: def dotProduct (v w : Fin n → Float) : Float :=
  (Array.zipWith (Vec.Computable.toArray v) (Vec.Computable.toArray w) Float.mul).foldl Float.add 0

content: def mulVec (M : Matrix (Fin n) (Fin m) Float) (v : (Fin m) → Float) : Fin n → Float :=
  fun i => (fun j => M i j) ⬝ᵥᶜ v

content: def vecMul (x : Fin n → Float) (M : Matrix (Fin n) (Fin m) Float) : Fin m → Float :=
  fun j => x ⬝ᵥᶜ fun i => M i j

content: def transpose {m n} {α} (M : Matrix m n α) : Matrix n m α :=
  fun i j => M j i

content: def diag {n} {α} (M : Matrix n n α) : n → α :=
  fun i => M i i

content: def mul (M : Matrix (Fin l) (Fin m) Float) (N : Matrix (Fin m) (Fin n) Float) :
    Matrix (Fin l) (Fin n) Float :=
  fun i k => (fun j => M i j) ⬝ᵥᶜ (fun j => N j k)

content: def sum (A : Matrix (Fin n) (Fin n) Float) : Float :=
  Vec.Computable.sum (fun i => Vec.Computable.sum (A i))

content: def trace (A : Matrix (Fin n) (Fin n) Float) : Float :=
  (Vec.Computable.toArray (fun i => A i i)).foldl (· + ·) 0

content: def covarianceMatrix {N n : ℕ} (Y : Matrix (Fin N) (Fin n) Float) (i j : Fin n) : Float :=
  Vec.Computable.sum (fun k => (Y k i) * (Y k j)) / (OfNat.ofNat N)

content: def diagonal (x : Fin n → Float) : Matrix (Fin n) (Fin n) Float :=
  fun i j => (if i = j then x i else 0)

content: def fromBlocks {l : Type} {m : Type} {n : Type} {o : Type} {α : Type} :
    Matrix n l α → Matrix n m α → Matrix o l α → Matrix o m α → Matrix (n ⊕ o) (l ⊕ m) α :=
  fun A B C D i j =>
    match i with
    | Sum.inl i =>
      match j with
      | Sum.inl j => A i j
      | Sum.inr j => B i j
    | Sum.inr i =>
      match j with
      | Sum.inl j => C i j
      | Sum.inr j => D i j

content: private def minorAux (A : Matrix (Fin n.succ) (Fin n.succ) Float) (a b : Fin n.succ) :
    Matrix (Fin n) (Fin n) Float :=
  fun i j =>
    let i' : Fin n.succ := if i.val < a.val then i else i.succ;
    let j' : Fin n.succ := if j.val < b.val then j else j.succ;
    A i' j'

content: def minor (A : Matrix (Fin n) (Fin n) Float) (a b : Fin n) :
    Matrix (Fin n.pred) (Fin n.pred) Float :=
  match n with
  | 0 => fun _ => Fin.elim0
  | _ + 1 => minorAux A a b

content: def det {n : ℕ} (A : Matrix (Fin n) (Fin n) Float) : Float :=
  if h : 0 < n then
    if n == 1 then A ⟨0, h⟩ ⟨0, h⟩ else
      (List.finRange n).foldl (fun s i =>
        s + (-1) ^ (Float.ofNat i.val) * A i ⟨0, h⟩ * det (minor A i ⟨0, h⟩)) 0
  else 0

content: def inv (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float :=
  (1 / det A) • adjugate A

content: def adjugate (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float :=
  transpose (cofactor A)

content: def cofactor (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float :=
  fun i j => (-1) ^ (Float.ofNat (i.val + j.val)) * (A i j)

content: def transpose {m n} {α} (M : Matrix m n α) : Matrix n m α :=
  fun i j => M j i

content: def upperTriangular [LT m] (M : Matrix m m R) :=
  M.BlockTriangular id

content: def zeroCone (x : ℝ) : Prop :=
  x = 0

content: def Vec.zeroCone {n} [Fintype n] (x : n → ℝ) : Prop :=
  ∀ i, Real.zeroCone (x i)

content: def Matrix.zeroCone {n m} [Fintype n] [Fintype m] (M : Matrix n m ℝ) : Prop :=
  ∀ i j, Real.zeroCone (M i j)

content: def addMatrixAffineConstraint (data : ProblemData) (H : Array (Array (Array Float)))
    (D : Array (Array Float)) : ProblemData :=
  let constraint := MatrixAffine.mk D.size H D
  { data with matrixAffineConstraints := data.matrixAffineConstraints.push constraint }

content: def upperTriangular [LT m] (M : Matrix m m R) :=
  M.BlockTriangular id

content: def lowerTriangular [LT m] (M : Matrix m m R) :=
  M.BlockTriangular OrderDual.toDual

content: def Matrix.PSDCone {n} [Fintype n] (A : Matrix n n ℝ) : Prop :=
  Matrix.PosSemidef A

-- Repository lemmas used:
content: lemma Real.one_sub_div_exp_pos_of_pos {x : ℝ} : 0 < x → 0 < 1 - 1 / Real.exp x

content: @[positivity (1 - (1 / (Real.exp (_ : ℝ))))]
def evalOneSubDivExp : PositivityExt where eval {_ _α} zα pα e := do
  let (.app (.app _sub _one) (.app (.app _div _one') (.app _exp (x : Q(ℝ))))) ←
    withReducible (whnf e) | throwError "not (1 - 1 / Real.exp x)"
  match ← core zα pα x with
  | .positive pa =>
      let pa' ← mkAppM ``Real.one_sub_div_exp_pos_of_pos #[pa]
      pure (.positive pa')
  | _ =>
      pure .none

content: lemma PosSemidef.PosDef_iff_det_ne_zero [DecidableEq n] {M : Matrix n n ℝ} (hM : M.PosSemidef) :
    M.PosDef ↔ M.det ≠ 0

content: lemma PosSemidef.PosSemidef_sqrt {A : Matrix n n ℝ} (hA : A.PosSemidef) :
    hA.1.sqrt.PosSemidef

content: lemma PosSemidef.add {M N : Matrix n n 𝕜} (hM : M.PosSemidef) (hN : N.PosSemidef) :
    (M + N).PosSemidef

content: @[simp]
lemma PosSemidef.sqrt_mul_sqrt {A : Matrix n n ℝ} (hA : A.PosSemidef) :
    hA.1.sqrt * hA.1.sqrt = A

content: lemma PosSemidef.conjTranspose_mul_mul (M N : Matrix n n 𝕜) (hM : M.PosSemidef) :
    (Nᴴ * M * N).PosSemidef

content: lemma eigenvectorMatrix_inv_mul : hA.eigenvectorMatrixInv * hA.eigenvectorMatrix = 1

content: theorem spectral_theorem'' :
    hA.eigenvectorMatrix * diagonal (RCLike.ofReal ∘ hA.eigenvalues) * hA.eigenvectorMatrixᴴ =
    A

content: lemma conjTranspose_eq_transpose {m n : Type _} {A : Matrix m n ℝ} : Aᴴ = Aᵀ

content: lemma IsHermitian.fromBlocks₁₁ [Fintype m] [DecidableEq m] {A : Matrix m m 𝕜} (B : Matrix m n 𝕜)
    (D : Matrix n n 𝕜) (hA : A.IsHermitian) :
    (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (D - Bᴴ * A⁻¹ * B).IsHermitian

content: lemma IsHermitian.fromBlocks₂₂ [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜) (B : Matrix m n 𝕜)
    {D : Matrix n n 𝕜} (hD : D.IsHermitian) :
    (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (A - B * D⁻¹ * Bᴴ).IsHermitian

content: lemma PosSemidef.fromBlocks₁₁ [Fintype m] [DecidableEq m] [Fintype n] {A : Matrix m m 𝕜}
    (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) (hA : A.PosDef) [Invertible A] :
    (fromBlocks A B Bᴴ D).PosSemidef ↔ (D - Bᴴ * A⁻¹ * B).PosSemidef

content: lemma PosSemidef.fromBlocks₂₂ [Fintype m] [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜)
    (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} (hD : D.PosDef) [Invertible D] :
    (fromBlocks A B Bᴴ D).PosSemidef ↔ (A - B * D⁻¹ * Bᴴ).PosSemidef

content: lemma schur_complement_eq₁₁ [Fintype m] [DecidableEq m] [Fintype n] {A : Matrix m m 𝕜}
    (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) (x : m → 𝕜) (y : n → 𝕜) [Invertible A]
    (hA : A.IsHermitian) :
    vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) =
      vecMul (star (x + (A⁻¹ * B).mulVec y)) A ⬝ᵥ (x + (A⁻¹ * B).mulVec y) +
        vecMul (star y) (D - Bᴴ * A⁻¹ * B) ⬝ᵥ y

content: lemma schur_complement_eq₂₂ [Fintype m] [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜)
    (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} (x : m → 𝕜) (y : n → 𝕜) [Invertible D]
    (hD : D.IsHermitian) :
    vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) =
      vecMul (star ((D⁻¹ * Bᴴ).mulVec x + y)) D ⬝ᵥ ((D⁻¹ * Bᴴ).mulVec x + y) +
        vecMul (star x) (A - B * D⁻¹ * Bᴴ) ⬝ᵥ x

content: lemma IsHermitian.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.IsHermitian)
    (hMdet : IsUnit M.det) : M⁻¹.IsHermitian

content: lemma PosDef.isUnit_det [DecidableEq n] {M : Matrix n n ℝ} (hM : M.PosDef) : IsUnit M.det

content: lemma PosDef.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : M⁻¹.PosDef

content: lemma PosDef.det_ne_zero [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : M.det ≠ 0

content: lemma PosDef.PosDef_sqrt {A : Matrix n n ℝ} (hA : A.PosDef) : hA.1.sqrt.PosDef

content: lemma PosDef.conjTranspose_mul_mul [DecidableEq n] (M N : Matrix n n 𝕜) (hM : M.PosDef)
    (hN : N.det ≠ 0) : (Nᴴ * M * N).PosDef

content: lemma PosSemiDef.IsSymm {n} {A : Matrix (Fin n) (Fin n) ℝ} (hA : PosSemidef A) : IsSymm A

content: lemma PosSemidef.det_nonneg {M : Matrix n n ℝ} (hM : M.PosSemidef) [DecidableEq n] : 0 ≤ det M

content: lemma isUnit_det_of_PosDef_inv [DecidableEq n] {M : Matrix n n ℝ} (h : M⁻¹.PosDef) :
    IsUnit M.det

content: lemma PosDef_inv_iff_PosDef [DecidableEq n] (M : Matrix n n ℝ) : M⁻¹.PosDef ↔ M.PosDef
    
lemma conj_symm {x : n → 𝕜} {M : Matrix n n 𝕜} (hM : M.IsHermitian) :
    star (star x ⬝ᵥ mulVec M x) = star x ⬝ᵥ mulVec M x

content: lemma det_add_det_le_det_add [Nonempty n] (A B : Matrix n n ℝ) (hA : A.PosSemidef)
    (hB : B.PosSemidef) : A.det + B.det ≤ (A + B).det

content: lemma det_add_det_le_det_add' [Nonempty n] (A B : Matrix n n ℝ) (hA : A.PosDef)
    (hB : B.PosSemidef) : A.det + B.det ≤ (A + B).det

content: lemma sum_exp_eq_sum_div {n} (x : Fin n → ℝ) (t : ℝ) :
    Vec.sum (Vec.exp (x - Vec.const n t)) = (Vec.sum (Vec.exp x)) / (Real.exp t)

content: lemma sum_exp_pos {n} (hn : 0 < n) (x : Fin n → ℝ) :
    0 < Vec.sum (Vec.exp x)

content: lemma eigenspace_add {f g : End R M} {a b : R} :
    eigenspace f a ⊓ eigenspace g b ≤ eigenspace (f + g) (a + b)

content: lemma eigenspace_one : eigenspace (1 : End R M) 1 = ⊤

content: lemma has_eigenvector_add {f g : End R M} {a b : R} {x : M} (hf : HasEigenvector f a x)
    (hg : HasEigenvector g b x) : HasEigenvector (f + g) (a + b) x

content: lemma has_eigenvector_one {x : M} (hx : x ≠ 0) : HasEigenvector (1 : End R M) 1 x

content: lemma IsHermitian.has_eigenvector_one_add {A : Matrix n n ℝ} (hA : A.IsHermitian) (i : n) :
    Module.End.HasEigenvector
      (1 + Matrix.toLin' A) (1 + (hA.eigenvalues i)) ((hA.eigenvectorBasis) i)

content: lemma det_add_det_le_det_add' [Nonempty n] (A B : Matrix n n ℝ) (hA : A.PosDef)
    (hB : B.PosSemidef) : A.det + B.det ≤ (A + B).det

content: lemma one_add_prod_le_prod_one_add {n : Type _} [Fintype n] [Nonempty n]
    (f : n → ℝ) (hf : ∀ i, 0 ≤ f i) : 1 + (∏ i, f i) ≤ ∏ i, (1 + f i)

content: lemma BlockTriangular.zero : BlockTriangular (0 : Matrix m m R) b

content: lemma BlockTriangular_diagonal [DecidableEq m] (d : m → R) :
    BlockTriangular (diagonal d) b

content: lemma BlockTriangular_blockDiagonal' [DecidableEq α]
    (d : ∀ (i : α), Matrix (m' i) (m' i) R) :
    BlockTriangular (blockDiagonal'  d) Sigma.fst

content: lemma BlockTriangular_blockDiagonal [DecidableEq α] (d : α → Matrix m m R) :
    BlockTriangular (blockDiagonal d) Prod.snd

content: lemma toBlock_inverse_mul_toBlock_eq_one_of_BlockTriangular [LinearOrder α]
    [Invertible M] (hM : BlockTriangular M b) (k : α) :
    M⁻¹.toBlock (fun i => b i < k) (fun i => b i < k) *
  M.toBlock (fun i => b i < k) (fun i => b i < k) = 1

content: lemma inv_toBlock_of_BlockTriangular [LinearOrder α]
    [Invertible M] (hM : BlockTriangular M b) (k : α) :
    (M.toBlock (fun i => b i < k) (fun i => b i < k))⁻¹ =
    M⁻¹.toBlock (fun i => b i < k) (fun i => b i < k)

content: lemma toSquareBlock_inv_mul_toSquareBlock_eq_one [LinearOrder α]
    [Invertible M] (hM : BlockTriangular M b) (k : α) :
    M⁻¹.toSquareBlock b k * M.toSquareBlock b k = 1

content: theorem toUpperTri_zero {m : Type _} [LinearOrder m] :
    Matrix.toUpperTri (0 : Matrix m m ℝ) = 0

content: theorem toUpperTri_smul {m : Type _} [LinearOrder m]
    (A : Matrix m m ℝ) (κ : ℝ) :
    κ • Matrix.toUpperTri A = Matrix.toUpperTri (κ • A)

content: theorem toUpperTri_add {m : Type _} [LinearOrder m]
    (A B : Matrix m m ℝ) :
    Matrix.toUpperTri (A + B) = Matrix.toUpperTri A + Matrix.toUpperTri B

content: lemma upperTriangular_toUpperTri {m : Type _} [LinearOrder m]
    (A : Matrix m m ℝ) : A.toUpperTri.upperTriangular

content: lemma upperTriangular.toUpperTri_eq {A : Matrix n n ℝ}
    (hA : upperTriangular A) : A.toUpperTri = A

content: lemma Real.log_nonneg_of_ge_one {x : ℝ} : 0 ≤ x - 1 → 0 ≤ log x

content: lemma Real.log_pos_of_gt_one {x : ℝ} : 0 < x - 1 → 0 < log x

content: lemma Real.one_sub_one_div_sq_nonneg_of_le_one {x : ℝ} :
    0 < x → 0 ≤ 1 - x → 0 ≤ (1 / x ^ (2 : ℝ)) - 1

content: lemma Real.one_sub_sq_nonneg_of_le_one {x : ℝ} : 0 ≤ x → 0 ≤ 1 - x → 0 ≤ 1 - x ^ (2 : ℝ)

content: lemma Real.exp_sub_one_pos_of_pos {x : ℝ} : 0 < x → 0 < Real.exp x - 1

content: lemma Real.one_sub_div_exp_pos_of_pos {x : ℝ} : 0 < x → 0 < 1 - 1 / Real.exp x

content: lemma Real.scaled_sq_diff_pos_of_pos {a x : ℝ} :
    0 < a - 1 → 0 < x → 0 < (a * x) ^ (2 : ℝ) - x ^ (2 : ℝ)
  
lemma det_eq_prod_eigenvalues (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) (as : n → ℝ)
    (hxs : ∀ j, Module.End.HasEigenvector (Matrix.toLin' A) (as j) (xs j)) : det A = ∏ i, as i

content: lemma IsHermitian.hasEigenvector_eigenvectorBasis (hA : A.IsHermitian) (i : n) :
    Module.End.HasEigenvector (Matrix.toLin' A) (hA.eigenvalues i) (hA.eigenvectorBasis i)

content: lemma log_eq_log {x y : ℝ} (hx : 0 < x) (hy : 0 < y) : x = y ↔ log x = log y

content: theorem exp_iff_expCone (t x : ℝ) : exp x ≤ t ↔ expCone x 1 t

content: @[simp]
lemma PosSemidef_zero : PosSemidef (0 : Matrix n n 𝕜)

content: lemma LogDetAtom.feasibility_PosDef {D Z : Matrix n n ℝ} (hD : D = LDL.diag hA)
    (hZ : Z = LDL.diag hA * (LDL.lower hA)ᵀ) : (fromBlocks D Z Zᵀ A).PosSemidef

content: lemma LogDetAtom.feasibility_PosDef' {D Z Y : Matrix n n ℝ} (hY : Y = LDL.diag hA * (LDL.lower hA)ᵀ)
    (hD : D = diagonal Y.diag) (hZ : Z = Y.toUpperTri) : (fromBlocks D Z Zᵀ A).PosSemidef

content: lemma LDL.diagEntries_pos {A : Matrix n n ℝ} (hA: A.PosDef) (i : n) :
    0 < LDL.diagEntries hA i

content: lemma LogDetAtom.solution_eq_atom {A : Matrix n n ℝ} (hA: A.PosDef) :
    ∑ i, Real.log (LDL.diagEntries hA i) = Real.log (A.det)

content: lemma LogDetAtom.feasibility_exp {A : Matrix n n ℝ} (hA: A.PosDef) (i : n) :
    LDL.diagEntries hA i ≤ ((LDL.diag hA) * ((LDL.lower hA)ᵀ)).diag i

content: lemma Real.inverse_eq_inv (a : ℝ) : Ring.inverse a = a⁻¹

content: lemma PosSemidef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 ≤ f i) :
    (diagonal f).PosSemidef
lemma PosDef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 < f i) : (diagonal f).PosDef


lemma upperTriangular_inv_of_upperTriangular [Fintype m] [LinearOrder m]
    [Invertible M] (hM : upperTriangular M) : upperTriangular M⁻¹

content: lemma lowerTriangular_inv_of_lowerTriangular [Fintype m] [LinearOrder m]
    [Invertible M] (hM : lowerTriangular M) : lowerTriangular M⁻¹

content: lemma upperTriangular.mul [Fintype m] [LinearOrder m] (hM : upperTriangular M)
    (hN : upperTriangular N) : upperTriangular (M * N)

content: lemma lowerTriangular.mul [Fintype m] [LinearOrder m] (hM : lowerTriangular M)
    (hN : lowerTriangular N) : lowerTriangular (M * N)

content: lemma lowerTriangular.transpose [Fintype m] [LinearOrder m]
    (hM : lowerTriangular M) : upperTriangular Mᵀ

content: lemma upperTriangular.transpose [Fintype m] [LinearOrder m]
    (hM : upperTriangular M) : lowerTriangular Mᵀ

content: lemma diag_inv_mul_diag_eq_one_of_upperTriangular [Fintype m] [LinearOrder m]
    [Invertible M] (hM : upperTriangular M) (k : m) :
    M⁻¹ k k * M k k = 1

content: lemma diag_inv_mul_diag_eq_one_of_lowerTriangular [Fintype m] [LinearOrder m]
    [Invertible M] (hM : lowerTriangular M) (k : m) : M⁻¹ k k * M k k = 1

content: @[simp]
lemma det_lowerInv : (lowerInv hS).det = 1

content: @[simp]
lemma det_lower : (lower hS).det = 1

content: lemma lower_eq_to_matrix :
    lower hS =
      ((@gramSchmidtBasis 𝕜 (n → 𝕜) _
        (NormedAddCommGroup.ofMatrix hS.transpose)
        (InnerProductSpace.ofMatrix hS.transpose)
  n _ _ _ (Pi.basisFun 𝕜 n)).toMatrix (Pi.basisFun 𝕜 n))ᵀ

-- Local definitions used:

-- Local lemmas used:
content: lemma IsHermitian₁₁_of_IsHermitian_toBlock {A B C D : Matrix n n ℝ}
    (h : (fromBlocks A B C D).IsHermitian) : IsHermitian A

content: lemma IsHermitian₂₂_of_IsHermitian_toBlock {A B C D : Matrix n n ℝ}
    (h : (fromBlocks A B C D).IsHermitian) : IsHermitian D

content: lemma PosSemidef₁₁_of_PosSemidef_toBlock {A B C D : Matrix n n ℝ}
    (h : (fromBlocks A B C D).PosSemidef) : PosSemidef A

content: lemma PosSemidef₂₂_of_PosSemidef_toBlock {A B C D : Matrix n n ℝ}
    (h : (fromBlocks A B C D).PosSemidef) :
    PosSemidef D

content: lemma LogDetAtom.optimality_D_posdef {t : n → ℝ} {Y Z D : Matrix n n ℝ}
    (ht : ∀ i, (t i).exp ≤ Y.diag i) (hD : D = Matrix.diagonal (Y.diag)) (_hZ : Z = Y.toUpperTri)
    (hPSD : (fromBlocks D Z Zᵀ A).PosSemidef) : D.PosDef

content: lemma LogDetAtom.optimality_Ddet_le_Adet {t : n → ℝ} {Y Z D : Matrix n n ℝ}
    (ht : ∀ i, (t i).exp ≤ Y.diag i) (hD : D = Matrix.diagonal (Y.diag)) (hZ : Z = Y.toUpperTri)
    (hPSD : (fromBlocks D Z Zᵀ A).PosSemidef) : D.det ≤ A.det

-- Target theorem and proof:
lemma LogDetAtom.cond_elim {t : n → ℝ} {Y Z D : Matrix n n ℝ} (ht : ∀ i, (t i).exp ≤ Y.diag i)
    (hD : D = Matrix.diagonal (Y.diag)) (hZ : Z = Y.toUpperTri)
    (hPSD : (fromBlocks D Z Zᵀ A).PosSemidef) : A.PosDef
:= by
  have h_D_pd : D.PosDef := LogDetAtom.optimality_D_posdef ht hD hZ hPSD
  have h_A_psd : A.PosSemidef := PosSemidef₂₂_of_PosSemidef_toBlock hPSD
  have h_Ddet_le_Adet : D.det ≤ A.det := LogDetAtom.optimality_Ddet_le_Adet ht hD hZ hPSD
  have h_Adet_pos : 0 < A.det := lt_of_lt_of_le h_D_pd.det_pos h_Ddet_le_Adet
  rw [h_A_psd.PosDef_iff_det_ne_zero]
  apply ne_of_gt h_Adet_pos