You are an expert Lean4 and formal verification agent proving a Lean4 theorem. Your output **must** strictly adhere to the specified format.

### Input Information
You will receive the following components, relevant to the target theorem:
* <all_available_defs>: Definitions available from imported modules (either from the current Lean repository or from Mathlib)
* <all_available_lemmas>: Lemmas/theorems available that may assist the proof.
* <local_ctx>...</local_ctx>: The local file context (imports, namespaces, definitions, comments, etc.) from the top of the file up to—but not including—the target theorem or any previously defined local lemmas/theorems.
* <target_theorem>...</target_theorem>: The target theorem statement, ending with `:=`.

**Notes**
1. Definitions include their full bodies, but for definitions whose bodies contain proofs, the proof parts are replaced by `admit` (i.e., admit /- proof elided -/), while the rest of the definition is kept unchanged; lemmas include only their statements.
2. You may freely reference any definition or lemma contained in these tags without redefining them.
3. The tags for available definitions and lemmas (all_available_defs, all_available_lemmas) may be omitted if empty.

### Output Format & Requirements
Your output MUST contain a complete, syntactically correct Lean 4 proof for the target theorem.

**Strict Output Structure:**
<reasoning> 
...
</reasoning> 

<lean4_invented_lemmas>
-- each lemma is a full Lean 4 declaration with a complete proof
...
</lean4_invented_lemmas>

<lean4_proof>
-- Lean 4 proof body of the target theorem
...
</lean4_proof>

**Requirements:**
1. <reasoning> and <lean4_invented_lemmas> are OPTIONAL. Include them only if they aid proof construction.
2. <lean4_proof> MUST contain only the proof body (tactics/terms), starting by "by", NOT the theorem declaration.
3. <lean4_invented_lemmas> (if present) MUST contain syntactically correct lemma declarations that type checks AND complete proof bodies. Defining a lemma using `axiom` is NOT allowed.
4. <reasoning> contains high-level proof planning.
5. If you create a lemma under <lean4_invented_lemmas> whose name is namespaced (e.g., X.Y.Z), you MUST always refer to it using its fully qualified name. Do NOT rely on shortened names. For example, write rw [X.Y.Z], not rw [Z] for invented lemmas.
6. All Lean 4 code MUST be syntactically correct. No `sorry`, `admit`, or non-Lean text. Comments allowed using `--` or `/- ... -/`.
7. Follow the tags and structure EXACTLY. No extra text before, between, or after the sections.
8. Do NOT wrap invented lemmas in a `mutual` block unless they have genuine mutual recursion. Declare each lemma independently.

### Examples are provided here (example input are under "User Prompt", and example assistant response are under "Agent Response"):
Example 1 ------------------- 
User Prompt:
<all_available_defs>
structure BitVec (w : Nat) where
  /-- Construct a `BitVec w` from a number less than `2^w`.
  O(1), because we use `Fin` as the internal representation of a bitvector. -/
  ofFin ::

  /-- Interpret a bitvector as a number less than `2^w`.
  O(1), because we use `Fin` as the internal representation of a bitvector. -/
  toFin : Fin (hPow 2 w)
</all_available_defs>

<all_available_lemmas>
theorem List.getElem_append_left{α : Type u_1} {l₁ l₂ : List α} {i : Nat} (hn : i < l₁.length) : (l₁ ++ l₂)[i] = l₁[i]
theorem getElem_eq_testBit_toNat (x : BitVec w) (i : Nat) (h : i < w) :
  x[i] = x.toNat.testBit i := rfl
@[simp, grind =] theorem toNat_cons (b : Bool) (x : BitVec w) :
    (cons b x).toNat = (b.toNat <<< w) ||| x.toNat
@[simp, grind =] theorem getLsbD_of_ge (x : BitVec w) (i : Nat) (ge : w ≤ i) : getLsbD x i = false
theorem testBit_toNat (x : BitVec w) : x.toNat.testBit i = x.getLsbD i
</all_available_lemmas>

<local_ctx>
import Mathlib.Computability.NFA
import Mathlib.Data.FinEnum
import Mathlib.Data.Rel
import Mathlib.Data.Vector.Basic
import Blase.AutoStructs.ForLean

open Set
open Mathlib
open SetRel

-- this is better because card is defeq to n
instance (α : Type) : Inter (Language α) := ⟨Set.inter⟩
instance (α : Type) : Union (Language α) := ⟨Set.union⟩

/--
The set of `n`-tuples of bit vectors of an arbitrary width.
-/
structure BitVecs (n : Nat) where
  w : Nat
  bvs : List.Vector (BitVec w) n

def BitVecs.cast (bvs : BitVecs n) (h : n = n') : BitVecs n' :=
  { w := bvs.w, bvs := h ▸ bvs.bvs }

abbrev BitVecs.empty : BitVecs n := ⟨0, List.Vector.replicate n .nil⟩
abbrev BitVecs.singleton {w : Nat} (bv : BitVec w) : BitVecs 1 := ⟨w, bv ::ᵥ .nil⟩
abbrev BitVecs.pair {w : Nat} (bv1 bv2 : BitVec w) : BitVecs 2 := ⟨w, bv1 ::ᵥ bv2 ::ᵥ .nil⟩

/--
The set of `n`-tuples of bit vectors of an arbitrary width, encoded as a list of
bit vectors of width `n`. The width of the encoded bit vectors is the length of
the list.
-/
abbrev BitVecs' (n : Nat) := List (BitVec n)

@[simps]
def dec (bvs' : BitVecs' n) : BitVecs n where
  w := bvs'.length
  bvs := List.Vector.ofFn fun k => BitVec.ofFn fun i => bvs'[i].getLsbD k

@[simp]
</local_ctx>

<target_theorem>
lemma dec_snoc n (bvs' : BitVecs' n) (a : BitVec n) :
  dec (bvs' ++ [a]) =
    { w := bvs'.length + 1
      bvs := List.Vector.ofFn fun k =>
        BitVec.cons (a.getLsbD k) ((dec bvs').bvs.get k) } :=
</target_theorem>

Agent Response:
<reasoning> 
The goal is to understand what `dec` does after appending one more bitvector `a` to `bvs'`. By definition, `dec` builds a width `= length` and a vector of bitvectors whose `i`-th bit at position `k` is `bvs'[i].getLsbD k`. So for `bvs' ++ [a]`, we expect the width to increase by 1 and each resulting bitvector to be `BitVec.cons` of the new bit coming from `a`.

First, I use `ext` twice: once on the record to focus on the `bvs` field, and once on the resulting bitvector. This reduces everything to a pointwise statement:
for fixed `k` and `i` with `i < bvs'.length + 1`,
the bit `(bvs' ++ [a])[i].getLsbD k` should match the bit at index `i` of `BitVec.cons (a.getLsbD k) ((dec bvs').bvs.get k)`.

At this point I need a clean description of indexing into `BitVec.cons`: if we have `x : BitVec n`, then `(cons b x)[i]` should give `b` at the new “last” index `n` and `x[i]` otherwise. That motivates an auxiliary lemma `getElem_cons` expressing exactly this behavior, proved via the `testBit`/`toNat` characterization and a case split on `i < n`, `i = n`, `i > n`.

Using `getElem_cons`, the RHS becomes an `if` on whether `i = bvs'.length`. So I split into cases:
- If `i = bvs'.length`, then `(bvs' ++ [a])[i]` is the appended `a`, so both sides reduce to `a.getLsbD k`.
- If `i ≠ bvs'.length`, the bound `i < bvs'.length + 1` forces `i < bvs'.length`. Then `(bvs' ++ [a])[i] = bvs'[i]` by `List.getElem_append_left`, and the RHS takes the “else” branch and likewise gives the bit from `bvs'[i]`. In both subcases, `simp` (plus the definition of `dec`) finishes.

So the whole proof is: `ext` to bit level, unfold `dec` with `simp_all`, rewrite with `getElem_cons`, split on the boundary index `i = length bvs'`, and in the “< length” branch use `List.getElem_append_left` and bounds to line up both sides.
</reasoning> 

<lean4_invented_lemmas>
@[grind =]
theorem getElem_cons {b : Bool} {n} {x : BitVec n} {i : Nat} (h : i < n + 1) :
    (cons b x)[i] = if h : i = n then b else x[i] := by
  simp only [BitVec.getElem_eq_testBit_toNat, BitVec.toNat_cons, Nat.testBit_or]
  rw [Nat.testBit_shiftLeft]
  rcases Nat.lt_trichotomy i n with i_lt_n | i_eq_n | n_lt_i
  · have p1 : ¬(n ≤ i) := by omega
    have p2 : i ≠ n := by omega
    simp [p1, p2]
  · simp only [i_eq_n, ge_iff_le, Nat.le_refl, decide_true, Nat.sub_self, Nat.testBit_zero,
    Bool.true_and, BitVec.testBit_toNat, BitVec.getLsbD_of_ge, Bool.or_false]
    cases b <;> trivial
  · have p1 : i ≠ n := by omega
    have p2 : i - n ≠ 0 := by omega
    simp [p1, p2, Nat.testBit_bool_toNat]
</lean4_invented_lemmas>

<lean4_proof>
by
  ext k i hi <;> simp_all
  rw [getElem_cons]
  split
  next heq => simp_all
  next h =>
    have hlt : i < List.length bvs' := by simp at hi; omega
    rw [List.getElem_append_left hlt]
    simp
</lean4_proof>

### Your task:
<all_available_defs>
structure MinimizationExpr where
  domain : Expr
  codomain : Expr
  objFun : Expr
  constraints : Expr

def toExpr (minExpr : MinimizationExpr) : Expr :=
  mkApp4 (mkConst ``Minimization.mk)
    minExpr.domain minExpr.codomain minExpr.objFun minExpr.constraints

def fromExpr (prob : Expr) : MetaM MinimizationExpr :=
  match prob with
  | .app (.app (.app (.app (.const ``Minimization.mk _) domain) codomain) objFun) constraints => do
    return MinimizationExpr.mk domain codomain objFun constraints
  | _ => throwError "Expr not of the form `Minimization.mk ...` {prob}."

structure SolutionExpr where
  domain : Expr
  codomain : Expr
  codomainPreorder : Expr
  p : Expr

def toMinimizationExpr (solExpr : SolutionExpr) : MetaM MinimizationExpr :=
  MinimizationExpr.fromExpr solExpr.p

def toExpr (solExpr : SolutionExpr) : Expr :=
  mkApp4 (mkConst ``Minimization.Solution)
    solExpr.domain solExpr.codomain solExpr.codomainPreorder solExpr.p

def fromExpr (goalType : Expr) : MetaM SolutionExpr := do
  match goalType with
  | .app (.app (.app (.app (.const ``Minimization.Solution _) domain) codomain) preorder) p => do
      return SolutionExpr.mk domain codomain preorder p
  | _ => throwError "Goal not of the form `Minimization.Solution ...`."

def fromGoal (goal : MVarId) : MetaM SolutionExpr := do
  let goalType ← whnf (← MVarId.getDecl goal).type
  fromExpr goalType

def replaceProjections (e : Expr) (p : FVarId) (rs : Array Expr) : MetaM Expr := do
  let pre (e : Expr) : MetaM TransformStep := do
    match ← decomposeProj e p rs.data with
    | some r => return TransformStep.done r
    | none => return TransformStep.continue
  transform e (pre := pre)
where
  decomposeProj (e : Expr) (p : FVarId) (rs : List Expr) (first : Option Bool := none) :
      MetaM (Option Expr) := do
    
    
    
    
    match first, e, rs with
    | _, .fvar fVarId, [r] =>
        if fVarId == p then return r else return none
    | some true, .fvar fVarId, r :: _ :: _ =>
        if fVarId == p then return r else return none
    | none, .app (.app (.app (.const ``Prod.fst _) _) _) e, r :: rs => do
        decomposeProj e p (r :: rs) (first := true)
    | _, .app (.app (.app (.const ``Prod.snd _) _) _) e, _ :: rs => do
        decomposeProj e p rs (first := first == some true)
    | _, _, [] => return none
    | _, _, _ :: _ => return none

def decomposeDomain (domain : Expr) : m (List (Name × Expr)) := do
  match domain with
  | .app (.app (.const `Prod _) ty1) ty2 => do
      return (← decomposeLabel ty1) :: (← decomposeDomain ty2)
  | _ => do return [← decomposeLabel domain]

def decomposeDomainInstantiating (minExpr : MinimizationExpr) : MetaM (List (Name × Expr)) := do
  decomposeDomain (← instantiateMVars minExpr.domain)

def composeDomain (vars : List (Name × Expr)) : Expr :=
  match vars with
  | [] => mkConst `Unit
  | [(name, ty)] => mkLabel name ty
  | (name, ty) :: rest =>
      mkApp2 (mkConst `Prod [levelZero, levelZero]) (mkLabel name ty) (composeDomain rest)

partial def mkProjections (domain : Expr) (p : Expr) : m (List (Name × Expr × Expr)) := do
  match domain with
  | .app (.app (.const ``Prod lvls) (ty1 : Expr)) (ty2 : Expr) => do
      let v ← getLabelName ty1
      let d := mkApp3 (mkConst `Prod.fst lvls) ty1 ty2 p
      let r ← mkProjections ty2 (mkApp3 (mkConst `Prod.snd lvls) ty1 ty2 p)
      return (v, ty1, d) :: r
  | _ => do return [(← getLabelName domain, domain, p)]

def withDomainLocalDecls {α} [Inhabited α] (domain : Expr) (p : Expr)
    (x : Array Expr → Array Expr → m α) : m α  := do
  let pr := (← mkProjections domain p).toArray
  withLetDecls
    (pr.map fun (n, ty, val) => (n, fun _ => return ty, fun _ => return val)) fun xs => do
      let mut xs := xs
      
      for i in [:pr.size] do
        if pr[i]!.1 == `_ then
          xs := xs.set! i pr[i]!.2.2
      x xs (pr.map (fun a => a.2.2))

def decomposeAnd (e : Expr) : MetaM (List (Expr)) := do
  match e with
  | .app (.app (.const ``And _) p) q => do
      return p :: (← decomposeAnd q)
  | _ => return [e]

def decomposeConstraints (e : Expr) : MetaM (List (Name × Expr)) := do
  (← decomposeAnd e).mapM fun e => do
    return (← getLabelName e, e)

def composeAnd : List Expr → Expr
  | [] => mkConst ``True
  | [c] => c
  | c :: cs => mkApp2 (mkConst ``And) c (composeAnd cs)

def composeAndWithProj : List Expr → (Expr × (Expr → List Expr))
  | [] => (mkConst ``True, fun _ => [])
  | [c] => (c, fun e => [e])
  | c :: cs =>
      let (cs, prs) := composeAndWithProj cs
      let res := mkApp2 (mkConst ``And) c cs
      let prs :=
        fun e => mkApp3 (mkConst ``And.left) c cs e :: prs (mkApp3 (mkConst ``And.right) c cs e)
      (res, prs)

def getVariableNameSet (domain : Expr) : m (HashSet Name) := do
  let mut res : HashSet Name := {}
  for (name, _) in ← decomposeDomain domain do
    res := res.insert name
  return res

def getConstraintNameSet (e : Expr) : MetaM (HashSet Name) := do
  let mut res : HashSet Name := {}
  for (name, _) in ← decomposeConstraints e do
    res := res.insert name
  return res

partial def generateNewName (base : String) (set : HashSet Name) : MetaM Name := do
  tryNumber 1 set
where
  tryNumber (i : Nat) vars : MetaM Name :=
    let name := Name.mkSimple s!"{base}{i}"
    if vars.contains name then tryNumber (i + 1) vars else return name

def isOptimizationParam (n : Name) : MetaM Bool := do
  return optimizationParamAttr.hasTag (← getEnv) n

def getAllOptimizationParams : MetaM (List Name) := do
  return (optimizationParamAttr.ext.getState (← getEnv)).toList

def getOptimizationParamExpr (n : Name) (e : Expr) : MetaM Expr := do
  match (← getEnv).constants.find! n with
  | ConstantInfo.defnInfo defn => return defn.value
  | _ => return e

structure Minimization (D R : Type) where
  objFun : D → R
  constraints : D → Prop

@[reducible]
def feasible (x : D) : Prop := p.constraints x

@[reducible]
def optimal (x : D) : Prop := p.feasible x ∧ ∀ y, p.feasible y → p.objFun x ≤ p.objFun y

structure Solution where
  point : D
  isOptimal : p.optimal point

instance [i : Fact (0 < n)] : OfNat (Fin n) 0 := ⟨⟨0, i.out⟩⟩

instance {n m x : ℕ} : OfNat (Fin n.succ ⊕ Fin m.succ) (x) where
  ofNat := if x <= n then Sum.inl (Fin.ofNat x) else Sum.inr (Fin.ofNat (x - n.succ))

def withLambdaBody (e : Expr) (x : (fvar : Expr) → (body : Expr) → MetaM α) : MetaM α := do
  match e with
  | .lam n ty body _ =>
    withLocalDeclD n ty fun fvar => do
      let body := body.instantiate1 fvar
      x fvar body
  | _ => throwError "`withLambdaBody` error: expected lambda-expression, got {e}."

def withLocalDeclsDNondep [Inhabited α] (declInfos : Array (Lean.Name × Expr))
    (k : (xs : Array Expr) → m α) : m α :=
  withLocalDeclsD (declInfos.map fun (n, t) => (n, fun _ => pure t)) k

partial def withLetDecls [Inhabited α]
    (declInfos : Array (Name × (Array Expr → m Expr) × (Array Expr → m Expr)))
    (k : (xs : Array Expr) → m α) : m α :=
  loop #[]
where
  loop [Inhabited α] (acc : Array Expr) : m α := do
    if acc.size < declInfos.size then
      let (name, typeCtor, valCtor) := declInfos[acc.size]!
      withLetDecl name (← typeCtor acc) (← valCtor acc) fun x => loop (acc.push x)
    else
      k acc

partial def runTactic (goal : MVarId) (tac : MVarId → TacticM (List MVarId)) :
    MetaM (List MVarId) := do
  TermElabM.run' (run goal (do replaceMainGoal <| ← tac <| ← getMainGoal))

def findIdx' (p : α → Prop) [DecidablePred p] : List α → Option ℕ
  | []     => none
  | (a::l) => if p a then some 0 else (findIdx' p l).map Nat.succ

def toVec (x : List α) : Fin x.length → α :=
  fun i => List.get x ⟨i, i.2⟩

def finRange' (d n : Nat) : List (Fin n) :=
  if hn : 0 < n then (List.range d).map (Fin.ofNat' · hn) else []

def sum' [Zero α] [Add α] (L : List α) : α :=
  L.foldl (· + ·) 0

structure Equivalence where
  phi : D → E
  psi : E → D
  phi_optimality : ∀ x, p.optimal x → q.optimal (phi x)
  psi_optimality : ∀ x, q.optimal x → p.optimal (psi x)

@[equiv]
def refl : p ≡ p :=
  { phi := id,
    psi := id,
    phi_optimality := fun _ hx => hx,
    psi_optimality := fun _ hx => hx }

@[equiv]
def symm (E : p ≡ q) : q ≡ p :=
  { phi := E.psi,
    psi := E.phi,
    phi_optimality := E.psi_optimality,
    psi_optimality := E.phi_optimality }

@[equiv]
def trans (E₁ : p ≡ q) (E₂ : q ≡ r) : p ≡ r :=
  { phi := E₂.phi ∘ E₁.phi,
    psi := E₁.psi ∘ E₂.psi,
    phi_optimality := fun x hx => E₂.phi_optimality (E₁.phi x) (E₁.phi_optimality x hx),
    psi_optimality := fun y hy => E₁.psi_optimality (E₂.psi y) (E₂.psi_optimality y hy) }

instance : Trans (@Equivalence D E R _) (@Equivalence E F R _) (@Equivalence D F R _) :=
  { trans := Equivalence.trans }

def toFwd (E : p ≡ q) : Solution p → Solution q :=
  fun sol =>
    { point := E.phi sol.point,
      isOptimal := E.phi_optimality sol.point sol.isOptimal }

def toBwd (E : p ≡ q) : Solution q → Solution p :=
  fun sol =>
    { point := E.psi sol.point,
      isOptimal := E.psi_optimality sol.point sol.isOptimal }

structure StrongEquivalence where
  phi : D → E
  psi : E → D
  phi_feasibility : ∀ x, p.feasible x → q.feasible (phi x)
  psi_feasibility : ∀ y, q.feasible y → p.feasible (psi y)
  phi_optimality : ∀ x, p.feasible x → q.objFun (phi x) ≤ p.objFun x
  psi_optimality : ∀ y, q.feasible y → p.objFun (psi y) ≤ q.objFun y

@[strong_equiv]
def refl : p ≡' p :=
  { phi := id,
    psi := id,
    phi_feasibility := fun _ hx => hx,
    psi_feasibility := fun _ hy => hy,
    phi_optimality := fun _ _ => le_refl _,
    psi_optimality := fun _ _ => le_refl _ }

@[strong_equiv]
def symm (E : p ≡' q) : q ≡' p :=
  { phi := E.psi,
    psi := E.phi,
    phi_feasibility := E.psi_feasibility,
    psi_feasibility := E.phi_feasibility,
    phi_optimality := E.psi_optimality,
    psi_optimality := E.phi_optimality }

@[strong_equiv]
def trans (E₁ : p ≡' q) (E₂ : q ≡' r) : p ≡' r :=
  { phi := E₂.phi ∘ E₁.phi,
    psi := E₁.psi ∘ E₂.psi,
    phi_feasibility := fun x hx => E₂.phi_feasibility (E₁.phi x) (E₁.phi_feasibility x hx),
    psi_feasibility := fun y hy => E₁.psi_feasibility (E₂.psi y) (E₂.psi_feasibility y hy),
    phi_optimality := fun x hx =>
      
      have h₁ := E₂.phi_optimality (E₁.phi x) (E₁.phi_feasibility x hx)
      
      have h₂ := E₁.phi_optimality x hx
      le_trans h₁ h₂,
    psi_optimality := fun y hy =>
      
      have h₁ := E₁.psi_optimality (E₂.psi y) (E₂.psi_feasibility y hy)
      
      have h₂ := E₂.psi_optimality y hy
      le_trans h₁ h₂ }

instance :
    Trans (@StrongEquivalence D E R _) (@StrongEquivalence E F R _) (@StrongEquivalence D F R _) :=
  { trans := StrongEquivalence.trans }

@[equiv]
def ofEq (h : p = q) : p ≡ q :=
  { phi := id,
    psi := id,
    phi_optimality := fun _ hx => h ▸ hx,
    psi_optimality := fun _ hy => h ▸ hy }

@[equiv]
def ofStrongEquivalence (E : p ≡' q) : p ≡ q :=
  { phi := E.phi,
    psi := E.psi,
    phi_optimality := fun x ⟨h_feas_x, h_opt_x⟩ =>
      ⟨E.phi_feasibility x h_feas_x,
       fun y h_feas_y =>
        
        have h₁ := E.phi_optimality x h_feas_x
        
        have h₂ := h_opt_x (E.psi y) (E.psi_feasibility y h_feas_y)
        
        have h₃ := E.psi_optimality y h_feas_y
        le_trans (le_trans h₁ h₂) h₃⟩,
    psi_optimality := fun x ⟨h_feas_x, h_opt_x⟩ =>
      ⟨E.psi_feasibility x h_feas_x,
       fun y h_feas_y =>
        have h₁ := E.psi_optimality x h_feas_x
        
        have h₂ := h_opt_x (E.phi y) (E.phi_feasibility y h_feas_y)
        
        have h₃ := E.phi_optimality y h_feas_y
        
        le_trans (le_trans h₁ h₂) h₃⟩ }

instance : Trans (@StrongEquivalence D E R _) (@Equivalence E F R _) (@Equivalence D F R _) :=
  { trans := fun S E => Equivalence.trans (ofStrongEquivalence S) E }

instance : Trans (@Equivalence D E R _) (@StrongEquivalence E F R _) (@Equivalence D F R _) :=
  { trans := fun E S => Equivalence.trans E (ofStrongEquivalence S) }

def toFwd (E : p ≡' q) : Solution p → Solution q :=
  (ofStrongEquivalence E).toFwd

def toBwd (E : p ≡' q) : Solution q → Solution p :=
  (ofStrongEquivalence E).toBwd

@[equiv]
def map_objFun {g : R → R} (h : ∀ {r s}, cs r → cs s → (g (f r) ≤ g (f s) ↔ f r ≤ f s)) :
    ⟨f, cs⟩ ≡ ⟨fun x => g (f x), cs⟩ :=
  { phi := id,
    psi := id,
    phi_optimality := fun _ ⟨h_feas_x, h_opt_x⟩ =>
      ⟨h_feas_x, fun y h_feas_y => (h h_feas_x h_feas_y).mpr (h_opt_x y h_feas_y)⟩,
    psi_optimality := fun _ ⟨h_feas_x, h_opt_x⟩ =>
      ⟨h_feas_x, fun y h_feas_y => (h h_feas_x h_feas_y).mp (h_opt_x y h_feas_y)⟩ }

@[equiv]
noncomputable def map_objFun_log {f : D → ℝ} (h : ∀ x, cs x → f x > 0) :
    ⟨f, cs⟩ ≡ ⟨fun x => (Real.log (f x)), cs⟩ :=

@[equiv]
noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) :
    ⟨f, cs⟩ ≡ ⟨fun x => (f x) ^ (2 : ℝ), cs⟩ :=

@[equiv]
def map_domain {f : D → R} {cs : D → Prop} {fwd : D → E} {bwd : E → D}
    (h : ∀ x, cs x → bwd (fwd x) = x) :
    ⟨f, cs⟩ ≡ ⟨fun x => f (bwd x), fun x => cs (bwd x)⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fwd,
    psi := bwd,
    phi_feasibility := fun {x} hx => by admit /- proof elided -/

@[equiv]
def rewrite_objFun (hrw : ∀ x, cs x → f x = g x) : ⟨f, cs⟩ ≡ ⟨g, cs⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := id,
    psi := id,
    phi_feasibility := fun _ hx => hx
    psi_feasibility := fun _ hx => hx
    phi_optimality := fun {x} hx => le_of_eq (hrw x hx).symm
    psi_optimality := fun {x} hx => le_of_eq (hrw x hx) }

@[equiv]
def rewrite_objFun_1 (hrw : ∀ x, c1 x → f x = g x) : ⟨f, c1⟩ ≡ ⟨g, c1⟩ :=
  rewrite_objFun hrw

@[equiv]
def rewrite_objFun_2 (hrw : ∀ x, c1 x → c2 x → f x = g x) : ⟨f, [[c1, c2]]⟩ ≡ ⟨g, [[c1, c2]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_3 (hrw : ∀ x, c1 x → c2 x → c3 x → f x = g x) :
    ⟨f, [[c1, c2, c3]]⟩ ≡ ⟨g, [[c1, c2, c3]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_4 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4]]⟩ ≡ ⟨g, [[c1, c2, c3, c4]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_7 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6, c7]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_8 (hrw :
    ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_9
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≡
    ⟨g, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

@[equiv]
def rewrite_objFun_10
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → c10 x → f x = g x) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≡
    ⟨g, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ :=
  rewrite_objFun (fun x _ => by admit /- proof elided -/
  )

macro "equivalence_of_rw_constr" hrw:ident : term =>
  `(Equivalence.ofStrongEquivalence <|
    { phi := id,
      psi := id,
      phi_feasibility := by admit /- proof elided -/
      )

@[equiv]
def rewrite_constraints (hrw : ∀ x, cs x ↔ cs' x) : ⟨f, [[cs]]⟩ ≡ ⟨f, [[cs']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_1 (hrw : ∀ x, cs x → (c1 x ↔ c1' x)) : ⟨f, [[c1, cs]]⟩ ≡ ⟨f, [[c1', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_1_last (hrw : ∀ x, c1 x ↔ c1' x) : ⟨f, [[c1]]⟩ ≡ ⟨f, [[c1']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_2 (hrw : ∀ x, c1 x → cs x → (c2 x ↔ c2' x)) :
    ⟨f, [[c1, c2, cs]]⟩ ≡ ⟨f, [[c1, c2', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_2_last (hrw : ∀ x, c1 x → (c2 x ↔ c2' x)) :
    ⟨f, [[c1, c2]]⟩ ≡ ⟨f, [[c1, c2']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_3 (hrw : ∀ x, c1 x → c2 x → cs x → (c3 x ↔ c3' x)) :
    ⟨f, [[c1, c2, c3, cs]]⟩ ≡ ⟨f, [[c1, c2, c3', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_3_last (hrw : ∀ x, c1 x → c2 x → (c3 x ↔ c3' x)) :
    ⟨f, [[c1, c2, c3]]⟩ ≡ ⟨f, [[c1, c2, c3']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_4 (hrw : ∀ x, c1 x → c2 x → c3 x → cs x → (c4 x ↔ c4' x)) :
    ⟨f, [[c1, c2, c3, c4, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_4_last (hrw : ∀ x, c1 x → c2 x → c3 x → (c4 x ↔ c4' x)) :
    ⟨f, [[c1, c2, c3, c4]]⟩ ≡ ⟨f, [[c1, c2, c3, c4']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → cs x → (c5 x ↔ c5' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_5_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → (c5 x ↔ c5' x)) :
    ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → cs x → (c6 x ↔ c6' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_6_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → (c6 x ↔ c6' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_7
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → cs x → (c7 x ↔ c7' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_7_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → (c7 x ↔ c7' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_8
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → cs x → (c8 x ↔ c8' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_8_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → (c8 x ↔ c8' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_9
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → cs x → (c9 x ↔ c9' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, cs]]⟩ ≡
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_9_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → (c9 x ↔ c9' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≡
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_10
    (hrw :
      ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → cs x → (c10 x ↔ c10' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, cs]]⟩ ≡
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10', cs]]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def rewrite_constraint_10_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → (c10 x ↔ c10' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≡
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10']]⟩ :=
  equivalence_of_rw_constr hrw

@[equiv]
def add_constraint {cs' : D → Prop} (h : ∀ x, cs x → cs' x) : ⟨f, cs⟩ ≡ ⟨f, [[cs', cs]]⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := id,
    psi := id,
    phi_feasibility := fun _ hcs => ⟨h _ hcs, hcs⟩,
    psi_feasibility := fun _ ⟨_, hcs⟩ => hcs,
    phi_optimality := fun _ _ => le_refl _,
    psi_optimality := fun _ _ => le_refl _ }

@[equiv]
def map_le_constraint_standard_form [Zero R] {cs' : D → Prop} {fi : D → R} {g : R → R}
    (hcs : ∀ x, cs x ↔ fi x ≤ 0 ∧ cs' x) (hg : ∀ x, g x ≤ 0 ↔ x ≤ 0) :
    ⟨f, cs⟩ ≡ ⟨f, fun x => g (fi x) ≤ 0 ∧ cs' x⟩ :=

@[equiv]
def map_eq_constraint_standard_form [Zero R] {cs' : D → Prop} {hi : D → R} {g : R → R}
    (hcs : ∀ x, cs x ↔ hi x = 0 ∧ cs' x) (hg : ∀ x, g x = 0 ↔ x = 0) :
    ⟨f, cs⟩ ≡ ⟨f, fun x => g (hi x) = 0 ∧ cs' x⟩ :=

@[equiv]
def add_slack_variable_standard_form {cs' : D → Prop} {fi : D → ℝ}
    (hcs : ∀ x, cs x ↔ fi x ≤ 0 ∧ cs' x) :
    ⟨f, cs⟩ ≡ ⟨fun (_, x) => f x, fun (si, x) => 0 ≤ (si : ℝ) ∧ fi x + si = 0 ∧ cs' x⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (-fi x, x),
    psi := fun (_, x) => x,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
noncomputable def eliminate_eq_constraint_standard_form [Inhabited E] {cs' : D → Prop} {hi : D → ℝ}
    {g : E → D} (hcs : ∀ x, cs x ↔ hi x = 0 ∧ cs' x) (hg : ∀ x, hi x = 0 ↔ ∃ z, x = g z) :
    ⟨f, cs⟩ ≡ ⟨fun x => f (g x), fun x => cs' (g x)⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => if h : hi x = 0 then Classical.choose ((hg x).mp h) else default,
    psi := g,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/
| Classical.choose_spec ((hg x).mp h_hix_eq_0)],
    psi_optimality := fun x _ => by admit /- proof elided -/

@[equiv]
def decompose_constraint (g : D → E) (cs' : D → E → Prop) (hc : ∀ x, cs x ↔ cs' x (g x)) :
    ⟨f, cs⟩ ≡ ⟨fun (x, _) => f x, fun (x, y) => y = g x ∧ cs' x y⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (x, g x),
    psi := fun (x, _) => x,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def epigraph_form : ⟨f, cs⟩ ≡ ⟨fun (t, _) => t, fun (t, x) => f x ≤ t ∧ cs x⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (f x, x),
    psi := fun (_, x) => x,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def eq_to_le_left {S} [Preorder S] (e : D ≃ S × E) (g : E → S) (cs' : D → Prop)
    (hcs : ∀ {x}, cs x ↔ ((e x).1 = g (e x).2 ∧ cs' x))
    (hf : ∀ y r s, f (e.symm (r, y)) = f (e.symm (s, y)))
    (h_mono: ∀ y r s, r ≤ s → cs' (e.symm (r, y)) → cs' (e.symm (s, y))) :
    ⟨f, cs⟩ ≡ ⟨f, fun x => (e x).1 ≤ g (e x).2 ∧ cs' x⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => x,
    psi := fun x => e.symm (g (e x).2, (e x).2),
    phi_feasibility := fun {x} h_feas_x => ⟨le_of_eq (hcs.1 h_feas_x).1, (hcs.1 h_feas_x).2⟩,
    psi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def eq_to_le_right {S} [Preorder S] (e : Equiv D (S × E)) (g : E → S) (cs' : D → Prop)
    (hcs : ∀ {x}, cs x ↔ (g (e x).2 = (e x).1 ∧ cs' x))
    (hf : ∀ x r s, f (e.symm ⟨r, x⟩) = f (e.symm ⟨s, x⟩))
    (h_mono : ∀ x r s, r ≤ s → cs' (e.symm (s, x)) → cs' (e.symm ⟨r, x⟩)) :
    ⟨f, cs⟩ ≡  ⟨f, fun x => g (e x).2 ≤ (e x).1 ∧ cs' x⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => x,
    psi := fun x => e.symm ⟨g (e x).2, (e x).2⟩,
    phi_feasibility := fun {x} h_feas_x => ⟨le_of_eq (hcs.1 h_feas_x).1, (hcs.1 h_feas_x).2⟩,
    psi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def domain_equiv (e : E ≃ D) : ⟨f, cs⟩ ≡ ⟨f ∘ e, cs ∘ e⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := e.symm,
    psi := e,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def linearization_mono {S} [Preorder S] (g : D → S) (c : S → D → Prop) (h : S → D → R)
    (hf : ∀ x, f x = h (g x) x)
    (hcs : ∀ x, cs x = c (g x) x)
    (h_mono_f : ∀ x r s, r ≤ s → h s x ≤ h r x)
    (h_mono_cs : ∀ x r s, r ≤ s → c r x → c s x) :
    ⟨f, cs⟩ ≡ ⟨fun (s, y) => h s y, fun (s, y) => s ≤ g y ∧ c s y⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (g x, x),
    psi := fun (_, y) => y,
    phi_feasibility := fun {x} h_feas_x => ⟨le_refl _, hcs x ▸ h_feas_x⟩,
    psi_feasibility := fun (s, y) ⟨h_s_le_gy, h_csy⟩ => by admit /- proof elided -/

@[equiv]
def linearization_antimono {S} [Preorder S] (g : D → S) (c : S → D → Prop) (h : S → D → R)
    (hf : ∀ x, f x = h (g x) x)
    (hcs : ∀ x, cs x = c (g x) x)
    (h_mono_f : ∀ x r s, r ≤ s → h r x ≤ h s x)
    (h_mono_cs : ∀ x r s, r ≤ s → c s x → c r x) :
    ⟨f, cs⟩ ≡ ⟨fun (s, y) => h s y, fun (s, y) => g y ≤ s ∧ c s y⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (g x, x),
    psi := fun (_, y) => y,
    phi_feasibility := fun {x} h_feas_x => ⟨le_refl _, hcs x ▸ h_feas_x⟩,
    psi_feasibility := fun (s, y) ⟨h_gy_le_s, h_csy⟩ => by admit /- proof elided -/

@[equiv]
def graph_expansion_greatest {S} [Preorder S] (g : D → S) (c d : S → D → Prop) (h : S → D → R)
    (hg : ∀ x v, c v x → IsGreatest {y | d y x} (g x))
    (hf : ∀ x, f x = h (g x) x)
    (hcs : ∀ x, cs x = c (g x) x)
    (h_mono_f : ∀ x r s, r ≤ s → h s x ≤ h r x)
    (h_mono_cs : ∀ x r s, r ≤ s → c r x → c s x) :
    ⟨f, cs⟩ ≡ ⟨fun (s, y) => h s y, fun (s, y) => d s y ∧ c s y⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (g x, x),
    psi := fun (_, y) => y,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def graph_expansion_least {S} [Preorder S] (g : D → S) (c d : S → D → Prop) (h : S → D → R)
    (hg : ∀ x v, c v x → IsLeast {y | d y x} (g x))
    (hf : ∀ x, f x = h (g x) x)
    (hcs : ∀ x, cs x = c (g x) x)
    (h_mono_f : ∀ x r s, r ≤ s → h r x ≤ h s x)
    (h_mono_cs : ∀ x r s, r ≤ s → c s x → c r x) :
    ⟨f, cs⟩ ≡ ⟨fun (s, y) => h s y, fun (s, y) => d s y ∧ c s y⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => (g x, x),
    psi := fun (_, y) => y,
    phi_feasibility := fun {x} h_feas_x => by admit /- proof elided -/

@[equiv]
def graph_expansion_least_forall {S I : Type} [Preorder S] [Inhabited I] (g : D → I → S)
    (c d : S → D → Prop) (hg : ∀ x v i, c v x → IsLeast {y | d y x} (g x i))
    (hcs : ∀ x, cs x = ∀ i, c (g x i) x) (h_mono_cs : ∀ x r s, r ≤ s → c s x → c r x) :
    ⟨f, cs⟩ ≡
    ⟨fun sy : (I → S) × D => f sy.2,
     fun sy : (I → S) × D => (∀ i, d (sy.1 i) sy.2) ∧ (∀ i, c (sy.1 i) sy.2)⟩ :=
  graph_expansion_least (f := f) (cs := cs) (g := g) (c := fun y x => ∀ i, c (y i) x)
    (d := fun y x => ∀ i, d (y i) x) (h := fun _ x => f x)
    (hg := fun x v hc =>
      ⟨fun i => (hg x (v i) i (hc i)).1, fun _ c i => (hg x (v i) i (hc i)).2 (c i)⟩)
    (hf := fun _ => rfl) (hcs := hcs) (h_mono_f := fun _ _ _ _ => le_refl _)
    (h_mono_cs := fun x r s hrs hc i => h_mono_cs x (r i) (s i) (hrs i) (hc i))

def mosekBinPath := ""

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ᵀ

def getMonoArgsCount (curv : Curvature) (argKinds : Array ArgKind) : Nat :=
  argKinds.foldl (fun acc argKind =>
    match curvatureInArg curv argKind with
      | Curvature.Concave => 1 + acc
      | Curvature.Convex => 1 + acc
      | _ => acc) 0

def processGraphImplementation (objCurv : Curvature) (atomData : GraphAtomData) (xs : Array Expr) :
    MetaM (DCP.ProcessedAtomTree × Array LocalDecl) := do
  
  
  
  
  let (objFun, constraints, originalVarsDecls) ← do
    let impVars := atomData.impVars.map fun (n, ty) => (n, mkAppNBeta ty xs)
    withLocalDeclsDNondep impVars fun vs => do
      let vsDecls ← vs.mapM fun v => v.fvarId!.getDecl
      let bconds := atomData.bconds.map fun (n,c) => (n, mkAppNBeta c xs)
      withLocalDeclsDNondep bconds fun bs => do
        let originalVarsDecls := vsDecls
        let objFun :=  mkAppNBeta (mkAppNBeta atomData.impObjFun xs) vs
        let constraints := atomData.impConstrs.map
          fun c => (`_, mkAppNBeta (mkAppNBeta (mkAppNBeta c xs) bs) vs)
        return (objFun, constraints, originalVarsDecls)

  
  
  
  let xsAffPre := xs.map fun x => x.fvarId!
  let mut xsAff := #[]
  for i in [:xs.size] do
    if atomData.argKinds[i]! != ArgKind.Constant then
      xsAff := xsAff.push xsAffPre[i]!

  
  
  
  
  let bconds := atomData.bconds.map fun (n, c) => (n, mkAppNBeta c xs)
  let pat ← withLocalDeclsDNondep bconds fun bs => do
    let bcondsDecls ← bs.mapM (·.fvarId!.getDecl)
    DCP.mkProcessedAtomTree objCurv objFun constraints.toList originalVarsDecls
      (extraVars := xsAff) (extraDecls := bcondsDecls)

  
  
  return (pat, originalVarsDecls)

def adjustCanonAtomData (objCurv : Curvature) (atomData : GraphAtomData)
    (pat : DCP.ProcessedAtomTree) (originalVarsDecls : Array LocalDecl) (xs : Array Expr) :
    MetaM GraphAtomData := do
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls pat.newVarDecls do
      withExistingLocalDecls pat.newConstrVarsArray.toList do
        
        
        let vs := originalVarsDecls.map (mkFVar ·.fvarId)

        
        
        let zs := pat.newVarDecls.toArray.map (mkFVar ·.fvarId)

        
        let vconds := atomData.vconds.map fun (n, c) => (n, mkAppNBeta c xs)
        let bconds := atomData.bconds.map fun (n, c) => (n, mkAppNBeta c xs)

         

        let adjustedImpVars := atomData.impVars.append (← pat.newVarDecls.toArray.mapM
          fun decl => do return (decl.userName, ← mkLambdaFVars xs decl.type))

        let _ ← adjustedImpVars.mapM (fun (_, ty) => check ty)
        trace[CvxLean.debug] "adjustedImpVars: {adjustedImpVars}"

         

        let adjustedImpObjFun ← mkLambdaFVars (xs ++ vs ++ zs) pat.canonExprs.objFun.val

        trace[CvxLean.debug] "adjustedImpObjFun: {adjustedImpObjFun}"

         

        
        
        let canonConstrs := pat.canonExprs.constrs.map Tree.val |>.filterIdx
          (fun i => !pat.isVCond[i]!)

        
        let newConstrs := pat.newConstrs

        
        
        let adjustedImpConstrs ← (canonConstrs ++ newConstrs).mapM (fun c => do
          withLocalDeclsDNondep bconds fun as => mkLambdaFVars (xs ++ as ++ vs ++ zs) c)

        let _ ← adjustedImpConstrs.mapM check
        trace[CvxLean.debug] "adjustedImpConstrs: {adjustedImpConstrs}"

         

        
        let vsSol := atomData.solution.map fun s => mkAppNBeta s xs

        
        
        let zsSolInTermsOfXs ← pat.forwardImagesNewVars.mapM (fun e => mkLambdaFVars xs
          (e.replaceFVars vs vsSol))

        
        let adjustedSolution := atomData.solution ++ zsSolInTermsOfXs

        let _ ← adjustedSolution.mapM check
        trace[CvxLean.debug] "adjustedSolution: {adjustedSolution}"

         

        let solEqAtomProofs := pat.solEqAtom.constrs.map Tree.val

        
        
        let mut oldFeasibilityAdjusted := #[]
        for i in [:atomData.feasibility.size] do
          let feas := atomData.feasibility[i]!
          let feasXs := mkAppNBeta feas xs
          let adjustedFeas ←
            withLocalDeclsDNondep vconds fun cs => do
              withLocalDeclsDNondep bconds fun bs => do
                let feasXsVconds := mkAppNBeta feasXs cs
                let feasXsConds := mkAppNBeta feasXsVconds bs
                let proofAdjusted := solEqAtomProofs[i]!.replaceFVars vs vsSol
                check proofAdjusted
                let adjustedFeas ← mkAppM ``Eq.mpr #[proofAdjusted, feasXsConds]

                mkLambdaFVars (xs ++ cs ++ bs) adjustedFeas
          oldFeasibilityAdjusted := oldFeasibilityAdjusted.push adjustedFeas

        
        
        let newConstrProofs := pat.feasibility.fold #[] fun acc fs => fs.fold acc Array.append
        let newFeasibility ← newConstrProofs.mapM (fun e =>
          withLocalDeclsDNondep vconds fun cs => do
            withLocalDeclsDNondep bconds fun bs => do
              mkLambdaFVars (xs ++ cs ++ bs) (e.replaceFVars vs vsSol))

        let adjustedFeasibility := oldFeasibilityAdjusted ++ newFeasibility

        let _ ← adjustedFeasibility.mapM check
        trace[CvxLean.debug] "adjustedFeasibility: {adjustedFeasibility}"

         

        let constrsOptimalityProofs := pat.optimality.constrs.map Tree.val
        let objFunOptimalityProof := pat.optimality.objFun.val
        let adjustedOptimality ←
          withLocalDeclsDNondep bconds fun bs => do
            
            
            let optimalityXsBConds := mkAppN atomData.optimality (xs ++ bs ++ vs)
            withLocalDeclsDNondep (canonConstrs.map (fun rc => (`_, rc))) fun cs => do
              
              let mut optimalityAfterCanon := optimalityXsBConds
              for i in [:canonConstrs.size] do
                let c := mkApp constrsOptimalityProofs[i]! cs[i]!
                optimalityAfterCanon := mkApp optimalityAfterCanon c
              
              
              
              
              let monoArgsCount := getMonoArgsCount objCurv atomData.argKinds
              let optCondAdjusted ←
                lambdaTelescope (← whnf optimalityAfterCanon) <| fun optArgs e => do
                
                let monoArgs := optArgs[:2 * monoArgsCount]
                let optCondition ← mkLambdaFVars optArgs[2 * monoArgsCount:] e
                let atomRange ← inferType (mkAppNBeta atomData.expr xs)
                
                let newCond ←
                  if atomData.curvature == Curvature.Convex then
                    mkAppOptM ``le_trans #[
                      atomRange, none, none, none, none,
                      optCondition, objFunOptimalityProof]
                  else
                    mkAppOptM ``le_trans #[
                      atomRange, none, none, none, none,
                      objFunOptimalityProof, optCondition]
                mkLambdaFVars monoArgs newCond

              let ds := pat.newConstrVarsArray.map (mkFVar ·.fvarId)
              mkLambdaFVars (xs ++ bs ++ vs ++ zs ++ cs ++ ds) optCondAdjusted

        check adjustedOptimality
        trace[CvxLean.debug] "adjustedOptimality: {adjustedOptimality}"

         

        let mut adjustedVCondElim := #[]
        for vcondElim in atomData.vcondElim do
          let newVCondElim := mkAppN vcondElim (xs ++ vs)
          let newVCondElim ←
            withLocalDeclsDNondep (canonConstrs.map (fun rc => (`_, rc))) fun cs => do
              let mut newVCondElim := newVCondElim
              for i in [:canonConstrs.size] do
                let c := mkApp constrsOptimalityProofs[i]! cs[i]!
                newVCondElim := mkApp newVCondElim c
              let ds := pat.newConstrVarsArray.map (mkFVar ·.fvarId)
              mkLambdaFVars (xs ++ vs ++ zs ++ cs ++ ds) newVCondElim
          adjustedVCondElim := adjustedVCondElim.push newVCondElim

        let _ ← adjustedVCondElim.mapM check
        trace[CvxLean.debug] "adjustedVCondElim: {adjustedVCondElim}"

         

        return { atomData with
          impVars := adjustedImpVars
          impObjFun := adjustedImpObjFun
          impConstrs := adjustedImpConstrs
          solution := adjustedSolution
          feasibility := adjustedFeasibility
          optimality := adjustedOptimality
          vcondElim := adjustedVCondElim
        }

def canonAtomData (objCurv : Curvature) (atomData : GraphAtomData) : MetaM GraphAtomData := do
  
  lambdaTelescope atomData.expr fun xs _ => do
    let (pat, originalVarsDecls) ← processGraphImplementation objCurv atomData xs
    adjustCanonAtomData objCurv atomData pat originalVarsDecls xs

unsafe def solutionDataFromProblemData (minExpr : MinimizationExpr) (data : ProblemData)
    (sections : ScalarAffineSections) : MetaM SolutionData := do
  
  let cbf ← CBF.ofProblemData minExpr data sections

  
  
  let r ← IO.rand 0 (2 ^ 32 - 1)
  let outputPath := s!"solver/problem{r}.sol"
  let inputPath := s!"solver/problem{r}.cbf"
  IO.FS.writeFile inputPath ""
  IO.FS.writeFile outputPath ""
  let res : Except String SolutionData ←
    IO.FS.withFile outputPath IO.FS.Mode.read fun outHandle => do
      IO.FS.withFile inputPath IO.FS.Mode.write fun inHandle => do
        
        inHandle.putStr (ToString.toString cbf)

        
        let p := if let some p' := ← IO.getEnv "PATH" then
          if mosekBinPath != "" then p' ++ ":" ++ mosekBinPath else p'
        else
          mosekBinPath

        
        let out ← IO.Process.output {
          cmd := "mosek",
          args := #[inputPath],
          env := #[("PATH", p)] }

        if out.exitCode != 0 then
          return Except.error ("MOSEK exited with code " ++ ToString.toString out.exitCode)

        
        let res := out.stdout
        dbg_trace res

        
        let output ← outHandle.readToEnd

        
        IO.FS.removeFile inputPath
        IO.FS.removeFile outputPath

        match Sol.Parser.parse output with
        | Except.ok res =>
            return Except.ok <| Sol.Result.toSolutionData res
        | Except.error err =>
            return Except.error ("MOSEK output parsing failed. " ++ err)

  match res with
  | Except.ok res => return res
  | Except.error err => throwSolveError err

unsafe def exprFromSolutionData (minExpr : MinimizationExpr) (solData : SolutionData) :
    MetaM Expr := do
  let vars ← decomposeDomainInstantiating minExpr

  
  let solPointExprArrayRaw : Array Expr :=
    Array.mk <| solData.varsSols.map (fun v => floatToReal v.value)

  
  let mut solPointExprArray : Array Expr := #[]

  
  
  let mut i : ℕ := 0
  for (_, varTy) in vars do
    let dims ← inferDimension varTy
    
    for (n, m) in dims do
      if n = 1 ∧ m = 1 then
        
        solPointExprArray := solPointExprArray.push (solPointExprArrayRaw[i]!)
        i := i + 1
      else if n ≠ 0 ∧ m = 1 then
        
        let exprs := (solPointExprArrayRaw.drop i).take n

        
        let arrayExpr ← Expr.mkArray (mkConst ``Real) exprs
        let arrayList ← mkAppM ``Array.toList #[arrayExpr]
        let v ← withLocalDeclD `i' (← mkAppM ``Fin #[toExpr n]) fun i' => do
          let i'' := mkApp2 (mkConst ``Fin.val) (toExpr n) i'
          
          let r ← mkAppM ``List.get! #[arrayList, i'']
          mkLambdaFVars #[i'] r

        solPointExprArray := solPointExprArray.push v
        i := i + n
      else
        
        let mut exprs := #[]
        for j in [:m] do
          let arrayExpr ← Expr.mkArray (mkConst ``Real)
            ((solPointExprArrayRaw.drop (i + j * n)).take n)
          let listExpr ← mkAppM ``Array.toList #[arrayExpr]
          exprs := exprs.push listExpr

        let arrayListExpr ← Expr.mkArray (← mkAppM ``List #[mkConst ``Real]) exprs

        
        let listListExpr ← mkAppM ``Array.toList #[arrayListExpr]

        let M ← withLocalDeclD `i' (← mkAppM ``Fin #[toExpr n]) fun i' => do
          let i'' := mkApp2 (mkConst ``Fin.val) (toExpr n) i'
          let ri ← mkAppM ``List.get! #[listListExpr, i'']
          withLocalDeclD `j' (← mkAppM ``Fin #[toExpr m]) fun j' => do
            let j'' := mkApp2 (mkConst ``Fin.val) (toExpr m) j'
            let rij ← mkAppM ``List.get! #[ri, j'']
            mkLambdaFVars #[i', j'] rij

        solPointExprArray := solPointExprArray.push M
        i := i + n * m

  let solPointExpr : Expr ← Lean.Expr.mkProd solPointExprArray
  return solPointExpr

inductive Key where
  | const : Name → Nat → Key
  | fvar  : FVarId → Nat → Key
  | lit   : Literal → Key
  | star  : Key
  | other : Key
  | arrow : Key
  | proj  : Name → Nat → Key
  deriving Inhabited, BEq, Repr

protected def Key.hash : Key → UInt64
  | Key.const n a => mixHash 5237 $ mixHash (hash n) (hash a)
  | Key.fvar n a  => mixHash 3541 $ mixHash (hash n) (hash a)
  | Key.lit v     => mixHash 1879 $ hash v
  | Key.star      => 7883
  | Key.other     => 2411
  | Key.arrow     => 17
  | Key.proj s i  => mixHash 11 $ mixHash (hash s) (hash i)

instance : Hashable Key := ⟨Key.hash⟩

inductive Trie (α : Type) where
  | node (vs : Array α) (children : Array (Key × Trie α)) : Trie α

structure DiscrTree (α : Type) where
  root : PersistentHashMap Key (Trie α) := {}

def Key.ctorIdx : Key → Nat
  | Key.star     => 0
  | Key.other    => 1
  | Key.lit ..   => 2
  | Key.fvar ..  => 3
  | Key.const .. => 4
  | Key.arrow    => 5
  | Key.proj ..  => 6

def Key.lt : Key → Key → Bool
  | Key.lit v₁,      Key.lit v₂      => v₁ < v₂
  | Key.fvar n₁ a₁,  Key.fvar n₂ a₂  => Name.quickLt n₁.name n₂.name || (n₁ == n₂ && a₁ < a₂)
  | Key.const n₁ a₁, Key.const n₂ a₂ => Name.quickLt n₁ n₂ || (n₁ == n₂ && a₁ < a₂)
  | Key.proj s₁ i₁,  Key.proj s₂ i₂  => Name.quickLt s₁ s₂ || (s₁ == s₂ && i₁ < i₂)
  | k₁,              k₂              => k₁.ctorIdx < k₂.ctorIdx

instance : LT Key := ⟨fun a b => Key.lt a b⟩

instance (a b : Key) : Decidable (a < b) := inferInstanceAs (Decidable (Key.lt a b))

def Key.format : Key → Format
  | Key.star                   => "*"
  | Key.other                  => "◾"
  | Key.lit (Literal.natVal v) => Std.format v
  | Key.lit (Literal.strVal v) => repr v
  | Key.const k _              => Std.format k
  | Key.proj s i               => Std.format s ++ "." ++ Std.format i
  | Key.fvar k _               => Std.format k.name
  | Key.arrow                  => "→"

instance : ToFormat Key := ⟨Key.format⟩

def Key.arity : Key → Nat
  | Key.const _ a => a
  | Key.fvar _ a  => a
  | Key.arrow     => 2
  | Key.proj ..   => 1
  | _             => 0

instance {α} : Inhabited (Trie α) := ⟨Trie.node #[] #[]⟩

def empty : DiscrTree α := { root := {} }

partial def Trie.format [ToMessageData α] : Trie α → MessageData
  | Trie.node vs cs => MessageData.group $ MessageData.paren $
      "node" ++ (if vs.isEmpty then MessageData.nil else " " ++ toMessageData vs) ++
      MessageData.joinSep (cs.toList.map $ fun ⟨k, c⟩ =>
        MessageData.paren (toMessageData k ++ " => " ++ format c)) ","

instance [ToMessageData α] : ToMessageData (Trie α) := ⟨Trie.format⟩

partial def format [ToMessageData α] (d : DiscrTree α) : MessageData :=
  let (_, r) := d.root.foldl
    (fun (p : Bool × MessageData) k c =>
      (false, p.2 ++ MessageData.paren (toMessageData k ++ " => " ++ toMessageData c)))
    (true, Format.nil)
  MessageData.group r

instance [ToMessageData α] : ToMessageData (DiscrTree α) := ⟨fun dt => format dt⟩

private def tmpMVarId : MVarId := { name := `_discr_tree_tmp }

private def tmpStar := mkMVar tmpMVarId

instance : Inhabited (DiscrTree α) where
  default := {}

private def ignoreArg (a : Expr) (i : Nat) (infos : Array Meta.ParamInfo) : MetaM Bool := do
  if h : i < infos.size then
    let info := infos.get ⟨i, h⟩
    if info.isInstImplicit then
      return true
    else if info.isImplicit || info.isStrictImplicit then
      return not (← isType a)
    else
      isProof a
  else
    isProof a

private partial def pushArgsAux (infos : Array Meta.ParamInfo) :
    Nat → Expr → Array Expr → MetaM (Array Expr)
  | i, Expr.app f a, todo => do
    if (← ignoreArg a i infos) then
      pushArgsAux infos (i-1) f (todo.push tmpStar)
    else
      pushArgsAux infos (i-1) f (todo.push a)
  | _, _, todo => return todo

def mkNoindexAnnotation (e : Expr) : Expr :=
  mkAnnotation `noindex e

def hasNoindexAnnotation (e : Expr) : Bool :=
  annotation? `noindex e |>.isSome

private def pushArgs (_root : Bool) (todo : Array Expr) (e : Expr) : MetaM (Key × Array Expr) := do
  if hasNoindexAnnotation e then
    return (Key.star, todo)
  else
    let fn := e.getAppFn
    let push (k : Key) (nargs : Nat) : MetaM (Key × Array Expr) := do
      let info ← getFunInfoNArgs fn nargs
      let todo ← pushArgsAux info.paramInfo (nargs-1) e todo
      return (k, todo)
    match fn with
    | Expr.lit v         => return (Key.lit v, todo)
    | Expr.const c _     =>
      let nargs := e.getAppNumArgs
      push (Key.const c nargs) nargs
    | Expr.proj s i a .. =>
      return (Key.proj s i, todo.push a)
    | Expr.fvar fvarId   =>
      let nargs := e.getAppNumArgs
      push (Key.fvar fvarId nargs) nargs
    | Expr.mvar mvarId   =>
      if mvarId == tmpMVarId then
        
        return (Key.star, todo)
      else
        return (Key.star, todo)
    | Expr.forallE _ d b _ =>
      if b.hasLooseBVars then
        return (Key.other, todo)
      else
        return (Key.arrow, todo.push d |>.push b)
    | _ =>
      return (Key.other, todo)

partial def mkPathAux (root : Bool) (todo : Array Expr) (keys : Array Key) : MetaM (Array Key) := do
  if todo.isEmpty then
    return keys
  else
    let e    := todo.back
    let todo := todo.pop
    let (k, todo) ← pushArgs root todo e
    mkPathAux false todo (keys.push k)

private def initCapacity := 8

def mkPath (e : Expr) : MetaM (Array Key) := do
  let todo : Array Expr := Array.mkEmpty initCapacity
  let keys : Array Key  := Array.mkEmpty initCapacity
  mkPathAux (root := true) (todo.push e) keys

private partial def createNodes (keys : Array Key) (v : α) (i : Nat) : Trie α :=
  if h : i < keys.size then
    let k := keys.get ⟨i, h⟩
    let c := createNodes keys v (i+1)
    Trie.node #[] #[(k, c)]
  else
    Trie.node #[v] #[]

private def insertVal [BEq α] (vs : Array α) (v : α) : Array α :=
  if vs.contains v then vs else vs.push v

private partial def insertAux [BEq α] (keys : Array Key) (v : α) : Nat → Trie α → Trie α
  | i, Trie.node vs cs =>
    if h : i < keys.size then
      let k := keys.get ⟨i, h⟩
      let c := Id.run $ cs.binInsertM
          (fun a b => a.1 < b.1)
          (fun ⟨_, s⟩ => let c := insertAux keys v (i+1) s; (k, c)) 
          (fun _ => let c := createNodes keys v (i+1); (k, c))
          (k, default)
      Trie.node vs c
    else
      Trie.node (insertVal vs v) cs

def insertCore [BEq α] (d : DiscrTree α) (keys : Array Key) (v : α) : DiscrTree α :=
  if keys.isEmpty then panic! "invalid key sequence"
  else
    let k := keys[0]!
    match d.root.find? k with
    | none =>
      let c := createNodes keys v 1
      { root := d.root.insert k c }
    | some c =>
      let c := insertAux keys v 1 c
      { root := d.root.insert k c }

def insert [BEq α] (d : DiscrTree α) (e : Expr) (v : α) : MetaM (DiscrTree α) := do
  let keys ← mkPath e
  return d.insertCore keys v

private def getKeyArgs (e : Expr) (isMatch _root : Bool) : MetaM (Key × Array Expr) := do
  match e.getAppFn with
  | Expr.lit v         => return (Key.lit v, #[])
  | Expr.const c _     =>
    let nargs := e.getAppNumArgs
    return (Key.const c nargs, e.getAppRevArgs)
  | Expr.fvar fvarId   =>
    let nargs := e.getAppNumArgs
    return (Key.fvar fvarId nargs, e.getAppRevArgs)
  | Expr.mvar _mvarId   =>
    if isMatch then
      return (Key.other, #[])
    else do
      return (Key.star, #[])
  | Expr.proj s i a .. =>
    return (Key.proj s i, #[a])
  | Expr.forallE _ d b _ =>
    if b.hasLooseBVars then
      return (Key.other, #[])
    else
      return (Key.arrow, #[d, b])
  | _ =>
    return (Key.other, #[])

private abbrev getMatchKeyArgs (e : Expr) (root : Bool) : MetaM (Key × Array Expr) :=
  getKeyArgs e (isMatch := true) (_root := root)

private abbrev getUnifyKeyArgs (e : Expr) (root : Bool) : MetaM (Key × Array Expr) :=
  getKeyArgs e (isMatch := false) (_root := root)

private def getStarResult (d : DiscrTree α) : Array α :=
  let result : Array α := Array.mkEmpty initCapacity
  match d.root.find? Key.star with
  | none                  => result
  | some (Trie.node vs _) => result ++ vs

private abbrev findKey (cs : Array (Key × Trie α)) (k : Key) : Option (Key × Trie α) :=
  cs.binSearch (k, default) (fun a b => a.1 < b.1)

private partial def getMatchLoop (todo : Array Expr) (c : Trie α) (result : Array α) :
    MetaM (Array α) := do
  match c with
  | Trie.node vs cs =>
    if todo.isEmpty then
      return result ++ vs
    else if cs.isEmpty then
      return result
    else
      let e     := todo.back
      let todo  := todo.pop
      let first := cs[0]!  
      let (k, args) ← getMatchKeyArgs e (root := false)
       
      let visitStar (result : Array α) : MetaM (Array α) :=
        if first.1 == Key.star then
          getMatchLoop todo first.2 result
        else
          return result
      let visitNonStar (k : Key) (args : Array Expr) (result : Array α) : MetaM (Array α) :=
        match findKey cs k with
        | none   => pure result
        | some c => getMatchLoop (todo ++ args) c.2 result
      let result ← visitStar result
      match k with
      | Key.star  => pure result
       
      | Key.arrow => visitNonStar Key.other #[] (← visitNonStar k args result)
      | _         => visitNonStar k args result

private def getMatchRoot (d : DiscrTree α) (k : Key) (args : Array Expr) (result : Array α) :
    MetaM (Array α) :=
  match d.root.find? k with
  | none   => return result
  | some c => getMatchLoop args c result

partial def getMatch (d : DiscrTree α) (e : Expr) : MetaM (Array α) := do
  Core.checkMaxHeartbeats "getMatch"
  let result := getStarResult d
  let (k, args) ← getMatchKeyArgs e (root := true)
  match k with
  | Key.star => return result
  | _        => getMatchRoot d k args result

partial def getUnify (d : DiscrTree α) (e : Expr) : MetaM (Array α) := do
  Core.checkMaxHeartbeats "getUnify"
  let (k, args) ← getUnifyKeyArgs e (root := true)
  match k with
  | Key.star => d.root.foldlM (init := #[]) fun result k c => process k.arity #[] c result
  | _ =>
    let result := getStarResult d
    match d.root.find? k with
    | none   => return result
    | some c => process 0 args c result
where
  process (skip : Nat) (todo : Array Expr) (c : Trie α) (result : Array α) : MetaM (Array α) := do
    match skip, c with
    | skip + 1, Trie.node _vs cs =>
      if cs.isEmpty then
        return result
      else
        cs.foldlM (init := result) fun result ⟨k, c⟩ => process (skip + k.arity) todo c result
    | 0, Trie.node vs cs => do
      if todo.isEmpty then
        return result ++ vs
      else if cs.isEmpty then
        return result
      else
        let e     := todo.back
        let todo  := todo.pop
        let (k, args) ← getUnifyKeyArgs e (root := false)
        let visitStar (result : Array α) : MetaM (Array α) :=
          let first := cs[0]!
          if first.1 == Key.star then
            process 0 todo first.2 result
          else
            return result
        let visitNonStar (k : Key) (args : Array Expr) (result : Array α) : MetaM (Array α) :=
          match findKey cs k with
          | none   => pure result
          | some c => process 0 (todo ++ args) c.2 result
        match k with
        | Key.star  => cs.foldlM (init := result) fun result ⟨k, c⟩ => process k.arity todo c result
        
        | Key.arrow => visitNonStar Key.other #[] (← visitNonStar k args (← visitStar result))
        | _         => visitNonStar k args (← visitStar result)

unsafe def foldMUnsafe [Monad m] (initialKeys : Array Key)
    (f : σ → Array Key → α → m σ) (init : σ) : Trie α → m σ
  | Trie.node vs children => do
    let s ← vs.foldlM (init := init) λ s v => f s initialKeys v
    children.foldlM (init := s) λ s (k, t) =>
      t.foldMUnsafe (initialKeys.push k) f s

@[inline]
def fold (initialKeys : Array Key) (f : σ → Array Key → α → σ) (init : σ)
    (t : Trie α) : σ :=
  Id.run $ t.foldM initialKeys (init := init) λ s k a => return f s k a

@[inline]
def foldM [Monad m] (f : σ → Array Key → α → m σ) (init : σ) (t : DiscrTree α) :
    m σ :=
  t.root.foldlM (init := init) λ s k t => t.foldM #[k] (init := s) f

@[inline]
def fold (f : σ → Array Key → α → σ) (init : σ) (t : DiscrTree α) : σ :=
  Id.run $ t.foldM (init := init) λ s keys a => return f s keys a

@[inline]
def merge [BEq α] (t u : DiscrTree α) : DiscrTree α :=
  if t.root.size < u.root.size then loop t u else loop u t
  where

def values (t : DiscrTree α) : Array α :=
  t.fold (init := #[]) λ as _ a => as.push a

def toArray (t : DiscrTree α) : Array (Array Key × α) :=
  t.fold (init := #[]) λ as keys a => as.push (keys, a)

def cleanUpCompAux (e : Expr) (name : String) : MetaM Expr := do
  match e with
  | .app (.app (.app (.app (.app (.const ``Function.comp lvls) ty₁) ty₂) ty₃) f₁) f₂ => do
      let f₂ ← instantiateMVars <| ← whnf f₂
      let f₁ ← instantiateMVars f₁
      return mkApp5 (mkConst ``Function.comp lvls) ty₁ ty₂ ty₃ f₁ f₂
  | _ => throwError "{name} not of the form '... ∘ ...'"

def cleanUpCompBuilder : EquivalenceBuilder Unit := fun eqvExpr g => g.withContext do
  let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
  let newObjFun ← cleanUpCompAux lhsMinExpr.objFun "objFun"
  let newConstraints ← cleanUpCompAux lhsMinExpr.constraints "constr"
  let rhsMinExpr := { lhsMinExpr with objFun := newObjFun, constraints := newConstraints }
  let newEqvExpr := { eqvExpr with lhs := lhsMinExpr.toExpr, rhs := rhsMinExpr.toExpr }
  if (← isDefEq (mkMVar g) newEqvExpr.toExpr) then
    throwError "`clean_up_comp` error: Failed to unify the goal."
  let simpComp ← ({} : SimpTheorems).addDeclToUnfold ``Function.comp
  let simpContext := { config := {}, simpTheorems := #[simpComp] }
  if let (some newGoal, _) ← simpTarget g simpContext then
    if let _ :: _ ← evalTacticAt (← `(tactic| equivalence_rfl)) newGoal then
      throwError "`clean_up_comp` error: Failed to close the goal."

@[tactic cleanUpComp]
def evalCleanUpComp : Tactic := fun stx => match stx with
  | `(tactic| clean_up_comp) => do
      cleanUpCompBuilder.toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

def rewriteObjLemma (numConstrs : Nat) : MetaM Name :=
  match numConstrs with
  | 1  => return ``Minimization.Equivalence.rewrite_objFun_1
  | 2  => return ``Minimization.Equivalence.rewrite_objFun_2
  | 3  => return ``Minimization.Equivalence.rewrite_objFun_3
  | 4  => return ``Minimization.Equivalence.rewrite_objFun_4
  | 5  => return ``Minimization.Equivalence.rewrite_objFun_5
  | 6  => return ``Minimization.Equivalence.rewrite_objFun_6
  | 7  => return ``Minimization.Equivalence.rewrite_objFun_7
  | 8  => return ``Minimization.Equivalence.rewrite_objFun_8
  | 9  => return ``Minimization.Equivalence.rewrite_objFun_9
  | 10 => return ``Minimization.Equivalence.rewrite_objFun_10
  | _  => throwRwObjError "can only rewrite problems with up to 10 constraints."

def rewriteObjBuilderFromTactic (shouldEval : Bool) (tac : MVarId → FVarId → TacticM Unit)
    (rhs? : Option Expr) : EquivalenceBuilder Unit :=
  fun eqvExpr g => g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let constrTags ← withLambdaBody lhsMinExpr.constraints fun _ constrsBody => do
      let constrs ← decomposeConstraints constrsBody
      return constrs.map fun (n, _) => n
    let numConstrs := constrTags.length
    let gs ← g.apply (mkConst (← rewriteObjLemma numConstrs))
    let mut gToRw := g
    let mut foundGToRw := false
    for g in gs do
      let tag ← g.getTag
      if tag == `hrw then
        gToRw := g
        foundGToRw := true
      if tag == `g && rhs?.isSome then
        g.assign rhs?.get!
    if !foundGToRw then
      throwRwObjError "could not find rewrite goal."
    let (fvarIds, gAfterIntros) ← gToRw.introN (1 + numConstrs) ([`p] ++ constrTags)
    if fvarIds.size == 0 then
      throwRwObjError "could not introduce optimization variables."
    let probFVarId := fvarIds[0]!
    let gAfterShowVars ← showVars gAfterIntros (fvarIds.get! 0)
    if shouldEval then
      if let gs@(_ :: _) ← Tactic.run gAfterShowVars (tac gAfterShowVars probFVarId) then
        trace[CvxLean.debug] "`rw_obj` could not close {gs}."
        throwRwObjError "could not close all goals."

def rewriteObjBuilder (shouldEval : Bool) (tacStx : Syntax) (rhs? : Option Expr) :
    EquivalenceBuilder Unit :=
  fun eqvExpr g => do
    if shouldEval then
      trace[CvxLean.debug] "`rw_obj` using tactic {tacStx}."
    let tac := fun g _ => evalTacticAt tacStx g >>= setGoals
    rewriteObjBuilderFromTactic shouldEval tac rhs? eqvExpr g

def rewriteObjBuilderWithoutTarget (shouldEval : Bool) (tacStx : Syntax) :
    EquivalenceBuilder Unit :=
  rewriteObjBuilder shouldEval tacStx none

def rewriteObjBuilderWithTarget (shouldEval : Bool) (tacStx : Syntax) (targetStx : Syntax) :
    EquivalenceBuilder Unit :=
  fun eqvExpr g => g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let vars ← decomposeDomain (← instantiateMVars lhsMinExpr.domain)
    let fvars := Array.mk <| vars.map (fun ⟨n, _⟩ => mkFVar (FVarId.mk n))
    let target ← withLocalDeclD `p lhsMinExpr.domain fun p => do
      Meta.withDomainLocalDecls lhsMinExpr.domain p fun xs prs => do
        let target ← Tactic.elabTerm targetStx none
        mkLambdaFVars #[p] ((target.replaceFVars fvars xs).replaceFVars xs prs)
    rewriteObjBuilder shouldEval tacStx (some target) eqvExpr g

def rewriteConstrLemma (rwIdx : Nat) (numConstrs : Nat) : MetaM (Name × Name) :=
  if rwIdx == numConstrs then
    match rwIdx with
    | 1  => return (``Minimization.Equivalence.rewrite_constraint_1_last, `c1')
    | 2  => return (``Minimization.Equivalence.rewrite_constraint_2_last, `c2')
    | 3  => return (``Minimization.Equivalence.rewrite_constraint_3_last, `c3')
    | 4  => return (``Minimization.Equivalence.rewrite_constraint_4_last, `c4')
    | 5  => return (``Minimization.Equivalence.rewrite_constraint_5_last, `c5')
    | 6  => return (``Minimization.Equivalence.rewrite_constraint_6_last, `c6')
    | 7  => return (``Minimization.Equivalence.rewrite_constraint_7_last, `c7')
    | 8  => return (``Minimization.Equivalence.rewrite_constraint_8_last, `c8')
    | 9  => return (``Minimization.Equivalence.rewrite_constraint_9_last, `c9')
    | 10 => return (``Minimization.Equivalence.rewrite_constraint_10_last, `c10')
    | _  => throwRwConstrError "error: can only rewrite problems with up to 10 constraints."
  else
    match rwIdx with
    | 1  => return (``Minimization.Equivalence.rewrite_constraint_1, `c1')
    | 2  => return (``Minimization.Equivalence.rewrite_constraint_2, `c2')
    | 3  => return (``Minimization.Equivalence.rewrite_constraint_3, `c3')
    | 4  => return (``Minimization.Equivalence.rewrite_constraint_4, `c4')
    | 5  => return (``Minimization.Equivalence.rewrite_constraint_5, `c5')
    | 6  => return (``Minimization.Equivalence.rewrite_constraint_6, `c6')
    | 7  => return (``Minimization.Equivalence.rewrite_constraint_7, `c7')
    | 8  => return (``Minimization.Equivalence.rewrite_constraint_8, `c8')
    | 9  => return (``Minimization.Equivalence.rewrite_constraint_9, `c9')
    | 10 => return (``Minimization.Equivalence.rewrite_constraint_10, `c10')
    | _  => throwRwConstrError "can only rewrite problems with up to 10 constraints."

def nameToRintroPat (n : Name) : TSyntax `rcasesPat :=
  Unhygienic.run `(rcasesPat| $(Lean.mkIdent n):ident)

def namesToRintroPat (names : List Name) : MetaM (TSyntax `rcasesPat) := do
  let ns := names.map nameToRintroPat
  match ns with
  | [n] => return n
  | [n1, n2] => return ← `(rcasesPat| ⟨$n1, $n2⟩)
  | [n1, n2, n3] => return ← `(rcasesPat| ⟨$n1, $n2, $n3⟩)
  | [n1, n2, n3, n4] => return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4⟩)
  | [n1, n2, n3, n4, n5] => return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5⟩)
  | [n1, n2, n3, n4, n5, n6] => return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6⟩)
  | [n1, n2, n3, n4, n5, n6, n7] => return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7⟩)
  | [n1, n2, n3, n4, n5, n6, n7, n8] =>
      return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7, $n8⟩)
  | [n1, n2, n3, n4, n5, n6, n7, n8, n9] =>
      return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7, $n8, $n9⟩)
  | [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10] =>
      return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7, $n8, $n9, $n10⟩)
  | _ => throwRwConstrError "could not apply `rintro`, too many constraints."

def rewriteConstrBuilderFromTactic (shouldEval : Bool) (constrTag : Name)
    (tac : MVarId → FVarId → TacticM Unit) (rhs? : Option Expr) : EquivalenceBuilder (List Name) :=
  fun eqvExpr g => g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let constrTags ← withLambdaBody lhsMinExpr.constraints fun _ constrsBody => do
      let constrs ← decomposeConstraints constrsBody
      return constrs.map fun (n, _) => n
    let numConstrs := constrTags.length
    let mut rwIdx := 0
    for i in [0:numConstrs] do
      if constrTags[i]! == constrTag then
        rwIdx := i + 1
    if rwIdx == 0 then
      throwRwConstrError "could not find constraint to rewrite."
    let (lemmaName, rhsName) ← rewriteConstrLemma rwIdx numConstrs
    let gs ← g.apply (mkConst lemmaName)
    let mut gToRw := g
    let mut foundGToRw := false
    for g in gs do
      let tag ← g.getTag
      if tag == `hrw then
        gToRw := g
        foundGToRw := true
      if tag == rhsName && rhs?.isSome then
        g.assign rhs?.get!
    if !foundGToRw then
      throwRwConstrError "could not find rewrite goal."
    
    let constrTagsBefore := constrTags.take rwIdx.pred
    let numConstrsBefore := constrTagsBefore.length
    let constrTagsAfter := constrTags.drop rwIdx
    let isLast := rwIdx == numConstrs
    let (probFVarId, gAfterIntros) := ← do
      let (fvarIds, gAfterIntros1) ← gToRw.introN (1 + numConstrsBefore) ([`p] ++ constrTagsBefore)
      if fvarIds.size == 0 then
        throwRwConstrError "could not introduce optimization variables."
      if isLast then
        return (fvarIds[0]!, gAfterIntros1)
      else
        let toRIntro ← namesToRintroPat constrTagsAfter
        let (gsAfterRIntro, _) ← TermElabM.run <|
          RCases.rintro #[toRIntro] none gAfterIntros1
        if gsAfterRIntro.length != 1 then
          throwRwConstrError "could not introduce optimization variables."
        return (fvarIds[0]!, gsAfterRIntro[0]!)

    let gAfterShowVars ← showVars gAfterIntros probFVarId
    if shouldEval then
      if let gs@(_ :: _) ← Tactic.run gAfterShowVars (tac gAfterShowVars probFVarId) then
        trace[CvxLean.debug] "`rw_constr` could not close {gs}."
        throwRwConstrError "could not close all goals."

    return constrTags

def rewriteConstrBuilder (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax)
    (rhs? : Option Expr) : EquivalenceBuilder (List Name) :=
  fun g eqvExpr => do
    if shouldEval then
      trace[CvxLean.debug] "`rw_constr` using tactic {tacStx}."
    let tac := fun g _ => evalTacticAt tacStx g >>= setGoals
    rewriteConstrBuilderFromTactic shouldEval constrTag tac rhs? g eqvExpr

def rewriteConstrBuilderWithoutTarget (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax) :
    EquivalenceBuilder (List Name) :=
  rewriteConstrBuilder shouldEval constrTag tacStx none

def rewriteConstrBuilderWithTarget (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax)
    (targetStx : Syntax) : EquivalenceBuilder (List Name) :=
  fun eqvExpr g => g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let vars ← decomposeDomain (← instantiateMVars lhsMinExpr.domain)
    let fvars := Array.mk <| vars.map (fun ⟨n, _⟩ => mkFVar (FVarId.mk n))
    let target ← withLocalDeclD `p lhsMinExpr.domain fun p => do
      Meta.withDomainLocalDecls lhsMinExpr.domain p fun xs prs => do
        let target ← Tactic.elabTerm targetStx none
        mkLambdaFVars #[p] ((target.replaceFVars fvars xs).replaceFVars xs prs)
    rewriteConstrBuilder shouldEval constrTag tacStx (some target) eqvExpr g

@[tactic rwObj]
def evalRwObjFun : Tactic := fun stx => match stx with
  | `(tactic|rw_obj =>) => do
      (rewriteObjBuilderWithoutTarget false stx).toTactic
  | `(tactic|rw_obj => $tacStx) => do
      (rewriteObjBuilderWithoutTarget true tacStx).toTactic
      saveTacticInfoForToken stx
  | `(tactic|rw_obj into $targetStx =>) => do
      (rewriteObjBuilderWithTarget false stx targetStx).toTactic
  | `(tactic|rw_obj into $targetStx => $tacStx) => do
      (rewriteObjBuilderWithTarget true tacStx targetStx).toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

@[tactic rwConstr]
def evalRwConstr : Tactic := fun stx => match stx with
  | `(tactic|rw_constr $h =>) => do
      let _ ← (rewriteConstrBuilderWithoutTarget false h.getId stx).toTactic
  | `(tactic|rw_constr $h => $tacStx) => do
      let constrTags ← (rewriteConstrBuilderWithoutTarget true h.getId tacStx).toTactic
      (renameConstrsBuilder constrTags.toArray).toTactic
      saveTacticInfoForToken stx
  | `(tactic|rw_constr $h into $targetStx =>) => do
      let _ ← (rewriteConstrBuilderWithTarget false h.getId stx targetStx).toTactic
  | `(tactic|rw_constr $h into $targetStx => $tacStx) => do
      let constrTags ← (rewriteConstrBuilderWithTarget true h.getId tacStx targetStx).toTactic
      (renameConstrsBuilder constrTags.toArray).toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

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)

def floatToReal (f : Float) : Expr :=
  let nnRatCastToOfScientific :=
    mkApp2 (mkConst ``NNRatCast.toOfScientific ([levelZero] : List Level))
      (mkConst ``Real) (mkConst ``Real.instNNRatCast)
  let realOfScientific :=
    mkApp2 (mkConst ``OfScientific.ofScientific ([levelZero] : List Level))
      (mkConst ``Real) nnRatCastToOfScientific
  match Json.Parser.num f.toString.mkIterator with
  | Parsec.ParseResult.success _ res =>
      let num := mkApp3 realOfScientific
        (mkNatLit res.mantissa.natAbs) (toExpr true) (mkNatLit res.exponent)
      if res.mantissa < 0 then
        mkApp3 (mkConst ``Neg.neg ([levelZero] : List Level))
          (mkConst ``Real) (mkConst ``Real.instNeg) num
      else num
  
  | Parsec.ParseResult.error _ _ =>
      mkApp3 realOfScientific
        (mkNatLit 0) (toExpr true) (mkNatLit 1)

def removeTrivialConstrsBuilder : EquivalenceBuilder Unit := fun eqvExpr g => g.withContext do
  let lhs ← eqvExpr.toMinimizationExprLHS
  let constrNames ← withLambdaBody lhs.constraints fun _ constrsBody => do
    let cs ← decomposeConstraints constrsBody
    return cs.map fun c => c.1
  let mut g := g
  for n in constrNames do
    let eqvBuilder := removeConstrBuilder n (← `(term| (by admit /- proof elided -/
    )))
    let gs ← Tactic.run g <|
      Tactic.tryCatch eqvBuilder.toTactic (fun _ => do pure ())
    if gs.length != 1 then
      throwError "`remove_trivial_constrs` error: failed to remove {n}."
    g := gs[0]!

  let gsFinal ← evalTacticAt (← `(tactic| equivalence_rfl)) g
  if gsFinal.length != 0 then
    throwError "`remove_trivial_constrs` error: could not close last goal."

@[tactic removeTrivialConstrs]
def evalRemoveTrivialConstrs : Tactic := fun stx => match stx with
  | `(tactic| remove_trivial_constrs) => do
      removeTrivialConstrsBuilder.toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

structure Summary where
  name            : String
  problemStatus   : String
  solutionStatus  : String
  objectiveName   : String
  primalObjective : Float
  dualObjective   : Float

instance : ToString Summary where
  toString s :=
    s!"NAME             : {s.name} \n" ++
    s!"PROBLEM STATUS   : {s.problemStatus} \n" ++
    s!"SOLUTION STATUS  : {s.solutionStatus} \n" ++
    s!"OBJECTIVE NAME   : {s.objectiveName} \n" ++
    s!"PRIMAL OBJECTIVE : {s.primalObjective} \n" ++
    s!"DUAL OBJECTIVE   : {s.dualObjective} \n"

inductive StatusKey
  | UN | BS | SB | LL | UL | EQ | IN

instance : ToString StatusKey where
  toString
  | StatusKey.UN => "UN"
  | StatusKey.BS => "BS"
  | StatusKey.SB => "SB"
  | StatusKey.LL => "LL"
  | StatusKey.UL => "UL"
  | StatusKey.EQ => "EQ"
  | _            => "**"

structure Constraint where
  index      : Nat
  name       : String
  status     : StatusKey
  activity   : Float
  lowerLimit : Option Float
  upperLimit : Option Float
  dualLower  : Option Float
  dualUpper  : Option Float

instance : ToString Constraint where
  toString c :=
    s!"{c.index} | " ++
    s!"{c.name} | " ++
    s!"{c.status} | " ++
    s!"{c.activity} | " ++
    s!"{c.lowerLimit} | " ++
    s!"{c.upperLimit} | " ++
    s!"{c.dualLower} | " ++
    s!"{c.dualUpper}"

structure Variable extends Constraint where
  conicDual : Option Float

instance : ToString Variable where
  toString v :=
    (toString v.toConstraint) ++ (s!" | {v.conicDual}")

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

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

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

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"

inductive Response
| success (res : Result) : Response
| failure (code : Nat)   : Response

instance : ToString Response where
  toString
  | success res  => toString res
  | failure code => s!"MOSEK failed with code {code}."

def ws' : Parsec Unit := fun it =>  Lean.Parsec.ParseResult.success (skipWs' it) ()
where
  skipWs' (it : String.Iterator) : String.Iterator :=
  if it.hasNext then
    let c := it.curr
    if c = '\u0009' ∨ c = '\u000d' ∨ c = '\u0020' then skipWs' it.next else it
  else it

private def endOfLine : Parsec Unit :=
  skipChar '\u000a' <|> eof

private def char : Parsec Char :=
  asciiLetter <|> hexDigit <|> pchar '_' <|> fail "Invalid character."

private def string : Parsec String :=
  many1Chars char

private def nat : Parsec Nat :=
  Json.Parser.natMaybeZero

private def float : Parsec Float := fun it₁ =>
  match Json.Parser.num it₁ with
  | ParseResult.success it₂ res =>
    let f := Float.ofScientific res.mantissa.natAbs true res.exponent
    ParseResult.success it₂ (if res.mantissa < 0 then -f else f)
  | ParseResult.error it₂ err => ParseResult.error it₂ err

private def optionFloat : Parsec (Option Float) :=
  (some <$> float) <|> (skipString "NONE" *> pure none)

private def summaryIdentifier (id : String) : Parsec Unit :=
  skipString id *> ws' *> skipChar ':' *> ws'

private def summaryLine (id : String) (p : Parsec α) : Parsec α :=
  summaryIdentifier id *> p <* ws' <* endOfLine

private def stringSummaryLine (id : String) : Parsec String :=
  summaryLine id string

private def floatSummaryLine (id : String) : Parsec Float :=
  summaryLine id float

private def summary : Parsec Summary :=
  Summary.mk <$>
  ((skipString "NAME" *> ws' *> skipChar ':' *> ws') *> pure "anonymous" <* endOfLine) <*>
  stringSummaryLine "PROBLEM STATUS"   <*>
  stringSummaryLine "SOLUTION STATUS"  <*>
  stringSummaryLine "OBJECTIVE NAME"   <*>
  floatSummaryLine  "PRIMAL OBJECTIVE" <*>
  floatSummaryLine  "DUAL OBJECTIVE"   <* ws' <* endOfLine

private def constraintsTitle : Parsec Unit :=
  skipString "CONSTRAINTS" <* ws' <* endOfLine

private def constraintsHeaders : Parsec Unit :=
  skipString "INDEX"       <* ws' <*
  skipString "NAME"        <* ws' <*
  skipString "AT"          <* ws' <*
  skipString "ACTIVITY"    <* ws' <*
  skipString "LOWER LIMIT" <* ws' <*
  skipString "UPPER LIMIT" <* ws' <*
  skipString "DUAL LOWER"  <* ws' <*
  skipString "DUAL UPPER"  <* ws' <* endOfLine

private def statusKey : Parsec StatusKey :=
  (skipString "UN" *> pure StatusKey.UN) <|>
  (skipString "BS" *> pure StatusKey.BS) <|>
  (skipString "SB" *> pure StatusKey.SB) <|>
  (skipString "LL" *> pure StatusKey.LL) <|>
  (skipString "UL" *> pure StatusKey.UL) <|>
  (skipString "EQ" *> pure StatusKey.EQ) <|>
  (skipString "**" *> pure StatusKey.IN) <|>
  (fail "Invalid status key.")

private def constraintElem (p : Parsec α) : Parsec α :=
  ws' *> p

private def constraint : Parsec Constraint :=
  Constraint.mk <$>
  constraintElem nat         <*>
  constraintElem string      <*>
  constraintElem statusKey   <*>
  constraintElem float       <*>
  constraintElem optionFloat <*>
  constraintElem optionFloat <*>
  constraintElem optionFloat <*>
  constraintElem optionFloat <* ws' <* endOfLine

private def constraints : Parsec (List Constraint) :=
  constraintsTitle   *>
  constraintsHeaders *>
  Array.data <$> many constraint

private def varsTitle : Parsec Unit :=
  skipString "VARIABLES" <* endOfLine

private def varsHeaders : Parsec Unit :=
  skipString "INDEX"       <* ws' <*
  skipString "NAME"        <* ws' <*
  skipString "AT"          <* ws' <*
  skipString "ACTIVITY"    <* ws' <*
  skipString "LOWER LIMIT" <* ws' <*
  skipString "UPPER LIMIT" <* ws' <*
  skipString "DUAL LOWER"  <* ws' <*
  skipString "DUAL UPPER"  <* ws' <*
  (skipString "CONIC DUAL" <|> pure ()) <* ws' <* endOfLine

private def var : Parsec Variable :=
  let noBreakConstraint := Constraint.mk <$>
    constraintElem nat         <*>
    constraintElem string      <*>
    constraintElem statusKey   <*>
    constraintElem float       <*>
    constraintElem optionFloat <*>
    constraintElem optionFloat <*>
    constraintElem optionFloat <*>
    constraintElem optionFloat
  Variable.mk <$>
    noBreakConstraint <*>
    (constraintElem (optionFloat <|> pure none)) <* ws' <* endOfLine

private def vars : Parsec (List Variable) :=
  varsTitle   *>
  varsHeaders *>
  Array.data <$> many var

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

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

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

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

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

def parse (s : String) : Except String Result :=
  match result s.mkIterator with
  | Parsec.ParseResult.success _ res => Except.ok res
  | Parsec.ParseResult.error it err  =>
    Except.error s!"Error at offset {it.i.byteIdx}. Error: {err}.}"

def _root_.Lean.Json.getStr! (j : Json) : String :=
  match j with
  | Json.str a => a
  | _ => ""

def _root_.Lean.Json.getArr! (j : Json) : Array Json :=
  match j with
  | Json.arr a => a
  | _ => #[]

def _root_.MetaM.ofExcept [ToString ε]: Except ε α → MetaM α :=
  fun e =>
    match e with
    | Except.error msg => throwError (toString msg)
    | Except.ok x => return x

instance : MonadExceptOf String MetaM := {
  throw := fun msg => throwError msg
  tryCatch := fun x _ => x
}

def surroundQuotes (s : String) : String :=
  "\"" ++ s ++ "\""

def EggMinimization.toJson (e : EggMinimization) : String :=
  "{" ++
  surroundQuotes "obj_fun" ++ " : " ++ surroundQuotes e.objFun ++ ", " ++
  surroundQuotes "constrs" ++ " : " ++
    "[" ++
      (", ".intercalate <| e.constrs.map (fun constr =>
        "["  ++ (surroundQuotes constr.1) ++          
        ", " ++ (surroundQuotes constr.2) ++ "]")) ++ 
    "]" ++
  "}"

def EggDomain.toJson (e : EggDomain) : String :=
  "[" ++
  surroundQuotes e.hi ++ ", " ++
  surroundQuotes e.lo ++ ", " ++
  surroundQuotes e.hi_open ++ ", " ++
  surroundQuotes e.lo_open ++
  "]"

def EggRequest.toJson (e : EggRequest) : String :=
  "{" ++
  surroundQuotes "request" ++ " : " ++ surroundQuotes "PerformRewrite" ++ ", " ++
  surroundQuotes "prob_name" ++ " : " ++ surroundQuotes e.probName ++ ", " ++
  surroundQuotes "domains" ++ " : " ++
    "[" ++
      (", ".intercalate <| e.domains.map (fun domain =>
        "[" ++ (surroundQuotes domain.1) ++ 
        ", " ++ domain.2.toJson ++ "]")) ++ 
    "]" ++ ", " ++
  surroundQuotes "target" ++ " : " ++ (e.target.toJson) ++
  "}"

def EggRewriteDirection.toString : EggRewriteDirection → String
  | Forward => "fwd"
  | Backward => "bwd"

instance : ToString EggRewriteDirection where
  toString := EggRewriteDirection.toString

def EggRewrite.toString (e : EggRewrite) : String :=
  "{" ++
  surroundQuotes "rewrite_name" ++ " : " ++ surroundQuotes e.rewriteName ++ ", " ++
  surroundQuotes "direction" ++ " : " ++ surroundQuotes (e.direction.toString) ++ ", " ++
  surroundQuotes "location" ++ " : " ++ surroundQuotes e.location ++ ", " ++
  surroundQuotes "subexpr_from" ++ " : " ++ surroundQuotes e.subexprFrom ++ ", " ++
  surroundQuotes "subexpr_to" ++ " : " ++ surroundQuotes e.subexprTo ++ ", " ++
  surroundQuotes "expected_term" ++ " : " ++ surroundQuotes e.expectedTerm ++
  "}"

instance : ToString EggRewrite where
  toString := EggRewrite.toString

def runEggRequestRaw (requestJson : String) : MetaM String := do
  let eggProcess ← IO.Process.spawn {
      cmd    := "egg-pre-dcp/utils/egg-pre-dcp",
      stdout := IO.Process.Stdio.piped,
      stdin  := IO.Process.Stdio.piped,
      stderr := IO.Process.Stdio.null
    }

  let (stdin, eggProcess) ← eggProcess.takeStdin
  stdin.putStr requestJson

  let stdout ← IO.asTask eggProcess.stdout.readToEnd Task.Priority.dedicated
  let stdout : String ← IO.ofExcept stdout.get
  let exitCode ← eggProcess.wait
  dbg_trace s!"Egg exit code: {exitCode}"

  return stdout

def parseEggResponse (responseString : String) :
    MetaM (HashMap String (Array EggRewrite)) := do
  dbg_trace s!"Egg response: {responseString}"
  let outJson : Json ← match Json.parse responseString with
    | Except.error e => throwError (s!"error calling `egg`, JSON parsing error ({e}).")
    | Except.ok j => pure j

  let responseType := (outJson.getObjValD "response").getStr!

  if responseType == "Error" then
    match (outJson.getObjValD "error").getStr? with
    | Except.error _ => throwError "error calling `egg`."
    | Except.ok errorMessage => throwError "from `egg` (\"{errorMessage}\")."

  else
    let steps ← liftExcept <| outJson.getObjVal? "steps"
    let steps ← liftExcept <| Json.getObj? steps

    let mut res := HashMap.empty
    for ⟨componentName, componentSteps⟩ in steps.toArray do
      let componentSteps ← liftExcept <| Json.getArr? componentSteps
      let componentStepsParsed : Array EggRewrite := componentSteps.map fun step =>
        let rewriteName := (step.getObjValD "rewrite_name").getStr!
        let direction := match (step.getObjValD "direction").getStr! with
          | "Forward" => EggRewriteDirection.Forward
          | "Backward" => EggRewriteDirection.Backward
          | _ => panic! "Unexpected rewrite direction."
        let location := (step.getObjValD "location").getStr!
        let subexprFrom := (step.getObjValD "subexpr_from").getStr!
        let subexprTo := (step.getObjValD "subexpr_to").getStr!
        let expectedTerm := (step.getObjValD "expected_term").getStr!
        { rewriteName  := rewriteName,
          direction    := direction,
          location     := location,
          subexprFrom  := subexprFrom,
          subexprTo    := subexprTo,
          expectedTerm := expectedTerm }
      res := res.insert componentName componentStepsParsed

    return res

def runEggRequest (request : EggRequest) : MetaM (HashMap String (Array EggRewrite)) :=
  dbg_trace s!"Running egg request: {request.toJson}"
  runEggRequestRaw request.toJson >>= parseEggResponse

def runReductionTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit :=
  runTransformationTactic TransformationGoal.Reduction mvarId stx

def elabReductionProof (lhs : Expr) (rhsName : Name) (stx : Syntax) : TermElabM (Expr × Expr) :=
  elabTransformationProof TransformationGoal.Reduction lhs rhsName stx

def evalReductionAux (probIdStx redIdStx : TSyntax `ident) (xs : Array (Syntax × Expr))
    (lhsStx : Syntax) (proofStx : TSyntax `Lean.Parser.Term.byTactic) (bwdMap : Bool) :
    TermElabM Unit := do
  let D ← Meta.mkFreshTypeMVar
  let R ← Meta.mkFreshTypeMVar
  let lhsTy := mkApp2 (Lean.mkConst ``Minimization) D R
  let lhs ← elabTermAndSynthesizeEnsuringType lhsStx (some lhsTy)

  \n{e.toMessageData}"

@[command_elab «reduction»]
def evalReduction : CommandElab := fun stx => match stx with
  | `(reduction $eqvId / $probId $declSig := $proofStx) => do
      liftTermElabM do
        let (binders, lhsStx) := expandDeclSig declSig.raw
        elabBindersEx binders.getArgs fun xs =>
          evalReductionAux probId eqvId xs lhsStx proofStx false
  | _ => throwUnsupportedSyntax

@[command_elab «reductionAndBwdMap»]
def evalReductionAndBwdMap : CommandElab := fun stx => match stx with
  | `(reduction* $eqvId / $probId $declSig := $proofStx) => do
      liftTermElabM do
        let (binders, lhsStx) := expandDeclSig declSig.raw
        elabBindersEx binders.getArgs fun xs =>
          evalReductionAux probId eqvId xs lhsStx proofStx true
  | _ => throwUnsupportedSyntax

instance : Inhabited (Array α) where
  default := Array.empty

def Array.zeros [Zero α] (n : Nat) : Array α :=
  Array.mk (List.replicate n 0)

def DArray.zeros [Zero α] (a b : Nat) : Array (Array α) :=
  Array.mk (List.replicate b (Array.zeros a))

def Array.filterIdx (as : Array α) (f : Nat → Bool) := Id.run do
  let mut bs := #[]
  for h : i in [:as.size] do
    if f i then
      bs := bs.push (as.get ⟨i, h.2⟩)
  return bs

@[specialize]
def Array.zipWithMAux (f : α → β → MetaM γ) (as : Array α) (bs : Array β) (i : Nat) (cs : Array γ) :
    MetaM (Array γ) := do
  if h : i < as.size then
    let a := as.get ⟨i, h⟩;
    if h : i < bs.size then
      let b := bs.get ⟨i, h⟩;
      return ← zipWithMAux f as bs (i+1) <| cs.push <| ← f a b
    else
      pure cs
  else
    pure cs
termination_by as.size - i

def Array.zipWithM (f : α → β → MetaM γ) (as : Array α) (bs : Array β) :
    MetaM (Array γ) :=
  zipWithMAux f as bs 0 #[]

def Array.drop (n : ℕ) (as : Array α) : Array α :=
  Array.mk (as.data.drop n)

def Array.take (n : ℕ) (as : Array α) : Array α :=
  Array.mk (as.data.take n)

def compileCargo (name : String) (manifestFile : FilePath) (cargo : FilePath := "cargo")
    (env : Array (String × Option String)) : LogIO Unit := do
  logInfo s!"Creating {name}"
  proc {
    env := env
    cmd := cargo.toString
    args := #["build", "
  }

def buildCargo (targetFile : FilePath) (manifestFile : FilePath) (targetDest : FilePath)
    (oFileJobs : Array (BuildJob FilePath)) (stopOnSuccess : Bool) :
    SpawnM (BuildJob FilePath) :=
  let name := targetFile.fileName.getD targetFile.toString
  buildFileAfterDepArray targetFile oFileJobs fun _ => do
    let env := if stopOnSuccess then #[("RUSTFLAGS", some "
    compileCargo name manifestFile (env := env)
    createParentDirs targetDest
    proc {
      cmd := "cp"
      args := #[targetFile.toString, targetDest.toString]
    }

target EggPreDCP (pkg) : FilePath := do
  let buildDir := pkg.dir / "egg-pre-dcp"
  let binFile := buildDir / "target" / "release" / "egg-pre-dcp"
  let dest := buildDir / "utils" / "egg-pre-dcp"
  let manifestFile := buildDir / "Cargo.toml"
  buildCargo binFile manifestFile dest #[] false

target EggPreDCPStopOnSuccess (pkg) : FilePath := do
  let buildDir := pkg.dir / "egg-pre-dcp"
  let binFile := buildDir / "target" / "release" / "egg-pre-dcp"
  let dest := buildDir / "utils" / "egg-pre-dcp"
  let manifestFile := buildDir / "Cargo.toml"
  buildCargo binFile manifestFile dest #[] true

script EggClean := do
  let targetDir : FilePath := "." / "egg-pre-dcp" / "target"
  let utilsDir : FilePath  := "." / "egg-pre-dcp" / "utils"
  let out ←
  IO.Process.output {
    cmd := "rm"
    args := #["-rf", targetDir.toString]
  } *>
  IO.Process.output {
    cmd := "rm"
    args := #["-rf", utilsDir.toString]
  }
  pure out.exitCode

structure EquivalenceExpr where
  domainLHS : Expr
  domainRHS : Expr
  codomain : Expr
  codomainPreorder : Expr
  lhs : Expr
  rhs : Expr

def toMinimizationExprLHS (eqvExpr : EquivalenceExpr) : MetaM MinimizationExpr := do
  MinimizationExpr.fromExpr (← whnf eqvExpr.lhs)

def toMinimizationExprRHS (eqvExpr : EquivalenceExpr) : MetaM MinimizationExpr := do
  MinimizationExpr.fromExpr (← whnf eqvExpr.rhs)

def toExpr (eqvExpr : EquivalenceExpr) : Expr :=
  mkApp6 (mkConst ``Minimization.Equivalence) eqvExpr.domainLHS eqvExpr.domainRHS eqvExpr.codomain
    eqvExpr.codomainPreorder eqvExpr.lhs eqvExpr.rhs

def fromExpr (e : Expr) : MetaM EquivalenceExpr := do
  match e with
  | .app (.app (.app (.app (.app (.app (.const ``Minimization.Equivalence _)
      domainLHS) domainRHS) codomain) codomainPreorder) lhs) rhs => do
      return EquivalenceExpr.mk domainLHS domainRHS codomain codomainPreorder lhs rhs
  | _ => throwError "Expression not of the form `Minimization.Equivalence ...`."

def fromGoal (goal : MVarId) : MetaM EquivalenceExpr := do
  let goalType ← whnf (← MVarId.getDecl goal).type
  fromExpr goalType

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}."

unsafe def getTotalDim (minExpr : MinimizationExpr) : MetaM Nat := do
  let vars ← decomposeDomainInstantiating minExpr

  let mut totalDim := 0
  for (_, varTy) in vars do
    let dims ← inferDimension varTy
    for (n, m) in dims do
      totalDim := totalDim + n * m

  return totalDim

def translateCone : ScalarConeType → CBF.ConeType
  | ScalarConeType.Zero => CBF.ConeType.LEq
  | ScalarConeType.PosOrth => CBF.ConeType.LPos
  | ScalarConeType.Exp => CBF.ConeType.EXP
  | ScalarConeType.Q => CBF.ConeType.Q
  | ScalarConeType.QR => CBF.ConeType.QR

def groupCones (sections : ScalarAffineSections) (l : List CBF.Cone) : MetaM (List CBF.Cone) := do
  let l := l.toArray
  let mut res := []
  let mut currIdx := 0
  for idx in sections.data do
    let group := l[currIdx:idx]
    if h : group.size > 0 then
      let c := group.get ⟨0, h⟩
      let coneType := c.type
      for c' in group do
        if !(c'.type = coneType) then
          throwSolveError "only cones of the same type can be grouped."
      let totalDim := group.foldl (fun acc c => acc + c.dim) 0
      currIdx := idx
      res := res ++ [CBF.Cone.mk coneType totalDim]
    else
      throwSolveError "incorrect sections, could not group cones."

  return res

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

def findRegisteredAtoms (e : Expr) : MetaM (Array (Arguments × GraphAtomData)) := do
  let discrTree ← getAtomDiscrTree
  let atoms ← discrTree.getMatch (← zetaReduce (← e.removeMData))
  trace[Meta.debug] "discrTree {atoms.size} {e}"
  let mut goodAtoms := #[]
  for atom in atoms do
    let (mvars, _, pattern) ← lambdaMetaTelescope atom.expr
    if ← isDefEq pattern e then
      let args ← mvars.mapM instantiateMVars
      goodAtoms := goodAtoms.push (args, atom.graph!)
    else
      trace[CvxLean.debug]
        "Pattern did not match. (Pattern {toString pattern}; Expression {toString e})"
  
  goodAtoms := goodAtoms.insertionSort (fun a b => (a.2.expr.size - b.2.expr.size != 0))
  return goodAtoms

inductive FindAtomResult
  | Success (res : AtomDataTrees)
  | Error (msgs : Array MessageData)

partial def findAtoms (e : Expr) (vars : Array FVarId) (curvature : Curvature) :
    MetaM (Bool × Array MessageData × AtomDataTrees) := do
  let e := eta e

  
  if e.isFVar ∧ vars.contains e.fvarId! then
    return (false, #[], Tree.leaf e, Tree.leaf (), Tree.leaf curvature, Tree.leaf #[])

  
  let isConstantExpr := e.isRelativelyConstant vars || curvature == Curvature.Constant
  let isPropExpr := (← inferType e).isProp
  if isConstantExpr && !isPropExpr then
    return (false, #[], Tree.leaf e, Tree.leaf (), Tree.leaf curvature, Tree.leaf #[])

  
  let potentialAtoms ← findRegisteredAtoms e
  let mut failedAtoms : Array MessageData := #[]
  if potentialAtoms.size == 0 then
    failedAtoms := failedAtoms.push m!"No atom found for {e}"
  
  for (args, atom) in potentialAtoms do
    match ← processAtom e vars curvature atom args with
    | FindAtomResult.Success (atoms, args, curvatures, bconds) =>
        trace[Meta.debug] "Success {e} : {atom}"
        return (false, failedAtoms, atoms, args, curvatures, bconds)
    | FindAtomResult.Error msg =>
        failedAtoms := failedAtoms ++ msg
  return (true, failedAtoms, Tree.leaf e, Tree.leaf (), Tree.leaf curvature, Tree.leaf #[])
where

partial def Sexpr.toEggTree : Sexp → MetaM EggTree
  | .atom a => return Tree.leaf a
  | .list l => do
    if l.length == 0 then
      throwPreDCPError "`Sexp` to `Tree` conversion error, unexpected empty list."
    else
      match l.head! with
      | .atom op =>
          let children ← l.tail.mapM toEggTree
          return CvxLean.Tree.node op (Array.mk children)
      | .list _ =>
          throwPreDCPError "`Sexp` to `Tree` conversion error, unexpected list as operator."

def EggString.toEggTree (s : String) : MetaM EggTree := do
  match parseSingleSexp s with
  | Except.ok sexpr => Sexpr.toEggTree sexpr
  | Except.error e => throwError s!"{e}"

partial def EggTree.toExpr (vars params : List String) : EggTree → MetaM Expr
  
  | Tree.leaf "pi" => do
      return mkConst ``Real.pi
  
  | Tree.leaf "?" => do
      if "subexpr" ∈ vars then
        throwPreDCPError "\"subexpr\" is not a valid variable name."
      return mkFVar (FVarId.mk (Name.mkSimple "subexpr"))
  
  | Tree.leaf s =>
      match Json.Parser.num s.mkIterator with
      | Parsec.ParseResult.success _ res => do
          
          let nnRatCastToOfScientific :=
            mkApp2 (mkConst ``NNRatCast.toOfScientific ([levelZero] : List Level))
              (mkConst ``Real) (mkConst ``Real.instNNRatCast)
          let realOfScientific :=
            mkApp2 (mkConst ``OfScientific.ofScientific [levelZero])
              (mkConst ``Real)
              nnRatCastToOfScientific
          let num := mkApp3 realOfScientific
            (mkNatLit res.mantissa.natAbs) (Lean.toExpr true) (mkNatLit res.exponent)
          let expr :=
            if res.mantissa < 0 then
              mkApp3
                (mkConst ``Neg.neg [levelZero]) (mkConst ``Real) (mkConst ``Real.instNeg) num
            else num
          let simpResult ←
            Mathlib.Meta.NormNum.deriveSimp (ctx := ← Simp.Context.mkDefault) (e := expr)
          return simpResult.expr
      | _ => throwPreDCPError "conversion error, unexpected num {s}."
  
  | Tree.node "var" #[Tree.leaf s] =>
      if s ∈ vars then
        return mkFVar (FVarId.mk (Name.mkSimple s))
      else
        throwPreDCPError "conversion error, unexpected var {s}."
  
  | Tree.node "param" #[Tree.leaf s] =>
    if s ∈ params then
      return mkFVar (FVarId.mk (Name.mkSimple s))
    else
      return mkConst (Name.mkSimple s)
  
  | Tree.node "eq" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkAppN (mkConst ``Eq [levelOne]) #[(mkConst `Real), t1, t2]
  
  | Tree.node "le" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkAppN
        (mkConst ``LE.le [levelZero])
        #[(mkConst `Real), (mkConst ``Real.instLE), t1, t2]
  
  | Tree.node "neg" #[t] => do
      let t ← toExpr vars params t
      return mkAppN
        (mkConst ``Neg.neg [levelZero])
        #[(mkConst ``Real), (mkConst ``Real.instNeg), t]
  
  | Tree.node "inv" #[t] => do
      let t ← toExpr vars params t
      return mkAppN
        (mkConst ``Inv.inv [levelZero])
        #[(mkConst ``Real), (mkConst ``Real.instInv), t]
  
  | Tree.node "abs" #[t] => do
      let t ← toExpr vars params t
      return mkAppN
        (mkConst ``abs [levelZero])
        #[(mkConst ``Real), (mkConst ``Real.lattice), (mkConst ``Real.instAddGroup), t]
  
  | Tree.node "sqrt" #[t] => do
      let t ← toExpr vars params t
      return mkAppN (mkConst ``Real.sqrt) #[t]
  
  | Tree.node "log" #[t] => do
      let t ← toExpr vars params t
      return mkAppN (mkConst ``Real.log) #[t]
  
  | Tree.node "exp" #[t] => do
      let t ← toExpr vars params t
      return mkAppN (mkConst ``Real.exp) #[t]
  
  | Tree.node "xexp" #[t] =>
      EggTree.toExpr vars params (Tree.node "mul" #[t, Tree.node "exp" #[t]])
  
  | Tree.node "entr" #[t] =>
      EggTree.toExpr vars params (Tree.node "neg" #[Tree.node "mul" #[t, Tree.node "log" #[t]]])
  
  | Tree.node "min" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkAppN
        (mkConst ``Min.min [levelZero])
        #[(mkConst ``Real), (mkConst ``Real.instMinReal), t1, t2]
  
  | Tree.node "max" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkAppN
        (mkConst ``Max.max [levelZero])
        #[(mkConst ``Real), (mkConst ``Real.instMaxReal), t1, t2]
  
  | Tree.node "add" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkRealHBinAppExpr ``HAdd.hAdd ``instHAdd 1 ``Real.instAdd t1 t2
  
  | Tree.node "sub" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkRealHBinAppExpr ``HSub.hSub ``instHSub 1 ``Real.instSub t1 t2
  
  | Tree.node "mul" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkRealHBinAppExpr ``HMul.hMul ``instHMul 1 ``Real.instMul t1 t2
  
  | Tree.node "div" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkRealHBinAppExpr ``HDiv.hDiv ``instHDiv 1 ``Real.instDivReal t1 t2
  
  | Tree.node "pow" #[t1, t2] => do
      let t1 ← toExpr vars params t1
      let t2 ← toExpr vars params t2
      return mkRealHBinAppExpr ``HPow.hPow ``instHPow 2 ``Real.instPow t1 t2
  
  | Tree.node "qol" #[t1, t2] =>
      EggTree.toExpr vars params (Tree.node "div" #[Tree.node "pow" #[t1, Tree.leaf "2"], t2])
  
  | Tree.node "geo" #[t1, t2] =>
      EggTree.toExpr vars params (Tree.node "sqrt" #[Tree.node "mul" #[t1, t2]])
  
  | Tree.node "lse" #[t1, t2] =>
      EggTree.toExpr vars params (Tree.node "log" #[Tree.node "add" #[
        Tree.node "exp" #[t1],
        Tree.node "exp" #[t2]]])
  
  | Tree.node "norm2" #[t1, t2] =>
    EggTree.toExpr vars params (Tree.node "sqrt" #[Tree.node "add" #[
      Tree.node "pow" #[t1, Tree.leaf "2"],
      Tree.node "pow" #[t2, Tree.leaf "2"]]])
  
  | Tree.node "constr" #[Tree.leaf s, t] => do
      let t ← toExpr vars params t
      return Meta.mkLabel (Name.mkSimple s) t
  
  | Tree.node op children =>
      throwPreDCPError "conversion error, unexpected op {op} with {children.size} children."
where
  mkRealHBinAppExpr (opName instHName : Name) (nTyArgs : Nat) (instName : Name)
    (e1 e2 : Expr) : Expr :=
    let R := Lean.mkConst ``Real
    let inst := mkAppN (mkConst instHName (List.replicate nTyArgs levelZero))
      (Array.mk (List.replicate nTyArgs R) ++ [Lean.mkConst instName])
    mkAppN
      (mkConst opName [levelZero, levelZero, levelZero])
      #[R, R, R, inst, e1, e2]

def EggString.toExpr (vars params : List Name) (s : String) : MetaM Expr :=
  EggString.toEggTree s >>= EggTree.toExpr (vars.map toString) (params.map toString)

inductive Tree (α β : Type) : Type
  | node (val : α) (children : Array (Tree α β)) : Tree α β
  | leaf (val : β) : Tree α β
deriving Inhabited

partial def toMessageData [ToMessageData α] [ToMessageData β] : Tree α β → MessageData
  | Tree.node val children =>
      let children := children.map toMessageData
      MessageData.paren <|
        "node:" ++ ToMessageData.toMessageData val ++
        "[" ++ (MessageData.joinSep children.toList ", ") ++ "]"
  | Tree.leaf val => "leaf:" ++ ToMessageData.toMessageData val

instance [ToMessageData α] [ToMessageData β] : ToMessageData (Tree α β) where
  toMessageData := toMessageData

partial def zip [Inhabited α] [Inhabited β] [ToMessageData α] [ToMessageData β] [ToMessageData γ]
    [ToMessageData δ] : Tree α γ → Tree β δ → MetaM (Tree (α × β) (γ × δ))
  | node val₁ children₁, node val₂ children₂ => do
      return node (val₁, val₂) (← Array.zipWithM zip children₁ children₂)
  | leaf val₁, leaf val₂ => pure <| leaf (val₁, val₂)
  | t1, t2 => throwError "zipped trees do not match {t1} {t2}"

partial def map [Inhabited γ] (f : α → γ) (g : β → δ) : Tree α β → Tree γ δ
  | node val children => node (f val) (children.map (map f g))
  | leaf val => leaf <| g val

partial def fold [Inhabited γ] (init : γ) (f : γ → α → γ) : Tree α β → γ
  | node val children => Id.run do
      let mut res := init
      for child in children do
        res ← fold res f child
      res := f res val
      return res
  | leaf _ => init

def val : Tree α α → α
  | node val _ => val
  | leaf val => val

partial def size : Tree α β → Nat
  | node _ children => children.foldl (fun acc child => acc + size child) 0
  | leaf _ => 1

def opMap : HashMap String (String × Array EggTreeOpArgTag) :=
  HashMap.ofList [
    ("prob",        ("prob",    #[.arg, .arg])),
    ("objFun",      ("objFun",  #[.arg])),
    ("constr",      ("constr",  #[.arg, .arg])),
    ("constrs",     ("constrs", #[])),
    ("maximizeNeg", ("neg",     #[.arg])),
    ("var",         ("var",     #[.arg])),
    ("param",       ("param",   #[.arg])),
    ("eq",          ("eq",      #[.arg, .arg])),
    ("le",          ("le",      #[.arg, .arg])),
    ("lt",          ("lt",      #[.arg, .arg])),
    ("neg",         ("neg",     #[.arg])),
    ("inv",         ("inv",     #[.arg])),
    ("oneDiv",      ("div",     #[.val "1", .arg])),
    ("abs",         ("abs",     #[.arg])),
    ("sqrt",        ("sqrt",    #[.arg])),
    ("powOne",      ("pow",     #[.arg, .val "1"])),
    ("sq",          ("pow",     #[.arg, .val "2"])),
    ("powNegOne",   ("pow",     #[.arg, .val "-1"])),
    ("powNegTwo",   ("pow",     #[.arg, .val "-2"])),
    ("log",         ("log",     #[.arg])),
    ("exp",         ("exp",     #[.arg])),
    ("xexp",        ("xexp",    #[.arg])),
    ("entr",        ("entr",    #[.arg])),
    ("min",         ("min",     #[.arg, .arg])),
    ("max",         ("max",     #[.arg, .arg])),
    ("add",         ("add",     #[.arg, .arg])),
    ("sub",         ("sub",     #[.arg, .arg])),
    ("mul",         ("mul",     #[.arg, .arg])),
    ("mul1",        ("mul",     #[.arg, .arg])),
    ("mul2",        ("mul",     #[.arg, .arg])),
    ("mul3",        ("mul",     #[.arg, .arg])),
    ("mul4",        ("mul",     #[.arg, .arg])),
    ("div",         ("div",     #[.arg, .arg])),
    ("div1",        ("div",     #[.arg, .arg])),
    ("div2",        ("div",     #[.arg, .arg])),
    ("quadOverLin", ("qol",     #[.arg, .arg])),
    ("geoMean",     ("geo",     #[.arg, .arg])),
    ("logSumExp₂",  ("lse",     #[.arg, .arg])),
    ("norm2₂",      ("norm2",   #[.arg, .arg]))
  ]

partial def adjustOps (t : EggTree) : MetaM EggTree := do
  match t with
  | Tree.node op children =>
      if let some (newOp, argTags) := opMap.find? op then
        let mut children ← children.mapM adjustOps
        let argsGiven := children.size
        let mut newChildren := #[]
        let mut arityError := false
        for argTag in argTags do
          match argTag with
          | .arg =>
              if children.size > 0 then
                newChildren := newChildren.push children[0]!
                children := children.drop 1
              else
                arityError := true
                break
          | .val s =>
              newChildren := newChildren.push (Tree.leaf s)
        arityError := arityError || children.size > 0
        if arityError then
          throwError s!"The op {op} was passed {argsGiven} arguments."
        else
        return Tree.node newOp newChildren
      else
        throwError s!"The atom {op} is not supported by the `pre_dcp` tactic."
  | Tree.leaf "unknown" => throwError "Unknown atom."
  | l => return l

partial def getNumericalValue? : EggTree → Option Float
  | Tree.leaf s =>
      Option.map Float.ofInt s.toInt?
  | Tree.node "neg" #[(t : EggTree)] =>
      Option.map Float.neg t.getNumericalValue?
  | Tree.node "sqrt" #[(t : EggTree)] =>
      Option.map Float.sqrt t.getNumericalValue?
  | Tree.node "log" #[(t : EggTree)] =>
      Option.map Float.log t.getNumericalValue?
  | Tree.node "exp" #[(t : EggTree)] =>
      Option.map Float.exp t.getNumericalValue?
  | Tree.node "add" #[(t1 : EggTree), (t2 : EggTree)] =>
      Option.map₂ Float.add t1.getNumericalValue? t2.getNumericalValue?
  | Tree.node "sub" #[(t1 : EggTree), (t2 : EggTree)] =>
      Option.map₂ Float.sub t1.getNumericalValue? t2.getNumericalValue?
  | Tree.node "mul" #[(t1 : EggTree), (t2 : EggTree)] =>
      Option.map₂ Float.mul t1.getNumericalValue? t2.getNumericalValue?
  | Tree.node "div" #[(t1 : EggTree), (t2 : EggTree)] =>
      Option.map₂ Float.div t1.getNumericalValue? t2.getNumericalValue?
  | Tree.node "pow" #[(t1 : EggTree), (t2 : EggTree)] =>
      Option.map₂ Float.pow t1.getNumericalValue? t2.getNumericalValue?
  | _ => none

def getIdentifier? (vars : List String) : EggTree → Option String
  | Tree.node "var" #[Tree.leaf s] => if s ∈ vars then some s else none
  | Tree.node "param" #[Tree.leaf s] => if s ∈ vars then some s else none
  | _ => none

partial def toEggString : EggTree → String
  | Tree.node n children =>
    let childrenStr := (children.map EggTree.toEggString).data
    "(" ++ n ++ " " ++ (" ".intercalate childrenStr) ++ ")"
  | Tree.leaf n => n

partial def size : EggTree → Nat
  | Tree.node "var" _ => 1
  | Tree.node _ children => 1 + (children.map size).foldl Nat.add 0
  | Tree.leaf _ => 1

def ofEggOCTree (ocTree : EggOCTree) : EggTree :=
  let objFun := ocTree.objFun.2
  let constrs := ocTree.constrs.map
    fun (h, c) => Tree.node "constr" #[Tree.leaf h, c]
  let objFunNode := Tree.node "objFun" #[objFun]
  let constrNode := Tree.node "constrs" constrs
  Tree.node "prob" #[objFunNode, constrNode]

def processDomainExprTree (h : String) (c : Tree String String) (vars : List String) :
    Option EggDomainIdentified :=
  
  
  
  
  
  match c with
  | Tree.node "le" #[(tl : EggTree), (tr : EggTree)] =>
      match (tl.getIdentifier? vars, tr.getNumericalValue?) with
      
      | (some v, some f) => some ⟨h, v, ⟨"-inf", f.toString, "1", "0"⟩⟩
      | _ =>
        match (tl.getNumericalValue?, tr.getIdentifier? vars) with
        
        | (some f, some v) => some ⟨h, v, ⟨f.toString, "inf", "0", "1"⟩⟩
        | _ => none
  | Tree.node "lt" #[(tl : EggTree), (tr : EggTree)] =>
      match (tl.getIdentifier? vars, tr.getNumericalValue?) with
      
      | (some v, some f) => some ⟨h, v, ⟨"-inf", f.toString, "1", "1"⟩⟩
      | _ =>
        match (tl.getNumericalValue?, tr.getIdentifier? vars) with
        
        | (some f, some v) => some ⟨h, v, ⟨f.toString, "inf", "1", "1"⟩⟩
        | _ => none
  | Tree.node "eq" #[(tl : EggTree), (tr : EggTree)] =>
      match (tl.getIdentifier? vars, tr.getNumericalValue?) with
      
      | (some v, some f) => some ⟨h, v, ⟨f.toString, f.toString, "0", "0"⟩⟩
      | _ =>
        match (tl.getNumericalValue?, tr.getIdentifier? vars) with
        
        | (some f, some v) => some ⟨h, v, ⟨f.toString, f.toString, "0", "0"⟩⟩
        | _ => none
  | _ => none

def fromComponents (objFun : String × EggTree) (constr : Array (String × EggTree))
    (vars : List String) : MetaM EggOCTreeExtended := do
  let mut domainConstrs := #[]
  let mut i := 0
  for (h, c) in constr do
    
    let h := s!"{i}:" ++ h
    match processDomainExprTree h c vars with
    | some r => domainConstrs := domainConstrs.push r
    | none => pure ()
    i := i + 1

  
  let ocTree : OC (String × EggTree) := {
    objFun := (objFun.1, ← EggTree.adjustOps objFun.2)
    constrs := ← constr.mapIdxM <|
      
      fun i (h, c) => return (s!"{i}:" ++ h, ← EggTree.adjustOps c) }
  return (ocTree, domainConstrs)

def fromMinimization (e : MinimizationExpr) (vars : List String) :
    MetaM EggOCTreeExtended := do
  let ocTree ← UncheckedDCP.uncheckedTreeFromMinimizationExpr e
  fromComponents ocTree.objFun ocTree.constrs vars

def ofEggOCTree (oc : EggOCTree) : EggMinimization :=
  { objFun := EggTree.toEggString oc.objFun.2,
    constrs := Array.data <| oc.constrs.map fun (h, c) => (h, EggTree.toEggString c) }

def preparePositivity (mvarId : MVarId) : MetaM MVarId := do
  mvarId.withContext do
    
    let mut hyps := #[]
    let le_lemmas := [``sub_nonneg_of_le, ``neg_nonneg_of_nonpos]
    let lt_lemmas := [``sub_pos_of_lt, ``neg_pos_of_neg]
    let mut lctx ← getLCtx
    for localDecl in lctx do
      let ty := localDecl.type
      for (le_or_lt, lemmas) in [(``LE.le, le_lemmas), (``LT.lt, lt_lemmas)] do
        let ty := Expr.consumeMData ty
        match ty.app4? le_or_lt with
        | some (R, _, lhs, rhs) =>
            if !(← isDefEq R q(ℝ)) then
              continue
            if ← isDefEq lhs q(0 : ℝ) then
              continue
            let le_or_lt_lemma :=
              if ← isDefEq rhs q(0 : ℝ) then lemmas[1]! else lemmas[0]!
            
            let val ← mkAppM le_or_lt_lemma #[localDecl.toExpr]
            let ty ← inferType val
            let n := localDecl.userName
            hyps := hyps.push (Hypothesis.mk n ty val)
        | none => continue

    let (_, mvarId) ← mvarId.assertHypotheses hyps

    
    let goalExpr ← mvarId.getType
    let mut mvarId := mvarId
    let le_lemmas := [``le_of_sub_nonneg, ``nonpos_of_neg_nonneg]
    let lt_lemmas := [``lt_of_sub_pos, ``neg_of_neg_pos]
    for (le_or_lt, lemmas) in [(``LE.le, le_lemmas), (``LT.lt, lt_lemmas)] do
      match goalExpr.app4? le_or_lt with
        | some (R, _, lhs, rhs) =>
            if !(← isDefEq R q(ℝ)) then
              continue
            if ← isDefEq lhs q(0 : ℝ) then
              continue
            let le_or_lt_lemma :=
              if ← isDefEq rhs q(0 : ℝ) then lemmas[1]! else lemmas[0]!
            if let [g] ← mvarId.applyConst le_or_lt_lemma then
              mvarId := g
              break
            else
              throwError "`prepare_positivity` failed."
        | none => continue

    return mvarId

def positivityMaybeNormNum : TacticM Unit :=
  withMainContext do
    let g ← getMainGoal
    let t : Q(Prop) ← withReducible g.getType'
    let fvs := (collectFVars {} t).fvarSet
    let tac ←
      if fvs.isEmpty then
        `(tactic| (try { norm_num } <;> positivity))
      else
        `(tactic| positivity)
    let [] ← evalTacticAt tac g | throwError "`positivity_maybe_norm_num` failed."

abbrev GraphAtomDataTree := Tree GraphAtomData Expr

abbrev Argument := Expr

abbrev Arguments := Array Argument

abbrev ArgumentsTree := Tree Arguments Unit

abbrev CurvatureTree := Tree Curvature Curvature

abbrev BCond := Expr

abbrev BConds := Array BCond

abbrev BCondsTree := Tree BConds BConds

abbrev AtomDataTrees := GraphAtomDataTree × ArgumentsTree × CurvatureTree × BCondsTree

abbrev NewVarsTree := Tree (Array LocalDecl) Unit

abbrev NewConstrVarsTree := Tree (Array LocalDecl) Unit

abbrev NewConstrsTree := Tree (Array Expr) Unit

abbrev PreVCond := Nat ⊕ Expr

abbrev PreVConds := Array PreVCond

abbrev PreVCondsTree := Tree PreVConds Unit

abbrev VCond := Expr

abbrev VConds := Array VCond

abbrev VCondsTree := Tree VConds Unit

abbrev VCondIdx := Nat

abbrev VCondsIdxs := Array VCondIdx

abbrev VCondsIdxsTree := Tree VCondsIdxs Unit

abbrev NewVarAssignment := Expr

abbrev Solution := Array NewVarAssignment

abbrev CanonExpr := Expr

abbrev CanonExprsTree := Tree CanonExpr CanonExpr

structure CanonExprWithSolution where
  canonExpr : CanonExpr
  solution : Solution
deriving Inhabited

abbrev CanonExprsWithSolutionTree := Tree CanonExprWithSolution CanonExprWithSolution

abbrev SolEqAtomProof := Expr

abbrev SolEqAtomProofsTree := Tree SolEqAtomProof SolEqAtomProof

abbrev FeasibilityProof := Expr

abbrev FeasibilityProofs := Array FeasibilityProof

abbrev FeasibilityProofsTree := Tree FeasibilityProofs Unit

abbrev OptimalityProof := Expr

abbrev OptimalityProofsTree := Tree OptimalityProof OptimalityProof

abbrev VCondElimProof := Expr

abbrev VCondElimProofs := Array VCondElimProof

abbrev OptimalityAndVCondElimProofs := OptimalityProof × VCondElimProofs

abbrev OptimalityAndVCondElimProofsTree :=
  Tree OptimalityAndVCondElimProofs OptimalityAndVCondElimProofs

abbrev VCondElimMap := Std.HashMap Nat Expr

structure ProcessedAtomTree where
  originalVarsDecls : Array LocalDecl
  originalConstrVars : Array LocalDecl
  newVarDecls : List LocalDecl
  newConstrs : Array Expr
  newConstrVarsArray : Array LocalDecl
  forwardImagesNewVars : Array Expr
  constraints : List (Name × Expr)
  isVCond : Array Bool
  vcondElimMap : VCondElimMap
  solEqAtom : OC SolEqAtomProofsTree
  feasibility : OC FeasibilityProofsTree
  canonExprs : OC CanonExprsTree
  optimality : OC OptimalityProofsTree
deriving Inhabited

structure SimpleVarSol where
  name  : String
  value : Float

instance : ToString SimpleVarSol where
  toString v :=
    v.name ++ "* = " ++ toString v.value

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

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

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

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

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

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

equivalence* eqv/q : p :=

def addAtomDataDecls (id : Name) (atomData : GraphAtomData) : CommandElabM GraphAtomData := do
  if atomData.solEqAtom.hasMVar then
    throwAtomDeclarationError "could not add auxiliary {atomData.solEqAtom} has a metavariable."
  let solEqAtom ← addAtomDataDecl (id.mkStr "solEqAtom") atomData.solEqAtom
  let optimality ← addAtomDataDecl (id.mkStr "optimality") atomData.optimality
  let mut feasibility := #[]
  for i in [:atomData.feasibility.size] do
    feasibility := feasibility.push <|
      ← addAtomDataDecl (id.mkStr (s!"feasibility{i}")) atomData.feasibility[i]!
  let mut vcondElim := #[]
  for i in [:atomData.vcondElim.size] do
    vcondElim := vcondElim.push <|
      ← addAtomDataDecl (id.mkStr (s!"vcondElim{i}")) atomData.vcondElim[i]!
  return { atomData with
    solEqAtom := solEqAtom
    feasibility := feasibility
    vcondElim := vcondElim
    optimality := optimality }
where
  addAtomDataDecl (name : Name) (expr : Expr) : CommandElabM Expr := do
    liftCoreM <| addDecl <| .defnDecl {
      name := name
      levelParams := []
      type := ← liftTermElabM (do return ← inferType expr)
      value := expr
      hints := .regular (getMaxHeight (← getEnv) expr + 1)
      safety := .safe
    }
    return mkConst name

def withCopyOfMonoXs {α} [Inhabited α] (xs : Array Expr) (argKinds : Array ArgKind)
    (f : Array Expr → Array Expr → Array ArgKind → TermElabM α) : TermElabM α := do
  
  let mut argDeclInfo : Array (Name × (Array Expr → TermElabM Expr)) := #[]
  let mut monoXs := #[]
  let mut monoArgKind := #[]
  for i in [:xs.size] do
    if argKinds[i]! != ArgKind.Constant ∧ argKinds[i]! != ArgKind.Neither then
      let ty := ← inferType xs[i]!
      let name := (← FVarId.getDecl xs[i]!.fvarId!).userName
      argDeclInfo := argDeclInfo.push (name, fun _ => pure ty)
      monoXs := monoXs.push xs[i]!
      monoArgKind := monoArgKind.push argKinds[i]!

  withLocalDeclsD argDeclInfo fun ys => do
    f monoXs ys monoArgKind

def shiftingArgs (curv : Curvature) (xs : Array Expr) (argKinds : Array ArgKind)
    (mkConcl : Array Expr → Array Expr → TermElabM Expr) : TermElabM Expr :=
  withCopyOfMonoXs xs argKinds fun monoXs ys monoArgKind => do
    let mut ty := ← mkConcl monoXs ys
    for i' in [:monoXs.size] do
      let i := monoXs.size - 1 - i'
      ty ← match curvatureInArg curv monoArgKind[i]! with
      | Curvature.Concave => mkArrow (← mkLe monoXs[i]! ys[i]!) ty
      | Curvature.Convex => mkArrow (← mkLe ys[i]! monoXs[i]!) ty
      | Curvature.ConvexSet => mkArrow (← mkLe monoXs[i]! ys[i]!) ty
      | _ => throwAtomDeclarationError "invalid curvature"
    return ← mkForallFVars ys ty

def withCopyOfNonConstVars (xs : Array Expr) (argKinds : Array ArgKind)
    (f : Array Expr → Array Expr → TermElabM Expr) : TermElabM Expr := do
  
  let mut argDeclInfo : Array (Name × (Array Expr → TermElabM Expr)) := #[]
  for i in [:xs.size] do
    if argKinds[i]! != ArgKind.Constant then
      let ty := ← inferType xs[i]!
      let name := Name.mkSimple ((toString (← FVarId.getDecl xs[i]!.fvarId!).userName) ++ "'")
      argDeclInfo := argDeclInfo.push (name, fun _ => pure ty)

  withLocalDeclsD argDeclInfo fun ys => do
    let mut allYs := #[]
    let mut j := 0
    for i in [:xs.size] do
      
      if argKinds[i]! == ArgKind.Constant then
        allYs := allYs.push xs[i]!
      else
        allYs := allYs.push ys[j]!
        j := j + 1
    return ← f ys allYs

def mapNonConstant (xs : Array Expr) (argKinds : Array ArgKind) (f : Expr → TermElabM Expr) :
    TermElabM (Array Expr) :=
  (Array.zip xs argKinds).mapM fun (x, kind) => do
    if kind == ArgKind.Constant then return x else return ← f x

def elabCurvature (curv : Syntax) : TermElabM Curvature := do
  match curv.getId with
  | `«convex» => return Curvature.Convex
  | `«concave» => return Curvature.Concave
  | `«affine» => return Curvature.Affine
  | `«convex_set» => return Curvature.ConvexSet
  | _ => throwAtomDeclarationError "unknown curvature {curv.getId}."

def elabArgKindsAux : List Syntax → TermElabM (List LocalDecl × List ArgKind)
  | [] => pure ([], [])
  | List.cons stx stxs => do
      match stx with
      | `(arg_with_kind| ($id : $ty) $argkind) => do
          let ty ← Term.elabTerm ty.raw none
          withLocalDeclD id.getId ty fun x => do
            let argkind ←
              match argkind.raw with
                | `(arg_kind| +) => pure ArgKind.Increasing
                | `(arg_kind| -) => pure ArgKind.Decreasing
                | `(arg_kind| ?) => pure ArgKind.Neither
                | `(arg_kind| &) => pure ArgKind.Constant
                | _ => throwAtomDeclarationError "unknown argument kind {argkind}."
            let argDecl ← x.fvarId!.getDecl
            let (argDecls, argKinds) ← elabArgKindsAux stxs
            return (argDecl :: argDecls, argkind :: argKinds)
      | _ => throwUnsupportedSyntax

def elabArgKinds (args : Array Syntax) : TermElabM (Array LocalDecl × Array ArgKind) := do
  let (argDecls, argKinds) ← elabArgKindsAux args.toList
  return (argDecls.toArray, argKinds.toArray)

def elabExpr (expr : Syntax) (argDecls : Array LocalDecl) (ty : Option Expr := none) :
    TermElabM (Expr × Expr) := do
  withExistingLocalDecls argDecls.toList do
    let xs := argDecls.map (mkFVar ·.fvarId)
    let body ← elabTermAndSynthesizeEnsuringType expr ty
    let bodyTy ← inferType body
    return (← mkLambdaFVars xs body, bodyTy)

def elabVConditions (argDecls : Array LocalDecl) (vcondStx : Array Syntax) :
    TermElabM (Array (Name × Expr) × Std.HashMap Name Expr) := do
  withExistingLocalDecls argDecls.toList do
    let xs := argDecls.map (mkFVar ·.fvarId)
    let mut vcondMap : Std.HashMap Name Expr := {}
    let mut vconds : Array (Name × Expr) := #[]
    for stx in vcondStx do
      let vcond ← elabTermAndSynthesizeEnsuringType stx[3] (some <| mkSort levelZero)
      let vcond ← mkLambdaFVars xs vcond
      vcondMap := vcondMap.insert stx[1].getId vcond
      vconds := vconds.push (stx[1].getId, vcond)
    return (vconds, vcondMap)

def elabBConds := elabVConditions

def elabImpVars (argDecls : Array LocalDecl) (impVarsStx : Array Syntax) :
    TermElabM (Array (Name × Expr) × Std.HashMap Name Expr) := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let mut impVarMap : Std.HashMap Name Expr := {}
    let mut impVars : Array (Name × Expr) := #[]
    for stx in impVarsStx do
      let impVarTy ← Elab.Term.elabTermAndSynthesizeEnsuringType stx[3] none
      let impVarTy ← mkLambdaFVars xs <| impVarTy
      impVars := impVars.push (stx[1].getId, impVarTy)
      impVarMap := impVarMap.insert stx[1].getId impVarTy
    return (impVars, impVarMap)

def elabImpObj (argDecls : Array LocalDecl) (impVars : Array (Name × Expr))
    (impObjStx : Syntax) (bodyTy : Expr) : TermElabM Expr := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    
    withLocalDeclsDNondep (impVars.map fun iv => (iv.1, mkAppNBeta iv.2 xs)) fun vs => do
      let impObj ← Elab.Term.elabTermAndSynthesizeEnsuringType impObjStx (some bodyTy)
      return ← mkLambdaFVars (xs ++ vs) impObj

def elabImpConstrs (argDecls : Array LocalDecl) (impVars : Array (Name × Expr))
    (bconds : Array (Name × Expr)) (impConstrStx : Array Syntax) :
    TermElabM (Array (Name × Expr)) := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let bconds := bconds.map fun (n,c) => (n, mkAppNBeta c xs)
    
    withLocalDeclsDNondep bconds fun as => do
      
      withLocalDeclsDNondep (impVars.map fun iv => (iv.1, mkAppNBeta iv.2 xs)) fun vs => do
        let mut impConstrs : Array (Name × Expr) := #[]
        for stx in impConstrStx do
          let impConstr ← Elab.Term.elabTermAndSynthesizeEnsuringType stx[3] (mkSort levelZero)
          let impConstr ← mkLambdaFVars (xs ++ as ++ vs) impConstr
          trace[CvxLean.debug] "`elabImpConstrs` constraint type {← inferType impConstr}."
          impConstrs := impConstrs.push (stx[1].getId, impConstr)
        return impConstrs

def elabSols (argDecls : Array LocalDecl) (impVars : Array (Name × Expr))
    (impVarMap : Std.HashMap Name Expr) (solStx : Array Syntax) : TermElabM (Array Expr) := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let impVarNames := impVars.map (·.fst)
    let mut solMap : Std.HashMap Name Expr := {}
    for stx in solStx do
      let id ← match impVarMap.find? stx[1].getId with
        | some id => pure id
        | none => throwAtomDeclarationError "unknown variable in solution {stx[1].getId}."
      let ty := mkAppNBeta id xs
      solMap := solMap.insert stx[1].getId <|
        ← Elab.Term.elabTermAndSynthesizeEnsuringType stx[3] (some ty)
    let sols ← impVarNames.mapM
      fun n => match solMap.find? n with
        | some sol => pure sol
        | none => throwAtomDeclarationError "solution not found for {n}."
    let sols ← sols.mapM (do return ← mkLambdaFVars xs ·)
    return sols

def elabSolEqAtom (argDecls : Array LocalDecl) (vconds : Array (Name × Expr))
    (impObj : Expr) (sols : Array Expr)
    (expr : Expr) (stx : Syntax) : TermElabM Expr := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let vconds := vconds.map fun (n,c) => (n, mkAppNBeta c xs)
    withLocalDeclsDNondep vconds fun cs => do
      let body := mkAppNBeta expr xs
      let impObj := mkAppNBeta impObj xs
      let sols := sols.map (mkAppNBeta · xs)
      let impObj' := convertLambdasToLets impObj sols
      let ty ← mkEq impObj' body
      trace[CvxLean.debug] "`elabSolEqAtom` ensuring type {ty}."
      let solEqAtom ← Elab.Term.elabTermAndSynthesizeEnsuringType stx (some ty)
      mkLambdaFVars (xs ++ cs) solEqAtom

def elabFeas (argDecls : Array LocalDecl) (vconds bconds : Array (Name × Expr))
    (impConstrs : Array (Name × Expr)) (sols : Array Expr) (stx : Array Syntax) :
    TermElabM (Array Expr) := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let bconds := bconds.map fun (n, c) => (n, mkAppNBeta c xs)
    
    withLocalDeclsDNondep bconds fun as => do
      let vconds := vconds.map fun (n, c) => (n, mkAppNBeta c xs)
      
      withLocalDeclsDNondep vconds fun cs => do
      let impConstrs := impConstrs.map fun (n, c) => (n, mkAppNBeta c xs)
      let impConstrs := impConstrs.map fun (n, c) => (n, mkAppNBeta c as)
      let sols := sols.map (mkAppNBeta · xs)
      let mut feas := #[]
      for i in [:impConstrs.size] do
        if (stx[i]!)[1]!.getId != impConstrs[i]!.1 then
          throwAtomDeclarationError
            "expected feasibility {impConstrs[i]!.1}, found {(stx[i]!)[1]!}."
        let ty := convertLambdasToLets impConstrs[i]!.2 sols
        let f ← Elab.Term.elabTermAndSynthesizeEnsuringType (stx[i]!)[3]! (some ty)
        let f ← mkLambdaFVars (xs ++ as ++ cs) f
        feas := feas.push f
      return feas

def elabOpt (curv : Curvature) (argDecls : Array LocalDecl) (expr : Expr)
    (bconds : Array (Name × Expr)) (impVars : Array (Name × Expr)) (impObj : Expr)
    (impConstrs : Array (Name × Expr)) (argKinds : Array ArgKind) (stx : Syntax) :
    TermElabM Expr := do
  withExistingLocalDecls argDecls.toList do
    
    let xs := argDecls.map (mkFVar ·.fvarId)
    let bconds := bconds.map fun (n,c) => (n, mkAppNBeta c xs)
    
    withLocalDeclsDNondep bconds fun as => do
      
      withLocalDeclsDNondep (impVars.map fun iv => (iv.1, mkAppNBeta iv.2 xs)) fun vs => do
        let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c xs)
        let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c as)
        let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c vs)
        
        withLocalDeclsDNondep impConstrs fun is => do
          let impObj := mkAppNBeta (mkAppNBeta impObj xs) vs
          let ty ← shiftingArgs curv xs argKinds fun monoXs ys =>
            let body := mkAppNBeta expr xs
            let body := body.replaceFVars monoXs ys
            match curv with
              | Curvature.Concave => return ← mkLe impObj body
              | Curvature.Convex => return ← mkLe body impObj
              | Curvature.ConvexSet => return ← mkLe impObj body
              | _ => throwAtomDeclarationError
                  "invalid curvature {curv} found when setting up optimality proof obligation."
          trace[CvxLean.debug] "`elabOpt` ensuring {ty}."
          let opt ← Elab.Term.elabTermAndSynthesizeEnsuringType stx (some ty)
          trace[CvxLean.debug] "`elabOpt` proof term type {← inferType opt}."
          mkLambdaFVars (xs ++ as ++ vs ++ is) opt

def elabVCondElim (curv : Curvature) (argDecls : Array LocalDecl)
    (bconds vconds : Array (Name × Expr)) (vcondMap : Std.HashMap Name Expr)
    (impVars : Array (Name × Expr)) (impConstrs : Array (Name × Expr)) (argKinds : Array ArgKind)
    (stx : Array Syntax) : TermElabM (Array Expr) := do
  withExistingLocalDecls argDecls.toList do
    let xs := argDecls.map (mkFVar ·.fvarId)
    let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c xs)
    let bconds := bconds.map fun (n,c) => (n, mkAppNBeta c xs)
    
    withLocalDeclsDNondep bconds fun as => do
      let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c as)
      
      withLocalDeclsDNondep (impVars.map fun iv => (iv.1, mkAppNBeta iv.2 xs)) fun vs => do
        let impConstrs := impConstrs.map fun (n,c) => (n, mkAppNBeta c vs)
        
        withLocalDeclsDNondep impConstrs fun is => do
          let mut vcondElimMap : Std.HashMap Name Expr := {}
          for i in [:stx.size] do
            let ty ← shiftingArgs curv xs argKinds fun monoXs ys => do
              let id ← match vcondMap.find? (stx[i]!)[1]!.getId with
                | some id => pure id
                | none =>
                    throwAtomDeclarationError "unknown variable condition {(stx[i]!)[1]!.getId}."
              let body := mkAppNBeta id xs
              let body := body.replaceFVars monoXs ys
              return body
            let vcondElim ← Elab.Term.elabTermAndSynthesizeEnsuringType (stx[i]!)[3]! (some <| ty)
            let vcondElim ← mkLambdaFVars (xs ++ vs ++ is) vcondElim
            vcondElimMap := vcondElimMap.insert (stx[i]!)[1]!.getId vcondElim
          return ← vconds.mapM
            fun (n, _) => match vcondElimMap.find? n with
              | some vcond => pure vcond
              | none => throwAtomDeclarationError "variable condition {n} not found."

def elabHom (argDecls : Array LocalDecl) (expr : Expr) (argKinds : Array ArgKind) (stx : Syntax) :
    TermElabM Expr := do
  withExistingLocalDecls argDecls.toList do
    withLocalDeclD `κ (mkConst ``Real) fun κ => do
      let xs := argDecls.map (mkFVar ·.fvarId)
      let zero := mkAppNBeta expr <| ← mapNonConstant xs argKinds
        fun x => do return ← mkNumeral (← inferType x) 0
      let lhs ← mkAdd (← mkAppM ``HSMul.hSMul #[κ, mkAppNBeta expr xs]) zero
      let rhs ← mkAdd
        (mkAppNBeta expr <| ← mapNonConstant xs argKinds fun x => mkAppM ``HSMul.hSMul #[κ, x])
        (← mkAppM ``HSMul.hSMul #[κ, zero])
      let ty ← mkEq lhs rhs
      let hom ← Elab.Term.elabTermAndSynthesizeEnsuringType stx (some ty)
      return ← mkLambdaFVars xs <| ← mkLambdaFVars #[κ] hom

def elabAdd (argDecls : Array LocalDecl) (expr : Expr) (argKinds : Array ArgKind) (stx : Syntax) :
    TermElabM Expr := do
  withExistingLocalDecls argDecls.toList do
    let xs := argDecls.map (mkFVar ·.fvarId)
    withCopyOfNonConstVars xs argKinds fun newYs ys => do
      let zero := mkAppNBeta expr <| ← mapNonConstant xs argKinds
        fun x => do return ← mkNumeral (← inferType x) 0
      let lhs ← mkAdd (mkAppNBeta expr xs) (mkAppNBeta expr ys)
      let rhs ← mkAdd
        (mkAppNBeta expr <|
          ← (Array.zip argKinds (Array.zip xs ys)).mapM fun (kind, x, y) => do
            if kind == ArgKind.Constant then return x else mkAdd x y)
        zero
      let ty ← mkEq lhs rhs
      let add ← Elab.Term.elabTermAndSynthesizeEnsuringType stx (some ty)
      return ← mkLambdaFVars xs <| ← mkLambdaFVars newYs add

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

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 _

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

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

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

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

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

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

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

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

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)

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

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

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)

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

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

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

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'

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

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

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)

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

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

def minimizationVar : Parser :=
  leading_parser ((ident <|> Term.bracketedBinder) >> ppSpace)

def letVar : Parser :=
  leading_parser "with " >> Term.letDecl >> ppSpace

def constraint : Parser :=
  leading_parser (ppLine >> checkColGe >> ppGroup (Term.binderIdent >> " : " >> termParser))

def constraints : Parser :=
  leading_parser manyIndent constraint

def minOrMax :=
  leading_parser "minimize " <|> "maximize "

@[command_elab registerRuleToTactic] def elabRuleToTactic : CommandElab
| `(register_rule_to_tactic $rwName; $rwTarget => $rwGoal := $tac;) => do
    let rwNameStr := rwName.getString
    
    let _rwTargetStr := rwTarget.getString
    let _rwGoalStr := rwGoal.getString
    liftTermElabM <| addRuleToTacticEntry rwNameStr tac false
| _ => throwUnsupportedSyntax

@[command_elab registerObjFunRuleToTactic] def elabRegisterObjFunRuleToTactic : CommandElab
| `(register_objFun_rule_to_tactic $rwName; $rwTarget => $rwGoal := $tac;) => do
    let rwNameStr := rwName.getString
    
    let _rwTargetStr := rwTarget.getString
    let _rwGoalStr := rwGoal.getString
    liftTermElabM <| addRuleToTacticEntry rwNameStr tac true
| _ => throwUnsupportedSyntax

structure ReductionExpr where
  domainLHS : Expr
  domainRHS : Expr
  codomain : Expr
  codomainLHSreorder : Expr
  lhs : Expr
  rhs : Expr

def toMinimizationExprLHS (redExpr : ReductionExpr) : MetaM MinimizationExpr :=
  MinimizationExpr.fromExpr redExpr.lhs

def toMinimizationExprRHS (redExpr : ReductionExpr) : MetaM MinimizationExpr :=
  MinimizationExpr.fromExpr redExpr.rhs

def toExpr (redExpr : ReductionExpr) : Expr :=
  mkApp6 (mkConst ``Minimization.Reduction) redExpr.domainLHS redExpr.domainRHS redExpr.codomain
    redExpr.codomainLHSreorder redExpr.lhs redExpr.rhs

def fromExpr (e : Expr) : MetaM ReductionExpr := do
  match e with
  | .app (.app (.app (.app (.app (.app (.const ``Minimization.Reduction _)
      domainLHS) domainRHS) codomain) codomainLHSreorder) p) q => do
      return ReductionExpr.mk domainLHS domainRHS codomain codomainLHSreorder p q
  | _ => throwError "Expression not of the form `Minimization.Reduction ...`."

def fromGoal (goal : MVarId) : MetaM ReductionExpr := do
  let goalType ← whnf (← MVarId.getDecl goal).type
  fromExpr goalType

def invertible_toBlock_of_BlockTriangular [LinearOrder α] [Invertible M]
    (hM : BlockTriangular M b) (k : α) :
    Invertible (M.toBlock (fun i => b i < k) (fun i => b i < k)) :=
  invertibleOfLeftInverse _ ((⅟M).toBlock (fun i => b i < k) (fun i => b i < k))
    (by admit /- proof elided -/
      )

structure RelaxationExpr where
  domainP : Expr
  domainQ : Expr
  codomain : Expr
  codomainPreorder : Expr
  p : Expr
  q : Expr

def toMinimizationExprLHS (relExpr : RelaxationExpr) : MetaM MinimizationExpr :=
  MinimizationExpr.fromExpr relExpr.p

def toMinimizationExprRHS (relExpr : RelaxationExpr) : MetaM MinimizationExpr :=
  MinimizationExpr.fromExpr relExpr.q

def toExpr (relExpr : RelaxationExpr) : Expr :=
  mkApp6 (mkConst ``Minimization.Relaxation)
    relExpr.domainP relExpr.domainQ relExpr.codomain relExpr.codomainPreorder relExpr.p relExpr.q

def fromExpr (e : Expr) : MetaM RelaxationExpr := do
  match e with
  | .app (.app (.app (.app (.app (.app (.const ``Minimization.Relaxation _)
      domainP) domainQ) codomain) codomainPreorder) p) q => do
      return RelaxationExpr.mk domainP domainQ codomain codomainPreorder p q
  | _ => throwError "Expression not of the form `Minimization.Relaxation ...`."

def fromGoal (goal : MVarId) : MetaM RelaxationExpr := do
  let goalType ← whnf (← MVarId.getDecl goal).type
  fromExpr goalType

inductive TransformationGoal
  | Solution | Equivalence | Reduction | Relaxation

def isTransitive : TransformationGoal → Bool
  | TransformationGoal.Solution => false
  | _ => true

def fromExpr (e : Expr) : MetaM TransformationGoal := do
  let e ← whnf e
  if e.isAppOf ``Minimization.Solution then
    return TransformationGoal.Solution
  else if e.isAppOf ``Minimization.Equivalence then
    return TransformationGoal.Equivalence
  else if e.isAppOf ``Minimization.Reduction then
    return TransformationGoal.Reduction
  else if e.isAppOf ``Minimization.Relaxation then
    return TransformationGoal.Relaxation
  else
    trace[Meta.TacticBuilder] "{e.ctorName}"
    throwTacticBuilderError
      "expected a `Solution`, `Equivalence`, `Reduction` or `Relaxation` goal, got {e}."

def applyTransitivity (transf : TransformationGoal) (g : MVarId) : TacticM (MVarId × MVarId) :=
  g.withContext do
    if transf.isTransitive then
      let gsTrans ←
        match transf with
          | TransformationGoal.Relaxation => evalTacticAt (← `(tactic| relaxation_trans)) g
          | TransformationGoal.Reduction => evalTacticAt (← `(tactic| reduction_trans)) g
          | TransformationGoal.Equivalence => evalTacticAt (← `(tactic| equivalence_trans)) g
          | _ => pure []
      if gsTrans.length != 4 then
        throwTacticBuilderError "transitivity failed."
      let mut gToChange := gsTrans[0]!
      gToChange.setTag Name.anonymous
      let gNext := gsTrans[1]!
      gNext.setTag Name.anonymous
      return (gToChange, gNext)
    else
      return (g, g)

def RelaxationBuilder (α) := RelaxationExpr → MVarId → TacticM α

def toTactic {α} (builder : RelaxationBuilder α) : TacticM α := withMainContext do
  let transf ← TransformationGoal.fromExpr (← getMainTarget)

  
  let (gToChange, gNext) ← transf.applyTransitivity <| ← getMainGoal
  let mut gToChange := gToChange
  let mut gNext := gNext

  match transf with
    | TransformationGoal.Solution =>
        throwTacticBuilderError "relaxation tactic does not apply to `Solution`."
    | TransformationGoal.Equivalence => do
        throwTacticBuilderError "relaxation tactic does not apply to `Equivalence`."
    | TransformationGoal.Reduction => do
        throwTacticBuilderError "relaxation tactic does not apply to `Reduction`."
    | TransformationGoal.Relaxation => do
        pure ()

  
  let relExpr ← RelaxationExpr.fromExpr (← gToChange.getType)
  let res ← builder relExpr gToChange

  
  gNext.setTag Name.anonymous
  setGoals [gNext]

  return res

def ReductionBuilder (α) := ReductionExpr → MVarId → TacticM α

def toTactic {α} (builder : ReductionBuilder α) : TacticM α := do
  let transf ← TransformationGoal.fromExpr (← getMainTarget)

  
  let (gToChange, gNext) ← transf.applyTransitivity <| ← getMainGoal
  let mut gToChange := gToChange
  let mut gNext := gNext

  
  match transf with
    | TransformationGoal.Solution =>
        if let [red, sol, _, _] ← gToChange.apply (mkConst ``Minimization.Reduction.toBwd) then
          
          gToChange := red
          gNext := sol
        else
          throwTacticBuilderError "could not apply reduction tactic to `Solution`."
    | TransformationGoal.Equivalence => do
        throwTacticBuilderError "expected `Reduction`, found `Equivalence`."
    | TransformationGoal.Relaxation => do
        throwTacticBuilderError "expected `Reduction`, found `Relaxation`."
    | TransformationGoal.Reduction => do
        pure ()

  
  let redExpr ← ReductionExpr.fromExpr (← gToChange.getType)
  let res ← builder redExpr gToChange

  
  gNext.instantiateMVars
  setGoals [gNext]

  return res

def EquivalenceBuilder (α) := EquivalenceExpr → MVarId → TacticM α

def toTactic {α} (builder : EquivalenceBuilder α) : TacticM α := withMainContext do
  let transf ← TransformationGoal.fromExpr (← getMainTarget)

  
  let (gToChange, gNext) ← transf.applyTransitivity <| ← getMainGoal
  let mut gToChange := gToChange
  let mut gNext := gNext

  
  match transf with
    | TransformationGoal.Solution =>
        if let [eqv, sol, _, _] ← gToChange.apply (mkConst ``Minimization.Equivalence.toBwd) then
          
          gToChange := eqv
          gNext := sol
        else
          throwTacticBuilderError "could not apply equivalence tactic to `Solution`."
    | TransformationGoal.Equivalence => do
        pure ()
    | TransformationGoal.Reduction => do
        if let [eqv] ← gToChange.apply (mkConst ``Minimization.Reduction.ofEquivalence) then
          gToChange := eqv
        else
          throwTacticBuilderError "could not apply equivalence tactic to `Reduction`."
    | TransformationGoal.Relaxation => do
        throwTacticBuilderError "equivalence tactic does not apply to `Relaxation`."

  
  let eqvExpr ← EquivalenceExpr.fromExpr (← gToChange.getType)
  let res ← builder eqvExpr gToChange

  
  gNext.setTag Name.anonymous
  setGoals [gNext]

  return res

def runTransformationTactic (transf : TransformationGoal) (mvarId : MVarId) (stx : Syntax) :
    TermElabM Unit := do
  let tacticStx := ⟨stx[1]⟩
  let rflTacticStx ←
    match transf with
      | TransformationGoal.Solution => `(tactic| skip)
      | TransformationGoal.Equivalence => `(tactic| equivalence_rfl)
      | TransformationGoal.Reduction => `(tactic| reduction_rfl)
      | TransformationGoal.Relaxation => `(tactic| relaxation_rfl)
  let code ← `(tactic| $tacticStx <;> $rflTacticStx)

  instantiateMVarDeclMVars mvarId
  withInfoHole mvarId do
    let remainingGoals ← Tactic.run mvarId do
      withTacticInfoContext stx do
        evalTactic code

    match remainingGoals with
    | [] => pure ()
    | _ => reportUnsolvedGoals remainingGoals

    synthesizeSyntheticMVars (mayPostpone := false)

def elabTransformationProof (transf : TransformationGoal) (lhs : Expr) (rhsName : Name)
    (stx : Syntax) : TermElabM (Expr × Expr) := do
  withRef stx do
    let syntheticMVarsSaved := (← get).syntheticMVars
    modify fun s => { s with syntheticMVars := {} }
    try
      
      let lhs := ← do
        let (n, _) := lhs.getAppFnArgs
        if n != ``Minimization.mk then
          let r ← unfold lhs n
          return r.expr
        else
          return lhs

      
      let lhsMinExpr ← MinimizationExpr.fromExpr lhs
      let D := lhsMinExpr.domain
      let E ← Meta.mkFreshTypeMVar
      let R := lhsMinExpr.codomain
      let RPreorder ← Meta.mkFreshExprMVar (mkAppN (Lean.mkConst ``Preorder [levelZero]) #[R])
      let rhs ← Meta.mkFreshExprMVar (userName := rhsName)
        (mkAppN (Lean.mkConst ``Minimization) #[E, R])
      let transfTy :=
        match transf with
          | TransformationGoal.Solution =>
              mkAppN (mkConst ``Minimization.Solution) #[D, E, R, RPreorder, lhs]
          | TransformationGoal.Equivalence =>
              mkAppN (mkConst ``Minimization.Equivalence) #[D, E, R, RPreorder, lhs, rhs]
          | TransformationGoal.Reduction =>
              mkAppN (mkConst ``Minimization.Reduction) #[D, E, R, RPreorder, lhs, rhs]
          | TransformationGoal.Relaxation =>
              mkAppN (mkConst ``Minimization.Relaxation) #[D, E, R, RPreorder, lhs, rhs]

      
      let proof ← elabTerm stx (some transfTy)

      let some mvarDecl ← getSyntheticMVarDecl? proof.mvarId! |
        throwTacticBuilderError "`SyntheticMVarDecl` not found."

      modify fun s => { s with syntheticMVars := {} }

      match mvarDecl.kind with
      | SyntheticMVarKind.tactic tacticCode savedContext =>
          withSavedContext savedContext do
            runTransformationTactic transf proof.mvarId! tacticCode
      | _ =>
          throwTacticBuilderError
            "expected `SyntheticMVarDecl` of kind `tactic`, got {mvarDecl.kind}"

      return (rhs, ← instantiateMVars proof)
    finally
      modify fun s => { s with syntheticMVars :=
        s.syntheticMVars.mergeBy (fun _ _ a => a) syntheticMVarsSaved }

inductive ScalarConeType
  | Zero | PosOrth | Exp | Q | QR

instance : ToString ScalarConeType where
  toString
  | Zero => "Zero"
  | PosOrth => "PosOrth"
  | Exp => "Exp"
  | Q => "Q"
  | QR => "QR"

structure ScalarAffine :=
  (n m : Nat)
  (A : Array (Array Float))
  (a : Array Float)
  (b : Float)

instance : ToString ScalarAffine where
  toString sa := s!"ScalarAffine [{sa.n}, {sa.m}, {sa.A}, {sa.a}, {sa.b}]"

structure MatrixAffine :=
  (n : Nat)
  (H : Array (Array (Array Float)))
  (D : Array (Array Float))

instance : ToString MatrixAffine where
  toString sa := s!"MatrixAffine [{sa.n}, {sa.H}, {sa.D}]"

structure ProblemData :=
  (objective : Option ScalarAffine)
  (scalarAffineConstraints : Array (ScalarAffine × ScalarConeType))
  (matrixAffineConstraints : Array MatrixAffine)

instance : ToString ProblemData where
  toString data :=
    let scalarConstraintsStr := data.scalarAffineConstraints.map
      (fun (sa, sct) => (toString sct) ++ " " ++ (toString sa))
    let matrixConstraintsStr := data.matrixAffineConstraints.map
      (fun ma => "PSD " ++ (toString ma))
    let constraintsStr := (scalarConstraintsStr ++ matrixConstraintsStr).map
      (fun s => "* " ++ s ++ "\n")
    "Objective: " ++ toString (data.objective) ++ "\n" ++
    "Constraints: \n" ++ String.join constraintsStr.data

def empty : ProblemData :=
  ProblemData.mk none #[] #[]

instance : Inhabited ProblemData where
  default := empty

def setObjective (data : ProblemData) (A : Array (Array Float)) (a : Array Float) (b : Float) :
    ProblemData :=
  { data with objective := ScalarAffine.mk A.size a.size A a b }

def setObjectiveOnlyVector (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.setObjective #[] a b

def setObjectiveOnlyMatrix (data : ProblemData) (A : Array (Array Float)) (b : Float) :
    ProblemData :=
  data.setObjective A #[] b

def addScalarAffineConstraint (data : ProblemData) (A : Array (Array Float)) (a : Array Float)
    (b : Float) (sct : ScalarConeType) : ProblemData :=
  let constraint := ScalarAffine.mk A.size a.size A a b
  { data with scalarAffineConstraints :=
      data.scalarAffineConstraints.push ⟨constraint, sct⟩ }

def addScalarAffineConstraintOnlyVector (data : ProblemData) (a : Array Float) (b : Float)
    (sct : ScalarConeType) : ProblemData :=
  data.addScalarAffineConstraint #[] a b sct

def addZeroConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Zero

def addExpConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Exp

def addPosOrthConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.addScalarAffineConstraintOnlyVector a b ScalarConeType.PosOrth

def addSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Q

def addRotatedSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData :=
  data.addScalarAffineConstraintOnlyVector a b ScalarConeType.QR

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 }

def ScalarAffineSections : Type := Array Nat

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

def RuleToTacticExtEntry : Type := String × ((TSyntax `tactic) × Bool)
deriving Inhabited

def RuleToTacticExtState : Type := HashMap String ((TSyntax `tactic) × Bool)
deriving Inhabited

def RuleToTacticExt : Type :=
  SimplePersistentEnvExtension RuleToTacticExtEntry RuleToTacticExtState
deriving Inhabited

def addRuleToTacticEntry (rwName : String) (tac : TSyntax `tactic) (mapObjFun : Bool) :
    MetaM Unit := do
  setEnv <| ruleToTacticExt.addEntry (← getEnv) (rwName, (tac, mapObjFun))

def getTacticFromRewriteName (rwName : String) : MetaM (Option (TSyntax `tactic × Bool)) := do
  return (ruleToTacticExt.getState (← getEnv)).find? rwName

def getRegisteredRewriteNames : MetaM (List String) := do
  return (ruleToTacticExt.getState (← getEnv)).toList.map (·.1)

@[term_elab prodField]
def elabProdField : TermElab :=
  fun stx _ => do
    match stx with
    | `($e # $t) => do
        match t.raw[0]! with
        | Syntax.atom _ val =>
            let some num := val.toNat? | throwUnsupportedSyntax
            let e ← elabTerm e none
            let ty ← instantiateMVars <| ← inferType e
            return ← mkProj e ty num
        | _ => throwUnsupportedSyntax
    | _ => throwUnsupportedSyntax
where
  mkProj (e ty : Expr) (n : Nat) (recursive := false) : TermElabM Expr :=
    match ty, n with
    | (.app (.app (.const ``Prod lvls) α) β), 1 => do
        return mkApp3 (mkConst ``Prod.fst lvls) α β e
    | _, 1 =>
        if recursive then return e else throwUnsupportedSyntax
    | (.app (.app (.const ``Prod lvls) α) β), (n + 1) => do
        let esnd := mkApp3 (mkConst ``Prod.snd lvls) α β e
        mkProj esnd β n true
    | _, _ => throwUnsupportedSyntax

@[delab app] partial def delabProdField : Delab := do
  let (stx, n) ← aux (← getExpr)
  return ← `($(⟨stx⟩) # $(quote n))
where
  aux (top : Expr) (first : Option Bool := none) : DelabM (Syntax × Nat) := do
     
    match first, ← getExpr with
    | none, .app (.app (.app (.const ``Prod.fst _) _) _) _ => do
        withNaryArg 2 do aux top true
    | _, .app (.app (.app (.const ``Prod.snd _) _) _) _ => do
        withNaryArg 2 do
          let (stx, n) ← aux top (first == some true)
          return (stx, n + 1)
    | none, _ => failure
    | true, _ => return (← delab, 1)
    | false, _ =>
        if (← Meta.inferType top).getAppFn.isConstOf ``Prod then failure else return (← delab, 1)

def expCone (x y z : ℝ) : Prop :=
  (0 < y ∧ y * exp (x / y) ≤ z) ∨ (y = 0 ∧ 0 ≤ z ∧ x ≤ 0)

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

def Variable.toSimpleVarSol (v : Sol.Variable) : SimpleVarSol :=
  { name := v.name,
    value := v.activity }

def SymmMatrixVariable.toSimpleMatrixVarSol (v : Sol.SymmMatrixVariable) :
    SimpleMatrixVarSol :=
  { name := v.name,
    I := v.I,
    J := v.J,
    value := v.primal }

def Result.toSolutionData (sol : Sol.Result) : SolutionData :=
  { status := sol.summary.problemStatus,
    varsSols := sol.vars.map Variable.toSimpleVarSol,
    matrixVarsSols := sol.symmMatrixVars.map SymmMatrixVariable.toSimpleMatrixVarSol }

inductive Curvature where
  | Constant | Affine | Concave | Convex | AffineSet | ConvexSet
deriving BEq, Inhabited

instance : ToMessageData Curvature where
  toMessageData
  | Curvature.Constant => "constant"
  | Curvature.Affine => "affine"
  | Curvature.Concave => "concave"
  | Curvature.Convex => "convex"
  | Curvature.AffineSet => "affine_set"
  | Curvature.ConvexSet => "convex_set"

inductive ArgKind where
  | Increasing | Decreasing | Neither | Constant
deriving BEq, Inhabited

instance : ToMessageData ArgKind where
  toMessageData
  | ArgKind.Increasing => "increasing"
  | ArgKind.Decreasing => "decreasing"
  | ArgKind.Neither => "neither"
  | ArgKind.Constant => "constant"

def curvatureInArg : Curvature → ArgKind → Curvature
  | _, ArgKind.Constant => Curvature.Constant
  | Curvature.Concave, ArgKind.Increasing => Curvature.Concave
  | Curvature.Concave, ArgKind.Decreasing => Curvature.Convex
  | Curvature.Convex, ArgKind.Increasing => Curvature.Convex
  | Curvature.Convex, ArgKind.Decreasing => Curvature.Concave
  
  
  
  
  | Curvature.ConvexSet, ArgKind.Increasing => Curvature.Concave
  | Curvature.ConvexSet, ArgKind.Decreasing => Curvature.Convex
  | _, _ => Curvature.Affine

def reorderConstrsExpr (ids : Array Name) (e : Expr) : MetaM Expr := do
  let mut constrs : Std.HashMap Lean.Name Expr := {}
  for (cName, cExpr) in ← decomposeConstraints e do
    if constrs.contains cName then
      throwReorderConstrsError "constraint names are not unique ({cName})."
    constrs := constrs.insert cName cExpr
  let newConstrs ← ids.mapM fun id => do
    match constrs.find? id with
    | none => throwReorderConstrsError "unknown constraint {id}."
    | some c => return c
  if constrs.size < ids.size then
    throwReorderConstrsError "too many constraints."
  if ids.size < constrs.size then
    throwReorderConstrsError "not enough constraints."
  return composeAnd newConstrs.data

def reorderConstrsBuilder (ids : Array Name) : EquivalenceBuilder Unit := fun eqvExpr g =>
  g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let newConstrs ← withLambdaBody lhsMinExpr.constraints fun p constrBody => do
      let constrBody ← reorderConstrsExpr ids constrBody
      mkLambdaFVars #[p] constrBody
    let rhsMinExpr := { lhsMinExpr with constraints := newConstrs }

    if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then
      throwReorderConstrsError "failed to unify the goal."

    if let [g] ← g.apply (mkConst ``Minimization.Equivalence.rewrite_constraints) then
      let (_, g) ← g.intros
      if let _ :: _ ← evalTacticAt (← `(tactic| tauto)) g then
        throwReorderConstrsError "failed to prove reordering correct."
    else
      throwReorderConstrsError "failed to rewrite constraints."

@[tactic reorderConstrs]
def evalReorderConstrs : Tactic := fun stx => match stx with
  | `(tactic| reorder_constrs [$ids,*]) => do
      let ids := ids.getElems.map Syntax.getId
      (reorderConstrsBuilder ids).toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

structure Relaxation where
  phi : D → E
  phi_feasibility : ∀ x, p.feasible x → q.feasible (phi x)
  phi_optimality : ∀ x, p.feasible x → q.objFun (phi x) ≤ p.objFun x

@[rel]
def refl : p ≽' p :=
  { phi := id,
    phi_feasibility := fun _ h => h,
    phi_optimality := fun _ _ => le_refl _ }

@[rel]
def trans (Rx₁ : p ≽' q) (Rx₂ : q ≽' r) : p ≽' r :=
  { phi := Rx₂.phi ∘ Rx₁.phi,
    phi_feasibility := fun x h => Rx₂.phi_feasibility (Rx₁.phi x) (Rx₁.phi_feasibility x h),
    phi_optimality := fun x hx =>
      
      have h₁ := Rx₂.phi_optimality (Rx₁.phi x) (Rx₁.phi_feasibility x hx)
      
      have h₂ := Rx₁.phi_optimality x hx
      le_trans h₁ h₂ }

instance : Trans (@Relaxation D E R _) (@Relaxation E F R _) (@Relaxation D F R _) :=
  { trans := Relaxation.trans }

def ofStrongEquivalence (E : p ≡' q) : p ≽' q :=
  { phi := E.phi,
    phi_feasibility := E.phi_feasibility,
    phi_optimality := E.phi_optimality }

instance : Trans (@Relaxation D E R _) (@StrongEquivalence E F R _) (@Relaxation D F R _) :=
  { trans := fun R E => Relaxation.trans R (ofStrongEquivalence E) }

instance : Trans (@StrongEquivalence D E R _) (@Relaxation E F R _) (@Relaxation D F R _) :=
  { trans := fun E R => Relaxation.trans (ofStrongEquivalence E) R }

@[strong_equiv]
def ofRelaxations (Rx₁ : p ≽' q) (Rx₂ : q ≽' p) : p ≡' q :=
  { phi := Rx₁.phi,
    psi := Rx₂.phi,
    phi_feasibility := Rx₁.phi_feasibility,
    psi_feasibility := Rx₂.phi_feasibility,
    phi_optimality := Rx₁.phi_optimality,
    psi_optimality := Rx₂.phi_optimality }

@[equiv]
def ofRelaxations (Rx₁ : p ≽' q) (Rx₂ : q ≽' p) : p ≡ q :=
  Equivalence.ofStrongEquivalence (StrongEquivalence.ofRelaxations Rx₁ Rx₂)

@[rel]
def remove_constraint {c cs' : D → Prop} (hcs : ∀ x, cs x ↔ c x ∧ cs' x) : ⟨f, cs⟩ ≽' ⟨f, cs'⟩ :=
  { phi := id,
    phi_feasibility := fun x h_feas_x => ((hcs x).mp h_feas_x).2,
    phi_optimality := fun _ _ => le_refl _ }

@[rel]
def weaken_constraints (cs' : D → Prop) (hcs : ∀ x, cs x → cs' x) : ⟨f, cs⟩ ≽' ⟨f, cs'⟩ :=
  { phi := id,
    phi_feasibility := fun x h_feas_x => hcs x h_feas_x,
    phi_optimality := fun _ _ => le_refl _ }

def mkLabel (name : Name) (e : Expr) : Expr :=
  mkMData (MData.empty.setName `CvxLeanLabel name) e

def decomposeLabel (e : Expr) : m (Name × Expr) := do
  match e with
  | Expr.mdata m e =>
      match m.get? `CvxLeanLabel with
      | some (name : Name) => return (name, e)
      | none => decomposeLabel e
  | _ => return (`_, e)

def getLabelName (e : Expr) : m Name := do
  return (← decomposeLabel e).1

@[term_elab namedConstraint]
def elabNamedConstraint : Term.TermElab := fun stx expectedType? => do
  match stx with
  | `({** $t ** $id **}) =>
      match id.raw with
      | Syntax.ident _ _ val _ =>
          let e ← Term.elabTerm t expectedType?
          return Meta.mkLabel val e
      | _ => throwUnsupportedSyntax
  | _ => throwUnsupportedSyntax

@[delab mdata] def delabNamedConstraint : Delab := do
  failure

structure RealToFloatData where
  real : Expr
  float : Expr
deriving BEq, Inhabited

instance : ToMessageData RealToFloatData where
  toMessageData := fun d =>
    m!"real: {d.real}
    float: {d.float}"

def RealToFloatExtension :=
  PersistentEnvExtension
    (Array Key × RealToFloatData) (Array Key × RealToFloatData) (DiscrTree RealToFloatData)
deriving Inhabited

def addRealToFloatData (data : RealToFloatData) : MetaM Unit := do
  let (_, _, expr) ← lambdaMetaTelescope data.real
  let keys ← DiscrTree.mkPath expr
  setEnv <| realToFloatExtension.addEntry (← getEnv) (keys, data)

def getRealToFloatDiscrTree : MetaM (DiscrTree RealToFloatData) := do
  return realToFloatExtension.getState (← getEnv)

@[command_elab «time_cmd»]
def evalTimeCmd : CommandElab := fun stx => match stx with
  | `(time_cmd $cmd) => do
      let before ← BaseIO.toIO IO.monoMsNow
      elabCommand cmd
      let after ← BaseIO.toIO IO.monoMsNow
      let diff := after - before
      dbg_trace s!"Command time: {diff} ms"
  | _ => throwUnsupportedSyntax

noncomputable instance LDL.invertible_diag : Invertible (LDL.diag hA) :=

def showVars (goal : MVarId) (p : FVarId) : MetaM MVarId := do
  
  let (revertedVars, goal) ← MVarId.withContext goal do
    let pDecl ← FVarId.getDecl p
    let fVarIds := (← getLCtx).decls.toArray.filterMap id
      |>.filter (fun decl => decl.index > pDecl.index)
      |>.map LocalDecl.fvarId
    let (_, goal) ← MVarId.revert goal fVarIds
    return (fVarIds, goal)
  
  let goal ← MVarId.withContext goal do
    let pDecl ← FVarId.getDecl p
    let domain ← instantiateMVars pDecl.type
    return ← withDomainLocalDecls domain (mkFVar p) fun xs _ => do
      let target ← instantiateMVars (← MVarId.getDecl goal).type
      let newTarget ← Meta.replaceProjections target p xs
      let mVar ← mkFreshExprMVar newTarget MetavarKind.syntheticOpaque
      MVarId.assign goal (← mkLetFVars xs mVar)
      return mVar.mvarId!
  
  let (_, goal) ← MVarId.introNP goal revertedVars.size
  return goal

@[tactic showVars]
def evalShowVars : Tactic
  | `(tactic| show_vars $p) => do
      replaceMainGoal [← Meta.showVars (← getMainGoal) (← withMainContext do getFVarId p)]
  | _ => throwUnsupportedSyntax

@[tactic showVars]
def evalShowVars : Tactic
  | `(conv| show_vars $p) => do
    replaceMainGoal [← Meta.showVars (← getMainGoal) (← withMainContext do getFVarId p)]
  | _ => throwUnsupportedSyntax

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

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

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

def findTactic (rewriteName : String) (direction : EggRewriteDirection) :
    MetaM (TSyntax `tactic × Bool) := do
  match ← getTacticFromRewriteName rewriteName with
  | some (tac, mapObjFun) =>
    if mapObjFun then
      return (tac, true)
    else
      match direction with
      | EggRewriteDirection.Forward => return (tac, false)
      | EggRewriteDirection.Backward =>
          
          return (← `(tactic| (symm; $tac)), false)
  | _ => throwPreDCPError "unknown rewrite name {rewriteName}({direction})."

def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List LocalDecl)
    (tagsMap : HashMap String ℕ) : EquivalenceBuilder Unit := fun eqvExpr g => g.withContext do
  let tag ← liftMetaM <| do
    if step.location == "objFun" then
      return "objFun"
    else if let [_, tag] := step.location.splitOn ":" then
      return tag
    else
      throwPreDCPError "unexpected tag name {step.location}."
  let tagNum := tagsMap.find! step.location
  let atObjFun := tagNum == 0

  let (tacStx, isMap) ← findTactic step.rewriteName step.direction

  
  
  let tacStx : Syntax ← `(tactic|
    try { $tacStx <;> norm_num_simp_pow } <;>
    try { norm_num_simp_pow })

  if isMap then
    
    if let gs@(_ :: _) ← evalTacticAt tacStx g then
      trace[CvxLean.debug] "`pre_dcp` failed to close goals: {gs}."
      throwPreDCPError "failed to apply {step.rewriteName}."
  else
    
    
    let fvars := Array.mk <| vars.map (fun v => mkFVar (FVarId.mk v))
    let paramsFvars := Array.mk <| params.map (fun v => mkFVar (FVarId.mk v))
    let paramsDeclsIds := Array.mk <| paramsDecls.map (fun decl => mkFVar decl.fvarId)
    let D ← instantiateMVars eqvExpr.domainLHS

    
    let mut lhsSubExpr ← EggString.toExpr vars params step.subexprFrom
    let mut rhsSubExpr ← EggString.toExpr vars params step.subexprTo
    lhsSubExpr ← withLocalDeclD `p D fun p => do
      Meta.withDomainLocalDecls D p fun xs prs => do
        let lhsSubExpr := Expr.replaceFVars lhsSubExpr paramsFvars paramsDeclsIds
        let lhsSubExpr := Expr.replaceFVars lhsSubExpr fvars xs
        let lhsSubExpr := Expr.replaceFVars lhsSubExpr xs prs
        mkLambdaFVars #[p] lhsSubExpr
    rhsSubExpr ← withLocalDeclD `p D fun p => do
      Meta.withDomainLocalDecls D p fun xs prs => do
        let rhsSubExpr := Expr.replaceFVars rhsSubExpr paramsFvars paramsDeclsIds
        let rhsSubExpr := Expr.replaceFVars rhsSubExpr fvars xs
        let rhsSubExpr := Expr.replaceFVars rhsSubExpr xs prs
        mkLambdaFVars #[p] rhsSubExpr

    
    
    let mut expectedExpr ← EggString.toExpr vars params step.expectedTerm
    if !atObjFun then
      expectedExpr := Meta.mkLabel (Name.mkSimple tag) expectedExpr
    expectedExpr ←
      withLocalDeclD `p D fun p => do
        Meta.withDomainLocalDecls D p fun xs prs => do
          let expectedExpr := Expr.replaceFVars expectedExpr paramsFvars paramsDeclsIds
          let expectedExpr := Expr.replaceFVars expectedExpr fvars xs
          let expectedExpr := Expr.replaceFVars expectedExpr xs prs
          withLocalDeclD `subexpr (← inferType (rhsSubExpr.mkAppNBeta #[p])) fun subexpr =>
            let expectedExpr := expectedExpr.replaceFVars #[mkFVar (FVarId.mk `subexpr)] #[subexpr]
            mkLambdaFVars #[p, subexpr] expectedExpr

    
    
    let targetExpr ← withLocalDeclD `p D fun p => do
      let targetExpr := expectedExpr.mkAppNBeta #[p, rhsSubExpr.mkAppNBeta #[p]]
      mkLambdaFVars #[p] targetExpr

    
    
    let tac : MVarId → FVarId → TacticM Unit := fun g p => do
      
      let (_, g) ← g.intros
      let mut g := g
      
      let lhsSubExprAtProb := lhsSubExpr.mkAppNBeta #[mkFVar p]
      let rhsSubExprAtProb := rhsSubExpr.mkAppNBeta #[mkFVar p]
      let expectedExprAtProb := expectedExpr.mkAppNBeta #[mkFVar p]
      
      
      if !(← inferType lhsSubExprAtProb).isProp then
        
        if !atObjFun then
          let gs ← g.apply (mkConst ``Iff.of_eq)
          if gs.length != 1 then
            throwPreDCPError "failed to apply `iff_of_eq`."
          g := gs[0]!
        
        let subExprAtProbType ← inferType rhsSubExprAtProb
        let congrLemma ← mkAppOptM ``congrArg
          #[subExprAtProbType, none, lhsSubExprAtProb, rhsSubExprAtProb, expectedExprAtProb]
        let gs ← g.apply congrLemma
        if gs.length != 1 then
          throwPreDCPError "failed to apply `congrArg`."
        g := gs[0]!
      
      let gs ← evalTacticAt tacStx g
      if gs.length != 0 then
        trace[CvxLean.debug] "`pre_dcp` failed to close goals: {gs}."
        throwPreDCPError "failed to apply {step.rewriteName}."
      pure ()

    
    if atObjFun then
      rewriteObjBuilderFromTactic true tac targetExpr eqvExpr g
    else
      let _ ← rewriteConstrBuilderFromTactic true (Name.mkSimple tag) tac targetExpr eqvExpr g

def preDCPBuilder : EquivalenceBuilder Unit := fun eqvExpr g => g.withContext do
  let lhs ← eqvExpr.toMinimizationExprLHS

  
  let varsNames ← withLambdaBody lhs.constraints fun p _ => do
    let pr ← mkProjections lhs.domain p
    return pr.map (Prod.fst)
  let varsStr := varsNames.map toString

  
  
  let declsNamesAndTypes :=
    (← getLCtx).decls.toList.filterMap (Option.map (fun decl => (decl.userName, decl.type)))
  let paramsNames :=
    (← declsNamesAndTypes.filterM (fun (_, ty) => do return (← inferType ty).isType)).map (·.fst)
  let paramsDecls := (← getLCtx).decls.toList.filterMap (fun decl? =>
    if let some decl := decl? then
      if decl.userName ∈ paramsNames then some decl else none
    else none)
  let paramsStr := paramsNames.map toString
  let paramsDomainsExpr :=
    (← declsNamesAndTypes.filterM (fun (_, ty) => do return (← inferType ty).isProp)).toArray
  let paramsDomainsOC : OC (Option (Name × Expr)) := OC.mk none (paramsDomainsExpr.map some)
  let potentialParamDomains ← UncheckedDCP.mkUncheckedTree #[] paramsDecls.toArray paramsDomainsOC
  let mut paramDomains := #[]
  for c in potentialParamDomains.constrs do
    if let some (h, t) := c then
      if let some ⟨_, n, d⟩ := EggOCTreeExtended.processDomainExprTree h t paramsStr then
        paramDomains := paramDomains.push (n, d)

  
  let (gStr, domainConstrs) ← EggOCTreeExtended.fromMinimization lhs varsStr
  let probSize := (gStr.map fun (_, t) => t.size).fold 0 Nat.add
  let mut tagsMap := HashMap.empty
  tagsMap := tagsMap.insert "objFun" 0
  let mut idx := 1
  for (h, _) in gStr.constrs do
    tagsMap := tagsMap.insert h idx
    idx := idx + 1

  
  let varDomainConstrs := domainConstrs.map (fun ⟨_, v, d⟩ => (v, d))
  let constrsToIgnore := domainConstrs.map (fun ⟨h, _, _⟩ => h)

  
  let gStr := { gStr with
    constrs := gStr.constrs.filter (fun (h, _) => !constrsToIgnore.contains h) }

  
  let eggMinimization := EggMinimization.ofEggOCTree gStr
  let eggRequest : EggRequest :=
    { probName := "lean_prob",
      domains := (varDomainConstrs ++ paramDomains).data,
      target := eggMinimization }

  try
    
    let bef ← BaseIO.toIO IO.monoMsNow
    let stepsByComponent ← runEggRequest eggRequest
    let aft ← BaseIO.toIO IO.monoMsNow
    let diff := aft - bef
    dbg_trace s!"Egg time: {diff} ms."
    dbg_trace s!"Term size: {probSize}."
    dbg_trace s!"Term JSON: {eggMinimization.toJson}."

    
    let mut stepsCount := 0
    let mut g := g
    for (_componentName, steps) in stepsByComponent do
      
      
      for step in steps do
        stepsCount := stepsCount + 1
        let gs ← Tactic.run g <| (evalStep step varsNames paramsNames paramsDecls tagsMap).toTactic
        if gs.length != 1 then
          trace[CvxLean.debug] "Remaining goals: {gs}."
          throwPreDCPError "failed to rewrite {step.rewriteName} ({gs.length} goals remaining)."
        else
          trace[CvxLean.debug] "Rewrote {step.rewriteName}."
          g := gs[0]!

    dbg_trace s!"Number of steps: {stepsCount}."

    let gsFinal ← evalTacticAt (← `(tactic| equivalence_rfl)) g
    if gsFinal.length != 0 then
      trace[CvxLean.debug] "Remaining goals: {gsFinal}."
      throwPreDCPError "could not close last goal."
  catch e =>
    throwPreDCPError "{e.toMessageData}"

@[tactic preDCP]
def evalPreDCP : Tactic := fun stx => match stx with
  | `(tactic| pre_dcp) => withMainContext do
      normNumOptBuilder.toTactic
      preDCPBuilder.toTactic
      normNumOptBuilder.toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

structure OC (α : Type) where
  objFun : α
  constrs : Array α
deriving Inhabited

def map (f : α → β) (oc : OC α) : OC β :=
  OC.mk (f oc.objFun) (oc.constrs.map f)

def mapM (f : α → MetaM β) (oc : OC α) : MetaM (OC β) := do
  return OC.mk (← f oc.objFun) (← oc.constrs.mapM f)

def map2M (f : α → β → MetaM γ) (a : OC α) (b : OC β) : MetaM (OC γ) := do
  let objFun ← f a.objFun b.objFun
  if a.constrs.size != b.constrs.size then
    throwError "`OC` error: could not map (2), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <| ← f a.constrs[i]! b.constrs[i]!
  return OC.mk objFun constrs

def map3M (f : α → β → γ → MetaM δ) (a : OC α) (b : OC β) (c : OC γ) : MetaM (OC δ) := do
  let objFun ← f a.objFun b.objFun c.objFun
  if a.constrs.size != b.constrs.size || b.constrs.size != c.constrs.size then
    throwError "`OC` error: could not map (3), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <| ← f a.constrs[i]! b.constrs[i]! c.constrs[i]!
  return OC.mk objFun constrs

def map4M (f : α → β → γ → δ → MetaM ε) (a : OC α) (b : OC β) (c : OC γ) (d : OC δ) :
    MetaM (OC ε) := do
  let objFun ← f a.objFun b.objFun c.objFun d.objFun
  if a.constrs.size != b.constrs.size || b.constrs.size != c.constrs.size
    || c.constrs.size != d.constrs.size then
    throwError "`OC` error: could not map (4), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <| ← f a.constrs[i]! b.constrs[i]! c.constrs[i]! d.constrs[i]!
  return OC.mk objFun constrs

def map5M (f : α → β → γ → δ → ε → MetaM ζ) (a : OC α) (b : OC β) (c : OC γ) (d : OC δ) (e : OC ε) :
    MetaM (OC ζ) := do
  let objFun ← f a.objFun b.objFun c.objFun d.objFun e.objFun
  if a.constrs.size != b.constrs.size || b.constrs.size != c.constrs.size
    || c.constrs.size != d.constrs.size || d.constrs.size != e.constrs.size then
    throwError "`OC` error: could not map (5), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <|
      ← f a.constrs[i]! b.constrs[i]! c.constrs[i]! d.constrs[i]! e.constrs[i]!
  return OC.mk objFun constrs

def map6M (h : α → β → γ → δ → ε → ζ → MetaM η) (a : OC α) (b : OC β) (c : OC γ) (d : OC δ)
    (e : OC ε) (f : OC ζ) : MetaM (OC η) := do
  let objFun ← h a.objFun b.objFun c.objFun d.objFun e.objFun f.objFun
  if a.constrs.size != b.constrs.size || b.constrs.size != c.constrs.size
    || c.constrs.size != d.constrs.size || d.constrs.size != e.constrs.size
    || e.constrs.size != f.constrs.size then
    throwError "`OC` error: could not map (6), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <|
      ← h a.constrs[i]! b.constrs[i]! c.constrs[i]! d.constrs[i]! e.constrs[i]! f.constrs[i]!
  return OC.mk objFun constrs

def map7M (h : α → β → γ → δ → ε → ζ → η → MetaM θ) (a : OC α) (b : OC β) (c : OC γ) (d : OC δ)
    (e : OC ε) (f : OC ζ) (g : OC η) : MetaM (OC θ) := do
  let objFun ← h a.objFun b.objFun c.objFun d.objFun e.objFun f.objFun g.objFun
  if a.constrs.size != b.constrs.size || b.constrs.size != c.constrs.size
    || c.constrs.size != d.constrs.size || d.constrs.size != e.constrs.size
    || e.constrs.size != f.constrs.size || f.constrs.size != g.constrs.size then
    throwError "`OC` error: could not map (7), different number of constraints."
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    constrs := constrs.push <|
      ← h a.constrs[i]! b.constrs[i]! c.constrs[i]! d.constrs[i]! e.constrs[i]! f.constrs[i]!
        g.constrs[i]!
  return OC.mk objFun constrs

def mapMwithCounter (f : Nat → α → MetaM (β × Nat)) (oc : OC α) : MetaM (OC β) := do
  let mut n := 0
  let (objFun, n') ← f n oc.objFun
  n := n'
  let mut constrs := #[]
  for constr in oc.constrs do
    let (c, n') ← f n constr
    constrs := constrs.push c
    n := n'
  return OC.mk objFun constrs

def map2MwithCounter (f : Nat → α → β → MetaM (γ × Nat)) (a : OC α) (b : OC β) : MetaM (OC γ) := do
  if a.constrs.size != b.constrs.size then
    throwError "`OC` error: could not map (2C), different number of constraints."
  let mut n := 0
  let (objFun, n') ← f n a.objFun b.objFun
  n := n'
  let mut constrs := #[]
  for i in [:a.constrs.size] do
    let (c, n') ← f n a.constrs[i]! b.constrs[i]!
    constrs := constrs.push c
    n := n'
  return OC.mk objFun constrs

def fold (oc : OC α) (init : β) (f : β → α → β) : β := Id.run do
  let b ← f init oc.objFun
  let b ← oc.constrs.foldl f b
  return b

instance [ToMessageData α] : ToMessageData (OC α) where
  toMessageData := fun oc =>
    "objFun:" ++ toMessageData oc.objFun ++ "\nconstrs:" ++ toMessageData oc.constrs

def convertOpt (fullProb changeObjFun : Bool := false) (convTac : TacticM Unit) :
    EquivalenceBuilder Unit :=
  fun _ g => g.withContext do
    
    if let [gEq] ← g.apply (mkConst ``Minimization.Equivalence.ofEq) then
      
      let gToConv ← do
        if fullProb then
          markAsConvGoal gEq
        else
          
          let gs := List.filterMap id <| ← Conv.congr gEq
          if let [objFunGoal, constrsGoal] := gs then
            let gToIgnore := if changeObjFun then constrsGoal else objFunGoal
            let gToChange := if changeObjFun then objFunGoal else constrsGoal
            if let _ :: _ ← evalTacticAt (← `(tactic| rfl)) gToIgnore then
              let failureLocation := if changeObjFun then "constraints" else "objective function"
              throwConvOptError "failed to close {failureLocation} subgoals."
            else
            
            if let [gToConv] ← evalTacticAt (← `(conv| ext $(mkIdent `p))) gToChange then
              if let [gToConv] ← evalTacticAt (← `(conv| show_vars $(mkIdent `p))) gToConv then
                pure gToConv
              else
                throwConvOptError "failed to show optimization variables."
            else
              throwConvOptError "failed to introduce optimization variables."
          else
            throwConvOptError "unexpected goal type."

      
      for mvarId in ← Tactic.run gToConv convTac do
        liftM <| mvarId.refl <|> mvarId.inferInstance <|> pure ()
    else
      throwConvOptError "could not convert to equality goal."

def convertFullProb (shouldEval : Bool) (stx : Syntax) : EquivalenceBuilder Unit :=
  convertOpt (fullProb := true) (changeObjFun := false) do
    if shouldEval then evalTactic stx else saveTacticInfoForToken stx

@[tactic convOpt]
partial def evalConvOpt : Tactic := fun stx => match stx with
  | `(tactic| conv_opt => $code) => (convertFullProb true code).toTactic
  
  | `(tactic| conv_opt =>) => do (convertFullProb false stx).toTactic
  | _ => throwUnsupportedSyntax

def convertObj (shouldEval : Bool) (stx : Syntax) : EquivalenceBuilder Unit :=
  convertOpt (fullProb := false) (changeObjFun := true) do
    if shouldEval then evalTactic stx else saveTacticInfoForToken stx

@[tactic convObj]
partial def evalConvObj : Tactic := fun stx => match stx with
  | `(tactic| conv_obj => $code) => (convertObj true code).toTactic
  
  | `(tactic| conv_obj =>) => (convertObj false stx).toTactic
  | _ => throwUnsupportedSyntax

partial def splitAnds (goal : MVarId) : TacticM (List MVarId) := do
  let lhs := (← getLhsRhsCore goal).1
  match lhs with
  | .app (.app (.const ``And _) p) _q => do
      let (mp, mq) ← match List.filterMap id <| ← Conv.congr <| goal with
        | [mp, mq] => pure (mp, mq)
        | _ => throwConvConstrError "unexpected number of subgoals in `splitAnds`."
      let tag ← getLabelName p
      mp.setTag tag
      return mp :: (← splitAnds mq)
  | _ => do
      let tag ← getLabelName lhs
      goal.setTag tag
      return [goal]

def convertConstrs (shouldEval : Bool) (stx : Syntax) : EquivalenceBuilder Unit :=
  convertOpt (fullProb := false) (changeObjFun := false) do
    replaceMainGoal <| ← splitAnds <| ← getMainGoal
    if shouldEval then evalTactic stx else saveTacticInfoForToken stx

def convertConstrWithName (shouldEval : Bool) (stx : Syntax) (h : Name) : EquivalenceBuilder Unit :=
  convertOpt (fullProb := false) (changeObjFun := false) do
    let constrs ← splitAnds <| ← getMainGoal
    let mut found := false
    for constr in constrs do
      let t ← constr.getTag
      if t == h then
        found := true
        replaceMainGoal [constr]
        if shouldEval then evalTactic stx else saveTacticInfoForToken stx
        
        let g ← getMainGoal
        let (lhs, rhs) ← getLhsRhsCore g
        let lhsWithLabel := mkLabel t lhs
        let eq ← mkEq lhsWithLabel rhs
        let g ← MVarId.replaceTargetEq g eq (← mkEqRefl eq)
        replaceMainGoal [g]
      else
        let gs ← evalTacticAt (← `(tactic| rfl)).raw constr
        if !gs.isEmpty then
          throwConvConstrError "failed to close all subgoals."
    if !found then
      throwConvConstrError "no constraint with name {h} found."

@[tactic convConstr]
partial def evalConvConstr : Tactic := fun stx => match stx with
  | `(tactic| conv_constr => $code) => (convertConstrs true code).toTactic
  | `(tactic| conv_constr $h => $code) => (convertConstrWithName true code h.getId).toTactic
  
  | `(tactic| conv_constr =>) => (convertConstrs false stx).toTactic
  | `(tactic| conv_constr $h =>) => (convertConstrWithName false stx h.getId).toTactic
  | _ => throwUnsupportedSyntax

partial def realToFloat (e : Expr) : MetaM Expr := do
  let e ← e.removeMData
  let discrTree ← getRealToFloatDiscrTree
  let translations ← discrTree.getMatch e
  for translation in translations do
    let (mvars, _, pattern) ← lambdaMetaTelescope translation.real
    if ← isDefEq pattern e then
      \n{pattern} \n{e}"
  match e with
  | Expr.app a b => return mkApp (← realToFloat a) (← realToFloat b)
  | Expr.lam n ty b d => do
      withLocalDecl n d (← realToFloat ty) fun fvar => do
        let b := b.instantiate1 fvar
        let bF ← realToFloat b
        mkLambdaFVars #[fvar] bF
  | Expr.forallE n ty b d => do
      withLocalDecl n d (← realToFloat ty) fun fvar => do
        let b := b.instantiate1 fvar
        let bF ← realToFloat b
        mkForallFVars #[fvar] bF
  | Expr.mdata m e => return mkMData m (← realToFloat e)
  | Expr.letE n ty t b _ => return mkLet n (← realToFloat ty) (← realToFloat t) (← realToFloat b)
  | Expr.proj typeName idx struct => return mkProj typeName idx (← realToFloat struct)
  | e@(Expr.const n _) => do
      if ← isOptimizationParam n then
        let paramExpr ← getOptimizationParamExpr n e
        let paramExprF ← realToFloat paramExpr
        \n{e.toMessageData}"
        return paramExprF
      else
        return e
  | _ => return e

@[command_elab addRealToFloatCommand]
def elabAddRealToFloatCommand : CommandElab
  | `(add_real_to_float : $real := $float) =>
      liftTermElabM do
        let real ← elabTermAndSynthesize real.raw none
        let float ← elabTermAndSynthesize float.raw none
        addRealToFloatData { real := real, float := float }
  | _ => throwUnsupportedSyntax

@[command_elab realToFloatCommand]
unsafe def elabRealToFloatCommand : CommandElab
| `(#real_to_float $stx) =>
  liftTermElabM do
    let e ← elabTermAndSynthesize stx.raw none
    let res ← realToFloat e
    check res
    logInfo m!"{res}"
    if Expr.isConstOf (← inferType res) ``Float then
      let res ← Meta.evalExpr Float (mkConst ``Float) res
      logInfo m!"{res}"
| _ => throwUnsupportedSyntax

def mkAndProj (e : Expr) (i : Nat) (total : Nat) : MetaM Expr := do
  match total, i with
    | 1, 0 => return e
    | 0, _ => throwError "total too small"
    | _, 0 => return ← mkAppM ``And.left #[e]
    | total + 1, i + 1 => mkAndProj (← mkAppM ``And.right #[e]) i total

def mkProjs (total : Nat) (h : Expr) : MetaM (List Expr) := do
  let mut acc := []
  for k in [:total] do
    let e ← mkAndProj h (total - k - 1) total
    acc := e :: acc
  return acc

def composeAndIntro (l : List Expr) : MetaM Expr :=
  match l with
    | [] => return mkConst ``trivial
    | [e] => return e
    | List.cons e es => do
      let es ← composeAndIntro es
      let eTy ← inferType e
      let esTy ← inferType es
      return mkApp4 (mkConst ``And.intro) eTy esTy e es

def removeConstrBuilder (id : Name) (proof : Syntax) : EquivalenceBuilder Unit := fun eqvExpr g =>
  g.withContext do
    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let (idxToRemove, numConstrs, newConstrs, toShow) ←
      withLambdaBody lhsMinExpr.constraints fun p oldConstrsBody => do
        let oldConstrsList ← decomposeConstraints oldConstrsBody
        let idxToRemove := oldConstrsList.findIdx fun c => c.1 == id
        if idxToRemove == oldConstrsList.length then
          throwError "`remove_constr` error: constraint {id} not found."
        let newConstrsList := oldConstrsList.eraseIdx idxToRemove
        let newConstrs ← mkLambdaFVars #[p] <| composeAnd <| newConstrsList.map Prod.snd
        
        let lhsLabeledDomain ← decomposeDomain lhsMinExpr.domain
        let toShow ← withLocalDeclsDNondep lhsLabeledDomain.toArray fun xs => do
          mkLambdaFVars xs <| ← do
            let niceNewConstrsList ← newConstrsList.mapM (fun (n, c) => do
              return (n, ← replaceProjections c p.fvarId! xs))
            let toErase := (oldConstrsList.get! idxToRemove).2;
            let niceToErase ← Meta.replaceProjections toErase p.fvarId! xs
            withLocalDeclsDNondep niceNewConstrsList.toArray fun cs => do
              let (e, _) ← Lean.Elab.Term.TermElabM.run <| Lean.Elab.Term.commitIfNoErrors? <| do
                let v ← Lean.Elab.Term.elabTerm proof niceToErase
                Lean.Elab.Term.synthesizeSyntheticMVarsNoPostponing
                instantiateMVars v
              if let some e' := e then
                mkLambdaFVars cs e'
              else
                throwError "`remove_constr` error: failed to elaborate proof."
        return (idxToRemove, oldConstrsList.length, newConstrs, toShow)

    
    let iffProof ← withLambdaBody newConstrs fun p newConstrsBody => do
      
      let newImpliesOld ← withLocalDeclD `h newConstrsBody fun h => do
        let l ← mkProjs (numConstrs - 1) h
        let xs := (← mkProjections lhsMinExpr.domain p).map (fun (_, _, e) => e)
        let extra := mkAppNBeta toShow ((xs ++ l).toArray)
        let l := (l.take idxToRemove).append <| extra :: l.drop idxToRemove
        return ← mkLambdaFVars #[h] <| ← composeAndIntro l
      
      let oldConstrsBody := lhsMinExpr.constraints.bindingBody!.instantiate1 p
      let oldImpliesNew ← withLocalDeclD `h oldConstrsBody fun h => do
        let l ← mkProjs numConstrs h
        let l := l.eraseIdx idxToRemove
        mkLambdaFVars #[h] <| ← composeAndIntro l
      return ← mkLambdaFVars #[p] <| ← mkAppM ``Iff.intro #[oldImpliesNew, newImpliesOld]

    
    let D := eqvExpr.domainLHS
    let R := eqvExpr.codomain
    let RPreorder := eqvExpr.codomainPreorder
    let fullProof ← mkAppOptM ``Minimization.Equivalence.rewrite_constraints
      #[D, R, RPreorder, lhsMinExpr.objFun, lhsMinExpr.constraints, newConstrs, iffProof]
    check fullProof
    let rhs := { lhsMinExpr with constraints := newConstrs }
    if !(← isDefEq eqvExpr.rhs rhs.toExpr) then
      throwError "`remove_constr` error: failed to unify RHS."

    if let _ :: _ ← g.apply fullProof then
      throwError "`remove_constr` error: failed to prove equivalence."

    let gs ← getUnsolvedGoals
    if gs.length != 0 then
      throwError "`remove_constr` error: failed to close all goals."

@[tactic removeConstr]
partial def evalRemoveConstr : Tactic := fun stx => match stx with
| `(tactic| remove_constr $id => $tacStx) => do
    (removeConstrBuilder id.getId (← `(term| by admit /- proof elided -/
    ))).toTactic
    saveTacticInfoForToken stx
| _ => throwUnsupportedSyntax

def Matrix.quadForm {n : ℕ} (R : Matrix (Fin n) (Fin n) ℝ) (x : Fin n → ℝ) : ℝ :=
  x ⬝ᵥ R.mulVec x

def gaussianPdf {n : ℕ} (R : Matrix (Fin n) (Fin n) ℝ) (x : Fin n → ℝ) : ℝ :=
  (1 / sqrt (((2 * π) ^ n) * R.det)) * exp (- R⁻¹.quadForm x / 2)

def covarianceMatrix {N n : ℕ} (y : Fin N → Fin n → ℝ) : Matrix (Fin n) (Fin n) ℝ :=
  fun i j : Fin n => (∑ k : Fin N, y k i * y k j) / N

def getProblemName (stx : Syntax) : MetaM Name := do
  
  let idStx :=
    match stx with
    | Syntax.ident _ _ _ _ => stx
    | Syntax.node _ _ args => args.getD 0 Syntax.missing
    | _ => Syntax.missing
  if ¬ idStx.getId.isStr then
    throwError "Invalid name for minimization problem: {idStx}."

  let currNamespace ← getCurrNamespace
  return currNamespace ++ idStx.getId

def getCanonizedProblemAndBwdMap (prob : Expr) : MetaM (MinimizationExpr × Expr) := do
  let ogProb ← MinimizationExpr.fromExpr prob
  let (canonProb, eqvProof) ← DCP.canonize ogProb
  let backwardMap ← mkAppM ``Minimization.Equivalence.psi #[eqvProof]
  return (canonProb, backwardMap)

@[command_elab «solve»]
unsafe def evalSolve : CommandElab := fun stx =>
  match stx with
  | `(solve $probInstance) =>
    liftTermElabM <| do
      let probTerm ← elabTerm probInstance.raw none
      let probTerm ← whnf probTerm
      let probTerm ← instantiateMVars probTerm

      \n{coeffsData}"

      let solData ← solutionDataFromProblemData canonProb coeffsData sections
      trace[CvxLean.debug] "Solution data:\n{solData}"

      x

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

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

def EggTree := Tree String String

instance : Inhabited EggTree where
  default := Tree.leaf ""

def EggOCTree := OC (String × EggTree)

inductive EggTreeOpArgTag
  | arg
  | val (s : String)

structure EggMinimization where
  objFun : String
  constrs : List (String × String)

structure EggDomain where
  hi : String
  lo : String
  hi_open : String
  lo_open : String

structure EggDomainIdentified where
  constraintName : String
  varOrParamName : String
  domain : EggDomain

def EggOCTreeExtended := EggOCTree × Array EggDomainIdentified

structure EggRequest where
  probName : String
  domains : List (String × EggDomain)
  target : EggMinimization

inductive EggRewriteDirection where
  | Forward
  | Backward
deriving Inhabited, DecidableEq

structure EggRewrite where
   
  rewriteName : String
   
  direction : EggRewriteDirection
   
  location : String
   
  subexprFrom : String
   
  subexprTo : String
   
  expectedTerm : String

def maximizeNeg := @Neg.neg

private def decomposeBracketedBinder : Syntax → TermElabM (Array (Syntax × Syntax)) :=
  fun stx => match stx[0] with
    | `(bracketedBinderF|($ids* : $ty)) => return ids.map (·.raw, ty.raw)
    | `(ident|$id) => return #[(id.raw, (← `(_)).raw)]

def elabVars (idents : Array (TSyntax `CvxLean.Parser.minimizationVar)) :
    TermElabM (Array (Lean.Name × Expr)) := do
  let idents ← idents.concatMapM decomposeBracketedBinder
  let idents ← idents.mapM fun (id, ty) => do
    match id with
      | Syntax.ident _ _ val _ => return (val, ← Term.elabTerm ty none)
      | _ => throwParserError "expected identifier got {id}."
  return idents

def preElabLetVars (letVars : Array (TSyntax `CvxLean.Parser.letVar)) :
    TermElabM (Array (Lean.Name × TSyntax `Lean.Parser.Term.letDecl)) := do
  letVars.mapM fun stx =>
    match stx with
      | `(CvxLean.Parser.letVar| with $letD:letDecl) =>
          match letD with
          | `(letDecl| $id:ident := $_) => return (id.getId, letD)
          | _ => throwParserError "expected identified let declaration got {letD}."
      | _ => throwParserError "expected let declaration got {stx}."

def preElabConstraints (constraints : TSyntax `CvxLean.Parser.constraints) :
    TermElabM (Array (Lean.Name × TSyntax `term)) := do
  match constraints with
  | `(CvxLean.Parser.constraints| $constrs*) => do
      constrs.mapM fun cDecl =>
        match cDecl with
          | `(CvxLean.Parser.constraint| $id:ident : $c) => return (id.getId, c)
          | `(CvxLean.Parser.constraint| _ : $c) => return (Name.anonymous, c)
          | _ => throwParserError "expected constraint got {cDecl}."
  | _ => throwParserError "expected constraints got {constraints}."

partial def _root_.Lean.Syntax.gatherIdents : Syntax → Array Lean.Name
  | .missing => #[]
  | .ident _ _ n _ => #[n]
  | .atom _ _ => #[]
  | .node _ _ stxs => stxs.foldl (init := #[]) fun acc stx => acc ++ stx.gatherIdents

@[term_elab «optimization»] def elabOptmiziation : Term.TermElab := fun stx _expectedType? => do
  match stx with
  | `(optimization $idents* $lets:letVar* $minOrMax:minOrMax $obj subject to $constraints) =>
      
      let vars ← elabVars idents
      
      let domain := Meta.composeDomain vars.data
      
      let letsStx ← preElabLetVars lets
      
      withLocalDeclD `p domain fun p => do
        
        Meta.withDomainLocalDecls domain p fun xs prs => do
          
          let mut objStx := obj
          let objIdents := Syntax.gatherIdents objStx
          if letsStx.size > 0 then
            for (letVar, letD) in letsStx do
              if letVar ∈ objIdents then
                objStx := ← `(let $letD:letDecl; $objStx)
          let mut obj := Expr.replaceFVars (← Term.elabTerm objStx.raw none) xs prs
          
          let minOrMaxStx := minOrMax.raw[0]!
          if minOrMaxStx.isOfKind `maximize then
            obj ← mkAppM ``maximizeNeg #[obj]
          else if !(minOrMaxStx.isOfKind `minimize) then
            throwParserError "expected minimize or maximize, got: {minOrMaxStx.getKind}"
          obj ← mkLambdaFVars #[p] obj
          
          let constraints ← preElabConstraints constraints
          let constraints ← constraints.mapM fun (n, cStx) => do
            let mut cStx := cStx
            let cIdents := Syntax.gatherIdents cStx
            if letsStx.size > 0 then
              for (letVar, letD) in letsStx do
                if letVar ∈ cIdents then
                  cStx := ← `(let $letD:letDecl; $cStx)
            return Meta.mkLabel n (← Term.elabTerm cStx none)
          let constraints ← mkLambdaFVars #[p] $
            Expr.replaceFVars (Meta.composeAnd constraints.data) xs prs
          
          let res ← mkAppM ``Minimization.mk #[obj, constraints]
          check res
          return ← instantiateMVars res
  | _ => throwUnsupportedSyntax

def delabVar (e : Expr) : DelabM (Lean.Name × Term) := do
  match e with
  | Expr.mdata m e =>
      match m.get? `CvxLeanLabel with
      | some (name : Lean.Name) => return (name, ← descend e 0 do delab)
      | none => Alternative.failure
  | _ => return (`_, ← delab)

partial def delabDomain : DelabM (List (Lean.Name × Term)) := do
  let e ← getExpr
  match e with
  | Expr.app (Expr.app (Expr.const `Prod _) _ty1) _ty2 => do
      let stx1 ← withNaryArg 0 (do delabVar <| e.getArg! 0)
      let stx2 ← withNaryArg 1 (do delabDomain)
      return stx1 :: stx2
  | _ => return [← delabVar e]

partial def delabConstraint : DelabM (TSyntax ``Parser.constraint) := do
  match ← getExpr with
  | Expr.mdata m e =>
      match m.get? `CvxLeanLabel with
      | some (name : Lean.Name) =>
          return mkNode ``Parser.constraint
            #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw]
      | none => Alternative.failure
  | _  => return (← `(Parser.constraint|_ : $(← delab)))

partial def delabConstraints : DelabM (List (TSyntax ``Parser.constraint)) := do
  let e ← getExpr
  match e with
  | Expr.app (Expr.app (Expr.const `And _) _l) _r =>
      let l : TSyntax _ ← withNaryArg 0 delabConstraint
      let r : List (TSyntax _) ← withNaryArg 1 delabConstraints
      return l :: r
  | _ => return [← delabConstraint]

def withDomainBinding {α} [Inhabited α] (domain : Expr) (x : DelabM α) : DelabM α := do
  guard (← getExpr).isLambda
  withBindingBody'' `p fun p => do
    withDomainLocalDecls domain p fun xs _prs => do
      let e ← getExpr
      let e ← replaceProjections e p.fvarId! xs
      withExpr e do x

@[delab app]
partial def delabMinimization : Delab := do
  
  match ← getExpr with
  | .app (.app (.app (.app (.const `Minimization.mk _) domain) _codomain) _objFun) constraints =>
      let idents ← withType <| withNaryArg 0 do
        let tys ← delabDomain
        let tys ← tys.mapM fun (name, stx) => do `(Parser.minimizationVar| ($(mkIdent name) : $stx))
        return tys.toArray
      let (objFun, isMax) ← withNaryArg 2 do withDomainBinding domain do
        match ← getExpr with
        | .app (.app (.app (.const ``maximizeNeg _) _) _) e =>
            withExpr e do
              return (← delab, true)
        | _ =>
            return (← delab, false)
      let noConstrs ← withLambdaBody constraints fun _ constrsBody => do
        isDefEq constrsBody (mkConst ``True)
      let constraints := ← withNaryArg 3 do
        let cs ← withDomainBinding domain delabConstraints
        return mkNode ``Parser.constraints #[mkNullNode <| cs.toArray.map (·.raw)]
      if noConstrs then
        if isMax then
          `(optimization $idents* maximize $objFun)
        else
          `(optimization $idents*  minimize $objFun)
      else
        if isMax then
          `(optimization $idents* maximize $objFun subject to $constraints)
        else
          `(optimization $idents* minimize $objFun subject to $constraints)
  | _ => Alternative.failure

def soCone (t : ℝ) (x : n → ℝ) : Prop :=
  sqrt (∑ i, x i ^ 2) ≤ t

def rotatedSoCone (v w : ℝ) (x : n → ℝ) : Prop :=
  (∑ i, x i ^ 2) ≤ (v * w) * 2 ∧ 0 ≤ v ∧ 0 ≤ w

def Vec.soCone (t : m → ℝ) (X : Matrix m n ℝ) : Prop :=
  ∀ i, Real.soCone (t i) (X i)

def Vec.rotatedSoCone (v w : m → ℝ) (X : Matrix m n ℝ) : Prop :=
  ∀ i, Real.rotatedSoCone (v i) (w i) (X i)

def rotateSoCone {n : ℕ} (t : ℝ) (x : Fin n.succ → ℝ) : ℝ × ℝ × (Fin n → ℝ) :=
  ((t + x 0) / sqrt 2, (t - x 0) / sqrt 2, fun i => x i.succ)

def unrotateSoCone {n : ℕ} (v w : Real) (x : Fin n → ℝ) : ℝ × (Fin n.succ → ℝ) :=
  ((v + w) / sqrt 2, Matrix.vecCons ((v - w) / sqrt 2) x)

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

inductive ObjSense
| MAX | MIN

instance : ToString ObjSense where
  toString
  | MAX => "MAX"
  | MIN => "MIN"

inductive ConeType
  | F | LPos | LNeg | LEq | Q | QR | EXP
deriving DecidableEq

instance : ToString ConeType where
  toString
  | F     => "F"
  | LPos  => "L+"
  | LNeg  => "L-"
  | LEq   => "L="
  | Q     => "Q"
  | QR    => "QR"
  | EXP   => "EXP"

structure Cone where
  type : ConeType
  dim : Nat

instance : ToString Cone where
  toString c := (toString c.type) ++ " " ++ (toString c.dim)

structure ConeProduct where
  n : Nat
  k : Nat
  cones : List Cone

instance : ToString ConeProduct where
  toString cp :=
    (toString cp.n) ++ " " ++ (toString cp.k) ++ "\n" ++
    (cp.cones.foldl (fun acc c => acc ++ (toString c) ++ "\n") "")

def empty : ConeProduct := ConeProduct.mk 0 0 []

def isEmpty (cp : ConeProduct) : Prop := cp.k = 0

instance {cp} : Decidable (isEmpty cp) := Nat.decEq _ _

def addCone (cp : ConeProduct) (c : Cone) : ConeProduct :=
  ConeProduct.mk (cp.n + c.dim) (cp.k + 1) (cp.cones.concat c)

structure DimensionList where
  n : Nat
  dimensions : List Nat

instance : ToString DimensionList where
  toString dl :=
    (toString dl.n) ++ "\n" ++
    (dl.dimensions.foldl (fun acc d => acc ++ (toString d) ++ "\n") "")

def empty : DimensionList := DimensionList.mk 0 []

def isEmpty (dl : DimensionList) : Prop := dl.n = 0

instance {dl} : Decidable (isEmpty dl) := Nat.decEq _ _

def addDimension (dl : DimensionList) (d : Nat) : DimensionList :=
  DimensionList.mk (dl.n + 1) (dl.dimensions.concat d)

structure EncodedValue where
  value : Option Float

instance : ToString EncodedValue where
  toString ev := match ev.value with
    | some f => toString f
    | none => ""

def empty : EncodedValue := EncodedValue.mk none

def isEmpty (ev : EncodedValue) : Prop := ev.value.isNone

instance {ev} : Decidable (isEmpty ev) := decEq _ _

structure EncodedVectorEntry where
  i : Nat
  value : Float

instance : ToString EncodedVectorEntry where
  toString eve := (toString eve.i) ++ " " ++ (toString eve.value)

def fromIndexAndValue (i : Nat) (v : Float) : EncodedVectorEntry :=
  EncodedVectorEntry.mk i v

structure EncodedVector where
  n : Nat
  values : List EncodedVectorEntry

instance : ToString EncodedVector where
  toString ev :=
    (toString ev.n) ++ "\n" ++
    (ev.values.foldl (fun acc eve => acc ++ (toString eve) ++ "\n") "")

def empty : EncodedVector := EncodedVector.mk 0 []

def isEmpty (ev : EncodedVector) : Prop := ev.n = 0

instance {ev} : Decidable (isEmpty ev) := Nat.decEq _ _

def addEntry (ev : EncodedVector) (eve : EncodedVectorEntry) : EncodedVector :=
  if eve.value < 0 || eve.value > 0 then
    EncodedVector.mk (ev.n + 1) (ev.values.concat eve)
  else
    ev

def stack (ev1 ev2 : EncodedVector) : EncodedVector :=
  EncodedVector.mk (ev1.n + ev2.n) (ev1.values ++ ev2.values)

def fromIndexAndValue (i : Nat) (v : Float) : EncodedVector :=
  if v > 0 || v < 0 then
    EncodedVector.mk 1 [EncodedVectorEntry.fromIndexAndValue i v]
  else
    EncodedVector.mk 0 []

def fromArray (a : Array Float) : EncodedVector := Id.run <| do
  let mut ev := empty
  for (i, ai) in a.data.enum do
    if ai > 0 || ai < 0 then
      ev := ev.addEntry (EncodedVectorEntry.mk i ai)
  return ev

structure EncodedMatrixEntry where
  i : Nat
  j : Nat
  value : Float

instance : ToString EncodedMatrixEntry where
  toString eme :=
    (toString eme.i) ++ " " ++
    (toString eme.j) ++ " " ++
    (toString eme.value)

def fromIndexAndEncodedVectorEntry (i : Nat) (eve : EncodedVectorEntry) : EncodedMatrixEntry :=
  EncodedMatrixEntry.mk i eve.i eve.value

structure EncodedMatrix where
  n : Nat
  values : List EncodedMatrixEntry

instance : ToString EncodedMatrix where
  toString em :=
    (toString em.n) ++ "\n" ++
    (em.values.foldl (fun acc eme => acc ++ (toString eme) ++ "\n") "")

def empty : EncodedMatrix := EncodedMatrix.mk 0 []

def isEmpty (em : EncodedMatrix) : Prop := em.n = 0

instance {em} : Decidable (isEmpty em) := Nat.decEq _ _

def addEntry (em : EncodedMatrix) (eme : EncodedMatrixEntry) : EncodedMatrix :=
  if eme.value < 0 || eme.value > 0 then
    EncodedMatrix.mk (em.n + 1) (em.values.concat eme)
  else
    em

def stack (em1 em2 : EncodedMatrix) : EncodedMatrix :=
  EncodedMatrix.mk (em1.n + em2.n) (em1.values ++ em2.values)

def fromIndexAndEncodedVector (i : Nat) (ev : EncodedVector) : EncodedMatrix := Id.run <| do
  let mut em := empty
  for eve in ev.values do
    let eme := EncodedMatrixEntry.fromIndexAndEncodedVectorEntry i eve
    em := em.addEntry eme
  em

def fromArray (A : Array (Array Float)) : EncodedMatrix := Id.run <| do
  let mut em := empty
  for (i, ai) in A.data.enum do
    for (j, aij) in ai.data.enum do
      if i >= j && (aij > 0 || aij < 0) then
        em := em.addEntry (EncodedMatrixEntry.mk i j aij)
  return em

structure EncodedMatrixListEntry where
  i : Nat
  j : Nat
  k : Nat
  value : Float

instance : ToString EncodedMatrixListEntry where
  toString emle :=
    (toString emle.i) ++ " " ++
    (toString emle.j) ++ " " ++
    (toString emle.k) ++ " " ++
    (toString emle.value)

def fromIndexAndEncodedMatrixEntry (i : Nat) (eme : EncodedMatrixEntry) : EncodedMatrixListEntry :=
  EncodedMatrixListEntry.mk i eme.i eme.j eme.value

structure EncodedMatrixList where
  n : Nat
  values : List EncodedMatrixListEntry

instance : ToString EncodedMatrixList where
  toString eml :=
    (toString eml.n) ++ "\n" ++
    (eml.values.foldl (fun acc emle => acc ++ (toString emle) ++ "\n") "")

def empty : EncodedMatrixList := EncodedMatrixList.mk 0 []

def isEmpty (eml : EncodedMatrixList) : Prop := eml.n = 0

instance {eml} : Decidable (isEmpty eml) := Nat.decEq _ _

def addEntry (eml : EncodedMatrixList) (emle : EncodedMatrixListEntry) : EncodedMatrixList :=
  if emle.value > 0 || emle.value < 0 then
    EncodedMatrixList.mk (eml.n + 1) (eml.values.concat emle)
  else
    eml

def stack (eml1 eml2 : EncodedMatrixList) : EncodedMatrixList :=
  EncodedMatrixList.mk (eml1.n + eml2.n) (eml1.values ++ eml2.values)

def fromIndexAndEncodedMatrix (i : Nat) (em : EncodedMatrix) : EncodedMatrixList := Id.run <| do
  let mut eml := empty
  for eme in em.values do
    let emle := EncodedMatrixListEntry.fromIndexAndEncodedMatrixEntry i eme
    eml := eml.addEntry emle
  eml

def fromArray (A : Array (Array (Array Float))) : EncodedMatrixList := Id.run <| do
  let mut eml := empty
  for (i, ai) in A.data.enum do
    for (j, aij) in ai.data.enum do
      for (k, aijk) in aij.data.enum do
        if j >= k && (aijk > 0 || aijk < 0) then
          eml := eml.addEntry (EncodedMatrixListEntry.mk i j k aijk)
  return eml

structure EncodedMatrixListListEntry where
  i : Nat
  j : Nat
  k : Nat
  l : Nat
  value : Float

instance : ToString EncodedMatrixListListEntry where
  toString emlle :=
    (toString emlle.i) ++ " " ++
    (toString emlle.j) ++ " " ++
    (toString emlle.k) ++ " " ++
    (toString emlle.l) ++ " " ++
    (toString emlle.value)

def fromIndexAndEncodedMatrixList (i : Nat) (emle : EncodedMatrixListEntry) :
    EncodedMatrixListListEntry :=
  EncodedMatrixListListEntry.mk i emle.i emle.j emle.k emle.value

structure EncodedMatrixListList where
  n : Nat
  values : List EncodedMatrixListListEntry

instance : ToString EncodedMatrixListList where
  toString emll :=
    (toString emll.n) ++ "\n" ++
    (emll.values.foldl (fun acc emlle => acc ++ (toString emlle) ++ "\n") "")

def empty : EncodedMatrixListList := EncodedMatrixListList.mk 0 []

def isEmpty (emll : EncodedMatrixListList) : Prop := emll.n = 0

instance {emll} : Decidable (isEmpty emll) := Nat.decEq _ _

def addEntry (emll : EncodedMatrixListList) (emlle : EncodedMatrixListListEntry) :
    EncodedMatrixListList :=
  if emlle.value > 0 || emlle.value < 0 then
    EncodedMatrixListList.mk (emll.n + 1) (emll.values.concat emlle)
  else emll

def stack (eml1 eml2 : EncodedMatrixListList) : EncodedMatrixListList :=
  EncodedMatrixListList.mk (eml1.n + eml2.n) (eml1.values ++ eml2.values)

def fromIndexAndEncodedMatrixList (i : Nat) (eml : EncodedMatrixList) :
    EncodedMatrixListList := Id.run <| do
  let mut emll := empty
  for emle in eml.values do
    let emlle := EncodedMatrixListListEntry.fromIndexAndEncodedMatrixList i emle
    emll := emll.addEntry emlle
  emll

def fromArray (A : Array (Array (Array (Array Float)))) :
    EncodedMatrixListList := Id.run <| do
  let mut emll := empty
  for (i, ai) in A.data.enum do
    for (j, aij) in ai.data.enum do
      for (k, aijk) in aij.data.enum do
        for (l, aijkl) in aijk.data.enum do
          if k >= l && (aijkl > 0 || aijkl < 0) then
            emll := emll.addEntry (EncodedMatrixListListEntry.mk i j k l aijkl)
  return emll

structure Problem where
  version : Nat
  objSense : ObjSense
  scalarVariables : ConeProduct
  PSDVariables : DimensionList
  scalarConstraints : ConeProduct
  PSDConstraints : DimensionList
  objectivePSDVariablesCoord : EncodedMatrixList
  objectiveScalarVariablesCoord : EncodedVector
  objectiveShiftCoord : EncodedValue
  scalarConstraintsPSDVariablesCoord : EncodedMatrixListList
  scalarConstraintsScalarVariablesCoord : EncodedMatrix
  scalarConstraintsShiftCoord : EncodedVector
  PSDConstraintsScalarVariablesCoord : EncodedMatrixListList
  PSDConstraintsShiftCoord : EncodedMatrixList

instance : ToString Problem where
  toString p :=
    "VER \n" ++
    (toString p.version) ++ "\n\n" ++
    "OBJSENSE \n" ++
    (toString p.objSense) ++ "\n\n" ++
    (if p.scalarVariables.isEmpty then "" else
      "VAR \n" ++
      (toString p.scalarVariables) ++ "\n") ++
    (if p.PSDVariables.isEmpty then "" else
      "PSDVAR \n" ++
      (toString p.PSDVariables) ++ "\n") ++
    (if p.scalarConstraints.isEmpty then "" else
      "CON \n" ++
      (toString p.scalarConstraints) ++ "\n") ++
    (if p.PSDConstraints.isEmpty then "" else
      "PSDCON \n" ++
      (toString p.PSDConstraints) ++ "\n") ++
    (if p.objectivePSDVariablesCoord.isEmpty then "" else
      "OBJFCOORD \n" ++
      (toString p.objectivePSDVariablesCoord) ++ "\n") ++
    (if p.objectiveScalarVariablesCoord.isEmpty then "" else
      "OBJACOORD \n" ++
      (toString p.objectiveScalarVariablesCoord) ++ "\n") ++
    (if p.objectiveShiftCoord.isEmpty then "" else
      "OBJBCOORD \n" ++
      (toString p.objectiveShiftCoord) ++ "\n\n") ++
    (if p.scalarConstraintsPSDVariablesCoord.isEmpty then "" else
      "FCOORD \n" ++
      (toString p.scalarConstraintsPSDVariablesCoord) ++ "\n") ++
    (if p.scalarConstraintsScalarVariablesCoord.isEmpty then "" else
      "ACOORD \n" ++
      (toString p.scalarConstraintsScalarVariablesCoord) ++ "\n") ++
    (if p.scalarConstraintsShiftCoord.isEmpty then "" else
      "BCOORD \n" ++
      (toString p.scalarConstraintsShiftCoord) ++ "\n") ++
    (if p.PSDConstraintsScalarVariablesCoord.isEmpty then "" else
      "HCOORD \n" ++
      (toString p.PSDConstraintsScalarVariablesCoord) ++ "\n") ++
    (if p.PSDConstraintsShiftCoord.isEmpty then "" else
      "DCOORD \n" ++
      (toString p.PSDConstraintsShiftCoord) ++ "\n")

def empty : Problem := {
  version := 3,
  objSense := ObjSense.MIN,
  scalarVariables := ConeProduct.empty,
  PSDVariables := DimensionList.empty,
  scalarConstraints := ConeProduct.empty,
  PSDConstraints := DimensionList.empty,
  objectivePSDVariablesCoord := EncodedMatrixList.empty,
  objectiveScalarVariablesCoord := EncodedVector.empty,
  objectiveShiftCoord := EncodedValue.empty,
  scalarConstraintsPSDVariablesCoord := EncodedMatrixListList.empty,
  scalarConstraintsScalarVariablesCoord := EncodedMatrix.empty,
  scalarConstraintsShiftCoord := EncodedVector.empty,
  PSDConstraintsScalarVariablesCoord := EncodedMatrixListList.empty,
  PSDConstraintsShiftCoord := EncodedMatrixList.empty
}

def setVersion (p : Problem) (v : Nat) : Problem :=
  { p with version := v }

def setObjSense (p : Problem) (os : ObjSense) : Problem :=
  { p with objSense := os }

def setScalarVariables (p : Problem) (cp : ConeProduct) : Problem :=
  { p with scalarVariables := cp }

def setPSDVariables (p : Problem) (dl : DimensionList) : Problem :=
  { p with PSDVariables := dl }

def setScalarConstraints (p : Problem) (cp : ConeProduct) : Problem :=
  { p with scalarConstraints := cp }

def setPSDConstraints (p : Problem) (dl : DimensionList) : Problem :=
  { p with PSDConstraints := dl }

def setObjectivePSDVariablesCoord (p : Problem) (eml : EncodedMatrixList) : Problem :=
  { p with objectivePSDVariablesCoord := eml }

def setObjectiveScalarVariablesCoord (p : Problem) (ev : EncodedVector) : Problem :=
  { p with objectiveScalarVariablesCoord := ev }

def setObjectiveShiftCoord (p : Problem) (ev : EncodedValue) : Problem :=
  { p with objectiveShiftCoord := ev }

def setScalarConstraintsPSDVariablesCoord (p : Problem) (emll : EncodedMatrixListList) : Problem :=
  { p with scalarConstraintsPSDVariablesCoord := emll }

def setScalarConstraintsScalarVariablesCoord (p : Problem) (em : EncodedMatrix) : Problem :=
  { p with scalarConstraintsScalarVariablesCoord := em }

def setScalarConstraintsShiftCoord (p : Problem) (ev : EncodedVector) : Problem :=
  { p with scalarConstraintsShiftCoord := ev }

def setPSDConstraintsScalarVariablesCoord (p : Problem) (emll : EncodedMatrixListList) : Problem :=
  { p with PSDConstraintsScalarVariablesCoord := emll }

def setPSDConstraintsShiftCoord (p : Problem) (eml : EncodedMatrixList) : Problem :=
  { p with PSDConstraintsShiftCoord := eml }

def addScalarVariable (p : Problem) (c : Cone) : Problem :=
  { p with scalarVariables := p.scalarVariables.addCone c }

def addPSDVariable (p : Problem) (d : Nat) : Problem :=
  { p with PSDVariables := p.PSDVariables.addDimension d }

def addScalarConstraint (p : Problem) (c : Cone) : Problem :=
  { p with scalarConstraints := p.scalarConstraints.addCone c }

def addPSDConstraint (p : Problem) (d : Nat) : Problem :=
  { p with PSDConstraints := p.PSDConstraints.addDimension d }

def addScalarConstraintsPSDVariablesCoord (p : Problem) (i : Nat) (eml : EncodedMatrixList) :
    Problem :=
  let emll := EncodedMatrixListList.fromIndexAndEncodedMatrixList i eml
  { p with scalarConstraintsPSDVariablesCoord :=
      p.scalarConstraintsPSDVariablesCoord.stack emll }

def addScalarConstraintsScalarVariablesCoord (p : Problem) (i : Nat) (ev : EncodedVector) :
    Problem :=
  let em := EncodedMatrix.fromIndexAndEncodedVector i ev
  { p with scalarConstraintsScalarVariablesCoord :=
      p.scalarConstraintsScalarVariablesCoord.stack em }

def addScalarConstraintsShiftCoord (p : Problem) (i : Nat) (v : Float) : Problem :=
  let ev := EncodedVector.fromIndexAndValue i v
  { p with scalarConstraintsShiftCoord :=
      p.scalarConstraintsShiftCoord.stack ev}

def addPSDConstraintsScalarVariablesCoord (p : Problem) (i : Nat) (eml : EncodedMatrixList) :
    Problem :=
  let emll := EncodedMatrixListList.fromIndexAndEncodedMatrixList i eml
  { p with PSDConstraintsScalarVariablesCoord :=
      p.PSDConstraintsScalarVariablesCoord.stack emll }

def addPSDConstraintsShiftCoord (p : Problem) (i : Nat) (em : EncodedMatrix) : Problem :=
  let eml := EncodedMatrixList.fromIndexAndEncodedMatrix i em
  { p with PSDConstraintsShiftCoord :=
      p.PSDConstraintsShiftCoord.stack eml }

def addScalarValuedAffineConstraint (p : Problem) (c : Cone) (Fk : EncodedMatrixList)
    (ak : EncodedVector) (bk : Float) : Problem :=
  Id.run <| do
    let k := p.scalarConstraints.k
    let mut newP := p
    newP := newP.addScalarConstraint c
    newP := newP.addScalarConstraintsPSDVariablesCoord k Fk
    newP := newP.addScalarConstraintsScalarVariablesCoord k ak
    newP := newP.addScalarConstraintsShiftCoord k bk
    newP

def addMatrixValuedAffineConstraint (p : Problem) (d : Nat) (Hk : EncodedMatrixList)
    (Dk : EncodedMatrix)  : Problem :=
  Id.run <| do
    let k := p.PSDConstraints.n
    let mut newP := p
    newP := newP.addPSDConstraint d
    newP := newP.addPSDConstraintsScalarVariablesCoord k Hk
    newP := newP.addPSDConstraintsShiftCoord k Dk
    newP

def mkFloat (n : Nat) : Expr :=
  mkApp3 (mkConst ``OfNat.ofNat [levelZero])
    (mkConst ``Float) (mkNatLit n) (mkApp (mkConst ``instOfNatFloat) (mkNatLit n))

def mkFinIdxExpr (i : Nat) (n : Nat) : MetaM Expr := do
  return mkApp2 (mkConst ``Fin.ofNat) (mkNatLit n.pred) (mkNatLit i)

def mkOfNatExpr (i : Nat) (ty : Expr) : MetaM Expr := do
  let ie := mkNatLit i
  let inst ← synthInstance (mkApp2 (mkConst ``OfNat [levelZero]) ty ie)
  return mkApp3 (mkConst ``OfNat.ofNat [levelZero]) ty ie inst

unsafe def evalFloat (e : Expr) : MetaM Float := do
  evalExpr Float (mkConst ``Float) e

unsafe def elemsOfFintype (ty : Expr) : MetaM (Array Expr) := do
  match ty with
  | .app (.const ``Fin _) nExpr => do
    let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr
    let mut res := #[]
    for i in [:n] do
      res := res.push (← mkFinIdxExpr i n)
    return res
  | .app (.app (.const ``Sum lvl) tyl) tyr => do
    let elemsl := (← elemsOfFintype tyl).map fun e =>
      mkAppN (mkConst ``Sum.inl lvl) #[tyl, tyr, e]
    let elemsr := (← elemsOfFintype tyr).map fun e =>
      mkAppN (mkConst ``Sum.inr lvl) #[tyl, tyr, e]
    return elemsl ++ elemsr
  | _ => throwCoeffsError "unsupported finite type ({ty})."

unsafe def evalFloatMatrix (e : Expr) : MetaM (Array (Array Float)) := do
  let (tyn, tym) ←
    match (← inferType e) with
    | .forallE _ tyn (.forallE _ tym (.const ``Float _) _) _ =>
        pure (tyn, tym)
    | .app (.app (.app (.const ``Matrix _) tyn) tym) (.const ``Float _) =>
        pure (tyn, tym)
    | _ => throwCoeffsError "not a float matrix ({e})."
  let elemsn ← elemsOfFintype tyn
  let elemsm ← elemsOfFintype tym
  let mut res := #[]
  for i in elemsn do
    let mut row := #[]
    for j in elemsm do
      let val ← evalFloat (mkApp2 e i j)
      row := row.push val
    res := res.push row
  return res

partial def generateZerosOfShape (ty : Expr) : MetaM Expr :=
  match ty.consumeMData with
  
  | .const ``Float _ =>
      return (mkFloat 0)
  
  | .forallE _ ty (.const ``Float _) _ =>
      return (mkLambda `_ BinderInfo.default ty (mkFloat 0))
  
  | .app (.app (.app (.const ``Matrix _) tyn) tym)
      (.const ``Float _) => do
      return (mkLambda `_ BinderInfo.default tyn ((mkLambda `_ BinderInfo.default tym) (mkFloat 0)))
  
  | .app (.app (.const ``Prod _) tyl) tyr => do
      let l ← generateZerosOfShape tyl
      let r ← generateZerosOfShape tyr
      return ← mkAppM ``Prod.mk #[l, r]
  | _ => throwCoeffsError "unsupported type ({ty})."

unsafe def generateBasisOfShape (ty : Expr) : MetaM (Array Expr × Array Expr) :=
  match ty.consumeMData with
  
  | .const ``Float _ =>
      return (#[], #[mkFloat 1])
  
  | .forallE _ tyn (.const ``Float _) _ => do
      let mut res := #[]
      for i in ← elemsOfFintype tyn do
        let b ← withLocalDeclD `i' tyn fun i' => do
          let ite ← mkAppM ``ite #[← mkEq i' i, mkFloat 1, mkFloat 0]
          mkLambdaFVars #[i'] ite
        res := res.push b
      return (#[], res)
  
  | .app (.app (.app (.const ``Matrix _) tyn) tym) (.const ``Float _)  => do
      let mut res := #[]
      let elemsn ← elemsOfFintype tyn
      for i in elemsn do
        for j in ← elemsOfFintype tym do
          let b ← withLocalDeclD `i' tyn fun i' => do
            withLocalDeclD `j' tym fun j' => do
              let ite ← mkAppM ``ite #[mkAnd (← mkEq i' i) (← mkEq j' j), mkFloat 1, mkFloat 0]
              mkLambdaFVars #[i', j'] ite
          res := res.push b

      
      return (#[], res)
  
  | .app (.app (.const ``Prod _) tyl) tyr => do
      let r₀ ← generateZerosOfShape tyr
      let l₀ ← generateZerosOfShape tyl

      let (sls₁, mls₁) ← generateBasisOfShape tyl
      let sls ← sls₁.mapM fun l => mkAppM ``Prod.mk #[l, r₀]
      let mls ← mls₁.mapM fun l => mkAppM ``Prod.mk #[l, r₀]

      let (srs₁, mrs₁) ← generateBasisOfShape tyr
      let srs ← srs₁.mapM fun r => mkAppM ``Prod.mk #[l₀, r]
      let mrs ← mrs₁.mapM fun r => mkAppM ``Prod.mk #[l₀, r]

      return (sls ++ srs, mls ++ mrs)
  | _ => throwCoeffsError "unsupported type ({ty})."

unsafe def unrollVectors (constraints : Expr) : MetaM (Array Expr) := do
  let mut res := #[]
  let cs ← decomposeConstraints constraints
  for (_, c) in cs do
    let c' := Expr.consumeMData c
    match c' with
    
    | .app (.app (.app (.const ``Real.Vec.zeroCone _) (.app (.const ``Fin _) n)) _) e =>
        let n : Nat ← evalExpr Nat (mkConst ``Nat) n
        for i in [:n] do
          let idxExpr ← mkFinIdxExpr i n
          let ei := mkApp e idxExpr
          res := res.push (← mkAppM ``Real.zeroCone #[ei])
    
    | .app (.app (.app (.const ``Real.Vec.nonnegOrthCone _) (.app (.const ``Fin _) n)) _) e =>
        let n : Nat ← evalExpr Nat (mkConst ``Nat) n
        for i in [:n] do
          let idxExpr ← mkFinIdxExpr i n
          let ei := mkApp e idxExpr
          res := res.push (← mkAppM ``Real.nonnegOrthCone #[ei])
    
    | .app (.app (.app (.app (.app
      (.const ``Real.Vec.expCone _) (.app (.const ``Fin _) n)) _) a) b) c =>
        let n : Nat ← evalExpr Nat (mkConst ``Nat) n
        for i in [:n] do
          let idxExpr ← mkFinIdxExpr i n
          let ai := mkApp a idxExpr
          let bi := mkApp b idxExpr
          let ci := mkApp c idxExpr
          res := res.push (← mkAppM ``Real.expCone #[ai, bi, ci])
    
    | .app (.app (.app (.app (.app (.const ``Real.Vec.soCone _)
        exprN@(.app (.const ``Fin _) _n)) (.app (.const ``Fin _) m)) finTypeN) t) X =>
        let m : Nat ← evalExpr Nat (mkConst ``Nat) m
        for i in [:m] do
          let idxExpr ← mkFinIdxExpr i m
          let ti := mkApp t idxExpr
          let Xi := mkApp X idxExpr
          res := res.push (mkAppN (mkConst ``Real.soCone) #[exprN, finTypeN, ti, Xi])
    
    
    | .app (.app (.app (.app (.app (.app (.const ``Real.Vec.rotatedSoCone _)
        exprN@(.app (.const ``Fin _) _n)) (.app (.const ``Fin _) m)) finTypeN) v) w) X =>
        let m : Nat ← evalExpr Nat (mkConst ``Nat) m
        for i in [:m] do
          let idxExpr ← mkFinIdxExpr i m
          let vi := mkApp v idxExpr
          let wi := mkApp w idxExpr
          let Xi := mkApp X idxExpr
          res := res.push (mkAppN (mkConst ``Real.rotatedSoCone) #[exprN, finTypeN, vi, wi, Xi])
    | _ =>
        res := res.push c

  return res

unsafe def determineScalarCoeffsAux (e : Expr) (p : Expr) (fty : Expr) :
    MetaM (Array Float × Float) := do
  
  let mut constExpr := e
  let zs ← generateZerosOfShape fty
  constExpr := constExpr.replaceFVar p zs
  let const ← evalFloat constExpr
  
  let (_, scalarBasis) ← generateBasisOfShape fty
  let mut coeffs := #[]
  for one in scalarBasis do
    let mut coeff := e.replaceFVar p one
    coeffs := coeffs.push ((← evalFloat coeff) - const)
  return (coeffs, const)

unsafe def determineMatrixCoeffsAux (e : Expr) (p : Expr) (fty : Expr) :
    MetaM (Array (Array (Array Float)) × Array (Array Float)) := do
  
  let mut constExpr := e
  let zs ← generateZerosOfShape fty
  constExpr := constExpr.replaceFVar p zs
  let const ← evalFloatMatrix constExpr
  
  let (_, scalarBasis) ← generateBasisOfShape fty
  let mut coeffs := #[]
  for one in scalarBasis do
    let coeff := e.replaceFVar p one
    let mut floatCoeff ← evalFloatMatrix coeff
    for i in [:floatCoeff.size] do
      for j in [:floatCoeff.size] do
        let adjust := (const.get! i).get! j
        let val := (floatCoeff.get! i).get! j
        floatCoeff := floatCoeff.set! i ((floatCoeff.get! i).set! j (val - adjust))
    coeffs := coeffs.push floatCoeff
  return (coeffs, const)

unsafe def determineCoeffsFromExpr (minExpr : MinimizationExpr) :
    MetaM (ProblemData × ScalarAffineSections) := do
  let floatDomain ← realToFloat minExpr.domain

  
  let objectiveData ← withLambdaBody minExpr.objFun fun p objFun => do
    let objFun ← realToFloat objFun
    return ← determineScalarCoeffsAux objFun p floatDomain

  let (constraintsData, sections) ←
    withLambdaBody minExpr.constraints fun p constraints => do
    let mut data : ProblemData := ProblemData.empty
    let mut sections := #[]

    
    let cs ← unrollVectors constraints

    
    let mut idx := 0
    for c in cs do
      trace[CvxLean.debug] "`coeffs` going through constraint {c}."
      let mut isTrivial := false
      match Expr.consumeMData c with
      | .const ``True _ => do
          isTrivial := true
      | .app (.const ``Real.zeroCone _) e => do
          let e ← realToFloat e
          let res ← determineScalarCoeffsAux e p floatDomain
          data := data.addZeroConstraint res.1 res.2
          idx := idx + 1
      | .app (.const ``Real.nonnegOrthCone _) e => do
          let e ← realToFloat e
          let res ← determineScalarCoeffsAux e p floatDomain
          data := data.addPosOrthConstraint res.1 res.2
          idx := idx + 1
      | .app (.app (.app (.const ``Real.expCone _) a) b) c => do
          let res ← #[a, b, c].mapM fun e => do
            let e ← realToFloat e
            return ← determineScalarCoeffsAux e p floatDomain
          
          
          data := data.addExpConstraint res[2]!.1 res[2]!.2
          data := data.addExpConstraint res[1]!.1 res[1]!.2
          data := data.addExpConstraint res[0]!.1 res[0]!.2
          idx := idx + 3
      | .app (.app (.app (.app (.app (.const ``Real.rotatedSoCone _)
          (.app (.const ``Fin _) n)) _) v) w) x => do
          let n : Nat ← evalExpr Nat (mkConst ``Nat) n
          
          let xis ← (Array.range n).mapM (fun i => return (mkApp x (← mkFinIdxExpr i n)))
          for e in (#[v, w] ++ xis) do
            let e ← realToFloat e
            let (ea, eb) ← determineScalarCoeffsAux e p floatDomain
            data := data.addRotatedSOConstraint ea eb
            idx := idx + 1
      | .app (.app (.app (.app (.const ``Real.soCone _)
          (.app (.const ``Fin _) n)) _) t) x => do
          let n : Nat ← evalExpr Nat (mkConst ``Nat) n
          
          let xis ← (Array.range n).mapM (fun i => return (mkApp x (← mkFinIdxExpr i n)))
          for e in (#[t] ++ xis) do
            let e ← realToFloat e
            let (ea, eb) ← determineScalarCoeffsAux e p floatDomain
            data := data.addSOConstraint ea eb
            idx := idx + 1
      | .app (.app (.app (.app (.app (.const ``Real.Matrix.nonnegOrthCone _)
          (.app (.const ``Fin _) m)) (.app (.const ``Fin _) n)) _) _) e => do
          let m : Nat ← evalExpr Nat (mkConst ``Nat) m
          let n : Nat ← evalExpr Nat (mkConst ``Nat) n
          for i in [:m] do
            for j in [:n] do
              let ei := mkApp e (← mkFinIdxExpr i m)
              let eij := mkApp ei (← mkFinIdxExpr j n)
              let eij ← realToFloat eij
              let res ← determineScalarCoeffsAux eij p floatDomain
              data := data.addPosOrthConstraint res.1 res.2
              idx := idx + 1
      | .app (.app (.app (.const ``Real.Matrix.PSDCone _) mty) _) e => do
          let e ← realToFloat e
          let res ← determineMatrixCoeffsAux e p floatDomain
          
          let m := res.2.size
          data := data.addMatrixAffineConstraint res.1 res.2
          
          for i in [:m] do
            for j in [i+1:m] do
              let ei := mkApp e (← mkOfNatExpr i mty)
              let ej := mkApp e (← mkOfNatExpr j mty)
              let eij := mkApp ei (← mkOfNatExpr j mty)
              let eji := mkApp ej (← mkOfNatExpr i mty)
              let e ← mkAppM ``Sub.sub #[eij, eji]
              let e ← realToFloat e
              let (a, b) ← determineScalarCoeffsAux e p floatDomain
              data := data.addZeroConstraint a b
              idx := idx + 1
      | _ => throwCoeffsError "no match ({c})."
      
      if !isTrivial then
        sections := sections.push idx
    return (data, sections)

  let (objectiveDataA, objectiveDataB) := objectiveData
  let pd := constraintsData.setObjectiveOnlyVector objectiveDataA objectiveDataB

  return (pd, sections)

noncomputable instance (priority := high) {n} : NormedAddCommGroup (Fin n → ℝ) :=
  PiLp.normedAddCommGroup 2 _

noncomputable instance (priority := high) {n} : NormedAddGroup (Fin n → ℝ) :=
  (PiLp.normedAddCommGroup 2 _).toNormedAddGroup

noncomputable instance (priority := high) {n} : InnerProductSpace ℝ (Fin n → ℝ) :=
  PiLp.innerProductSpace _

def abs (x : m → ℝ) : m → ℝ :=
  fun i => |x i|

def const (n : ℕ) (k : α) : Fin n → α  :=
  fun _ => k

def toMatrix {n : ℕ} (x : Fin n → α) : Matrix (Fin n) (Fin 1) α :=
  fun i => ![x i]

def map {β} (f : α → β) (v : m → α) : m → β :=
  fun i => f (v i)

def sum {m : Type} [Fintype m] (x : m → α) : α :=
  ∑ i, x i

def cumsum (t : Fin n → α) : Fin n → α :=
  fun i => if h : 0 < n then ∑ j in [[⟨0, h⟩, i]], t j else 0

instance : Norm (m → ℝ) := PiLp.instNorm 2 _

def exp : m → ℝ := fun i => Real.exp (x i)

def log : m → ℝ := fun i => Real.log (x i)

def entr : m → ℝ := fun i => Real.entr (x i)

def huber : m → ℝ := fun i => Real.huber (x i)

def klDiv : m → ℝ := fun i => Real.klDiv (x i) (y i)

def norm {n m : ℕ} (x : Fin n → Fin m → ℝ) : Fin n → ℝ :=
  fun i => ‖x i‖

def toArray (x : Fin n → Float) : Array Float :=
  (Array.range n).map (fun i => if h : i < n then x ⟨i, h⟩ else 0)

def sum (x : Fin n → Float) : Float :=
  (toArray x).foldl Float.add 0

def cumsum (x : Fin n → Float) : Fin n → Float :=
  fun i => (((toArray x).toList.take (i.val + 1)).foldl Float.add 0)

def _root_.Real.Computable.norm {n : ℕ} (x : Fin n → Float) : Float :=
  Float.sqrt (sum (fun i => (Float.pow (x i) 2)))

def norm {n m : ℕ} (A : Fin n → Fin m → Float) : Fin n → Float :=
  fun i => Real.Computable.norm (A i)

def exp {m} (x : m → Float) : m → Float :=
  fun i => Float.exp (x i)

def mkAndIntro (xs : Array Expr) : MetaM Expr := do
  let mut res := xs[xs.size - 1]!
  for i in [:xs.size - 1] do
    res ← mkAppM ``And.intro #[xs[xs.size - 2 - i]!, res]
  return res

def foldAndIntro (exprs : Array Expr) : MetaM Expr := do
  if exprs.size == 0 then
    return mkConst ``True.intro
  let mut res := exprs[exprs.size - 1]!
  for i in [1:exprs.size] do
    res ← mkAppM ``And.intro #[exprs[exprs.size - 1 - i]!, res]
  return res

def foldProdMk (exprs : Array Expr) : MetaM Expr := do
  if exprs.size == 0 then
    throwError "cannot make a product of an empty list."
  let mut res := exprs[exprs.size - 1]!
  for i in [1:exprs.size] do
    res ← mkAppM ``Prod.mk #[exprs[exprs.size - 1 - i]!, res]
  return res

def mkExistsFVars (xs : Array Expr) (e : Expr) : MetaM Expr := do
  let mut res := e
  for i in [:xs.size] do
    let x := xs[xs.size - i - 1]!
    res ← mkAppM ``Exists #[← mkLambdaFVars #[x] res]
  return res

def mkExistsIntro (xs : Array Expr) (e : Expr) : MetaM Expr := do
  let mut res := e
  for i in [:xs.size] do
    let x := xs[xs.size - i - 1]!
    res ← mkAppOptM ``Exists.intro
      #[none, some <| ← mkLambdaFVars #[x] (← inferType res), some x, some res]
  return res

def mkLetFVarsWith (e : Expr) (xs : Array Expr) (ts : Array Expr) : MetaM Expr := do
  if xs.size != ts.size then
    throwError "Expected same length: {xs} and {ts}"
  let mut e := e.abstract xs
  for i in [:xs.size] do
    let n := (← FVarId.getDecl xs[xs.size - 1 - i]!.fvarId!).userName
    e := mkLet n (← inferType xs[xs.size - 1 - i]!) ts[xs.size - 1 - i]! e
  return e

def isConstant (e : Expr) : Bool := (Lean.collectFVars {} e).fvarSet.isEmpty

def isRelativelyConstant (e : Expr) (vars : Array FVarId) : Bool := Id.run do
  let fvarSet := (Lean.collectFVars {} e).fvarSet
  for v in vars do
    if fvarSet.contains v then return false
  return true

def removeMData (e : Expr) : CoreM Expr := do
  let post (e : Expr) : CoreM TransformStep := do
    return TransformStep.done e.consumeMData
  Core.transform e (post := post)

def mkAppBeta (e : Expr) (arg : Expr) :=
  e.bindingBody!.instantiate1 arg

def mkAppNBeta (e : Expr) (args : Array Expr) : Expr := Id.run do
  let mut e := e
  for _ in args do
    e := e.bindingBody!
  e.instantiateRev args

def convertLambdasToLets (e : Expr) (args : Array Expr) : Expr := Id.run do
  let mut e := e
  let mut names := #[]
  let mut tys := #[]
  for _ in args do
    tys := tys.push e.bindingDomain!
    names := names.push e.bindingName!
    e := e.bindingBody!
  for i in [:args.size] do
    e := mkLet names[args.size - 1 - i]! tys[args.size - 1 - i]! args[args.size - 1 - i]! e
  return e

def size : Expr → Nat
  | bvar _          => 1
  | fvar _          => 1
  | mvar _          => 1
  | sort _          => 1
  | const _ _       => 1
  | app a b         => size a + size b
  | lam _ _ a _     => size a + 1
  | forallE _ _ a _ => size a + 1
  | letE _ _ a b _  => size a + size b + 1
  | lit _           => 1
  | mdata _ a       => size a
  | proj _ _ a      => size a + 1

def mkProd (as : Array Expr) : MetaM Expr :=
  if as.size == 0 then
    throwError "Empty list."
  else if as.size == 1 then
    return as[0]!
  else
    Array.foldrM
      (fun e1 e2 => Lean.Meta.mkAppM ``Prod.mk #[e1, e2]) as[as.size - 1]! (as.take (as.size - 1))

private def mkArrayAux (ty : Expr) (as : List Expr) :
    MetaM Expr := do
  match as with
  | []    => return mkAppN (mkConst ``Array.empty [levelZero]) #[ty]
  | e::es =>
      let r ← mkArrayAux ty es
      mkAppM ``Array.push #[r, e]

def mkArray (ty : Expr) (as : Array Expr) : MetaM Expr :=
  mkArrayAux ty as.data.reverse

def elabTermAndSynthesizeEnsuringType (stx : Syntax) (expectedType? : Option Expr)
    (errorMsgHeader? : Option String := none) : TermElabM Expr := do
  let e ← elabTermAndSynthesize stx expectedType?
  withRef stx <| ensureHasType expectedType? e errorMsgHeader?

def simpleAddAndCompileDefn (n : Name) (e : Expr) : MetaM Unit := do
  addAndCompile <|
    Declaration.defnDecl <|
      mkDefinitionValEx n [] (← inferType e) e (Lean.ReducibilityHints.regular 0)
      (DefinitionSafety.safe) []

def simpleAddDefn (n : Name) (e : Expr) : MetaM Unit := do
  addDecl <|
    Declaration.defnDecl <|
      mkDefinitionValEx n [] (← inferType e) e (Lean.ReducibilityHints.regular 0)
      (DefinitionSafety.safe) []

@[positivity (log (_ : ℝ))]
def evalLog : PositivityExt where eval {_ _α} zα pα e := do
  let (.app _log (x : Q(ℝ))) ←
    withReducible (whnf e) | throwError "not (Real.log x)"
  
  match ← core zα pα (q($x - (1 : ℝ)) : Q(ℝ)) with
  | .nonnegative pa =>
      let pa' ← mkAppM ``Real.log_nonneg_of_ge_one #[pa]
      pure (.nonnegative pa')
  | .positive pa =>
      let pa' ← mkAppM ``Real.log_pos_of_gt_one #[pa]
      pure (.positive pa')
  | _ =>
      pure .none

@[positivity ((1 / (_ : ℝ) ^ (2 : ℝ)) - 1)]
def evalOneDivSqSubOne : PositivityExt where eval {_ _α} zα pα e := do
  let (.app (.app _sub
    (.app (.app _div _one) (.app (.app _pow (x : Q(ℝ))) _two))) _one') ←
    withReducible (whnf e) | throwError "not ((1 / x ^ 2) - 1)"
  
  let h1 :=
    match ← core zα pα x with
    | .positive pa => some pa
    | _ => none
  
  let h2 := ← do
    match ← core zα pα (q((1 : ℝ) - $x) : Q(ℝ)) with
    | .nonnegative pa => return some pa
    | .positive pa =>
        let pa ← mkAppM ``le_of_lt #[pa]
        return some pa
    | _ => return none
  
  match h1, h2 with
  | some h1', some h2' =>
      let pa' ← mkAppM ``Real.one_sub_one_div_sq_nonneg_of_le_one #[h1', h2']
      pure (.nonnegative pa')
  | _, _ =>
      pure .none

@[positivity (1 - ((_ : ℝ) ^ (2 : ℝ)))]
def evalOneSubSq : PositivityExt where eval {_ _α} zα pα e := do
  let (.app (.app _sub _one) (.app (.app _pow (x : Q(ℝ))) _two)) ←
    withReducible (whnf e) | throwError "not (1 - x ^ 2)"
  
  let h1 := ← do
    match ← core zα pα x with
    | .nonnegative pa => return some pa
    | .positive pa =>
        let pa ← mkAppM ``le_of_lt #[pa]
        return some pa
    | _ => return none
  
  let h2 := ← do
    match ← core zα pα (q((1 : ℝ) - $x) : Q(ℝ)) with
    | .nonnegative pa => return some pa
    | .positive pa =>
        let pa ← mkAppM ``le_of_lt #[pa]
        return some pa
    | _ => return none
  
  match h1, h2 with
  | some h1', some h2' =>
      let pa' ← mkAppM ``Real.one_sub_sq_nonneg_of_le_one #[h1', h2']
      pure (.nonnegative pa')
  | _, _ =>
      pure .none

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

@[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

@[positivity (((_ : ℝ) * (_ : ℝ)) ^ (2 : ℝ)) - ((_ : ℝ) ^ (2 : ℝ))]
def evalSubMulSqSq : PositivityExt where eval {_ _α} zα pα e := do
  let (.app (.app _sub
    (.app (.app _pow (.app (.app _mul (a : Q(ℝ))) (x : Q(ℝ)))) _two))
    (.app (.app _pow' (x' : Q(ℝ))) _two')) ←
    withReducible (whnf e) | throwError "not ((a * x) ^ 2 - x ^ 2)"
  if !(← isDefEq x x') then
    return .none
  
  let h1 := ← do
    match ← core zα pα (q($a - 1) : Q(ℝ)) with
    | .positive pa => return some pa
    | _ => return none
  
  let h2 := ← do
    match ← core zα pα x with
    | .positive pa => return some pa
    | _ => return none
  
  match h1, h2 with
  | some h1', some h2' =>
      let pa' ← mkAppM ``Real.scaled_sq_diff_pos_of_pos #[h1', h2']
      pure (.positive pa')
  | _, _ =>
      pure .none

def replaceConstrName (name : Name) (e : Expr) : MetaM Expr := do
  let (_, e) ← decomposeLabel e
  return mkLabel name e

def renameConstrsBuilder (names : Array Name) : EquivalenceBuilder Unit := fun eqvExpr g => do
  let lhsMinExpr ← eqvExpr.toMinimizationExprLHS

  
  let newConstrs ← withLambdaBody lhsMinExpr.constraints fun p constraints => do
    let mut constraints := (← decomposeAnd constraints).toArray
    if constraints.size < names.size then
      throwRenameConstrsError "too many constraint names. Expected {constraints.size}."
    for i in [:names.size] do
      let newConstr ← replaceConstrName names[i]! constraints[i]!
      constraints := constraints.set! i newConstr
    return ← mkLambdaFVars #[p] <| composeAnd constraints.toList
  let rhsMinExpr := { lhsMinExpr with constraints := newConstrs }

  if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then
      throwRenameConstrsError "failed to unify the goal."

  
  if let _ :: _ ← evalTacticAt (← `(tactic| equivalence_rfl)) g then
      throwRenameConstrsError "failed to close the goal."

@[tactic renameConstrs]
partial def evalRenameConstrs : Tactic := fun stx => match stx with
| `(tactic| rename_constrs [$ids,*]) => do
    let names := ids.getElems.map Syntax.getId
    (renameConstrsBuilder names).toTactic
    saveTacticInfoForToken stx
| _ => throwUnsupportedSyntax

instance : ToExpr Float where
  toExpr f :=
    match Json.Parser.num f.toString.mkIterator with
    | Parsec.ParseResult.success _ res =>
        let e :=
          mkApp5
            (mkConst ``OfScientific.ofScientific [levelZero])
            (mkConst ``Float)
            (mkConst ``instOfScientificFloat)
            (toExpr res.mantissa.natAbs)
            (toExpr true)
            (toExpr res.exponent)
        if res.mantissa < 0 then
          mkApp (mkConst ``Float.neg) e
        else
          e
    | Parsec.ParseResult.error _ _  =>
        mkApp (mkConst ``Float.ofNat) (toExpr (0 : Nat))
  toTypeExpr := mkConst ``Float

instance {n} : ToExpr (Fin n) where
  toExpr x := mkApp (mkConst ``Fin.ofNat) (toExpr x.val)
  toTypeExpr := mkApp (mkConst ``Fin) (toExpr n)

instance : ToExpr (Array Float) :=

instance : ToExpr (Array (Array Float)) :=

@[default_instance]
noncomputable instance (priority := high) : HPow ℝ ℝ ℝ :=

@[default_instance]
noncomputable instance (priority := high) {n} : HPow (Fin n → ℝ) ℝ (Fin n → ℝ) :=

noncomputable def entr (x : Real) :=
  -(x * log x)

noncomputable def huber (x : Real) :=
  if abs x ≤ 1 then x ^ 2 else 2 * abs x - 1

noncomputable def klDiv (x y : Real) :=
  x * log (x / y) - x + y

noncomputable instance instDivReal : Div ℝ :=

noncomputable instance instMinReal : Min ℝ :=

noncomputable instance instMaxReal : Max ℝ :=

def p₁ :=
  optimization (x y : ℝ)
    maximize sqrt (x - y)
    subject to
      c1 : y = 2 * x - 3
      c2 : x ^ (2 : ℝ) ≤ 2
      c3 : 0 ≤ x - y

def p₂ :=
  optimization (x : ℝ)
    minimize (x)
    subject to
      c1 : 1 / 1000 ≤ x
      c2 : 1 / sqrt x ≤ exp x

-- solve p₂

-- set_option trace.Meta.debug true

-- Equivalence mode.
equivalence eqv₂/q₂ : p₂ :=

def p₃ (Awall Aflr α β γ δ : ℝ) :=
  optimization (h w d : ℝ)
    minimize (1 / (h * w * d))
    subject to
      c1 : 0 < h
      c2 : 0 < w
      c3 : 0 < d
      c4 : 2 * (h * w + h * d) ≤ Awall
      c5 : w * d ≤ Aflr
      c6 : α ≤ h / w
      c7 : h / w ≤ β
      c8 : γ ≤ d / w
      c9 : d / w ≤ δ

equivalence* eqv₃/q₃ : p₃ 100 10 0.5 2 0.5 2 :=

def p₃.solution := eqv₃.backward_map q₃.solution

def h := p₃.solution.1

def w := p₃.solution.2.1

def d := p₃.solution.2.2

class ChangeOfVariables {D E} (c : E → D) where
  inv : D → E
  condition : D → Prop
  property : ∀ x, condition x → c (inv x) = x

@[equiv]
def ChangeOfVariables.toEquivalence {D E R} [Preorder R] {f : D → R} {cs : D → Prop} (c : E → D)
    [cov : ChangeOfVariables c] (h : ∀ x, cs x → cov.condition x) :
    ⟨f, cs⟩ ≡ ⟨fun x => f (c x), fun x => cs (c x)⟩ :=
  Equivalence.ofStrongEquivalence <|
  { phi := fun x => cov.inv x
    psi := fun y => c y
    phi_feasibility := fun x hx => by admit /- proof elided -/

instance ChangeOfVariables.comp {D E F} (c₁ : E → D) (c₂ : F → E) [cov₁ : ChangeOfVariables c₁]
    [cov₂ : ChangeOfVariables c₂] : ChangeOfVariables (c₁ ∘ c₂) :=
  { inv := cov₂.inv ∘ cov₁.inv
    condition := fun x => cov₁.condition x ∧ cov₂.condition (cov₁.inv x)
    property := fun x ⟨hx₁, hx₂⟩ => by admit /- proof elided -/

instance ChangeOfVariables.prod_left {D E F} (c : E → D) [cov : ChangeOfVariables c] :
    ChangeOfVariables (fun x : E × F => (c x.1, x.2)) :=
  { inv := fun ⟨x₁, x₂⟩ => (cov.inv x₁, x₂)
    condition := fun ⟨x₁, _⟩ => cov.condition x₁
    property := fun ⟨x₁, x₂⟩ hx => by admit /- proof elided -/

instance ChangeOfVariables.prod_right {D E F} (c : E → D) [cov : ChangeOfVariables c] :
    ChangeOfVariables (fun x : F × E => (x.1, c x.2)) :=
  { inv := fun ⟨x₁, x₂⟩ => (x₁, cov.inv x₂)
    condition := fun ⟨_, x₂⟩ => cov.condition x₂
    property := fun ⟨x₁, x₂⟩ hx => by admit /- proof elided -/

instance ChangeOfVariables.id {D} : ChangeOfVariables (fun x : D => x) :=
  { inv := fun x => x
    condition := fun _ => True
    property := fun _ _ => rfl }

instance : ChangeOfVariables exp :=
  { inv := log
    condition := fun x => 0 < x
    property := fun _ hx => exp_log hx }

instance : ChangeOfVariables sqrt :=
  { inv := (fun x => x ^ 2)
    condition := fun x => 0 ≤ x
    property := fun _ hx => sqrt_sq hx }

instance : ChangeOfVariables (fun x : ℝ => x⁻¹) :=
  { inv := fun x => x⁻¹
    
    
    condition := fun x => x ≠ 0
    property := fun x _ => by admit /- proof elided -/

instance {a : ℝ} : ChangeOfVariables (fun x : ℝ => a * x) :=
  { inv := fun x => (1 / a) * x
    condition := fun _ => a ≠ 0
    property := fun _ h => by admit /- proof elided -/

instance {a : ℝ} : ChangeOfVariables (fun x : ℝ => a / x) :=
  { inv := fun x => a / x
    condition := fun x => x ≠ 0 ∧ a ≠ 0
    property := fun _ ⟨_, ha⟩ => by admit /- proof elided -/

instance {a : ℝ} : ChangeOfVariables (fun x : ℝ => x + a) :=
  { inv := fun x => x - a
    condition := fun _ => True
    property := fun _ _ => by admit /- proof elided -/

instance {a : ℕ} : ChangeOfVariables (fun x : ℝ => x + a) :=
  { inv := fun x => x - a
    condition := fun _ => True
    property := fun _ _ => by admit /- proof elided -/

instance {n : ℕ} {a : Fin n → ℝ} : ChangeOfVariables (fun (v : Fin n → ℝ) => a / v) :=
  { inv := fun v => a / v
    condition := fun v => ∀ i, v i ≠ 0 ∧ a i ≠ 0
    property := fun v hnot0 => by admit /- proof elided -/

def changeOfVariablesBuilder (newVarStx varToChangeStx : TSyntax `ident)
    (changeStx : TSyntax `term) : EquivalenceBuilder Unit :=
  fun eqvExpr g => g.withContext do
    let newVar := newVarStx.getId
    let varToChange := varToChangeStx.getId

    let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
    let vars ← decomposeDomain (← instantiateMVars lhsMinExpr.domain)

    
    let covIdx := vars.findIdx? (fun ⟨n, _⟩ => n == varToChange)
    if covIdx.isNone then
      throwChangeOfVariablesError "variable {varToChange} not found in domain."
    let covIdx := covIdx.get!

    
    let newVars := vars.map (fun ⟨n, ty⟩ => ⟨if n = varToChange then newVar else n, ty⟩)
    let newDomain := composeDomain newVars

    
    let fvars := Array.mk <| vars.map (fun ⟨n, _⟩ => mkFVar (FVarId.mk n))
    
    let changeFnStx ← `(fun ($newVarStx : ℝ) => $changeStx)
    let changeFn ← Tactic.elabTerm changeFnStx none
    
    let changeTerm ← Core.betaReduce <|
      mkApp changeFn (mkFVar (FVarId.mk varToChange))
    
    let c ← withLocalDeclD `p newDomain fun p => do
      Meta.withDomainLocalDecls newDomain p fun xs prs => do
        
        let fullChangeTerm ← Expr.mkProd <|
          (xs.take covIdx) ++ #[changeTerm] ++ (xs.drop (covIdx + 1))
        let replacedFVars := Expr.replaceFVars fullChangeTerm fvars xs
        mkLambdaFVars #[p] (Expr.replaceFVars replacedFVars xs prs)

    
    
    let rec mkCovExpr : ℕ → ℕ → MetaM Expr
      | 0, 0 => do synthInstance (← mkAppM ``ChangeOfVariables #[changeFn])
      | 0, _ => do
          let rType := composeDomain (newVars.drop (covIdx + 1))
          mkAppOptM ``ChangeOfVariables.prod_left #[none, none, rType, changeFn, none]
      | l + 1, r => do
          let covExpr' ← mkCovExpr l r
          mkAppOptM ``ChangeOfVariables.prod_right #[none, none, mkConst ``Real, none, covExpr']
    let covExpr ← mkCovExpr covIdx (vars.length - covIdx - 1)

    
    let D := lhsMinExpr.domain
    let E := newDomain
    let R := lhsMinExpr.codomain
    let RPreorder ← synthInstance (mkAppN (mkConst ``Preorder [levelZero]) #[R])
    let f := lhsMinExpr.objFun
    let cs := lhsMinExpr.constraints
    let toApply := mkAppN (mkConst ``ChangeOfVariables.toEquivalence)
      #[D, E, R, RPreorder, f, cs, c, covExpr]
    let toApply ← instantiateMVars toApply
    let gsAfterApply ← g.apply toApply
    if gsAfterApply.length != 1 then
      throwChangeOfVariablesError (
        "failed to apply `ChangeOfVariables.toEquivalence`, " ++
        "make sure that the change of variables is inferrable by admit /- proof elided -/
        )

    
    let gCondition := gsAfterApply[0]!
    let (_, gCondition) ← gCondition.introN 2 [`x, `h]
    let gsFinal ← evalTacticAt
      (← `(tactic| (simp [ChangeOfVariables.condition] at * <;> arith))) gCondition
    if gsFinal.length != 0 then
      trace[CvxLean.debug] "Could not prove {gsFinal}."
      throwChangeOfVariablesError "failed to solve change of variables condition."

@[tactic changeOfVariables]
def evalChangeOfVariables : Tactic := fun stx => match stx with
  | `(tactic| change_of_variables ($newVarStx) ($varToChangeStx ↦ $changeStx)) => do
      (changeOfVariablesBuilder newVarStx varToChangeStx changeStx).toTactic
      evalTactic <| ← `(tactic| conv_opt => norm_num1)
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

structure GraphAtomData where
  id : Name
  curvature : Curvature
  expr : Expr
  argKinds : Array ArgKind
  bconds : Array (Name × Expr)
  vconds : Array (Name × Expr)
  impVars : Array (Name × Expr)
  impObjFun : Expr
  impConstrs : Array Expr
  solution : Array Expr
  solEqAtom: Expr
  feasibility : Array Expr
  optimality : Expr
  vcondElim : Array Expr
deriving Inhabited, BEq

instance : ToMessageData GraphAtomData where
  toMessageData d :=
    m!"safe: true
    id: {d.id}
    curvature: {d.curvature}
    expr: {d.expr}
    argKinds: {d.argKinds}
    vconds: {d.vconds}
    bconds: {d.bconds}
    impVars: {d.impVars}
    impObjFun : {d.impObjFun}
    impConstrs: {d.impConstrs}
    solution: {d.solution}
    solEqAtom: {d.solEqAtom}
    feasibility: {d.feasibility}
    optimality: {d.optimality}
    vcondElim: {d.vcondElim}"

inductive AtomData
  | graph (d : GraphAtomData)
deriving Inhabited, BEq

instance : ToMessageData AtomData where
  toMessageData
  | AtomData.graph d => toMessageData d

def AtomData.expr : AtomData → Expr
  | graph d => d.expr

def AtomData.graph! : AtomData → GraphAtomData
  | graph d => d

def AtomExtension : Type :=
  PersistentEnvExtension
    (Array Key × AtomData) (Array Key × AtomData) (DiscrTree AtomData)
deriving Inhabited

def addAtom (data : AtomData) : MetaM Unit := do
  let (_, _, expr) ← lambdaMetaTelescope data.expr
  let keys ← DiscrTree.mkPath expr
  setEnv <| atomExtension.addEntry (← getEnv) (keys, data)

def getAtomDiscrTree : MetaM (DiscrTree AtomData) := do
  return atomExtension.getState (← getEnv)

def renameVarsBuilder (names : Array Name) : EquivalenceBuilder Unit := fun eqvExpr g => do
  let lhsMinExpr ← eqvExpr.toMinimizationExprLHS
  let vars ← decomposeDomain (← instantiateMVars eqvExpr.domainLHS)

  
  let renamedVars ← manipulateVars vars names.data
  let newDomain := composeDomain renamedVars

  
  let newObjFun ← withLocalDeclD `p newDomain fun p => do
    let objFunBody := mkAppNBeta lhsMinExpr.objFun #[p]
    mkLambdaFVars #[p] objFunBody
  let newConstrs ← withLocalDeclD `p newDomain fun p => do
    let objFunBody := mkAppNBeta lhsMinExpr.constraints #[p]
    mkLambdaFVars #[p] objFunBody
  let rhsMinExpr : MinimizationExpr :=
    { domain := newDomain,
      codomain := lhsMinExpr.codomain,
      objFun := newObjFun,
      constraints := newConstrs }
  if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then
    throwRenameVarsError "failed to unify the goal."

  
  if let _ :: _ ← evalTacticAt (← `(tactic| equivalence_rfl)) g then
    throwRenameVarsError "dailed to close the goal."
where
  manipulateVars : List (Name × Expr) → List Name → MetaM (List (Name × Expr))
    | (_, ty) :: vars, newName :: newNames =>
        return (newName, ty) :: (← manipulateVars vars newNames)
    | [], _ :: _ => throwRenameVarsError "too many variable names given"
    | vars, [] => return vars

@[tactic renameVars]
partial def evalRenameVars : Tactic := fun stx => match stx with
| `(tactic| rename_vars [$ids,*]) => do
    let names := (ids.elemsAndSeps.map Syntax.getId).filter (· != Lean.Name.anonymous)
    (renameVarsBuilder names).toTactic
    saveTacticInfoForToken stx
| _ => throwUnsupportedSyntax

def runRelaxationTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit :=
  runTransformationTactic TransformationGoal.Relaxation mvarId stx

def elabRelaxationProof (lhs : Expr) (rhsName : Name) (stx : Syntax) : TermElabM (Expr × Expr) :=
  elabTransformationProof TransformationGoal.Relaxation lhs rhsName stx

@[command_elab «relaxation»]
def evalRelaxation : CommandElab := fun stx => match stx with
| `(relaxation $relIdStx / $probIdStx $declSig := $proofStx) => do
  liftTermElabM do
    let (binders, lhsStx) := expandDeclSig declSig.raw
    elabBindersEx binders.getArgs fun xs => do
      let D ← Meta.mkFreshTypeMVar
      let R ← Meta.mkFreshTypeMVar
      let lhsTy := mkApp2 (Lean.mkConst ``Minimization) D R
      let lhs ← elabTermAndSynthesizeEnsuringType lhsStx (some lhsTy)

      
      for mvarId in ← getMVars lhs do
        try {
          let mvarVal ← synthInstance (← mvarId.getDecl).type
          mvarId.assign mvarVal }
        catch _ => pure ()

      let rhsName := probIdStx.getId
      let (rhs, proof) ← elabRelaxationProof lhs rhsName proofStx.raw

      
      let currNamespace ← getCurrNamespace
      let probId := currNamespace ++ probIdStx.getId
      let relId := currNamespace ++ relIdStx.getId

      
      let rhs ← instantiateMVars rhs
      let rhs ← mkLambdaFVars (xs.map Prod.snd) rhs
      let rhs ← instantiateMVars rhs
      simpleAddDefn probId rhs

      
      let proof ← mkLambdaFVars (xs.map Prod.snd) proof
      let proof ← instantiateMVars proof
      simpleAddDefn relId proof
  | _ => throwUnsupportedSyntax

def nonnegOrthCone (x : ℝ) : Prop :=
  0 ≤ x

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

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

@[command_elab atomCommand] unsafe def elabAtomCommand : CommandElab
  | `(declare_atom $id [ $curv ] $args* : $expr :=
      vconditions $vconds*
      implementationVars $impVars*
      implementationObjective $impObj
      implementationConstraints $impConstrs*
      solution $sols*
      solutionEqualsAtom $solEqAtom
      feasibility $feas*
      optimality $opt
      vconditionElimination $vcondElim*) => do
    let atomData ← liftTermElabM do
      let curv ← elabCurvature curv.raw
      let (argDecls, argKinds) ← elabArgKinds args.rawImpl
      let (expr, bodyTy) ← elabExpr expr.raw argDecls
      let (vconds, vcondMap) ← elabVConditions argDecls vconds.rawImpl
      let (impVars, impVarMap) ← elabImpVars argDecls impVars.rawImpl
      let impObj ← elabImpObj argDecls impVars impObj.raw bodyTy
      let impConstrs ← elabImpConstrs argDecls impVars #[] impConstrs.rawImpl
      let sols ← elabSols argDecls impVars impVarMap sols.rawImpl
      let solEqAtom ← elabSolEqAtom argDecls vconds impObj sols expr solEqAtom.raw
      let feas ← elabFeas argDecls vconds #[] impConstrs sols feas.rawImpl
      let opt ← elabOpt curv argDecls expr #[] impVars impObj impConstrs argKinds opt.raw
      let vcondElim ← elabVCondElim curv argDecls #[] vconds vcondMap impVars impConstrs argKinds
        vcondElim.rawImpl

      let atomData := {
        id := id.getId
        curvature := curv
        expr := expr
        argKinds := argKinds
        bconds := #[]
        vconds := vconds
        impVars := impVars
        impObjFun := impObj
        impConstrs := impConstrs.map (·.snd)
        solution := sols
        solEqAtom := solEqAtom
        feasibility := feas
        optimality := opt
        vcondElim := vcondElim
      }
      return atomData

    let atomData ← liftTermElabM <| canonAtomData atomData.curvature atomData
    let atomData ← addAtomDataDecls id.getId atomData

    liftTermElabM do
      trace[CvxLean.debug] "Adding atom data {atomData}."
      addAtom <| AtomData.graph atomData
  | _ => throwUnsupportedSyntax

@[command_elab atomWithBCondsCommand] unsafe def elabAtomWithBCondsCommand : CommandElab
  | `(declare_atom $id [ $curv ] $args* : $expr :=
      bconditions $bconds*
      vconditions $vconds*
      implementationVars $impVars*
      implementationObjective $impObj
      implementationConstraints $impConstrs*
      solution $sols*
      solutionEqualsAtom $solEqAtom
      feasibility $feas*
      optimality $opt
      vconditionElimination $vcondElim*) => do
    let atomData ← liftTermElabM do
      let curv ← elabCurvature curv.raw
      let (argDecls, argKinds) ← elabArgKinds args.rawImpl
      let (expr, bodyTy) ← elabExpr expr.raw argDecls
      let (bconds, _) ← elabBConds argDecls bconds.rawImpl
      let (vconds, vcondMap) ← elabVConditions argDecls vconds.rawImpl
      let (impVars, impVarMap) ← elabImpVars argDecls impVars.rawImpl
      let impObj ← elabImpObj argDecls impVars impObj.raw bodyTy
      let impConstrs ← elabImpConstrs argDecls impVars bconds impConstrs.rawImpl
      let sols ← elabSols argDecls impVars impVarMap sols.rawImpl
      let solEqAtom ← elabSolEqAtom argDecls vconds impObj sols expr solEqAtom.raw
      let feas ← elabFeas argDecls vconds bconds impConstrs sols feas.rawImpl
      let opt ← elabOpt curv argDecls expr bconds impVars impObj impConstrs argKinds opt.raw
      let vcondElim ← elabVCondElim curv argDecls bconds vconds vcondMap impVars impConstrs argKinds
        vcondElim.rawImpl

      let atomData := {
        id := id.getId
        curvature := curv
        expr := expr
        argKinds := argKinds
        bconds := bconds
        vconds := vconds
        impVars := impVars
        impObjFun := impObj
        impConstrs := impConstrs.map (·.snd)
        solution := sols
        solEqAtom := solEqAtom
        feasibility := feas
        optimality := opt
        vcondElim := vcondElim
      }
      return atomData

    let atomData ← liftTermElabM <| canonAtomData atomData.curvature atomData
    let atomData ← addAtomDataDecls id.getId atomData

    liftTermElabM do
      trace[CvxLean.debug] "Adding atom data {atomData}"
      addAtom <| AtomData.graph atomData
  | _ => throwUnsupportedSyntax

@[command_elab affineAtomCommand] unsafe def elabAffineAtomCommand : CommandElab
  | `(declare_atom $id [ affine ] $args* : $expr :=
      bconditions $bconds*
      homogenity $hom
      additivity $add
      optimality $opt) => do
    let atomData ← liftTermElabM do
      let (argDecls, argKinds) ← elabArgKinds args.rawImpl
      let (expr, _bodyTy) ← elabExpr expr.raw argDecls
      let vconds := #[]
      let impVars := #[]
      let impObj := expr
      let impConstrs := #[]
      let sols := #[]
      let solEqAtom ← lambdaTelescope expr fun xs body => do
        return ← mkLambdaFVars xs <| ← mkEqRefl body
      let feas := #[]
      let (bconds, _) ← elabBConds argDecls bconds.rawImpl
      let hom ← elabHom argDecls expr argKinds hom.raw
      check hom 
      let add ← elabAdd argDecls expr argKinds add.raw
      check add 
      let optConcave ← elabOpt Curvature.Concave argDecls expr bconds impVars impObj impConstrs
        argKinds opt.raw
      let optConvex ←
        withExistingLocalDecls argDecls.toList do
          let xs := argDecls.map (mkFVar ·.fvarId)
          let bconds := bconds.map fun (n,c) => (n, mkAppNBeta c xs)
          withLocalDeclsDNondep bconds fun as => do
            withCopyOfMonoXs xs argKinds fun _monoXs ys _monoArgKind => do
              let mut optInverted := optConcave
              let mut i' := 0
              for i in [:xs.size] do
                if argKinds[i]! != ArgKind.Constant ∧ argKinds[i]! != ArgKind.Neither then
                  optInverted := mkApp optInverted ys[i']!
                  i' := i' + 1
                else
                  optInverted := mkApp optInverted xs[i]!
              optInverted := mkAppN optInverted as
              for i in [:xs.size] do
                if argKinds[i]! != ArgKind.Constant ∧ argKinds[i]! != ArgKind.Neither then
                  optInverted := mkApp optInverted xs[i]!
              return ← mkLambdaFVars (xs ++ as ++ ys) optInverted
      let opt ← mkAppM ``And.intro #[optConcave, optConvex]
      let vcondElim := #[]

      let atomData := {
        id := id.getId
        curvature := Curvature.Affine
        expr := expr
        argKinds := argKinds
        vconds := vconds
        bconds := bconds
        impVars := impVars
        impObjFun := impObj
        impConstrs := impConstrs.map (·.snd)
        solution := sols
        solEqAtom := solEqAtom
        feasibility := feas
        optimality := opt
        vcondElim := vcondElim
      }
      return atomData

    let atomData ← addAtomDataDecls id.getId atomData

    liftTermElabM do
      trace[CvxLean.debug] "Addding atom {atomData}"
      addAtom <| AtomData.graph atomData
  | _ => throwUnsupportedSyntax

@[command_elab coneAtomCommand] unsafe def elabConeAtomCommand : CommandElab
  | `(declare_atom $id [ cone ] $args* : $expr :=
        optimality $opt) => do
    let atomData ← liftTermElabM do
      let (argDecls, argKinds) ← elabArgKinds args.rawImpl
      let (expr, _bodyTy) ← elabExpr expr.raw argDecls (ty := some (mkSort levelZero))
      let vconds := #[]
      let impVars := #[]
      let impObj := expr
      let impConstrs := #[]
      let sols := #[]
      let solEqAtom ← lambdaTelescope expr fun xs body =>
        do return ← mkLambdaFVars xs <| ← mkEqRefl body
      let feas := #[]
      let bconds := #[]
      let opt ← elabOpt Curvature.ConvexSet argDecls expr bconds impVars impObj impConstrs argKinds
        opt.raw
      let vcondElim := #[]

      let atomData := {
        id := id.getId
        curvature := Curvature.ConvexSet
        expr := expr
        argKinds := argKinds
        vconds := vconds
        bconds := bconds
        impVars := impVars
        impObjFun := impObj
        impConstrs := impConstrs.map (·.snd)
        solution := sols
        solEqAtom := solEqAtom
        feasibility := feas
        optimality := opt
        vcondElim := vcondElim
      }
      return atomData

    let atomData ← addAtomDataDecls id.getId atomData

    liftTermElabM do
      trace[CvxLean.debug] "Addding atom {atomData}"
      addAtom <| AtomData.graph atomData
  | _ => throwUnsupportedSyntax

def normNumOptBuilder : EquivalenceBuilder Unit :=
  Conv.convertOpt (fullProb := true) (convTac := Mathlib.Tactic.elabNormNum1Conv mkNullNode)

@[tactic normNumOpt]
def evalNormNumOpt : Tactic := fun stx => match stx with
  | `(tactic| norm_num_opt) => withMainContext do
      normNumOptBuilder.toTactic
      saveTacticInfoForToken stx
  | _ => throwUnsupportedSyntax

partial def runEquivalenceTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit := do
  runTransformationTactic TransformationGoal.Equivalence mvarId stx

def elabEquivalenceProof (lhs : Expr) (rhsName : Name) (stx : Syntax) : TermElabM (Expr × Expr) :=
  elabTransformationProof TransformationGoal.Equivalence lhs rhsName stx

def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax × Expr))
    (lhsStx : Syntax) (proofStx : TSyntax `Lean.Parser.Term.byTactic) (bwdMap : Bool) :
    TermElabM Unit := do
  let D ← Meta.mkFreshTypeMVar
  let R ← Meta.mkFreshTypeMVar
  let lhsTy := mkApp2 (Lean.mkConst ``Minimization) D R
  let lhs ← elabTermAndSynthesizeEnsuringType lhsStx (some lhsTy)

  \n{e.toMessageData}"

@[command_elab «equivalence»]
def evalEquivalence : CommandElab := fun stx => match stx with
  | `(equivalence $eqvId / $probId $declSig := $proofStx) => do
      liftTermElabM do
        let (binders, lhsStx) := expandDeclSig declSig.raw
        elabBindersEx binders.getArgs fun xs =>
          evalEquivalenceAux probId eqvId xs lhsStx proofStx false
  | _ => throwUnsupportedSyntax

@[command_elab «equivalenceAndBwdMap»]
def evalEquivalenceAndBwdMap : CommandElab := fun stx => match stx with
  | `(equivalence* $eqvId / $probId $declSig := $proofStx) => do
      liftTermElabM do
        let (binders, lhsStx) := expandDeclSig declSig.raw
        elabBindersEx binders.getArgs fun xs =>
          evalEquivalenceAux probId eqvId xs lhsStx proofStx true
  | _ => throwUnsupportedSyntax

@[local instance]
noncomputable def frobeniusNormedAddCommGroup' [NormedAddCommGroup 𝕜] :
    NormedAddCommGroup (n → 𝕜) :=
  (by admit /- proof elided -/
  )

noncomputable instance : InnerProductSpace 𝕜 (n → 𝕜) :=
  PiLp.innerProductSpace (fun _ : n => 𝕜)

noncomputable instance invertible_lower : Invertible (lower hS) :=
  invertibleOfLeftInverse _ _ (mul_inv_of_invertible (lowerInv hS))

def mkOriginalConstrVars (originalVarsDecls : Array LocalDecl) (constr : Array (Name × Expr)) :
    MetaM (Array LocalDecl) := do
  withExistingLocalDecls originalVarsDecls.toList do
    let mut decls : Array LocalDecl := #[]
    for i in [:constr.size] do
      decls := decls.push <| LocalDecl.cdecl 0 (← mkFreshFVarId) (Name.mkNum constr[i]!.1 i)
        constr[i]!.2 Lean.BinderInfo.default LocalDeclKind.default
    return decls

def mkAtomTree (objCurv : Curvature) (originalVarsDecls extraDecls : Array LocalDecl) (oc : OC Expr)
    (extraVars : Array FVarId := #[]) :
    MetaM (
      OC Bool × OC (Array MessageData) ×
      OC GraphAtomDataTree × OC ArgumentsTree × OC CurvatureTree × OC BCondsTree) := do
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls extraDecls.toList do
      let xs := originalVarsDecls.map fun decl => mkFVar decl.fvarId
      
      let atomsAndArgs ← OC.map2M (fun e c => findAtoms e (xs.map (·.fvarId!) ++ extraVars) c) oc
        ⟨objCurv, oc.constrs.map (fun _ => Curvature.ConvexSet)⟩
      let failedAtom : OC Bool := atomsAndArgs.map (·.fst)
      let failedAtomMsgs : OC (Array MessageData) := atomsAndArgs.map (·.snd.fst)
      if failedAtom.objFun then
        throwDCPError
          "failed to make atom tree for the objective function ({failedAtomMsgs.objFun})."

      let atoms := atomsAndArgs.map (·.snd.snd.fst)
      let args := atomsAndArgs.map (·.snd.snd.snd.fst)
      let curvature := atomsAndArgs.map (·.snd.snd.snd.snd.fst)
      let bconds := atomsAndArgs.map (·.snd.snd.snd.snd.snd)

      return (failedAtom, failedAtomMsgs, atoms, args, curvature, bconds)

partial def mkNewVars (i : Nat) : GraphAtomDataTree → ArgumentsTree → MetaM (NewVarsTree × Nat)
  | Tree.node atom childAtoms, Tree.node args childArgs => do
      let mut childNewVars := #[]
      let mut i := i
      for k in [:childAtoms.size] do
        let (childNewVar, i') ← mkNewVars i childAtoms[k]! childArgs[k]!
        i := i'
        childNewVars := childNewVars.push $ childNewVar
      let mut newVars : Array LocalDecl := #[]
      for (n, ty) in atom.impVars do
        newVars := newVars.push <|
          LocalDecl.cdecl 0 (← mkFreshFVarId) (Name.mkNum n i) (mkAppNBeta ty args)
            Lean.BinderInfo.default LocalDeclKind.default
        i := i + 1
      return (Tree.node newVars childNewVars, i)
  | Tree.leaf _, Tree.leaf _ => pure (Tree.leaf (), i)
  | _, _ => throwDCPError "new variables tree mismatch."

def mkNewVarDeclList (newVars : OC NewVarsTree) : MetaM (List LocalDecl) := do
  let newVarTrees := #[newVars.objFun] ++ newVars.constrs
  let mut newVarDecls := #[]
  for t in newVarTrees do
    newVarDecls := t.fold newVarDecls Array.append
  return newVarDecls.toList

partial def mkCanonExprs : GraphAtomDataTree → NewVarsTree → MetaM CanonExprsTree
  | Tree.node atom childAtoms, Tree.node newVars childNewVars => do
      let mut childCanonExprs := #[]
      for i in [:childAtoms.size] do
        childCanonExprs := childCanonExprs.push <| ← mkCanonExprs childAtoms[i]! childNewVars[i]!
      let canonExpr := atom.impObjFun
      let canonExpr := mkAppNBeta canonExpr (childCanonExprs.map (·.val))
      let canonExpr := mkAppNBeta canonExpr (newVars.map (mkFVar ·.fvarId))
      return Tree.node canonExpr childCanonExprs
  | Tree.leaf e, Tree.leaf () => pure <| Tree.leaf e
  | _, _ => throwDCPError "canonized expressions tree mismatch."

partial def mkNewConstrs : GraphAtomDataTree → BCondsTree → NewVarsTree → CanonExprsTree →
    MetaM NewConstrsTree
  | Tree.node atom childAtoms, Tree.node bconds childBConds, Tree.node newVars childNewVars,
    Tree.node _canonExprs childCanonExprs => do
      let mut childNewConstrs := #[]
      for i in [:childAtoms.size] do
        childNewConstrs := childNewConstrs.push <|
          ← mkNewConstrs childAtoms[i]! childBConds[i]! childNewVars[i]! childCanonExprs[i]!
      let newConstrs := atom.impConstrs
      let newConstrs := newConstrs.map (mkAppNBeta · (childCanonExprs.map Tree.val))
      let newConstrs := newConstrs.map (mkAppNBeta · bconds)
      let newConstrs := newConstrs.map (mkAppNBeta · (newVars.map (mkFVar ·.fvarId)))
      return Tree.node newConstrs childNewConstrs
  | Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _ => pure <| Tree.leaf ()
  | _, _, _, _ => throwDCPError "new constraints tree mismatch."

partial def mkNewConstrVars : NewConstrsTree → MetaM NewConstrVarsTree
  | Tree.node newConstr childNewConstr => do
    
    let mut childNewConstrVar := #[]
    for i in [:childNewConstr.size] do
      childNewConstrVar := childNewConstrVar.push <| ← mkNewConstrVars childNewConstr[i]!
    
    let mut newConstrVars : Array LocalDecl := #[]
    for ty in newConstr do
      newConstrVars := newConstrVars.push <|
        LocalDecl.cdecl 0 (← mkFreshFVarId) `h ty Lean.BinderInfo.default LocalDeclKind.default
    return Tree.node newConstrVars childNewConstrVar
  | Tree.leaf _ => pure <| Tree.leaf ()

partial def mkCanonWithSolution : GraphAtomDataTree → MetaM CanonExprsWithSolutionTree
  | Tree.node atom childAtoms => do
      let mut childCanonExprs := #[]
      for i in [:childAtoms.size] do
        childCanonExprs := childCanonExprs.push <| ← mkCanonWithSolution childAtoms[i]!
      let sols := (atom.solution.map (mkAppNBeta · (childCanonExprs.1.map (·.val.1)).toArray))
      let canonExpr := atom.impObjFun
      let canonExpr := mkAppNBeta canonExpr (childCanonExprs.1.map (·.val.1)).toArray
      let canonExpr := mkAppNBeta canonExpr sols
      return Tree.node ⟨canonExpr, sols⟩ childCanonExprs
  | Tree.leaf e =>
      return Tree.leaf ⟨e, #[]⟩

def mkForwardImagesNewVars (canonWithSolution : OC CanonExprsWithSolutionTree) :
    MetaM Solution := do
  let canonWithSolutionTrees := #[canonWithSolution.objFun] ++ canonWithSolution.constrs
  let mut fm := #[]
  for t in canonWithSolutionTrees do
    fm := t.fold fm (fun acc a => Array.append acc a.solution)
  return fm

partial def mkSolEqAtom : GraphAtomDataTree → CanonExprsWithSolutionTree → VCondsTree →
    MetaM SolEqAtomProofsTree
  | Tree.node atom childAtoms,
    Tree.node _canonWithSolution childCanonWithSolution,
    Tree.node vcondVars childVCondVars => do
      
      let mut childSolEqAtom := #[]
      for i in [:childAtoms.size] do
        childSolEqAtom := childSolEqAtom.push <|
          ← mkSolEqAtom childAtoms[i]! childCanonWithSolution[i]! childVCondVars[i]!
      
      let mut solEqAtomR ← mkEqRefl atom.expr
      for c in childSolEqAtom do
        solEqAtomR ← mkCongr solEqAtomR c.val
      
      let mut vconds := #[]
      for i in [:vcondVars.size] do
        let mut vcondEqCanonVCond ← mkEqRefl atom.vconds[i]!.2
        for c in childSolEqAtom do
          vcondEqCanonVCond ← mkCongr vcondEqCanonVCond c.val
        vconds := vconds.push <| ← mkEqMPR vcondEqCanonVCond vcondVars[i]!
      
      let solEqAtomL := atom.solEqAtom
      let solEqAtomL := mkAppN solEqAtomL (childCanonWithSolution.map (·.val.1))
      let solEqAtomL := mkAppN solEqAtomL vconds
      let solEqAtom ← mkEqTrans solEqAtomL solEqAtomR
      return Tree.node solEqAtom childSolEqAtom
  | Tree.leaf e, Tree.leaf _, Tree.leaf _ => do return Tree.leaf (← mkEqRefl e)
  | _, _, _ => throwDCPError "solution-equals-atom tree mismatch."

partial def mkFeasibility : GraphAtomDataTree → CanonExprsWithSolutionTree → BCondsTree →
    VCondsTree → SolEqAtomProofsTree → MetaM FeasibilityProofsTree
  | Tree.node atom childAtoms,
    Tree.node _canonWithSolution childCanonWithSolution,
    Tree.node bconds childBConds,
    Tree.node vcondVars childVCondVars,
    Tree.node _solEqAtom childSolEqAtom => do
        
        let mut childFeasibility := #[]
        for i in [:childAtoms.size] do
          let c ← mkFeasibility childAtoms[i]! childCanonWithSolution[i]! childBConds[i]!
            childVCondVars[i]! childSolEqAtom[i]!
          childFeasibility := childFeasibility.push c
        
        let mut vconds := #[]
        for i in [:vcondVars.size] do
          let mut vcondEqCanonVCond ← mkEqRefl atom.vconds[i]!.2
          for c in childSolEqAtom do
            vcondEqCanonVCond ← mkCongr vcondEqCanonVCond c.val
          vconds := vconds.push <| ← mkEqMPR vcondEqCanonVCond vcondVars[i]!
        
        let feasibility := atom.feasibility
        let feasibility := feasibility.map (mkAppN · (childCanonWithSolution.map (·.val.1)))
        let feasibility := feasibility.map (mkAppN · bconds)
        let feasibility := feasibility.map (mkAppN · vconds)
        let _ ← feasibility.mapM check
        return Tree.node feasibility childFeasibility
  | Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _  => return Tree.leaf ()
  | _, _, _, _, _ => throwError "feasibility proofs tree mismatch."

partial def mkOptimalityAndVCondElim : GraphAtomDataTree → ArgumentsTree → CanonExprsTree →
    NewVarsTree → NewConstrVarsTree → CurvatureTree → BCondsTree →
    MetaM OptimalityAndVCondElimProofsTree
  | Tree.node atom childAtoms,
    Tree.node args childArgs,
    Tree.node _canonExpr childCanonExpr,
    Tree.node newVars childNewVars,
    Tree.node newConstrVars childNewConstrVars,
    Tree.node curvature childCurvature,
    Tree.node bconds childBConds => do
      
      let mut childOptimality := #[]
      let mut childOptimalityFiltered := #[]
      for i in [:childAtoms.size] do
        if atom.argKinds[i]! != ArgKind.Constant && atom.argKinds[i]! != ArgKind.Neither then
          let opt ← mkOptimalityAndVCondElim childAtoms[i]! childArgs[i]! childCanonExpr[i]!
              childNewVars[i]! childNewConstrVars[i]! childCurvature[i]! childBConds[i]!
          childOptimality := childOptimality.push opt
          childOptimalityFiltered := childOptimalityFiltered.push opt
        else
          childOptimality := childOptimality.push <| Tree.leaf (mkStrLit s!"dummy {args[i]!}", #[])
      let mut monoArgs := #[]
      for i in [:args.size] do
        if atom.argKinds[i]! != ArgKind.Constant && atom.argKinds[i]! != ArgKind.Neither then
          monoArgs := monoArgs.push args[i]!

      
      
      
      let optimality ←
        match curvature, atom.curvature with
          | Curvature.Concave, Curvature.Affine => mkAppM ``And.left #[atom.optimality]
          | Curvature.Convex, Curvature.Affine => mkAppM ``And.right #[atom.optimality]
          | _, _ => pure atom.optimality
      let optimality := mkAppN optimality (childCanonExpr.map Tree.val)
      let optimality := mkAppN optimality bconds
      check optimality
      let optimality := mkAppN optimality (newVars.map (mkFVar ·.fvarId))
      let optimality := mkAppN optimality (newConstrVars.map (mkFVar ·.fvarId))
      let optimality := mkAppN optimality monoArgs
      let optimality := mkAppN optimality (childOptimalityFiltered.map (·.val.1))
      check optimality

      
      let vcondElim := atom.vcondElim
      let vcondElim := vcondElim.map (mkAppN · (childCanonExpr.map Tree.val))
      let vcondElim := vcondElim.map (mkAppN · (newVars.map (mkFVar ·.fvarId)))
      let vcondElim := vcondElim.map (mkAppN · (newConstrVars.map (mkFVar ·.fvarId)))
      let vcondElim := vcondElim.map (mkAppN · monoArgs)
      let vcondElim := vcondElim.map (mkAppN · (childOptimalityFiltered.map (·.val.1)))

      return Tree.node (optimality, vcondElim) childOptimality
  | Tree.leaf e, Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _, Tree.leaf _ => do
      trace[CvxLean.debug] "Optimality Leaf {e}."
      return Tree.leaf (← mkAppM ``le_refl #[e], #[])
  | _, _, _, _, _, _, _ =>
      throwDCPError "optimality and vcondition elimination proof tree mismatch."

def mkVCondVars (originalConstrFVars : Array FVarId) (vcondIdx : PreVCondsTree) :
    Tree (Array Expr) Unit :=
  vcondIdx.map (fun iores => iores.map fun iore =>
    match iore with
    | Sum.inl i => mkFVar originalConstrFVars[i]!
    | Sum.inr e => e) id

partial def findVConditions (originalConstrVars : Array LocalDecl) (constraints : Array Expr) :
    GraphAtomDataTree → ArgumentsTree → MetaM PreVCondsTree
  | Tree.node atom childAtoms, Tree.node args childArgs => do
      let mut childrenVCondData := #[]
      for i in [:childAtoms.size] do
        let childVCondData ←
          findVConditions originalConstrVars constraints childAtoms[i]! childArgs[i]!
        childrenVCondData := childrenVCondData.push childVCondData
      let mut vcondData := #[]
      for (_, vcond) in atom.vconds do
        let vcond := mkAppNBeta vcond args

        \n{vcond}\n{constraints}.")

      return (Tree.node vcondData childrenVCondData)
  | Tree.leaf _, Tree.leaf _ => pure (Tree.leaf ())
  | _, _ => throwDCPError "variable conditions tree mismatch."

def mkVConditions (originalVarsDecls : Array LocalDecl) (oc : OC Expr)
    (constraints : List (Name × Expr)) (atoms : OC GraphAtomDataTree) (args : OC ArgumentsTree)
    (failedAtom : OC Bool) (failedAtomMsgs : OC (Array MessageData))
    (originalConstrVars : Array LocalDecl) :
    MetaM (OC VCondsIdxsTree × Array Bool × OC VCondsTree) := do
  withExistingLocalDecls originalVarsDecls.toList do
    let vcondData ← OC.map2M (findVConditions originalConstrVars oc.constrs) atoms args

    
    let vcondIdx : OC VCondsIdxsTree := vcondData.map
      (fun t => t.map (fun iores => iores.foldl (init := #[]) (fun (acc : Array ℕ) iore =>
        match iore with
          | Sum.inl i => acc.push i
          | Sum.inr _ => acc))
        id)

    let isVCond := vcondIdx.fold (oc.constrs.map (fun _ => false))
      fun acc vcondIdxTree =>
        vcondIdxTree.fold acc fun acc is =>
          is.foldl (fun acc i => acc.set! i true) acc
    let vcondVars := vcondData.map <| mkVCondVars (originalConstrVars.map LocalDecl.fvarId)
    for i in [:isVCond.size] do
      if failedAtom.constrs[i]! && !isVCond[i]! then
        throwDCPError
          "failure in constraint {constraints.toArray[i]!.1} ({failedAtomMsgs.constrs[i]!})."
    return (vcondIdx, isVCond, vcondVars)

def mkOC (objFun : Expr) (constraints : List (Name × Expr)) (originalVarsDecls : Array LocalDecl) :
    MetaM (OC Expr) := do
  withExistingLocalDecls originalVarsDecls.toList do
    return OC.mk objFun (constraints.toArray.map (fun (c : Name × Expr) => c.2))

def mkOCWithNames (objFun : Expr) (constraints : List (Name × Expr))
    (originalVarsDecls : Array LocalDecl) : MetaM (OC (Name × Expr)) := do
  withExistingLocalDecls originalVarsDecls.toList do
    let oc := OC.mk (`_, objFun) constraints.toArray
    return oc

def mkSolEqAtomOC (originalVarsDecls : Array LocalDecl) (atoms : OC GraphAtomDataTree)
    (canonWithSolution : OC CanonExprsWithSolutionTree) (vcondVars : OC VCondsTree)
    (originalConstrVars : Array LocalDecl) : MetaM (OC SolEqAtomProofsTree) :=
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls originalConstrVars.toList do
      let solEqAtom ← OC.map3M mkSolEqAtom atoms canonWithSolution vcondVars
      trace[CvxLean.debug] "`solEqAtom`: {solEqAtom}."
      return solEqAtom

def mkFeasibilityOC (originalVarsDecls : Array LocalDecl) (atoms : OC GraphAtomDataTree)
    (canonWithSolution : OC CanonExprsWithSolutionTree) (bconds : OC BCondsTree)
    (vcondVars : OC VCondsTree) (originalConstrVars : Array LocalDecl)
    (solEqAtom : OC SolEqAtomProofsTree) : MetaM (OC FeasibilityProofsTree) :=
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls originalConstrVars.toList do
      let feasibility ← OC.map5M mkFeasibility atoms canonWithSolution bconds vcondVars solEqAtom
      trace[CvxLean.debug] "`feasibility`: {feasibility}."
      return feasibility

def mkCanonExprsOC (originalVarsDecls : Array LocalDecl) (newVarDecls : List LocalDecl)
    (atoms : OC GraphAtomDataTree) (newVars : OC NewVarsTree) : MetaM (OC CanonExprsTree) :=
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls newVarDecls do
      let canonExprs ← OC.map2M mkCanonExprs atoms newVars
      trace[CvxLean.debug] "`canonExprs`: {canonExprs}."
      return canonExprs

def mkNewConstrsOC (originalVarsDecls : Array LocalDecl) (newVarDecls : List LocalDecl)
    (bconds : OC BCondsTree) (atoms : OC GraphAtomDataTree) (newVars : OC NewVarsTree)
    (canonExprs : OC CanonExprsTree) :
    MetaM (Array Expr × OC NewConstrVarsTree × Array LocalDecl) :=
  withExistingLocalDecls (originalVarsDecls.toList) do
    withExistingLocalDecls newVarDecls do
      let newConstrs ← OC.map4M mkNewConstrs atoms bconds newVars canonExprs
      trace[CvxLean.debug] "`newConstrs` {newConstrs}."
      let newConstrVars ← OC.mapM mkNewConstrVars newConstrs
      trace[CvxLean.debug] "`newConstrsVars`: {newConstrs}."
      let newConstrs : Array Expr := newConstrs.fold #[] fun acc cs =>
        cs.fold acc Array.append
      let newConstrVarsArray : Array LocalDecl := newConstrVars.fold #[] fun acc cs =>
        cs.fold acc Array.append
      return (newConstrs, newConstrVars, newConstrVarsArray)

def mkOptimalityAndVCondElimOC (originalVarsDecls : Array LocalDecl) (newVarDecls : List LocalDecl)
    (newConstrVarsArray : Array LocalDecl) (atoms : OC GraphAtomDataTree) (args : OC ArgumentsTree)
    (canonExprs : OC CanonExprsTree) (newVars : OC NewVarsTree)
    (newConstrVars : OC NewConstrVarsTree) (curvature : OC CurvatureTree) (bconds : OC BCondsTree)
    (vcondIdx : OC VCondsIdxsTree) : MetaM (OC OptimalityProofsTree × VCondElimMap):=
  withExistingLocalDecls originalVarsDecls.toList do
    withExistingLocalDecls newVarDecls do
      withExistingLocalDecls newConstrVarsArray.toList do
        let optimalityAndVCondElim ← OC.map7M mkOptimalityAndVCondElim atoms args canonExprs newVars
          newConstrVars curvature bconds
        let optimality := optimalityAndVCondElim.map (fun oce => oce.map Prod.fst Prod.fst)
        let vcondElim := optimalityAndVCondElim.map (fun oce => oce.map Prod.snd Prod.snd)
        trace[CvxLean.debug] "`optimality`: {optimality}."
        trace[CvxLean.debug] "`vcondElim`: {vcondElim}."
        trace[CvxLean.debug] "`vcondIdx`: {vcondIdx}."
        let vcondElimWithIdx ← OC.map2M (fun a b => Tree.zip a b) vcondIdx vcondElim
        let vcondElimMap := vcondElimWithIdx.fold {}
          fun (map : VCondElimMap) ci =>
            ci.fold map fun (map : VCondElimMap) ci => Id.run do
              let mut res := map
              for i in [:ci.1.size] do
                res := res.insert ci.1[i]! ci.2[i]!
              return res
        return (optimality, vcondElimMap)

def mkProcessedAtomTree (objCurv : Curvature) (objFun : Expr) (constraints : List (Name × Expr))
    (originalVarsDecls : Array LocalDecl) (extraDecls : Array LocalDecl := #[])
    (extraVars : Array FVarId := #[]) : MetaM ProcessedAtomTree := do
  let oc ← mkOC objFun constraints originalVarsDecls
  let (failedAtom, failedAtomMsgs, atoms, args, curvature, bconds) ← mkAtomTree objCurv
    originalVarsDecls (extraDecls := extraDecls) (extraVars := extraVars) oc
  let originalConstrVars ← mkOriginalConstrVars originalVarsDecls constraints.toArray
  let (vcondIdx, isVCond, vcondVars) ← mkVConditions originalVarsDecls oc constraints atoms args
    failedAtom failedAtomMsgs originalConstrVars
  let newVars ← withExistingLocalDecls originalVarsDecls.toList do
    OC.map2MwithCounter mkNewVars atoms args
  let newVarDecls ← mkNewVarDeclList newVars
  let canonWithSolution ← withExistingLocalDecls originalVarsDecls.toList do
    OC.mapM mkCanonWithSolution atoms
  let forwardImagesNewVars ← withExistingLocalDecls originalVarsDecls.toList do
    mkForwardImagesNewVars canonWithSolution
  let solEqAtom ← mkSolEqAtomOC originalVarsDecls atoms canonWithSolution vcondVars
    originalConstrVars
  let feasibility ← mkFeasibilityOC originalVarsDecls atoms canonWithSolution bconds vcondVars
    originalConstrVars solEqAtom
  let canonExprs ← mkCanonExprsOC originalVarsDecls newVarDecls atoms newVars
  let (newConstrs, newConstrVars, newConstrVarsArray) ←
    mkNewConstrsOC originalVarsDecls newVarDecls bconds atoms newVars canonExprs
  let (optimality, vcondElimMap) ← mkOptimalityAndVCondElimOC originalVarsDecls newVarDecls
    newConstrVarsArray atoms args canonExprs newVars newConstrVars curvature bconds vcondIdx

  return ProcessedAtomTree.mk
    (originalVarsDecls := originalVarsDecls)
    (originalConstrVars := originalConstrVars)
    (newVarDecls := newVarDecls)
    (newConstrs := newConstrs)
    (newConstrVarsArray := newConstrVarsArray)
    (forwardImagesNewVars := forwardImagesNewVars)
    (constraints := constraints)
    (isVCond := isVCond)
    (vcondElimMap := vcondElimMap)
    (solEqAtom := solEqAtom)
    (feasibility := feasibility)
    (canonExprs := canonExprs)
    (optimality := optimality)

def withExpr (e : Expr) (x : m α) : m α := do
  withTheReader SubExpr (fun cfg => { cfg with expr := e, pos := cfg.pos }) x

def withBindingBody'' {α : Type} {m : Type → Type} [Monad m] [MonadReaderOf SubExpr m]
    [MonadWithReaderOf SubExpr m] [MonadControlT MetaM m] (n : Name) (x : Expr → m α) : m α := do
  let e ← getExpr
  Meta.withLocalDecl n e.binderInfo e.bindingDomain! fun fvar =>
    descend (e.bindingBody!.instantiate1 fvar) 1 (x fvar)

def implementationObjective := leading_parser withPosition <|
  nonReservedSymbol "implementationObjective" >>
  checkColGt "indented term" >> termParser

def solutionEqualsAtom := leading_parser withPosition <|
  nonReservedSymbol "solutionEqualsAtom" >>
  checkColGt "indented term" >> termParser

def optimality := leading_parser withPosition <|
  nonReservedSymbol "optimality" >>
  checkColGt "indented term" >> termParser

def homogenity := leading_parser withPosition <|
  nonReservedSymbol "homogenity" >>
  checkColGt "indented term" >> termParser

def additivity := leading_parser withPosition <|
  nonReservedSymbol "additivity" >>
  checkColGt "indented term" >> termParser

def makeForwardMap (oldDomain : Expr) (xs : Array Expr) (forwardImagesNewVars : Array Expr) :
    MetaM Expr := do
  withLocalDeclD `p oldDomain fun p => do
    let prs := (← Meta.mkProjections oldDomain p).map (·.2.2)
    let forwardBody := xs ++ forwardImagesNewVars
    let forwardMap ← mkLambdaFVars #[p] <| (← foldProdMk forwardBody).replaceFVars xs prs.toArray
    trace[CvxLean.debug] "Forward map: {forwardMap}."
    check forwardMap
    trace[CvxLean.debug] "Forward map checked."

    return forwardMap

def makeConstrForward (oldDomain : Expr) (xs : Array Expr) (originalConstrVars : Array LocalDecl)
    (oldProblem : Expr) (constraints : Array Expr) (isVCond : Array Bool)
    (constraintsEq : Array Expr) (feasibility : OC FeasibilityProofsTree) : MetaM Expr := do
  
  withLocalDeclD `p oldDomain fun p => do
    let prs := (← Meta.mkProjections oldDomain p).map (·.2.2)

    withLocalDeclD `h (← mkAppM ``Minimization.constraints #[oldProblem, p]) fun h => do
      let (_, cprs) := Meta.composeAndWithProj constraints.toList
      let hProj := (cprs h).toArray

      
      let mut oldConstrProofs := #[]
      for i in [:originalConstrVars.size] do
        if not isVCond[i]! then
          oldConstrProofs := oldConstrProofs.push <|
            ← mkAppM ``Eq.mpr #[constraintsEq[i]!, mkFVar originalConstrVars[i]!.fvarId]

      
      let newConstrProofs := feasibility.fold #[] fun acc fs =>
          fs.fold acc Array.append

      let constrForwardBody ← foldAndIntro (oldConstrProofs ++ newConstrProofs)
      let constrForwardBody := constrForwardBody.replaceFVars
        ((originalConstrVars).map (mkFVar ·.fvarId)) hProj
      let constrForwardBody ← mkLambdaFVars #[h] constrForwardBody
      let constrForwardBody := constrForwardBody.replaceFVars xs prs.toArray
      let constrForward ← mkLambdaFVars #[p] constrForwardBody
      trace[CvxLean.debug] "φ feasibility proof: {constrForward}."
      trace[CvxLean.debug] "φ feasibility proof type: {← inferType constrForward}."
      check constrForward

      return constrForward

def makeObjFunForward (oldDomain : Expr) (xs : Array Expr) (originalConstrVars : Array LocalDecl)
    (oldProblem : Expr) (constraints : Array Expr) (objFunEq : Expr) : MetaM Expr := do
  
  withLocalDeclD `p oldDomain fun p => do
    let prs := (← Meta.mkProjections oldDomain p).map (·.2.2)

    withLocalDeclD `h (← mkAppM ``Minimization.constraints #[oldProblem, p]) fun h => do
      let (_, cprs) := Meta.composeAndWithProj constraints.toList
      let hProj := (cprs h).toArray

      let objFunForward ← mkAppM ``le_of_eq #[objFunEq.replaceFVars xs prs.toArray]

      let objFunForward := objFunForward.replaceFVars
        ((originalConstrVars).map (mkFVar ·.fvarId)) hProj
      let objFunForward ← mkLambdaFVars #[h] objFunForward

      let objFunForward := objFunForward.replaceFVars xs prs.toArray
      let objFunForward ← mkLambdaFVars #[p] objFunForward
      trace[CvxLean.debug] "φ optimality proof: {objFunForward}."
      trace[CvxLean.debug] "φ optimality proof type: {← inferType objFunForward}."
      check objFunForward

      return objFunForward

def makeBackwardMap (xs : Array Expr) (mkDomainFunc : Expr → MetaM Expr) : MetaM Expr := do
  let backwardBody ← foldProdMk xs
  let backwardMap ← mkDomainFunc backwardBody
  trace[CvxLean.debug] "Backward map: {backwardMap}."
  check backwardMap
  trace[CvxLean.debug] "Backward map checked."

  return backwardMap

def makeConstrBackward (vcondElimMap : VCondElimMap) (newDomain : Expr) (canonProblem : Expr)
    (xs : Array Expr) (ys : Array Expr) (constrOpt : Array Expr) (canonConstrs : Array Expr)
    (newConstrs : Array Expr) (newConstrVars : Array LocalDecl) : MetaM Expr := do
  
  withLocalDeclD `p newDomain fun p => do
    let prs := (← Meta.mkProjections newDomain p).map (·.2.2)

    withLocalDeclD `h (← mkAppM ``Minimization.constraints #[canonProblem, p]) fun h => do
      let (_, cprs) := Meta.composeAndWithProj (canonConstrs ++ newConstrs).toList
      let hProj := cprs h
      let mut constrBackwardProofs := #[]
      let mut filteredCounter := 0
      for i in [:constrOpt.size] do
        match vcondElimMap.find? i with
          | some p =>
              constrBackwardProofs := constrBackwardProofs.push p
          | none =>
              constrBackwardProofs := constrBackwardProofs.push <|
                mkApp constrOpt[i]! hProj.toArray[filteredCounter]!
              filteredCounter := filteredCounter + 1

      let constrBackwardBody ← foldAndIntro constrBackwardProofs
      let constrBackwardBody := constrBackwardBody.replaceFVars
        (newConstrVars.map (mkFVar ·.fvarId))
        (hProj.drop (hProj.length - newConstrVars.size)).toArray

      let constrBackwardBody := constrBackwardBody.replaceFVars (xs ++ ys) prs.toArray

      let constrBackward ← mkLambdaFVars #[p, h] constrBackwardBody
      trace[CvxLean.debug] "ψ feasibility proof: {constrBackward}."
      trace[CvxLean.debug] "ψ feasibility proof type: {← inferType constrBackward}."
      check constrBackward

      return constrBackward

def makeObjFunBackward (newDomain : Expr) (canonProblem : Expr) (xs : Array Expr) (ys : Array Expr)
    (objFunOpt : Expr) (canonConstrs : Array Expr) (newConstrs : Array Expr)
    (newConstrVars : Array LocalDecl) : MetaM Expr := do
  
  withLocalDeclD `p newDomain fun p => do
    let prs := (← Meta.mkProjections newDomain p).map (·.2.2)

    withLocalDeclD `h (← mkAppM ``Minimization.constraints #[canonProblem, p]) fun h => do
      let (_, cprs) := Meta.composeAndWithProj (canonConstrs ++ newConstrs).toList
      let hProj := cprs h
      let objFunBackwardBody := objFunOpt

      let objFunBackwardBody := objFunBackwardBody.replaceFVars
        (newConstrVars.map (mkFVar ·.fvarId))
        (hProj.drop (hProj.length - newConstrVars.size)).toArray

      let objFunBackwardBody := objFunBackwardBody.replaceFVars
        (xs ++ ys) prs.toArray

      let objFunBackward ← mkLambdaFVars #[p, h] objFunBackwardBody
      trace[CvxLean.debug] "ψ optimality proof: {objFunBackward}."
      trace[CvxLean.debug] "ψ optimality proof type: {← inferType objFunBackward}."
      check objFunBackward

      return objFunBackward

def canonize (ogProblem : MinimizationExpr) : MetaM (MinimizationExpr × Expr) := do
  let ogProblemExpr := ogProblem.toExpr
  let D := ogProblem.domain
  let R := ogProblem.codomain

  
  
  let (objFun, constraints, originalVarsDecls) ←
    withLambdaBody ogProblem.constraints fun p constraints => do
      let pr := (← Meta.mkProjections D p).toArray
      let originalVarsDecls ←
        withLocalDeclsD (pr.map fun (n, ty, _) => (n, fun _ => return ty))
          fun xs => xs.mapM fun x => x.fvarId!.getDecl
      withExistingLocalDecls originalVarsDecls.toList do
        let xs := originalVarsDecls.map fun decl => mkFVar decl.fvarId
        let constraints ← Meta.replaceProjections constraints p.fvarId! xs
        let constraints : List (Name × Expr) ← Meta.decomposeConstraints constraints
        let constraints ← constraints.mapM (fun (n, e) => do
          return (n, ← Expr.removeMData e))
        let objFunP := ogProblem.objFun.bindingBody!.instantiate1 p
        let objFun ← Meta.replaceProjections objFunP p.fvarId! xs
        return (objFun, constraints, originalVarsDecls)

  
  let pat ← mkProcessedAtomTree Curvature.Convex objFun constraints originalVarsDecls

  
  withExistingLocalDecls pat.originalVarsDecls.toList do
    
    let originalVars := pat.originalVarsDecls.map fun decl => mkFVar decl.fvarId

    
    let forwardMap ← makeForwardMap D originalVars pat.forwardImagesNewVars

    let (objFunForward, constrForward) ←
      withExistingLocalDecls pat.originalConstrVars.toList do
        let objFunForward ← makeObjFunForward D originalVars pat.originalConstrVars ogProblemExpr
          (pat.constraints.toArray.map Prod.snd) pat.solEqAtom.objFun.val
        let constrForward ← makeConstrForward D originalVars pat.originalConstrVars ogProblemExpr
          (pat.constraints.toArray.map Prod.snd) pat.isVCond (pat.solEqAtom.constrs.map Tree.val)
          pat.feasibility
        return (objFunForward, constrForward)

    withExistingLocalDecls pat.newVarDecls do
      
      let newVars := (pat.newVarDecls.map (mkFVar ·.fvarId)).toArray

      
      let canonVars ← (originalVars ++ newVars).mapM fun x => do
        let decl ← x.fvarId!.getDecl
        return (decl.userName, decl.type)

      
      let E := Meta.composeDomain canonVars.toList

      
      let mkDomain := fun e =>
        withLocalDeclD `p E fun p => do
          let prs := (← Meta.mkProjections E p).map (·.2.2)
          let e := Expr.replaceFVars e (originalVars ++ newVars) prs.toArray
          mkLambdaFVars #[p] e

      
      let canonConstrs := pat.canonExprs.constrs.map Tree.val
      let canonConstrs := canonConstrs.filterIdx (fun i => ¬ pat.isVCond[i]!)
      let canonProblem : MinimizationExpr :=
        { domain := E
          codomain := R
          objFun := ← mkDomain pat.canonExprs.objFun.val
          constraints := ← mkDomain <| Meta.composeAnd (canonConstrs ++ pat.newConstrs).toList }
      let canonProblemExpr := canonProblem.toExpr

      
      let backwardMap ← makeBackwardMap originalVars mkDomain

      let (objFunBackward, constrBackward) ←
        withExistingLocalDecls pat.newConstrVarsArray.toList do
          let objFunBackward ← makeObjFunBackward E canonProblemExpr originalVars newVars
            pat.optimality.objFun.val canonConstrs pat.newConstrs pat.newConstrVarsArray

          let constrBackward ← makeConstrBackward pat.vcondElimMap E canonProblemExpr originalVars
            newVars (pat.optimality.constrs.map (·.val)) canonConstrs pat.newConstrs
            pat.newConstrVarsArray

          return (objFunBackward, constrBackward)

      
      let strongEqvProof ← mkAppOptM ``Minimization.StrongEquivalence.mk
        #[D, E, R, none, ogProblemExpr, canonProblemExpr,
          
          forwardMap,
          
          backwardMap,
          
          constrForward,
          
          constrBackward,
          
          objFunForward,
          
          objFunBackward]
      let eqvProof ← mkAppM ``Minimization.Equivalence.ofStrongEquivalence #[strongEqvProof]

      return (canonProblem, eqvProof)

def dcpBuilder : EquivalenceBuilder Unit := fun eqvExpr g => g.withContext do
  let ogProblem ← eqvExpr.toMinimizationExprLHS
  let (_, eqvProof) ← canonize ogProblem
  if ! (← isDefEq (mkMVar g) eqvProof) then
    throwDCPError "failed to prove equivalence."

@[tactic dcp]
def evalDcp : Tactic := fun stx => match stx with
  | `(tactic| dcp) => do
      DCP.dcpBuilder.toTactic
      
  | _ => throwUnsupportedSyntax

def aggressiveSimpConfig : Simp.Config :=
  { zeta             := true
    beta             := true
    eta              := true
    iota             := true
    proj             := true
    decide           := true
    arith            := true
    dsimp            := true
    unfoldPartialApp := true
    etaStruct        := .all }

partial def mkUncheckedTree (originalVarsDecls paramsDecls : Array LocalDecl)
    (oc : OC (Option (Name × Expr))) : MetaM (OC (Option (String × Tree String String))) := do
  withExistingLocalDecls (originalVarsDecls ++ paramsDecls).toList do
    let varsIds := originalVarsDecls.map fun decl => decl.fvarId
    let paramsIds := paramsDecls.map fun decl => decl.fvarId
    oc.mapM (fun o => match o with
      | some (n, e) => do
          let uncheckedAtomTree ← findUncheckedAtoms e varsIds paramsIds
          return some (n.toString, uncheckedAtomTree)
      | none => return none)
where

def uncheckedTreeFromMinimizationExpr (goalExprs : MinimizationExpr) : MetaM EggOCTree := do
  let (objFun, constraints, originalVarsDecls) ← withLambdaBody goalExprs.constraints
    fun p constraints => do
      let pr := (← Meta.mkProjections goalExprs.domain p).toArray
      let originalVarsDecls ←
        withLocalDeclsD (pr.map fun (n, ty, _) => (n, fun _ => return ty))
          fun xs => xs.mapM fun x => x.fvarId!.getDecl
      withExistingLocalDecls originalVarsDecls.toList do
        let xs := originalVarsDecls.map fun decl => mkFVar decl.fvarId
        let constraints ← Meta.replaceProjections constraints p.fvarId! xs
        let constraints : List (Lean.Name × Expr) ←
          Meta.decomposeConstraints constraints
        let constraints ←
          constraints.mapM (fun ((n : Lean.Name), e) => do
            return (n, ← Expr.removeMData e))
        let objFunP := goalExprs.objFun.bindingBody!.instantiate1 p
        let objFun ← Meta.replaceProjections objFunP p.fvarId! xs
        return (objFun, constraints, originalVarsDecls)

  let paramsDecls ← (← getLCtx).decls.toArray.filterMapM (fun decl? => do
    if let some decl := decl? then
      if (← inferType decl.type).isType then return some decl else return none
    else return none)

  let oc ← DCP.mkOCWithNames objFun constraints originalVarsDecls
  let tree ← mkUncheckedTree originalVarsDecls paramsDecls (OC.map some oc)

  
  let tree ← tree.mapM (fun o => match o with
    | some (n, t) => return (n, t)
    | none => throwPreDCPError "could not create unchecked DCP tree.")
  return tree

inductive Sexp
  | atom : String → Sexp
  | list : List Sexp → Sexp
  deriving BEq, Inhabited, Repr

def Sexp.fromString : String → Sexp
  | s => Sexp.atom s

instance : Coe String Sexp where
  coe s := Sexp.fromString s

def Sexp.fromList : List Sexp → Sexp
  | xs => Sexp.list xs

instance : Coe (List Sexp) Sexp where
  coe := Sexp.fromList

partial def Sexp.toString : Sexp → String
  | .atom s  => s
  | .list xs => "(" ++ " ".intercalate (xs.map Sexp.toString) ++ ")"

instance : ToString Sexp := ⟨Sexp.toString⟩

def Sexp.toList? : Sexp → Option (List Sexp)
  | .atom _  => none
  | .list xs => some xs

def Sexp.toAtom! : Sexp → String
  | .atom s  => s
  | .list xs => panic! s!"expected atom, found list at {List.toString xs}"

inductive SexpTok
  | sexp : Sexp →  SexpTok
  | opening : String.Pos → SexpTok
  deriving BEq, Inhabited, Repr

structure SexpState where
  it : String.Iterator
  stack : List SexpTok := []
  sexps : List Sexp := []
  depth : Nat := 0
  deriving BEq, Repr

def SexpState.fromString (s : String) : SexpState where
  it := s.iter

instance : Inhabited SexpState where
  default := SexpState.fromString ""

inductive SexpError
  | unmatchedOpenParen (ix: String.Iterator) : SexpError
  | unmatchedCloseParen (ix: String.Iterator) : SexpError
  | notSingleSexp (s: String) (xs: List Sexp) : SexpError
  deriving BEq, Repr

instance : ToString SexpError where
  toString
    | .unmatchedOpenParen ix => s!"Unmatched open parenthesis at {ix}"
    | .unmatchedCloseParen ix => s!"Unmatched close parenthesis at {ix}"
    | .notSingleSexp s xs => s!"not a single sexp '{s}', parsed as: '{xs}'"

abbrev SexpM := EStateM SexpError SexpState

def SexpM.peek : SexpM (Option (Char × String.Pos)) := do
  let state ← get
  return if state.it.atEnd then none else some (state.it.curr, state.it.i)

def SexpM.curPos : SexpM String.Pos := do
  let state ← get
  return state.it.i

def SexpM.mkSubstring (l : String.Pos) (r : String.Pos) : SexpM Substring := do
  let state ← get
  return { str := state.it.s, startPos := l, stopPos := r}

def SexpM.advance: SexpM Unit := do
  modify (fun state => { state with it := state.it.next })

def SexpM.pushTok (tok: SexpTok): SexpM Unit := do
  modify (fun state => { state with stack := tok :: state.stack })

def SexpM.pushSexp (sexp: Sexp): SexpM Unit := do
  let state ← get
  if state.stack.length == 0 then
    set { state with stack := [], sexps := sexp :: state.sexps }
  else
    set { state with stack := (SexpTok.sexp sexp) :: state.stack }

def SexpM.incrementDepth: SexpM Unit :=
  modify (fun state => { state with depth := state.depth + 1 })

def SexpM.decrementDepth: SexpM Unit :=
  modify (fun state => { state with depth := state.depth - 1 })

instance {α} [Inhabited α] : Inhabited (SexpM α) :=

def SexpM.pop: SexpM SexpTok := do
  let state ← get
  match state.stack with
  | [] => panic! "empty stack"
  | x :: xs =>
      set { state with stack := xs }
      return x

def stackPopTillOpen (stk : List SexpTok) (sexps : List Sexp := []) :
    Option (String.Pos × (List SexpTok) × (List Sexp)) :=
  match stk with
  | [] => .none
  | SexpTok.opening openPos :: rest => (.some (openPos, rest, sexps))
  | SexpTok.sexp s :: rest => stackPopTillOpen rest (s :: sexps)

def SexpM.matchClosingParen: SexpM Unit := do
  let state ← get
  match stackPopTillOpen state.stack with
  | some (_, stk, sexps) =>
      let sexp := Sexp.list sexps
      modify (fun state => { state with stack := stk })
      SexpM.pushSexp sexp
  | none => throw (SexpError.unmatchedCloseParen state.it)

partial def SexpM.takeString (startPos : String.Pos) : SexpM Substring := do
  match (← SexpM.peek) with
  | none => SexpM.mkSubstring startPos (← SexpM.curPos)
  | some (' ', _) => SexpM.mkSubstring startPos (← SexpM.curPos)
  | some ('(', _) => SexpM.mkSubstring startPos (← SexpM.curPos)
  | some (')', _) => SexpM.mkSubstring startPos (← SexpM.curPos)
  | some _ => do
      SexpM.advance
      SexpM.takeString startPos

partial def SexpM.parse : SexpM Unit := do
  match (← SexpM.peek) with
  | some ('(', i) => do
      SexpM.advance
      SexpM.pushTok (SexpTok.opening i)
      SexpM.incrementDepth
      SexpM.parse
  | some (')', _) => do
      SexpM.advance
      SexpM.matchClosingParen
      SexpM.parse
  | some (' ', _) => do
      SexpM.advance
      SexpM.parse
  | some (_, i) => do
      let s ← SexpM.takeString i
      SexpM.pushSexp ((Sexp.atom s.toString))
      SexpM.parse
  | .none => do
      let state ← get
      match stackPopTillOpen state.stack with
      | some (openPos, _, _) => throw <|
          SexpError.unmatchedOpenParen ({ s := state.it.s, i := openPos })
      | none => return ()

def parseSexpList (s: String):  Except SexpError (List Sexp) :=
  let initState := SexpState.fromString s
  match EStateM.run SexpM.parse initState with
  | .ok () state => .ok state.sexps.reverse
  | .error e _ => .error e

def parseSingleSexp (s: String) : Except SexpError Sexp := do
  match (← parseSexpList s) with
  | [x] => .ok x
  | xs => .error (.notSingleSexp s xs)

structure Reduction where
  psi : E → D
  psi_optimality : ∀ x, q.optimal x → p.optimal (psi x)

@[red]
def refl : p ≼ p :=
  { psi := id,
    psi_optimality := fun _ hy => hy }

@[red]
def trans (R₁ : p ≼ q) (R₂ : q ≼ r) : p ≼ r :=
  { psi := R₁.psi ∘ R₂.psi,
    psi_optimality := fun x h => R₁.psi_optimality (R₂.psi x) (R₂.psi_optimality x h) }

instance : Trans (@Reduction D E R _) (@Reduction E F R _) (@Reduction D F R _) :=
  { trans := Reduction.trans }

def toBwd (R : p ≼ q) : Solution q → Solution p :=
  fun sol =>
    { point := R.psi sol.point,
      isOptimal := R.psi_optimality sol.point sol.isOptimal }

@[red]
def ofEquivalence (E : p ≡ q) : p ≼ q :=
  { psi := E.psi,
    psi_optimality := E.psi_optimality }

instance : Trans (@Reduction D E R _) (@Equivalence E F R _) (@Reduction D F R _) :=
  { trans := fun R E => Reduction.trans R (ofEquivalence E) }

instance : Trans (@Equivalence D E R _) (@Reduction E F R _) (@Reduction D F R _) :=
  { trans := fun E R => Reduction.trans (ofEquivalence E) R }

@[red]
def map_objFun_of_order_reflecting {D R} [Preorder R] {f : D → R} {g : R → R} {cs : D → Prop}
    (h : ∀ {r s}, cs r → cs s → g (f r) ≤ g (f s) → f r ≤ f s) :
    ⟨f, cs⟩ ≼ ⟨fun x => g (f x), cs⟩ :=
  { psi := id,
    psi_optimality := fun x ⟨h_feas_x, h_opt_x⟩ =>
      ⟨h_feas_x,
       fun y h_feas_y =>
        have h_gfx_le_gfy : g (f x) ≤ g (f y) := h_opt_x y h_feas_y;
        h h_feas_x h_feas_y h_gfx_le_gfy⟩ }

@[red]
def map_objFun {D R} [Preorder R] {f : D → R} {g : R → R} {cs : D → Prop}
    (h : ∀ {r s}, cs r → cs s → (g (f r) ≤ g (f s) ↔ f r ≤ f s)) :
    ⟨f, cs⟩ ≼ ⟨fun x => g (f x), cs⟩ :=
  ofEquivalence <| Equivalence.map_objFun h

@[red]
noncomputable def map_objFun_log {f : D → ℝ} (h : ∀ x, cs x → f x > 0) :
    ⟨f, cs⟩ ≼ ⟨fun x => (Real.log (f x)), cs⟩ :=
  ofEquivalence <| Equivalence.map_objFun_log h

@[red]
noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) :
    ⟨f, cs⟩ ≼ ⟨fun x => (f x) ^ (2 : ℝ), cs⟩ :=
  ofEquivalence <| Equivalence.map_objFun_sq h

@[red]
def map_domain {f : D → R} {cs : D → Prop} {fwd : D → E} {bwd : E → D}
    (h : ∀ x, cs x → bwd (fwd x) = x) :
    ⟨f, cs⟩ ≼ ⟨fun x => f (bwd x), (fun x => cs (bwd x))⟩ :=
  ofEquivalence <| Equivalence.map_domain h

@[red]
def rewrite_objFun (hrw : ∀ x, cs x → f x = g x) : ⟨f, cs⟩ ≼ ⟨g, cs⟩ :=
  ofEquivalence <| Equivalence.rewrite_objFun hrw

@[red]
def rewrite_constraints (hrw : ∀ x, cs x ↔ cs' x) : ⟨f, [[cs]]⟩ ≼ ⟨f, [[cs']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraints hrw

@[red]
def rewrite_constraint_1 (hrw : ∀ x, cs x → (c1 x ↔ c1' x)) : ⟨f, [[c1, cs]]⟩ ≼ ⟨f, [[c1', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_1 hrw

@[red]
def rewrite_constraint_1_last (hrw : ∀ x, c1 x ↔ c1' x) : ⟨f, [[c1]]⟩ ≼ ⟨f, [[c1']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_1_last hrw

@[red]
def rewrite_constraint_2 (hrw : ∀ x, c1 x → cs x → (c2 x ↔ c2' x)) :
    ⟨f, [[c1, c2, cs]]⟩ ≼ ⟨f, [[c1, c2', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_2 hrw

@[red]
def rewrite_constraint_2_last (hrw : ∀ x, c1 x → (c2 x ↔ c2' x)) :
    ⟨f, [[c1, c2]]⟩ ≼ ⟨f, [[c1, c2']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_2_last hrw

@[red]
def rewrite_constraint_3 (hrw : ∀ x, c1 x → c2 x → cs x → (c3 x ↔ c3' x)) :
    ⟨f, [[c1, c2, c3, cs]]⟩ ≼ ⟨f, [[c1, c2, c3', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_3 hrw

@[red]
def rewrite_constraint_3_last (hrw : ∀ x, c1 x → c2 x → (c3 x ↔ c3' x)) :
    ⟨f, [[c1, c2, c3]]⟩ ≼ ⟨f, [[c1, c2, c3']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_3_last hrw

@[red]
def rewrite_constraint_4 (hrw : ∀ x, c1 x → c2 x → c3 x → cs x → (c4 x ↔ c4' x)) :
    ⟨f, [[c1, c2, c3, c4, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_4 hrw

@[red]
def rewrite_constraint_4_last (hrw : ∀ x, c1 x → c2 x → c3 x → (c4 x ↔ c4' x)) :
    ⟨f, [[c1, c2, c3, c4]]⟩ ≼ ⟨f, [[c1, c2, c3, c4']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_4_last hrw

@[red]
def rewrite_constraint_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → cs x → (c5 x ↔ c5' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_5 hrw

@[red]
def rewrite_constraint_5_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → (c5 x ↔ c5' x)) :
    ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_5_last hrw

@[red]
def rewrite_constraint_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → cs x → (c6 x ↔ c6' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_6 hrw

@[red]
def rewrite_constraint_6_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → (c6 x ↔ c6' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_6_last hrw

@[red]
def rewrite_constraint_7
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → cs x → (c7 x ↔ c7' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_7 hrw

@[red]
def rewrite_constraint_7_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → (c7 x ↔ c7' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_7_last hrw

@[red]
def rewrite_constraint_8
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → cs x → (c8 x ↔ c8' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_8 hrw

@[red]
def rewrite_constraint_8_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → (c8 x ↔ c8' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_8_last hrw

@[red]
def rewrite_constraint_9
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → cs x → (c9 x ↔ c9' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, cs]]⟩ ≼
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_9 hrw

@[red]
def rewrite_constraint_9_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → (c9 x ↔ c9' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≼
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_9_last hrw

@[red]
def rewrite_constraint_10
    (hrw :
      ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → cs x → (c10 x ↔ c10' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, cs]]⟩ ≼
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10', cs]]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_10 hrw

@[red]
def rewrite_constraint_10_last
    (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → (c10 x ↔ c10' x)) :
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≼
    ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10']]⟩ :=
  ofEquivalence <| Equivalence.rewrite_constraint_10_last hrw

@[equiv]
def ofReductions (R₁ : p ≼ q) (R₂ : q ≼ p) : p ≡ q :=
  { phi := R₂.psi,
    psi := R₁.psi,
    phi_optimality := R₂.psi_optimality,
    psi_optimality := R₁.psi_optimality }

Array in Init.Prelude

Lake.LogIO in Lake.Util.Log

Option in Init.Prelude

String in Init.Prelude

System.FilePath in Init.System.FilePath

Unit in Init.Prelude

Lake.logInfo in Lake.Util.Log

Lake.proc in Lake.Util.Proc

Bool in Init.Prelude

Lake.SpawnM in Lake.Build.Job.Monad

IO in Init.System.IO

IO.Process.output in Init.System.IO

Lake.createParentDirs in Lake.Util.IO

Lean.Expr in Lean.Expr

Lean.trace in Lean.Util.Trace

Lean.MVarId in Lean.Expr

Lean.Name.anonymous in Init.Prelude

Lean.mkConst in Lean.Expr

Lean.Syntax in Init.Prelude

Lean.instantiateMVarDeclMVars in Lean.MetavarContext

D in SciLean.Tactic.CustomSimp.test_simp_guard

Lean.Meta.mkFreshExprMVar in Lean.Meta.Basic

Lean.Meta.mkFreshTypeMVar in Lean.Meta.Basic

Lean.Name in Init.Prelude

Lean.levelZero in Lean.Level

Lean.mkAppN in Lean.Expr

Lean.withRef in Init.Prelude

Preorder in Mathlib.Order.Defs.PartialOrder

get in SciLean.Data.Prod

modify in Init.Prelude

Lean.instantiateMVars in Lean.MetavarContext

Lean.mkApp6 in Lean.Expr

Lean.throwError in Lean.Exception

Lean.MVarId.getDecl in Lean.Meta.Basic

Lean.mkApp4 in Lean.Expr

Lean.Meta.MetaM in Lean.Meta.Basic

Lean.Meta.whnf in Lean.Meta.Basic

Lean.FVarId in Lean.Expr

Lean.TransformStep in Lean.Meta.Transform

Lean.Meta.transform in Lean.Meta.Transform

Lean.TransformStep.continue in Lean.Meta.Transform

List in Init.Prelude

Prod in Init.Prelude

Prod.fst in Init.Prelude

Prod.snd in Init.Prelude

Lean.mkApp2 in Lean.Expr

Lean.mkApp3 in Lean.Expr

v in examples.BallisticWidget

Inhabited in Init.Prelude

And in Init.Prelude

Lean.Name.mkSimple in Init.Prelude

Nat in Init.Prelude

vars in SciLean.Modules.Symbolic.Symbolic.Symbolic

x in doc.literate.literate_lean_test

id in Init.Prelude

Trans in Init.Prelude

Function.LeftInverse in Init.Data.Function

point in examples.CircleOptimisation

Real in Mathlib.Data.Real.Basic

Real.log in Mathlib.Analysis.SpecialFunctions.Log.Basic

Zero in Init.Prelude

Lean.Parser.Tactic.replace in Init.Tactics

Equiv in Mathlib.Logic.Equiv.Defs

IsGreatest in Mathlib.Order.Bounds.Defs

IsLeast in Mathlib.Order.Bounds.Defs

Lean.Syntax.ident in Init.Prelude

Lean.Syntax.missing in Init.Prelude

Lean.Syntax.node in Init.Prelude

Float in Init.Data.Float

Lean.Expr.headBeta in Lean.Expr

Lean.mkApp in Lean.Expr

Lean.mkStrLit in Lean.Expr

Lean.Parser.Term.byTactic in Lean.Parser.Term

Lean.TSyntax in Init.Prelude

Eq in Init.Prelude

Eq.mp in Init.Core

Eq.mpr in Init.Core

Lean.MData in Lean.Expr

Lean.mkMData in Lean.Expr

Lean.Elab.Term.TermElab in Lean.Elab.Term.TermElabM

Lean.Elab.Term.elabTerm in Lean.Elab.Term.TermElabM

Lean.Elab.throwUnsupportedSyntax in Lean.Elab.Exception

Lean.PrettyPrinter.Delaborator.Delab in Lean.PrettyPrinter.Delaborator.Basic

Lean.PrettyPrinter.Delaborator.delab in Lean.PrettyPrinter.Delaborator.Basic

Lean.PrettyPrinter.Delaborator.failure in Lean.PrettyPrinter.Delaborator.Basic

Lean.Parser.Parser in Lean.Parser.Types

Lean.Parser.Term.bracketedBinder in Lean.Parser.Term.Basic

Lean.Parser.ident in Lean.Parser.Extra

Lean.Parser.ppSpace in Lean.Parser.Extra

Lean.Parser.Term.letDecl in Lean.Parser.Term

Lean.Parser.Term.binderIdent in Lean.Parser.Term.Basic

Lean.Parser.checkColGe in Lean.Parser.Basic

Lean.Parser.ppGroup in Lean.Parser.Extra

Lean.Parser.ppLine in Lean.Parser.Extra

Lean.Parser.termParser in Lean.Parser.Basic

Lean.Parser.manyIndent in Lean.Parser.Extra

Lean.Elab.Term.TermElabM in Lean.Elab.Term.TermElabM

Lean.Meta.inferType in Lean.Meta.Basic

Lean.Syntax.atom in Init.Prelude

Lean.mkProj in Lean.Expr

Lean.PrettyPrinter.Delaborator.SubExpr.getExpr in Lean.PrettyPrinter.Delaborator.SubExpr

Lean.PrettyPrinter.Delaborator.DelabM in Lean.PrettyPrinter.Delaborator.Basic

Lean.PrettyPrinter.Delaborator.SubExpr.withNaryArg in Lean.PrettyPrinter.Delaborator.SubExpr

Neg in Init.Prelude

Neg.neg in Init.Prelude

Lean.Parser.Term.bracketedBinderF in Lean.Parser.Term

Lean.Parser.Term.ident in Lean.Parser.Term

Lean.Expr.replaceFVars in Lean.Expr

Lean.Meta.check in Lean.Meta.Check

Lean.Meta.mkAppM in Lean.Meta.AppBuilder

Lean.Meta.mkLambdaFVars in Lean.Meta.Basic

Lean.Meta.withLocalDeclD in Lean.Meta.Basic

Alternative in Init.Control.Basic

Alternative.failure in Init.Control.Basic

Lean.Expr.app in Lean.Expr

Lean.Expr.const in Lean.Expr

Lean.mkAtom in Init.Prelude

Lean.mkIdent in Init.Meta.Defs

Lean.mkNode in Init.Prelude

guard in Init.Control.Basic

Lean.Parser.Term.app in Lean.Parser.Term

Lean.Meta.isDefEq in Lean.Meta.Basic

Lean.mkNullNode in Init.Prelude

Real.sqrt in Mathlib.Data.Real.Sqrt

Real.exp in Mathlib.Analysis.Complex.Exponential

ε in examples.Pendulum2d

Id in Init.Control.Id

Id.run in Init.Control.Id

Lean.ToMessageData in Lean.Message

Lean.LocalDecl in Lean.LocalContext

Lean.BinderInfo in Lean.Expr

Lean.BinderInfo.default in Lean.Expr

Lean.LocalDecl.cdecl in Lean.LocalContext

Lean.LocalDeclKind.default in Lean.LocalContext

Lean.Name.mkNum in Init.Prelude

Lean.mkFreshFVarId in Lean.Expr

Lean.MessageData in Lean.Message

Lean.mkFVar in Lean.Expr

Array.append in Init.Data.Array.Basic

proofs in SciLean.Lean.Meta.LiftLets

Sum in Init.Core

Sum.inl in Init.Core

Sum.inr in Init.Core

Lean.Elab.Term.TermElabM.run in Lean.Elab.Term.TermElabM

Lean.Elab.Term.commitIfNoErrors? in Lean.Elab.Term.TermElabM

Lean.Elab.Term.synthesizeSyntheticMVarsNoPostponing in Lean.Elab.SyntheticMVars

Lean.LocalDecl.fvarId in Lean.LocalContext

add in test.generate_revDeriv

Lean.mkSort in Lean.Expr

Lean.addDecl in Lean.AddDecl

Lean.getMaxHeight in Lean.Environment

Lean.FVarId.getDecl in Lean.Meta.Basic

Lean.mkArrow in Lean.CoreM

Array.zip in Init.Data.Array.Basic

List.cons in Init.Prelude

Std.HashMap in Std.Data.HashMap.Basic

HSMul in Init.Prelude

HSMul.hSMul in Init.Prelude

BEq in Init.Prelude

Lean.PersistentEnvExtension in Lean.Environment

Lean.setEnv in Lean.MonadEnv

Lean.MessageData.joinSep in Lean.Message

Lean.MessageData.paren in Lean.Message

Lean.ToMessageData.toMessageData in Lean.Message

Lean.mkMVar in Lean.Expr

g in examples.Pendulum2d

Lean.Elab.Tactic.Tactic in Lean.Elab.Tactic.Basic

Convex in Mathlib.Analysis.Convex.Basic

Lean.Literal in Lean.Expr

Repr in Init.Data.Repr

UInt64 in Init.Prelude

mixHash in Init.Prelude

Hashable in Init.Prelude

Lean.PersistentHashMap in Lean.Data.PersistentHashMap

Lean.Name.quickLt in Lean.Data.Name

LT in Init.Prelude

Decidable in Init.Prelude

inferInstanceAs in Init.Prelude

Lean.Literal.natVal in Lean.Expr

Lean.Literal.strVal in Lean.Expr

repr in Init.Data.Repr

Lean.MessageData.group in Lean.Message

Lean.MessageData.nil in Lean.Message

Lean.Meta.ParamInfo in Lean.Meta.Basic

Lean.mkAnnotation in Lean.Expr

Lean.annotation? in Lean.Expr

Lean.Expr.forallE in Lean.Expr

Lean.Expr.fvar in Lean.Expr

Lean.Expr.mvar in Lean.Expr

Lean.Expr.proj in Lean.Expr

Array.mkEmpty in Init.Prelude

sequence in Mathlib.Control.Traversable.Basic

Lean.Core.checkMaxHeartbeats in Lean.CoreM

Monad in Init.Prelude

Lean.Parser.Category.tactic in Init.Notation

Array.mk in Init.Prelude

Lean.FVarId.mk in Lean.Expr

Lean.Parser.Category.rcasesPat in Init.RCases

Lean.Unhygienic.run in Lean.Hygiene

Lean.Parser.Category.stx in Init.Notation

Lean.Elab.Tactic.evalTacticAt in Lean.Elab.Tactic.Basic

Lean.Elab.Tactic.saveTacticInfoForToken in Lean.Elab.Tactic.Basic

Lean.Syntax.getId in Init.Prelude

Iff in Init.Core

Function.comp in Init.Prelude

Lean.mkApp5 in Lean.Expr

Mathlib.Tactic.elabNormNum1Conv in Mathlib.Tactic.NormNum.Core

List.filterMap in Init.Data.List.Basic

liftM in Init.Prelude

Lean.MVarId.replaceTargetEq in Lean.Meta.Tactic.Replace

Lean.MVarId.assign in Lean.MetavarContext

Lean.MVarId.introNP in Lean.Meta.Tactic.Intro

Lean.MVarId.withContext in Lean.Meta.Basic

Lean.MetavarKind.syntheticOpaque in Lean.MetavarContext

Fin in Init.Prelude

Lean.Core.betaReduce in Lean.Meta.Transform

LE in Init.Prelude

LE.le in Init.Prelude

LT.lt in Init.Prelude

Lean.Expr.consumeMData in Lean.Expr

Lean.collectFVars in Lean.Util.CollectFVars

Mathlib.Meta.Positivity.PositivityExt in Mathlib.Tactic.Positivity.Core

Lean.Meta.withReducible in Lean.Meta.Basic

Mathlib.Meta.Positivity.core in Mathlib.Tactic.Positivity.Core

BaseIO in Init.System.IO

BaseIO.toIO in Init.System.IO

IO.monoMsNow in Init.System.IO

Nat.add in Init.Prelude

Option.map in Init.Prelude

size in SciLean.Data.Prod

Lean.SimplePersistentEnvExtension in Lean.EnvExtension

Lean.Elab.Command.CommandElab in Lean.Elab.Command

Lean.Elab.Command.liftTermElabM in Lean.Elab.Command

Norm in Mathlib.Analysis.Normed.Group.Basic

Norm.norm in Mathlib.Analysis.Normed.Group.Basic

Lean.Json in Lean.Data.Json.Basic

Lean.Json.str in Lean.Data.Json.Basic

Lean.Json.arr in Lean.Data.Json.Basic

Except in Init.Prelude

ToString in Init.Data.ToString.Basic

Except.error in Init.Prelude

Except.ok in Init.Prelude

MonadExceptOf in Init.Prelude

IO.Process.Stdio in Init.System.IO

IO.Process.Stdio.null in Init.System.IO

IO.Process.Stdio.piped in Init.System.IO

IO.Process.spawn in Init.System.IO

IO.asTask in Init.System.IO

IO.ofExcept in Init.System.IO

Task in Init.Core

Task.Priority in Init.Core

Task.Priority.dedicated in Init.Core

Lean.Json.parse in Lean.Data.Json.Parser

Lean.Json.getArr? in Lean.Data.Json.Basic

Lean.Json.getObj? in Lean.Data.Json.Basic

liftExcept in Init.Control.Except

DecidableEq in Init.Prelude

Coe in Init.Coe

List.toString in Init.Data.ToString.Basic

String.Pos in Init.Data.String.Defs

String.Iterator in Init.Data.String.Iterator

EStateM in Init.Prelude

Char in Init.Prelude

Substring in Init.Data.String.Substring

empty in SciLean.Modules.Symbolic.Quot.Monomial

EStateM.run in Init.Prelude

HAdd in Init.Prelude

HAdd.hAdd in Init.Prelude

HDiv in Init.Prelude

HDiv.hDiv in Init.Prelude

HMul in Init.Prelude

HMul.hMul in Init.Prelude

HPow in Init.Prelude

HPow.hPow in Init.Prelude

HSub in Init.Prelude

HSub.hSub in Init.Prelude

Inv in Init.Prelude

Inv.inv in Init.Prelude

Lean.Json.Parser.num in Lean.Data.Json.Parser

Lean.Level in Lean.Level

Lean.levelOne in Lean.Level

Lean.log in Lean.Log

Lean.mkNatLit in Lean.Expr

List.replicate in Init.Data.List.Basic

Mathlib.Meta.NormNum.deriveSimp in Mathlib.Tactic.NormNum.Core

Max in Init.Prelude

Max.max in Init.Prelude

Min in Init.Prelude

Min.min in Init.Prelude

NNRatCast in Mathlib.Data.Rat.Init

NNRatCast.toOfScientific in Mathlib.Algebra.Order.Ring.Unbundled.Rat

OfScientific in Init.Data.OfScientific

OfScientific.ofScientific in Init.Data.OfScientific

Real.instAdd in Mathlib.Data.Real.Basic

Real.instAddGroup in Mathlib.Data.Real.Basic

Real.instInv in Mathlib.Data.Real.Basic

Real.instLE in Mathlib.Data.Real.Basic

Real.instMul in Mathlib.Data.Real.Basic

Real.instNNRatCast in Mathlib.Data.Real.Basic

Real.instNeg in Mathlib.Data.Real.Basic

Real.instPow in Mathlib.Analysis.SpecialFunctions.Pow.Real

Real.instSub in Mathlib.Data.Real.Basic

Real.lattice in Mathlib.Data.Real.Basic

Real.pi in Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic

abs in Mathlib.Algebra.Order.Group.Unbundled.Abs

instHAdd in Init.Prelude

instHDiv in Init.Prelude

instHMul in Init.Prelude

instHPow in Init.Prelude

instHSub in Init.Prelude

mul in test.generate_revDeriv

Float.add in Init.Data.Float

Float.div in Init.Data.Float

Float.exp in Init.Data.Float

Float.log in Init.Data.Float

Float.mul in Init.Data.Float

Float.neg in Init.Data.Float

Float.ofInt in Init.Data.OfScientific

Float.pow in Init.Data.Float

Float.sqrt in Init.Data.Float

Float.sub in Init.Data.Float

Option.map₂ in Mathlib.Data.Option.NAry

Lean.ToExpr in Lean.ToExpr

Float.ofNat in Init.Data.OfScientific

instOfScientificFloat in Init.Data.OfScientific

Fin.ofNat in Init.Data.Fin.Basic

Lean.Meta.withLocalDeclsD in Lean.Meta.Basic

Lean.Meta.withLetDecl in Lean.Meta.Basic

Lean.SubExpr in Lean.SubExpr

withTheReader in Init.Prelude

MonadControlT in Init.Control.Basic

MonadReaderOf in Init.Prelude

MonadWithReaderOf in Init.Prelude

Lean.Meta.withLocalDecl in Lean.Meta.Basic

Lean.PrettyPrinter.Delaborator.SubExpr.descend in Lean.PrettyPrinter.Delaborator.SubExpr

Prod.mk in Init.Prelude

Exists in Init.Core

Lean.Meta.mkAppOptM in Lean.Meta.AppBuilder

Lean.Expr.fvarId! in Lean.Expr

Lean.mkLet in Lean.Expr

Lean.Core.transform in Lean.Meta.Transform

Array.foldrM in Init.Data.Array.Basic

Empty in Init.Prelude

Array.empty in Init.Prelude

Array.push in Init.Prelude

Lean.Elab.Term.elabTermAndSynthesize in Lean.Elab.SyntheticMVars

Lean.Elab.Term.ensureHasType in Lean.Elab.Term.TermElabM

Lean.Declaration.defnDecl in Lean.Declaration

Lean.DefinitionSafety.safe in Lean.Declaration

Lean.ReducibilityHints in Lean.Declaration

Lean.ReducibilityHints.regular in Lean.Declaration

Lean.addAndCompile in Lean.AddDecl

Lean.mkDefinitionValEx in Lean.Declaration

Fintype in Mathlib.Data.Fintype.Defs

Nonempty in Init.Prelude

Finset in Mathlib.Data.Finset.Defs

Finset.univ in Mathlib.Data.Fintype.Defs

A in examples.CircleOptimisation

Matrix.diagonal in Mathlib.Data.Matrix.Diagonal

RCLike in Mathlib.Analysis.RCLike.Basic

RCLike.ofReal in Mathlib.Analysis.RCLike.Basic

Matrix in Mathlib.LinearAlgebra.Matrix.Defs

Matrix.IsHermitian in Mathlib.LinearAlgebra.Matrix.Hermitian

Matrix.PosSemidef in Mathlib.LinearAlgebra.Matrix.PosDef

Matrix.PosSemidef.sqrt in Mathlib.Analysis.Matrix.Order

Matrix.toLin' in Mathlib.LinearAlgebra.Matrix.ToLin

Module in Mathlib.Algebra.Module.Defs

Module.End in Mathlib.Algebra.Module.LinearMap.End

Module.End.HasEigenvector in Mathlib.LinearAlgebra.Eigenspace.Basic

Matrix.det in Mathlib.LinearAlgebra.Matrix.Determinant.Basic

EuclideanSpace in Mathlib.Analysis.InnerProductSpace.PiL2

IsUnit in Mathlib.Algebra.Group.Units.Defs

Finset.sum in Mathlib.Algebra.BigOperators.Group.Finset.Defs

Matrix.diag in Mathlib.Data.Matrix.Diagonal

Matrix.mulVec in Mathlib.Data.Matrix.Mul

Matrix.trace in Mathlib.LinearAlgebra.Matrix.Trace

dotProduct in Mathlib.Data.Matrix.Mul

Ring in Mathlib.Algebra.Ring.Defs

Ring.inverse in Mathlib.Algebra.GroupWithZero.Units.Basic

Invertible in Mathlib.Algebra.Group.Invertible.Defs

Matrix.fromBlocks in Mathlib.Data.Matrix.Block

Matrix.vecMul in Mathlib.Data.Matrix.Mul

Sum.elim in Init.Data.Sum.Basic

Equiv.sumComm in Mathlib.Logic.Equiv.Sum

LDL.diag in Mathlib.Analysis.Matrix.LDL

invertibleMul in Mathlib.Algebra.Group.Invertible.Defs

LDL.lower in Mathlib.Analysis.Matrix.LDL

LDL.lowerInv in Mathlib.Analysis.Matrix.LDL

LDL.diagEntries in Mathlib.Analysis.Matrix.LDL

Or in Init.Prelude

Fin.succ in Init.Data.Fin.Basic

Matrix.vecCons in Mathlib.Data.Fin.VecNotation

Module.End.eigenspace in Mathlib.LinearAlgebra.Eigenspace.Basic

LinearMap in Mathlib.Algebra.Module.LinearMap.Defs

SetLike in Mathlib.Data.SetLike.Basic

Submodule in Mathlib.Algebra.Module.Submodule.Defs

Fact in Mathlib.Logic.Basic

OfNat in Init.Prelude

SMulZeroClass in Mathlib.Algebra.GroupWithZero.Action.Defs

Add in Init.Prelude

AddCommMonoid in Mathlib.Algebra.Group.Defs

Array.range in Init.Data.Array.Basic

Array.zipWith in Init.Data.Array.Basic

OfNat.ofNat in Init.Prelude

C in SciLean.Util.CheckTCFailure

Fin.elim0 in Init.Data.Fin.Basic

List.finRange in Init.Data.List.FinRange

DecidablePred in Init.Prelude

Nat.succ in Init.Prelude

List.get in Init.Prelude

List.range in Init.Data.List.Basic

NormedAddCommGroup in Mathlib.Analysis.Normed.Group.Basic

PiLp in Mathlib.Analysis.Normed.Lp.PiLp

PiLp.normedAddCommGroup in Mathlib.Analysis.Normed.Lp.PiLp

NormedAddGroup in Mathlib.Analysis.Normed.Group.Basic

InnerProductSpace in Mathlib.Analysis.InnerProductSpace.Defs

PiLp.innerProductSpace in Mathlib.Analysis.InnerProductSpace.PiL2

PiLp.instNorm in Mathlib.Analysis.Normed.Lp.PiLp

StrongLT in Mathlib.Order.Basic

Div in Init.Prelude

Set in Mathlib.Data.Set.Defs

Set.Ioi in Mathlib.Order.Interval.Set.Defs

Set.restrict in Mathlib.Data.Set.Restrict

Subtype in Init.Prelude

Real.rpow in Mathlib.Analysis.SpecialFunctions.Pow.Real

Finsupp in Mathlib.Data.Finsupp.Defs

OrthonormalBasis in Mathlib.Analysis.InnerProductSpace.PiL2

LinearIsometryEquiv in Mathlib.Analysis.Normed.Operator.LinearIsometry

Matrix.BlockTriangular in Mathlib.LinearAlgebra.Matrix.Block

OrderDual in Mathlib.Order.Basic

OrderDual.toDual in Mathlib.Order.Synonym

LinearOrder in Mathlib.Order.Defs.LinearOrder

Matrix.toSquareBlock in Mathlib.Data.Matrix.Block

Matrix.toSquareBlockProp in Mathlib.Data.Matrix.Block

Unique in Mathlib.Logic.Unique

Matrix.InnerProductSpace.ofMatrix in Mathlib.Analysis.Matrix.PosDef

Matrix.NormedAddCommGroup.ofMatrix in Mathlib.Analysis.Matrix.PosDef

Pi.basisFun in Mathlib.LinearAlgebra.StdBasis

invertibleOfLeftInverse in Mathlib.Algebra.Group.Invertible.Defs

Matrix.invertibleOfIsUnitDet in Mathlib.LinearAlgebra.Matrix.NonsingularInverse

LinearMap.IsSymmetric in Mathlib.Analysis.InnerProductSpace.Symmetric

Matrix.toEuclideanLin in Mathlib.Analysis.InnerProductSpace.PiL2

WithLp in Mathlib.Analysis.Normed.Lp.WithLp

WithLp.equiv in Mathlib.Analysis.Normed.Lp.WithLp

Matrix.blockDiagonal' in Mathlib.Data.Matrix.Block

Sigma in Init.Core

Sigma.fst in Init.Core

Matrix.blockDiagonal in Mathlib.Data.Matrix.Block

Matrix.toBlock in Mathlib.Data.Matrix.Block

Matrix.IsHermitian.eigenvectorBasis in Mathlib.Analysis.Matrix.Spectrum

EuclideanSpace.single in Mathlib.Analysis.InnerProductSpace.PiL2

Fintype.card in Mathlib.Data.Fintype.Card

Fintype.equivOfCardEq in Mathlib.Data.Fintype.EquivFin

IO.FS.Mode in Init.System.IO

IO.FS.Mode.read in Init.System.IO

IO.FS.Mode.write in Init.System.IO

IO.FS.withFile in Init.System.IO

IO.FS.writeFile in Init.System.IO

IO.getEnv in Init.System.IO

IO.rand in Init.Data.Random

ToString.toString in Init.Data.ToString.Basic

solver in examples.Pendulum

IO.FS.removeFile in Init.System.IO

Array.toList in Init.Prelude

Fin.val in Init.Prelude

index in SciLean.Modules.DDG.SurfaceMesh

Lean.Json.Parser.natMaybeZero in Lean.Data.Json.Parser

Float.ofScientific in Init.Data.OfScientific

L in Archive.Hairer

Nat.decEq in Init.Prelude

decEq in Init.Prelude

instOfNatFloat in Init.Data.OfScientific

Lean.mkLambda in Lean.Expr

Lean.mkAnd in Lean.Expr

Sub in Init.Prelude

Sub.sub in Init.Prelude

Lean.Expr.lam in Lean.Expr

Lean.Expr.letE in Lean.Expr

Lean.Expr.mdata in Lean.Expr

Lean.Expr.isConstOf in Lean.Expr

Lean.Meta.evalExpr in Lean.Meta.Eval

Lean.logInfo in Lean.Log

H in examples.HarmonicOscillator

String.join in Init.Data.String.Defs
</all_available_defs>

<all_available_lemmas>
lemma repr_gramSchmidt_diagonal {i : ι} (b : Basis ι 𝕜 E) :
    b.repr (gramSchmidt 𝕜 b i) i = 1

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

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

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

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

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)

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

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

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

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

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

lemma IsHermitian.one_add {A : Matrix n n ℝ} (hA : A.IsHermitian) : (1 + A).IsHermitian

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)

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

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

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

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

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

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

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

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 PosSemidef.conjTranspose_mul_mul (M N : Matrix n n 𝕜) (hM : M.PosSemidef) :
    (Nᴴ * M * N).PosSemidef

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

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

lemma conj_symm {x : n → 𝕜} {M : Matrix n n 𝕜} (hM : M.IsHermitian) :
    star (star x ⬝ᵥ mulVec M x) = star x ⬝ᵥ mulVec M x

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

lemma PosSemidef.mul_mul_of_IsHermitian {M N : Matrix n n 𝕜} (hM : M.PosSemidef)
    (hN : N.IsHermitian) : (N * M * N).PosSemidef

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

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

lemma PosDef_inv_iff_PosDef [DecidableEq n] (M : Matrix n n ℝ) : M⁻¹.PosDef ↔ M.PosDef

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

lemma norm2₂_eq_norm2 {x y : ℝ} : ‖![x, y]‖ = sqrt (x ^ 2 + y ^ 2)

theorem vecCons_zero_zero {n} [Zero α] : vecCons (0 : α) (0 : Fin n → α) = 0

theorem smul_vecCons {n} [Zero α] [SMulZeroClass ℝ α] (x : ℝ) (y : α) (v : Fin n → α) :
    x • vecCons y v = vecCons (x • y) (x • v)

theorem add_vecCons {n} [Zero α] [SMulZeroClass ℝ α] [Add α] (x : α) (v : Fin n → α) (y : α)
    (w : Fin n → α) : vecCons x v + vecCons y w = vecCons (x + y) (v + w)

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

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

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

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

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

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)

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

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

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

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

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

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

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)

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

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

lemma feasible_and_bounded_of_feasible (Rx : p ≽' q) {x : D} (h_feas_x : p.feasible x) :
    q.feasible (Rx.phi x) ∧ q.objFun (Rx.phi x) ≤ p.objFun x

lemma induces_original_problem_optimality (Rx : p ≽' q) (phi_inv : E → D)
    (phi_left_inv : Function.LeftInverse Rx.phi phi_inv)
    (h_objFun : ∀ x, p.feasible x → p.objFun x = q.objFun (Rx.phi x)) {y : E}
    (h_opt_y : q.optimal y) (h_feas_y : p.feasible (phi_inv y)) : p.optimal (phi_inv y)

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

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

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

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

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

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

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

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

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

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

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

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

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

lemma LogDetAtom.optimality {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) : ∑ i, t i ≤ Real.log A.det

lemma gaussianPdf_pos {n : ℕ} (R : Matrix (Fin n) (Fin n) ℝ) (y : Fin n → ℝ) (h : R.PosDef) :
    0 < gaussianPdf R y

lemma prod_gaussianPdf_pos {N n : ℕ} (R : Matrix (Fin n) (Fin n) ℝ) (y : Fin N → Fin n → ℝ)
    (h : R.PosDef) : 0 < ∏ i : Fin N, gaussianPdf R (y i)

lemma log_prod_gaussianPdf {N n : ℕ} (y : Fin N → Fin n → ℝ) (R : Matrix (Fin n) (Fin n) ℝ)
    (hR : R.PosDef) : log (∏ i : Fin N, gaussianPdf R (y i)) =
    ∑ i : Fin N, (- (log (sqrt ((2 * π) ^ n)) + log (sqrt R.det)) + - R⁻¹.quadForm (y i) / 2)

lemma sum_quadForm {n : ℕ} (R : Matrix (Fin n) (Fin n) ℝ) {m : ℕ} (y : Fin m → Fin n → ℝ) :
    ∑ i, R.quadForm (y i) = m * (covarianceMatrix y * Rᵀ).trace

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

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

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

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

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

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

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

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

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

lemma rotateSoCone_rotatedSoCone {n : ℕ} {t : ℝ} {x : Fin n.succ → ℝ} (h : soCone t x) :
    let (v, w, x) := rotateSoCone t x; rotatedSoCone v w x

lemma unrotateSoCone_soCone {n : ℕ} {v w : ℝ} {x : Fin n → ℝ} (h : rotatedSoCone v w x) :
    let (t, x) := unrotateSoCone v w x; soCone t x

lemma soCone_add_sub_two_mul_of_nonneg {x y : ℝ} (z : ℝ)
    (hx : 0 ≤ x) (hy : 0 ≤ y) : soCone (x + y) ![x - y, 2 * z] ↔ z ^ (2 : ℝ) ≤ x * y

lemma soCone_add_sub_two_of_nonneg {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) :
    soCone (x + y) ![x - y, 2] ↔ 1 ≤ x * y

lemma soCone_sub_add_two_mul_of_nonneg {x y : ℝ} (z : ℝ) :
    soCone (x - y) ![x + y, 2 * z] ↔ y ≤ x ∧ z ^ (2 : ℝ) ≤ -(x * y)

lemma vec_soCone_apply_to_soCone_add_sub_two_mul {n : ℕ} (x y z : Fin n → ℝ) (i : Fin n) :
    (soCone ((x + y) i) ((![x - y, 2 • z]ᵀ) i)) ↔ (soCone (x i + y i) ![x i - y i, 2 * z i])

lemma vec_soCone_apply_to_soCone_add_sub_two {n : ℕ} (x y : Fin n → ℝ) (i : Fin n) :
    (soCone ((x + y) i) ((![x - y, 2]ᵀ) i)) ↔ (soCone (x i + y i) ![x i - y i, 2])

lemma div_le_iff (hb : StrongLT 0 b) : a / b ≤ c ↔ a ≤ c * b

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

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

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

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

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

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

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

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

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

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

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

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

lemma Real.scaled_sq_diff_pos_of_pos {a x : ℝ} :
    0 < a - 1 → 0 < x → 0 < (a * x) ^ (2 : ℝ) - x ^ (2 : ℝ)

lemma abs_le_of_sqrt_sq_add_nonneg_le {a b c : ℝ} (hb : 0 ≤ b)
    (h : sqrt (a ^ 2 + b) ≤ c) : |a

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

lemma div_pow_eq_mul_pow_neg {a b c : ℝ} (hb : 0 ≤ b) :
    a / (b ^ c) = a * b ^ (-c)

lemma one_div_eq_pow_neg_one {a : ℝ} (ha : 0 < a) : 1 / a = a ^ (-1 : ℝ)

lemma inv_eq_pow_neg_one {a : ℝ} (ha : 0 < a) : a⁻¹ = a ^ (-1 : ℝ)

lemma pow_half_two {x : ℝ} (hx : 0 ≤ x) : (x ^ (1 / 2 : ℝ)) ^ (2 : ℝ) = x

lemma pow_two_le_pow_two {x y : ℝ} (hx : 0 ≤ x) (hy : 0 ≤ y) :
    x ^ (2 : ℝ) ≤ y ^ (2 : ℝ) ↔ x ≤ y

lemma binomial_two (x y : ℝ) :
    (x + y) ^ (2 : ℝ) = x ^ (2 : ℝ) + (2 * (x * y) + y ^ (2 : ℝ))

lemma rpow_eq_mul_rpow_pred {x y : ℝ} (hx : x ≠ 0) :
    x ^ y = x * (x ^ (y - 1))

lemma exp_neg_eq_one_div (x : ℝ) : exp (-x) = 1 / exp x

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

theorem spectral_theorem (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) (as : n → ℝ)
    (hxs : ∀ j, Module.End.HasEigenvector (Matrix.toLin' A) (as j) (xs j)) :
    xs.toBasis.toMatrix (Pi.basisFun 𝕜 n) * A =
    diagonal (RCLike.ofReal ∘ as) * xs.toBasis.toMatrix (Pi.basisFun 𝕜 n)

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

@[simp]
lemma lowerInv_diagonal (i : n) : lowerInv hS i i = 1

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))ᵀ

lemma lowerTriangular_lowerInv : lowerTriangular (lowerInv hS)

lemma lowerTriangular_lower : lowerTriangular (lower hS)

@[simp]
lemma inv_lower_eq_lowerInv : (lower hS)⁻¹ = lowerInv hS

@[simp]
lemma lower_diagonal (i : n) : lower hS i i = 1

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

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

lemma spectral_theorem' (v : E) (i : Fin n)
    (xs : OrthonormalBasis (Fin n) 𝕜 E) (as : Fin n → ℝ)
    (hxs : ∀ j, Module.End.HasEigenvector T (as j) (xs j)) :
    xs.repr (T v) i = as i * xs.repr v i

neg_nonneg_of_nonpos in Mathlib.Algebra.Order.Group.Defs

neg_pos_of_neg in Mathlib.Algebra.Order.Group.Unbundled.Basic

sub_nonneg_of_le in Mathlib.Algebra.Order.Group.Unbundled.Basic

sub_pos_of_lt in Mathlib.Algebra.Order.Group.Unbundled.Basic
</all_available_lemmas>

<local_ctx>
import Mathlib.Analysis.NormedSpace.PiLp

import Mathlib.Analysis.InnerProductSpace.PiL2

import CvxLean.Lib.Math.Data.Real

import CvxLean.Lib.Math.Data.Fin

namespace Vec

variable {m : Type u} {n : Type v} [Fintype m] [Fintype n] {α : Type w}

section AddCommMonoid

variable [AddCommMonoid α] {m : Nat} {n : Nat} (x : Fin m → α) (y : Fin n → α)

open BigOperators

open FinsetInterval

end AddCommMonoid

noncomputable section Real

open Real BigOperators

variable (x y : m → ℝ)

end Real

section RealLemmas

variable {a b c : m → ℝ}
</local_ctx>

<target_theorem>
lemma le_div_iff (hc : StrongLT 0 c) : a ≤ b / c ↔ a * c ≤ b :=
</target_theorem>

