Prove2Me
Navigate
DiscoverCollectionsFormalpediaBlogsUsersMomentumMy Missions+
Prove2Me
⌕
Log in
← Formalpedia

Definitions bundle OAIQ2Mt000 of the split of OpenAI, Approximate counting of common bases of two matroids

Definition
OAIQ2Mt000

by dbenbenn · Oct 26, 2026 · Mathlib 0df444a (Lean v4.33.1)

openai-math

A definitions bundle of the split of OpenAI's proof of OAI.common_bases_fpras, carved from OpenAI's openai/math development (OpenAI, Approximate counting of common bases of two matroids, OpenAI Math Release, September 23, 2026, result family 114, github.com/openai/math at commit adc7f12, Apache License 2.0) and ported to Lean 4.33.1 and Mathlib 0df444a. The declarations and their proofs are OpenAI's.

Contents. It holds 1071 declarations of OpenAI's Lean: 845 definitions, 13 inductive types or structures, 5 instances and 208 theorems. The definitions and types are OAI.CommonBasesFPRAS.AccuratePhase, OAI.CommonBasesFPRAS.BitWord, OAI.CommonBasesFPRAS.ChainIndex, OAI.CommonBasesFPRAS.ChainState, OAI.CommonBasesFPRAS.ClassObservable, OAI.CommonBasesFPRAS.DefectState, OAI.CommonBasesFPRAS.DrawTree, OAI.CommonBasesFPRAS.DrawTree.All, OAI.CommonBasesFPRAS.DrawTree.Fits, OAI.CommonBasesFPRAS.DrawTree.Matches, OAI.CommonBasesFPRAS.DrawTree.Reindexed, OAI.CommonBasesFPRAS.DrawTree.bernoulli, OAI.CommonBasesFPRAS.DrawTree.bind, OAI.CommonBasesFPRAS.DrawTree.expectation, OAI.CommonBasesFPRAS.DrawTree.history, OAI.CommonBasesFPRAS.DrawTree.iterate, OAI.CommonBasesFPRAS.DrawTree.map, OAI.CommonBasesFPRAS.DrawTree.optionBind, OAI.CommonBasesFPRAS.DrawTree.optionValue, OAI.CommonBasesFPRAS.DrawTree.pureSome, OAI.CommonBasesFPRAS.DrawTree.rationalCoin, OAI.CommonBasesFPRAS.DrawTree.run, OAI.CommonBasesFPRAS.DrawTree.sample, OAI.CommonBasesFPRAS.DrawTree.uniformChoice, OAI.CommonBasesFPRAS.DrawTree.uniformVia, OAI.CommonBasesFPRAS.EdgeRespects, OAI.CommonBasesFPRAS.ExchangeAdjacent, OAI.CommonBasesFPRAS.ExchangeChoice, OAI.CommonBasesFPRAS.FPRASBank, OAI.CommonBasesFPRAS.FairBank, OAI.CommonBasesFPRAS.FiniteChain, OAI.CommonBasesFPRAS.FiniteChain.Inside, OAI.CommonBasesFPRAS.FiniteChain.Outside, OAI.CommonBasesFPRAS.FiniteChain.Sample, OAI.CommonBasesFPRAS.FiniteChain.Sample.toList, OAI.CommonBasesFPRAS.FiniteChain.apply, OAI.CommonBasesFPRAS.FiniteChain.cappedTrace, OAI.CommonBasesFPRAS.FiniteChain.cappedTraceKernel, OAI.CommonBasesFPRAS.FiniteChain.centeredPoint, OAI.CommonBasesFPRAS.FiniteChain.duration and 818 more.

Sources. They come from these modules of lean/OAI/Combinatorics/MatroidCounting, in whole or in part: MatroidCounting_CommonBases.

Imports. It imports the published bundle Def_CommonBasesFPRAS.

Imported by (directly, in this split): the statements OAI.CommonBasesFPRAS.MachineCode.Effect.safe_algorithmHistory, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_guard, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_historyTick, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_phaseSample, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_positiveTest, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_restartRepeat, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_sampleTick, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_start, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_storedStep, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_witness, OAI.CommonBasesFPRAS.MachineCode.frozen_machine, OAI.CommonBasesFPRAS.MachineCode.frozen_uniform_safe, OAI.CommonBasesFPRAS.MachineCode.output_certificate, OAI.CommonBasesFPRAS.MachineCode.safe_single, OAI.CommonBasesFPRAS.actual_history_success and OAI.CommonBasesFPRAS.rational_algorithm_success; the proofs of OAI.CommonBasesFPRAS.MachineCode.Effect.safe_algorithmHistory, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_guard, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_historyTick, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_phaseSample, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_positiveTest, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_restartRepeat, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_sampleTick, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_start, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_storedStep, OAI.CommonBasesFPRAS.MachineCode.Effect.safe_witness, OAI.CommonBasesFPRAS.MachineCode.frozen_machine, OAI.CommonBasesFPRAS.MachineCode.frozen_uniform_safe, OAI.CommonBasesFPRAS.MachineCode.output_certificate, OAI.CommonBasesFPRAS.MachineCode.safe_single, OAI.CommonBasesFPRAS.actual_history_success, OAI.CommonBasesFPRAS.rational_algorithm_success and OAI.common_bases_fpras.

Definition code
import Mathlib
import Definitions.Def_CommonBasesFPRAS

section
-- module Solutions.OAIQ2_Mat.Compat
namespace OAI

/-! Compatibility with the newer Lean core OpenAI's code was written against: lemmas it uses by
names this environment (Lean 4.33.1, Mathlib 0df444a) does not have yet. Each is the
corresponding `if_pos` / `if_neg` / `dif_pos` / `dif_neg`. The `dite` forms are stated as
`dite p a b`, not with the `if hp : p then …` notation: its eta-expanded lambdas form is a higher-order pattern
that `rw` cannot find (DepthThree, PeriodicTiling 2026-10-25). -/
section OAICompat434
theorem ite_eq_left {α : Sort _} {p : Prop} {_ : Decidable p} {a b : α} (h : p) :
    (if p then a else b) = a := if_pos h
theorem ite_eq_right {α : Sort _} {p : Prop} {_ : Decidable p} {a b : α} (h : ¬p) :
    (if p then a else b) = b := if_neg h
theorem dite_eq_left {α : Sort _} {p : Prop} {_ : Decidable p} {a : p → α} {b : ¬p → α} (h : p) :
    dite p a b = a h := dif_pos h
theorem dite_eq_right {α : Sort _} {p : Prop} {_ : Decidable p} {a : p → α} {b : ¬p → α} (h : ¬p) :
    dite p a b = b h := dif_neg h
/-- Mathlib's newer `TensorProduct.inductionOn` (no `zero` case: it is the `tmul` case at
`0 ⊗ₜ 0`); ours has only `TensorProduct.induction_on`, which asks for one. Not named
`TensorProduct.inductionOn`: inside `namespace OAI` that would make `open TensorProduct` open
`OAI.TensorProduct` instead of Mathlib's. -/
@[elab_as_elim]
theorem tensorInductionOn {R M N : Type*} [CommSemiring R] [AddCommMonoid M] [AddCommMonoid N]
    [Module R M] [Module R N] {motive : TensorProduct R M N → Prop} (z : TensorProduct R M N)
    (tmul : ∀ x y, motive (TensorProduct.tmul R x y))
    (add : ∀ x y, motive x → motive y → motive (x + y)) : motive z :=
  TensorProduct.induction_on z (by simpa using tmul 0 0) tmul add
/-- Mathlib's newer case names for `FreeGroup.induction_on` (`one`, where ours says `C1`). -/
@[elab_as_elim]
theorem freeGroupInductionOn {α : Type*} {C : FreeGroup α → Prop} (z : FreeGroup α) (one : C 1)
    (of : ∀ x, C (FreeGroup.of x)) (inv_of : ∀ x, C (FreeGroup.of x) → C (FreeGroup.of x)⁻¹)
    (mul : ∀ x y, C x → C y → C (x * y)) : C z :=
  FreeGroup.induction_on z one of inv_of mul
/-- The first projection of a probability measure on a product: found by instance search in d13f23b
(total `Measure.map`), here a measurable map. -/
instance isProbabilityMeasure_map_fst {α β : Type*} [MeasurableSpace α] [MeasurableSpace β]
    {μ : MeasureTheory.Measure (α × β)} [MeasureTheory.IsProbabilityMeasure μ] :
    MeasureTheory.IsProbabilityMeasure (μ.map Prod.fst) :=
  MeasureTheory.Measure.isProbabilityMeasure_map measurable_fst.aemeasurable
instance isProbabilityMeasure_map_snd {α β : Type*} [MeasurableSpace α] [MeasurableSpace β]
    {μ : MeasureTheory.Measure (α × β)} [MeasureTheory.IsProbabilityMeasure μ] :
    MeasureTheory.IsProbabilityMeasure (μ.map Prod.snd) :=
  MeasureTheory.Measure.isProbabilityMeasure_map measurable_snd.aemeasurable
instance isProbabilityMeasure_map_subtype_val {α : Type*} [MeasurableSpace α] {p : α → Prop}
    {μ : MeasureTheory.Measure {a // p a}} [MeasureTheory.IsProbabilityMeasure μ] :
    MeasureTheory.IsProbabilityMeasure (μ.map Subtype.val) :=
  MeasureTheory.Measure.isProbabilityMeasure_map measurable_subtype_coe.aemeasurable
/-- Mathlib d13f23b's `YoungDiagram.card_transpose`. -/
@[simp]
theorem _root_.YoungDiagram.card_transpose (μ : YoungDiagram) : μ.transpose.card = μ.card := by
  unfold YoungDiagram.card
  apply Finset.card_bij (fun c _ => c.swap)
  · intro c hc
    have : c ∈ μ.transpose := hc
    rw [YoungDiagram.mem_transpose] at this
    exact this
  · intro a _ b _ h
    simpa using h
  · intro b hb
    refine ⟨b.swap, ?_, by simp⟩
    show b.swap ∈ μ.transpose
    rw [YoungDiagram.mem_transpose]
    simpa using hb
/-- For a measurable equivalence `e`, `IsProbabilityMeasure (μ.map e)` is found by instance search
(Mathlib d13f23b finds it for every map `f`: there `Measure.map` of a non-measurable map is a Dirac
mass, here it is `0`, so only the measurable case can be shimmed). -/
instance isProbabilityMeasure_map_measurableEquiv {α β : Type*} [MeasurableSpace α]
    [MeasurableSpace β] {μ : MeasureTheory.Measure α} [MeasureTheory.IsProbabilityMeasure μ]
    (e : α ≃ᵐ β) : MeasureTheory.IsProbabilityMeasure (μ.map e) :=
  MeasureTheory.Measure.isProbabilityMeasure_map e.measurable.aemeasurable
end OAICompat434

end OAI
end

section
-- module Solutions.OAIQ2_Mat.MatroidCounting_CommonBases
set_option linter.all false
set_option linter.unusedVariables false
set_option linter.unusedTactic false
set_option linter.unreachableTactic false

namespace OAI

namespace CommonBasesFPRAS

section

section
open scoped RealInnerProductSpace BigOperators Matrix

variable {V : Type*} [AddCommGroup V] [Module ℝ V]

def OnePositive (B : LinearMap.BilinForm ℝ V) : Prop :=
  ∀ e, 0 < B e e → ∀ v, B v e = 0 → B v v ≤ 0

section PositiveMatrix
variable {ι : Type*} [Fintype ι] [sectionInstance7 : DecidableEq ι]

def weightedSumLinear (a : ι → ℝ) : (ι → ℝ) →ₗ[ℝ] ℝ where
  toFun v := ∑ i, a i * v i
  map_add' x y := by simp [mul_add, Finset.sum_add_distrib]
  map_smul' c x := by simp [Finset.mul_sum, mul_left_comm]

def diagonalScale (A : Matrix ι ι ℝ) (s : ι → ℝ) : Matrix ι ι ℝ :=
  fun i j => s i * A i j * s j

def cubicContraction (C : ι → Matrix ι ι ℝ) (x : ι → ℝ) : Matrix ι ι ℝ :=
  fun j k => ∑ i, x i*C i j k

end PositiveMatrix

section RawSignature
open scoped BigOperators Classical
variable {ι : Type*} [sectionInstance8 : Fintype ι] [sectionInstance9 : DecidableEq ι]

noncomputable def rankWeight (M : Matroid ι) (q : ℝ) (A : Set ι) : ℝ :=
  (q⁻¹) ^ (M.eRk A).toNat

noncomputable def rawHessian (M : Matroid ι) (q : ℝ) : Matrix ι ι ℝ :=
  fun i j => if i = j then 0 else rankWeight M q {i,j}

noncomputable def rawPositive (M : Matroid ι) (q : ℝ) (i : ι) : ℝ :=
  if M.IsNonloop i then q⁻¹ else 1

noncomputable def parallelIndicator (M : Matroid ι) (C : Set ι) (i : ι) : ℝ :=
  if M.IsNonloop i ∧ M.closure {i} = C then 1 else 0

end RawSignature
section MatroidFaces
open scoped Matroid BigOperators Classical
variable {ι : Type*} [sectionInstance10 : Fintype ι] [sectionInstance11 : DecidableEq ι]

noncomputable def faceHessian (f : Set ι → ℝ) (A : Set ι) :
    Matrix {i // i ∉ A} {i // i ∉ A} ℝ :=
  fun i j => if i = j then 0 else f (A ∪ {i.val,j.val})

noncomputable def faceMatroid (M : Matroid ι) (A : Set ι) : Matroid {i // i ∉ A} :=
  (M / A).comap Subtype.val

end MatroidFaces

section DerivativeClosure
open scoped BigOperators Classical Matrix
variable {ι : Type*} [sectionInstance12 : Fintype ι] [sectionInstance13 : DecidableEq ι]
noncomputable def coeffDeriv (a : ι → ℝ) (f : Set ι → ℝ) (A : Set ι) : ℝ :=
  ∑ i, if i ∈ A then 0 else a i * f (insert i A)

noncomputable def coeffDerivs : List (ι → ℝ) → (Set ι → ℝ) → Set ι → ℝ
  | [], f => f
  | a :: as, f => coeffDeriv a (coeffDerivs as f)

def FullPositive (f : Set ι → ℝ) (k : ℕ) : Prop :=
  (∀ A, A.ncard + k ≤ Fintype.card ι → 0 < f A) ∧
  (∀ A, Fintype.card ι < A.ncard + k → f A = 0)

noncomputable def padMatrix (p : ι → Prop) [DecidablePred p]
    (B : Matrix {i // p i} {i // p i} ℝ) : Matrix ι ι ℝ :=
  fun i j => if hi : p i then if hj : p j then B ⟨i,hi⟩ ⟨j,hj⟩ else 0 else 0

noncomputable def faceEraseEquiv (A : Set ι) (i : {i // i ∉ A}) :
    {j : {j // j ∉ A} // j ≠ i} ≃ {j // j ∉ insert i.val A} where
  toFun j := ⟨j.val.val, by
    simp only [Set.mem_insert_iff, not_or]
    exact ⟨fun h => j.property (Subtype.ext h), j.val.property⟩⟩
  invFun j := ⟨⟨j.val, fun h => j.property (Set.mem_insert_of_mem _ h)⟩,
    fun h => j.property (Or.inl (congrArg Subtype.val h))⟩
  left_inv j := rfl
  right_inv j := rfl

noncomputable def faceCubic (f : Set ι → ℝ) (A : Set ι) :
    {i // i ∉ A} → Matrix {i // i ∉ A} {i // i ∉ A} ℝ :=
  fun i j k => if i = j ∨ i = k ∨ j = k then 0 else f (insert i.val (A ∪ {j.val,k.val}))

noncomputable def slotDirection (E : Finset ι) (i : ι) : ℝ := if i ∈ E then 1 else 0

omit sectionInstance12 in
lemma slotDirection_nonneg [Fintype ι] (E : Finset ι) (i : ι) : 0 ≤ slotDirection E i := by
  unfold slotDirection
  split_ifs <;> norm_num

lemma coeffDeriv_slot (E : Finset ι) (f : Set ι → ℝ) (A : Set ι) :
    coeffDeriv (slotDirection E) f A =
      ∑ i ∈ E, if i ∈ A then 0 else f (insert i A) := by
  classical
  unfold coeffDeriv slotDirection
  rw [← Finset.sum_subset (Finset.subset_univ E)]
  · apply Finset.sum_congr rfl
    intro i hi
    simp [hi]
  · intro i _ hi
    simp [hi]

lemma coeffDeriv_pair (a b : ι) (hab : a ≠ b) (f : Set ι → ℝ) (A : Set ι)
    (ha : a ∉ A) (hb : b ∉ A) :
    coeffDeriv (slotDirection {a,b}) f A = f (insert a A) + f (insert b A) := by
  rw [coeffDeriv_slot]
  simp [hab, ha, hb]

end DerivativeClosure

section MatrixSplit

open scoped BigOperators
variable {ι : Type*} [Fintype ι] [sectionInstance15 : DecidableEq ι]

def splitMatrix (V : Matrix ι ι ℝ) (sa sb : ι → ℝ) (u : ℝ) :
    Matrix (ι ⊕ Bool) (ι ⊕ Bool) ℝ
  | .inl i, .inl j => V i j
  | .inl i, .inr false => sb i
  | .inl i, .inr true => sa i
  | .inr false, .inl i => sb i
  | .inr true, .inl i => sa i
  | .inr a, .inr b => if a = b then 0 else u

def oldVector (h : ι → ℝ) : (ι ⊕ Bool) → ℝ
  | .inl i => h i
  | .inr _ => 0

def splitPositive : (ι ⊕ Bool) → ℝ
  | .inl _ => 0
  | .inr _ => 1

def splitDifference : (ι ⊕ Bool) → ℝ
  | .inl _ => 0
  | .inr false => 1
  | .inr true => -1

def futureChild (V : Matrix ι ι ℝ) (c : ι → ℝ) : Matrix (Option ι) (Option ι) ℝ
  | some i, some j => V i j
  | some i, none => c i
  | none, some j => c j
  | none, none => 0

def extendVector (h : ι → ℝ) (a : ℝ) : Option ι → ℝ
  | some i => h i
  | none => a

end MatrixSplit

section RecursiveTransport
open scoped BigOperators Classical Matrix
variable {Γ : Type*} [sectionInstance16 : Fintype Γ] [sectionInstance17 : DecidableEq Γ]

noncomputable def pairDirection (p : Γ × Γ) : Γ → ℝ := slotDirection {p.1,p.2}

noncomputable def slotTotal (ps : List (Γ × Γ)) (f : Set Γ → ℝ) : Set Γ → ℝ :=
  coeffDerivs (ps.map pairDirection) f

lemma slotTotal_nil (f : Set Γ → ℝ) (A : Set Γ) : slotTotal [] f A = f A := rfl

lemma slotTotal_cons (p : Γ × Γ) (ps : List (Γ × Γ)) (f : Set Γ → ℝ)
    (A : Set Γ) (hp : p.1 ≠ p.2) (ha : p.1 ∉ A) (hb : p.2 ∉ A) :
    slotTotal (p::ps) f A = slotTotal ps f (insert p.1 A) +
      slotTotal ps f (insert p.2 A) := by
  exact coeffDeriv_pair p.1 p.2 hp (slotTotal ps f) A ha hb

def SlotsFresh : List (Γ × Γ) → Set Γ → Prop
  | [], _ => True
  | p::ps, B => p.1 ≠ p.2 ∧ p.1 ∉ B ∧ p.2 ∉ B ∧
      SlotsFresh ps (insert p.1 (insert p.2 B))

omit sectionInstance16 sectionInstance17 in
lemma SlotsFresh.mono [Fintype Γ] [DecidableEq Γ] {ps : List (Γ × Γ)} {A B : Set Γ}
    (h : SlotsFresh ps B) (hAB : A ⊆ B) : SlotsFresh ps A := by
  induction ps generalizing A B with
  | nil => trivial
  | cons p ps ih =>
    obtain ⟨hp,ha,hb,ht⟩ := h
    exact ⟨hp, fun hx => ha (hAB hx), fun hx => hb (hAB hx),
      ih ht (Set.insert_subset_insert (Set.insert_subset_insert hAB))⟩

lemma slotTotal_pos (ps : List (Γ × Γ)) (f : Set Γ → ℝ)
    (hf : ∀ A, 0 < f A) (A : Set Γ) (hps : SlotsFresh ps A) :
    0 < slotTotal ps f A := by
  induction ps generalizing A with
  | nil => exact hf A
  | cons p ps ih =>
    obtain ⟨hp,ha,hb,ht⟩ := hps
    rw [slotTotal_cons p ps f A hp ha hb]
    apply add_pos
    · apply ih _ (ht.mono _)
      intro x hx
      rcases hx with rfl | hx
      · exact Set.mem_insert _ _
      · exact Set.mem_insert_of_mem _ (Set.mem_insert_of_mem _ hx)
    · apply ih _ (ht.mono _)
      intro x hx
      exact Set.mem_insert_of_mem _ hx

def appendPoint {ι : Type*} (e : ι → Γ) (a : Γ) : Option ι → Γ
  | none => a
  | some i => e i

noncomputable def pairMatrix {ι : Type*} [DecidableEq ι]
    (f : Set Γ → ℝ) (A : Set Γ) (e : ι → Γ) : Matrix ι ι ℝ :=
  fun i j => if i = j then 0 else f (insert (e i) (insert (e j) A))

def HasSlotSignature (f : Set Γ → ℝ) : Prop :=
  ∀ (ps : List (Γ × Γ)) (A : Set Γ),
    OnePositive (faceHessian (slotTotal ps f) A).toBilin'

section Clique
variable {ι : Type*} [Fintype ι] [sectionInstance19 : DecidableEq ι]

noncomputable def cliqueEnergy (s W H : ι → ℝ) : ℝ :=
  (∑ i, ∑ j, min (W i*s i) (W j*s j) * (H i-H j)^2) / 2

end Clique

variable {ι : Type*} [sectionInstance20 : Fintype ι] [sectionInstance21 : DecidableEq ι]

def splitEmbedding (e : ι → Γ) (a b : Γ) : (ι ⊕ Bool) → Γ
  | .inl i => e i
  | .inr false => a
  | .inr true => b

noncomputable def transportDemand (ps : List (Γ × Γ)) (f H : Set Γ → ℝ)
    (A : Set Γ) (e : ι → Γ) (h : ι → ℝ) : ℝ :=
  ∑ i, h i * slotTotal ps (fun S => f S*H S) (insert (e i) A)

noncomputable def transportPotential (ps : List (Γ × Γ)) (f : Set Γ → ℝ)
    (A : Set Γ) (e : ι → Γ) (h : ι → ℝ) (W : Γ → ℝ) : ℝ :=
  ∑ i, slotTotal ps f (insert (e i) A) * (h i)^2 / W (e i)

def slotPunctures : List (Γ × Γ) → List ((Γ × Γ) × List (Γ × Γ))
  | [] => []
  | p::ps => (p,ps) :: (slotPunctures ps).map (fun x => (x.1,p::x.2))

noncomputable def transportCorrection (ps : List (Γ × Γ)) (f : Set Γ → ℝ)
    (A : Set Γ) (e : ι → Γ) (h : ι → ℝ) (W : Γ → ℝ) : ℝ :=
  ((slotPunctures ps).map (fun x =>
    (pairMatrix (slotTotal x.2 f) A e).toBilin' h h / W x.1.1)).sum

noncomputable def transportEnergy {ι : Type*} [Fintype ι] (ps : List (Γ × Γ))
    (f H : Set Γ → ℝ) (A : Set Γ) (e : ι → Γ) (W : Γ → ℝ) : ℝ :=
  match ps with
  | [] => cliqueEnergy (fun i => f (insert (e i) A)) (fun i => W (e i))
      (fun i => H (insert (e i) A))
  | p::ps => transportEnergy ps f H (insert p.2 A) (appendPoint e p.1) W +
      transportEnergy ps f H (insert p.1 A) (appendPoint e p.2) W
termination_by ps.length

end RecursiveTransport

section TransportRoot
open scoped BigOperators Classical Matrix
variable {Γ ι : Type*} [sectionInstance22 : Fintype Γ] [sectionInstance23 : DecidableEq Γ]
  [Fintype ι] [sectionInstance25 : DecidableEq ι]

def rootEmbedding (p d : Γ) : Bool → Γ := fun b => if b then d else p

noncomputable def rootDemand (sp sd : ℝ) : Bool → ℝ :=
  fun b => if b then -(1/sd) else 1/sp

noncomputable def rootCoefficient (ps : List (Γ × Γ)) (f : Set Γ → ℝ)
    (A : Set Γ) (p d : Γ) (W : Γ → ℝ) : ℝ :=
  let sp := slotTotal ps f (insert p A)
  let sd := slotTotal ps f (insert d A)
  1/(sp*W p) + 1/(sd*W d) + 2/(sp*sd)*
    ((slotPunctures ps).map (fun x => slotTotal x.2 f (insert p (insert d A))/W x.1.1)).sum

end TransportRoot

section TransportGraph
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance26 : Fintype Γ] [sectionInstance27 : DecidableEq Γ]

def ExchangeAdjacent (S T : Set Γ) : Prop :=
  ∃ (A : Set Γ) (a b : Γ), a ≠ b ∧ a ∉ A ∧ b ∉ A ∧
    S = insert a A ∧ T = insert b A

noncomputable def slotVertices {ι : Type*} [Fintype ι]
    (ps : List (Γ × Γ)) (A : Set Γ) (e : ι → Γ) : Finset (Set Γ) :=
  match ps with
  | [] => Finset.univ.image (fun i => insert (e i) A)
  | p::ps => slotVertices ps (insert p.2 A) (appendPoint e p.1) ∪
      slotVertices ps (insert p.1 A) (appendPoint e p.2)
termination_by ps.length

noncomputable def transportEdges {ι : Type*} [Fintype ι] [DecidableEq ι]
    (ps : List (Γ × Γ)) (A : Set Γ) (e : ι → Γ) : Finset (Set Γ × Set Γ) :=
  match ps with
  | [] => (Finset.univ.filter (fun ij : ι × ι => ij.1 ≠ ij.2)).image
      (fun ij => (insert (e ij.1) A, insert (e ij.2) A))
  | p::ps => transportEdges ps (insert p.2 A) (appendPoint e p.1) ∪
      transportEdges ps (insert p.1 A) (appendPoint e p.2)
termination_by ps.length

noncomputable def missingMultiplier {ι : Type*} [Fintype ι]
    (ps : List (Γ × Γ)) (e : ι → Γ) (W : Γ → ℝ) (S : Set Γ) : ℝ :=
  (∑ i, if e i ∈ S then W (e i) else 0) +
    (ps.map (fun p => if p.1 ∈ S ∧ p.2 ∈ S then W p.1 else 0)).sum

noncomputable def slotEdgeEnergy {ι : Type*} [Fintype ι]
    (ps : List (Γ × Γ)) (e : ι → Γ) (W : Γ → ℝ)
    (f H : Set Γ → ℝ) (ST : Set Γ × Set Γ) : ℝ :=
  min (missingMultiplier ps e W ST.1 * f ST.1)
      (missingMultiplier ps e W ST.2 * f ST.2) * (H ST.1-H ST.2)^2

noncomputable def slotGraphEdges {ι : Type*} [Fintype ι]
    (ps : List (Γ × Γ)) (A : Set Γ) (e : ι → Γ) : Finset (Set Γ × Set Γ) :=
  ((slotVertices ps A e).product (slotVertices ps A e)).filter
    (fun ST => ExchangeAdjacent ST.1 ST.2)

noncomputable def slotGraphEnergy {ι : Type*} [Fintype ι]
    (ps : List (Γ × Γ)) (f H : Set Γ → ℝ) (A : Set Γ) (e : ι → Γ) (W : Γ → ℝ) : ℝ :=
  (∑ ST ∈ slotGraphEdges ps A e, slotEdgeEnergy ps e W f H ST)/2

end TransportGraph

section CoefficientInequalities
open scoped BigOperators Classical Matrix
variable {ι : Type*} [sectionInstance28 : Fintype ι] [sectionInstance29 : DecidableEq ι]

omit sectionInstance28 sectionInstance29 in
lemma SlotsFresh.swap [Fintype ι] [DecidableEq ι] {ps : List (ι × ι)} {p r : ι × ι} {A : Set ι}
    (h : SlotsFresh (p::r::ps) A) : SlotsFresh (r::p::ps) A := by
  obtain ⟨hp,hpa,hpb,hr,hra,hrb,hps⟩ := h
  refine ⟨hr,?_,?_,hp,?_,?_,?_⟩
  · exact fun hx => hra (Or.inr (Or.inr hx))
  · exact fun hx => hrb (Or.inr (Or.inr hx))
  · simp only [Set.mem_insert_iff, not_or] at hra hrb ⊢
    exact ⟨Ne.symm hra.1,Ne.symm hrb.1,hpa⟩
  · simp only [Set.mem_insert_iff, not_or] at hra hrb ⊢
    exact ⟨Ne.symm hra.2.1,Ne.symm hrb.2.1,hpb⟩
  · convert hps using 1 ; ext x ; simp ; tauto

end CoefficientInequalities

section MatroidWeights
open scoped BigOperators Classical Matrix
variable {ι : Type*} [sectionInstance30 : Fintype ι] [sectionInstance31 : DecidableEq ι]

omit sectionInstance31 in
lemma coeffDeriv_scale [DecidableEq ι] (a : ι → ℝ) (f : Set ι → ℝ) (c : ℝ) (A : Set ι) :
    coeffDeriv a (fun S => c * f S) A = c * coeffDeriv a f A := by
  simp only [coeffDeriv, Finset.mul_sum]
  apply Finset.sum_congr rfl
  intro i _
  split_ifs <;> ring

lemma coeffDerivs_scale (as : List (ι → ℝ)) (f : Set ι → ℝ) (c : ℝ) (A : Set ι) :
    coeffDerivs as (fun S => c * f S) A = c * coeffDerivs as f A := by
  induction as generalizing A with
  | nil => rfl
  | cons a as ih =>
    change coeffDeriv a (coeffDerivs as (fun S => c * f S)) A = _
    have h : coeffDerivs as (fun S => c * f S) = fun S => c * coeffDerivs as f S := by
      funext S; exact ih S
    rw [h,coeffDeriv_scale]
    rfl

noncomputable def matroidWeight (M : Matroid ι) (n : ℕ) (q : ℝ) (S : Set ι) : ℝ :=
  q ^ (n - (M.eRk S).toNat)

omit sectionInstance30 sectionInstance31 in
lemma matroidWeight_pos [Fintype ι] [DecidableEq ι] (M : Matroid ι) (n : ℕ) (q : ℝ) (hq : 0 < q) (S : Set ι) :
    0 < matroidWeight M n q S := pow_pos hq _

end MatroidWeights

section IndexedSlotCoefficients
open scoped BigOperators Classical
variable {ι : Type*} [sectionInstance32 : Fintype ι] [DecidableEq ι]

lemma SlotsFresh.perm {ps qs : List (ι × ι)} (hperm : ps.Perm qs) (A : Set ι) :
    SlotsFresh ps A ↔ SlotsFresh qs A := by
  induction hperm generalizing A with
  | nil => rfl
  | cons p h ih =>
    change (_ ∧ _ ∧ _ ∧ SlotsFresh _ _) ↔ (_ ∧ _ ∧ _ ∧ SlotsFresh _ _)
    rw [ih]
  | swap p r ps => exact ⟨SlotsFresh.swap,SlotsFresh.swap⟩
  | trans h₁ h₂ ih₁ ih₂ => exact (ih₁ A).trans (ih₂ A)

noncomputable def slotDefect (ps : List (ι × ι)) (f : Set ι → ℝ)
    (A : Set ι) (p r : ι × ι) : ℝ :=
  slotTotal ((ps.erase p).erase r) f (insert r.1 (insert r.2 A))

omit sectionInstance32 in
lemma slots_perm_two [Fintype ι] {ps : List (ι × ι)} {p r : ι × ι}
    (hp : p ∈ ps) (hr : r ∈ ps) (hpr : p ≠ r) :
    ps.Perm (p::r::(ps.erase p).erase r) := by
  exact (List.perm_cons_erase hp).trans
    ((List.perm_cons_erase ((List.mem_erase_of_ne hpr.symm).mpr hr)).cons p)

lemma slotDefect_pos (ps : List (ι × ι)) (f : Set ι → ℝ)
    (hf : ∀ S, 0 < f S) (A : Set ι) (hps : SlotsFresh ps A)
    (p r : ι × ι) (hp : p ∈ ps) (hr : r ∈ ps) (hpr : p ≠ r) :
    0 < slotDefect ps f A p r := by
  have hh := (SlotsFresh.perm (slots_perm_two hp hr hpr) A).mp hps
  apply slotTotal_pos _ f hf _ (hh.2.2.2.2.2.2.mono _)
  intro x hx
  simp only [Set.mem_insert_iff] at hx ⊢
  tauto

end IndexedSlotCoefficients

section VarianceTree
open scoped BigOperators Classical
variable {ι : Type*} [sectionInstance34 : Fintype ι] [sectionInstance35 : DecidableEq ι]

noncomputable def slotVariance (ps : List (ι × ι)) (f H : Set ι → ℝ) (A : Set ι) : ℝ :=
  slotTotal ps (fun S => f S*(H S)^2) A -
    (slotTotal ps (fun S => f S*H S) A)^2 / slotTotal ps f A

end VarianceTree

section StateGeometry
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance36 : Fintype Γ] [sectionInstance37 : DecidableEq Γ]

def PairSingle (p : Γ × Γ) (S : Set Γ) : Prop :=
  (p.1 ∈ S ∧ p.2 ∉ S) ∨ (p.2 ∈ S ∧ p.1 ∉ S)
def PairFull (p : Γ × Γ) (S : Set Γ) : Prop := p.1 ∈ S ∧ p.2 ∈ S
def PairEmpty (p : Γ × Γ) (S : Set Γ) : Prop := p.1 ∉ S ∧ p.2 ∉ S

omit sectionInstance36 sectionInstance37 in
lemma PairSingle.not_full [Fintype Γ] [DecidableEq Γ] {p : Γ × Γ} {S : Set Γ} (h : PairSingle p S) : ¬ PairFull p S := by
  unfold PairSingle PairFull at *
  tauto
omit sectionInstance36 sectionInstance37 in
lemma PairSingle.not_empty [Fintype Γ] [DecidableEq Γ] {p : Γ × Γ} {S : Set Γ} (h : PairSingle p S) : ¬ PairEmpty p S := by
  unfold PairSingle PairEmpty at *
  tauto
omit sectionInstance36 sectionInstance37 in
lemma PairFull.not_empty [Fintype Γ] [DecidableEq Γ] {p : Γ × Γ} {S : Set Γ} (h : PairFull p S) : ¬ PairEmpty p S := by
  unfold PairFull PairEmpty at *
  tauto

def PlainState (gs : List (Γ × Γ)) (S : Set Γ) : Prop := ∀ p ∈ gs, PairSingle p S
def DefectState (gs : List (Γ × Γ)) (p r : Γ × Γ) (S : Set Γ) : Prop :=
  p ∈ gs ∧ r ∈ gs ∧ p ≠ r ∧ PairEmpty p S ∧ PairFull r S ∧
    ∀ t ∈ gs, t ≠ p → t ≠ r → PairSingle t S

lemma DefectState.not_plain {gs : List (Γ × Γ)} {p r : Γ × Γ} {S : Set Γ}
    (h : DefectState gs p r S) : ¬ PlainState gs S := by
  intro hs
  exact (hs p h.1).not_empty h.2.2.2.1

lemma DefectState.unique {gs : List (Γ × Γ)} {p r p' r' : Γ × Γ} {S : Set Γ}
    (h : DefectState gs p r S) (h' : DefectState gs p' r' S) : p = p' ∧ r = r' := by
  obtain ⟨hp,hr,hpr,hep,hfr,hs⟩ := h
  obtain ⟨hp',hr',hpr',hep',hfr',hs'⟩ := h'
  have hpp : p = p' := by
    by_contra hne
    by_cases hpr' : p = r'
    · subst r'; exact hfr'.not_empty hep
    · exact (hs' p hp hne hpr').not_empty hep
  subst p'
  refine ⟨rfl,?_⟩
  by_contra hne
  exact (hs' r hr hpr.symm hne).not_full hfr

omit sectionInstance36 sectionInstance37 in
lemma SlotsFresh.elements_fresh [Fintype Γ] [DecidableEq Γ] {ps : List (Γ × Γ)} {A : Set Γ}
    (h : SlotsFresh ps A) (p : Γ × Γ) (hp : p ∈ ps) : p.1 ∉ A ∧ p.2 ∉ A := by
  induction ps generalizing A with
  | nil => simp at hp
  | cons q ps ih =>
    rcases List.mem_cons.mp hp with rfl | hp
    · exact ⟨h.2.1,h.2.2.1⟩
    · have hh := ih h.2.2.2 hp
      exact ⟨fun hx => hh.1 (Or.inr (Or.inr hx)), fun hx => hh.2 (Or.inr (Or.inr hx))⟩

lemma SlotsFresh.pair_ne_of_mem {ps : List (Γ × Γ)} {A : Set Γ} {p : Γ × Γ}
    (h : SlotsFresh (p::ps) A) {r : Γ × Γ} (hr : r ∈ ps) : p ≠ r := by
  intro heq
  subst r
  exact (h.2.2.2.elements_fresh p hr).1 (Set.mem_insert _ _)

end StateGeometry

section RestrictedEnergy
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance38 : Fintype Γ] [sectionInstance39 : DecidableEq Γ]

def Respects (A B S : Set Γ) : Prop := A ⊆ S ∧ Disjoint B S

def EdgeRespects (A B : Set Γ) (ST : Set Γ × Set Γ) : Prop :=
  Respects A B ST.1 ∧ Respects A B ST.2

noncomputable def restrictedEnergy (E : Finset (Set Γ × Set Γ))
    (c : (Set Γ × Set Γ) → ℝ) (H : Set Γ → ℝ) (A B : Set Γ) : ℝ :=
  (∑ ST ∈ E, if EdgeRespects A B ST then c ST*(H ST.1-H ST.2)^2 else 0)/2

end RestrictedEnergy

section ChainIdentification
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance40 : Fintype Γ] [sectionInstance41 : DecidableEq Γ]

def FixedOutside (gs ps : List (Γ × Γ)) (A B : Set Γ) : Prop :=
  ∀ r ∈ gs, r ∉ ps → (r.1 ∈ A ∧ r.2 ∈ B) ∨ (r.2 ∈ A ∧ r.1 ∈ B)

noncomputable def chainMultiplier (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (S : Set Γ) : ℝ :=
  if h : ∃ p r, DefectState gs p r S then w h.choose h.choose_spec.choose else 1

lemma chainMultiplier_plain (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (S : Set Γ) (h : PlainState gs S) : chainMultiplier gs w S = 1 := by
  unfold chainMultiplier
  rw [dite_eq_right]
  rintro ⟨p,r,hpr⟩
  exact hpr.not_plain h

lemma chainMultiplier_defect (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (S : Set Γ) {p r : Γ × Γ} (h : DefectState gs p r S) :
    chainMultiplier gs w S = w p r := by
  have he : ∃ p r, DefectState gs p r S := ⟨p,r,h⟩
  unfold chainMultiplier
  rw [dite_eq_left he]
  have hh := he.choose_spec.choose_spec.unique h
  exact congrArg₂ w hh.1 hh.2

omit sectionInstance41 in
lemma chainMultiplier_pos [DecidableEq Γ] (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (hw : ∀ p r, 0 < w p r) (S : Set Γ) : 0 < chainMultiplier gs w S := by
  unfold chainMultiplier
  split_ifs <;> first | exact hw _ _ | norm_num

def ChainState (gs : List (Γ × Γ)) (S : Set Γ) : Prop :=
  PlainState gs S ∨ ∃ p r, DefectState gs p r S

noncomputable def chainEdges (gs : List (Γ × Γ)) : Finset (Set Γ × Set Γ) :=
  Finset.univ.filter (fun ST => ChainState gs ST.1 ∧ ChainState gs ST.2 ∧
    ExchangeAdjacent ST.1 ST.2)

noncomputable def chainConductance (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) (ST : Set Γ × Set Γ) : ℝ :=
  min (chainMultiplier gs w ST.1*f ST.1) (chainMultiplier gs w ST.2*f ST.2)

lemma SlotsFresh.pair_contains_unique {ps : List (Γ × Γ)} {A : Set Γ}
    (h : SlotsFresh ps A) {p r : Γ × Γ} (hp : p ∈ ps) (hr : r ∈ ps)
    {x : Γ} (hxp : x = p.1 ∨ x = p.2) (hxr : x = r.1 ∨ x = r.2) : p = r := by
  by_contra hne
  have hh := (SlotsFresh.perm (slots_perm_two hp hr hne) A).mp h
  have hr₁ := hh.2.2.2.2.1
  have hr₂ := hh.2.2.2.2.2.1
  rcases hxp with rfl | rfl <;> rcases hxr with heq | heq
  · exact hr₁ (Or.inl heq.symm)
  · exact hr₂ (Or.inl heq.symm)
  · exact hr₁ (Or.inr (Or.inl heq.symm))
  · exact hr₂ (Or.inr (Or.inl heq.symm))

noncomputable def pairMultiplier (ps : List (Γ × Γ)) (w : (Γ × Γ) → ℝ) (x : Γ) : ℝ :=
  if h : ∃ p ∈ ps, x = p.1 ∨ x = p.2 then w h.choose else 1

end ChainIdentification

section TransversalVariance
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance42 : Fintype Γ] [sectionInstance43 : DecidableEq Γ]

def VarianceNode (gs ps : List (Γ × Γ)) (A B : Set Γ) : Prop :=
  Disjoint A B ∧ SlotsFresh ps (A ∪ B) ∧ (∀ p ∈ ps, p ∈ gs) ∧ FixedOutside gs ps A B

end TransversalVariance

section ChoiceEnumeration
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance44 : Fintype Γ] [sectionInstance45 : DecidableEq Γ]

def pairSupport (ps : List (Γ × Γ)) : Set Γ :=
  {x | ∃ p ∈ ps, x = p.1 ∨ x = p.2}

omit sectionInstance44 sectionInstance45 in
@[simp] lemma pairSupport_nil [Fintype Γ] [DecidableEq Γ] : pairSupport ([] : List (Γ × Γ)) = ∅ := by ext; simp [pairSupport]
omit sectionInstance44 sectionInstance45 in
lemma pairSupport_cons [Fintype Γ] [DecidableEq Γ] (p : Γ × Γ) (ps : List (Γ × Γ)) :
    pairSupport (p::ps) = insert p.1 (insert p.2 (pairSupport ps)) := by
  ext x
  simp only [pairSupport,Set.mem_ofPred_eq,List.mem_cons,Set.mem_insert_iff]
  aesop

noncomputable def choiceVertices (ps : List (Γ × Γ)) (A : Set Γ) : Finset (Set Γ) :=
  match ps with
  | [] => {A}
  | p::ps => choiceVertices ps (insert p.1 A) ∪ choiceVertices ps (insert p.2 A)

omit sectionInstance44 sectionInstance45 in
lemma choiceVertices_fixed [Fintype Γ] [DecidableEq Γ] (ps : List (Γ × Γ)) (A S : Set Γ)
    (hS : S ∈ choiceVertices ps A) : A ⊆ S := by
  induction ps generalizing A with
  | nil =>
    rw [choiceVertices,Finset.mem_singleton] at hS
    exact hS ▸ Set.Subset.rfl
  | cons p ps ih =>
    rw [choiceVertices] at hS
    rcases Finset.mem_union.mp hS with hS | hS
    · exact (Set.subset_insert _ _).trans (ih _ hS)
    · exact (Set.subset_insert _ _).trans (ih _ hS)

omit sectionInstance44 sectionInstance45 in
lemma choiceVertices_excluded [Fintype Γ] [DecidableEq Γ] (ps : List (Γ × Γ)) (A B S : Set Γ)
    (hAB : Disjoint A B) (hps : ∀ p ∈ ps, p.1 ∉ B ∧ p.2 ∉ B)
    (hS : S ∈ choiceVertices ps A) : Disjoint B S := by
  induction ps generalizing A with
  | nil =>
    rw [choiceVertices,Finset.mem_singleton] at hS
    exact hS ▸ hAB.symm
  | cons p ps ih =>
    obtain ⟨ha,hb⟩ := hps p List.mem_cons_self
    have hrest := fun r hr => hps r (List.mem_cons_of_mem _ hr)
    rw [choiceVertices] at hS
    rcases Finset.mem_union.mp hS with hS | hS
    · exact ih _ (Set.disjoint_insert_left.mpr ⟨ha,hAB⟩) hrest hS
    · exact ih _ (Set.disjoint_insert_left.mpr ⟨hb,hAB⟩) hrest hS

lemma choiceVertices_disjoint (ps : List (Γ × Γ)) (A : Set Γ) (p : Γ × Γ)
    (hps : SlotsFresh (p::ps) A) :
    Disjoint (choiceVertices ps (insert p.1 A)) (choiceVertices ps (insert p.2 A)) := by
  apply Finset.disjoint_left.mpr
  intro S h₁ h₂
  have hp₁ : p.1 ∈ S := choiceVertices_fixed ps (insert p.1 A) S h₁ (Set.mem_insert _ _)
  have hp₂ : Disjoint {p.1} S := choiceVertices_excluded ps (insert p.2 A) {p.1} S
    (by simp only [Set.disjoint_singleton_right,Set.mem_insert_iff,not_or]; exact ⟨hps.1,hps.2.1⟩)
    (by
      intro r hr
      have hh := hps.2.2.2.elements_fresh r hr
      exact ⟨fun heq => hh.1 (Or.inl heq),fun heq => hh.2 (Or.inl heq)⟩) h₂
  exact Set.disjoint_left.mp hp₂ rfl hp₁

lemma slotTotal_eq_choice_sum (ps : List (Γ × Γ)) (A : Set Γ) (f : Set Γ → ℝ)
    (hps : SlotsFresh ps A) : slotTotal ps f A = ∑ S ∈ choiceVertices ps A, f S := by
  induction ps generalizing A with
  | nil => simp [choiceVertices,slotTotal_nil]
  | cons p ps ih =>
    rw [slotTotal_cons p ps f A hps.1 hps.2.1 hps.2.2.1,choiceVertices,
      Finset.sum_union (choiceVertices_disjoint ps A p hps)]
    congr 1
    · exact ih _ (hps.2.2.2.mono (by intro x hx; simp only [Set.mem_insert_iff] at hx ⊢; tauto))
    · exact ih _ (hps.2.2.2.mono (Set.subset_insert _ _))

lemma mem_choiceVertices_iff (ps : List (Γ × Γ)) (A S : Set Γ) (hps : SlotsFresh ps A) :
    S ∈ choiceVertices ps A ↔
      A ⊆ S ∧ S ⊆ A ∪ pairSupport ps ∧ PlainState ps S := by
  induction ps generalizing A with
  | nil => simp [choiceVertices,pairSupport_nil,PlainState,Set.Subset.antisymm_iff,and_comm]
  | cons p ps ih =>
    have hf₁ : SlotsFresh ps (insert p.1 A) := hps.2.2.2.mono (by
      intro x hx; simp only [Set.mem_insert_iff] at hx ⊢; tauto)
    have hf₂ : SlotsFresh ps (insert p.2 A) := hps.2.2.2.mono (Set.subset_insert _ _)
    rw [choiceVertices,Finset.mem_union,ih _ hf₁,ih _ hf₂]
    have hnot₁ : p.1 ∉ pairSupport ps := by
      rintro ⟨r,hr,h⟩
      have hh := hps.2.2.2.elements_fresh r hr
      rcases h with he | he
      · exact hh.1 (Or.inl he.symm)
      · exact hh.2 (Or.inl he.symm)
    have hnot₂ : p.2 ∉ pairSupport ps := by
      rintro ⟨r,hr,h⟩
      have hh := hps.2.2.2.elements_fresh r hr
      rcases h with he | he
      · exact hh.1 (Or.inr (Or.inl he.symm))
      · exact hh.2 (Or.inr (Or.inl he.symm))
    constructor
    · rintro (⟨hfix,hsupp,hsing⟩ | ⟨hfix,hsupp,hsing⟩)
      · refine ⟨(Set.subset_insert _ _).trans hfix,?_,?_⟩
        · intro x hx
          have hh := hsupp hx
          rw [pairSupport_cons]
          simp only [Set.mem_union,Set.mem_insert_iff] at hh ⊢
          tauto
        · intro r hr
          rcases List.mem_cons.mp hr with rfl | hr
          · left
            refine ⟨hfix (Set.mem_insert _ _),?_⟩
            intro hbad
            have hh := hsupp hbad
            rcases hh with hh | hh
            · rcases hh with hh | hh
              · exact hps.1 hh.symm
              · exact hps.2.2.1 hh
            · exact hnot₂ hh
          · exact hsing r hr
      · refine ⟨(Set.subset_insert _ _).trans hfix,?_,?_⟩
        · intro x hx
          have hh := hsupp hx
          rw [pairSupport_cons]
          simp only [Set.mem_union,Set.mem_insert_iff] at hh ⊢
          tauto
        · intro r hr
          rcases List.mem_cons.mp hr with rfl | hr
          · right
            refine ⟨hfix (Set.mem_insert _ _),?_⟩
            intro hbad
            have hh := hsupp hbad
            rcases hh with hh | hh
            · rcases hh with hh | hh
              · exact hps.1 hh
              · exact hps.2.1 hh
            · exact hnot₁ hh
          · exact hsing r hr
    · rintro ⟨hfix,hsupp,hsing⟩
      have hp := hsing p List.mem_cons_self
      have hrest : PlainState ps S := fun r hr => hsing r (List.mem_cons_of_mem _ hr)
      rcases hp with hp | hp
      · left
        refine ⟨Set.insert_subset hp.1 hfix,?_,hrest⟩
        intro x hx
        have hh := hsupp hx
        rw [pairSupport_cons] at hh
        simp only [Set.mem_union,Set.mem_insert_iff] at hh ⊢
        rcases hh with hh | rfl | rfl | hh
        · exact Or.inl (Or.inr hh)
        · exact Or.inl (Or.inl rfl)
        · exact False.elim (hp.2 hx)
        · exact Or.inr hh
      · right
        refine ⟨Set.insert_subset hp.1 hfix,?_,hrest⟩
        intro x hx
        have hh := hsupp hx
        rw [pairSupport_cons] at hh
        simp only [Set.mem_union,Set.mem_insert_iff] at hh ⊢
        rcases hh with hh | rfl | rfl | hh
        · exact Or.inl (Or.inr hh)
        · exact False.elim (hp.2 hx)
        · exact Or.inl (Or.inl rfl)
        · exact Or.inr hh

lemma choiceVertices_plain (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) :
    choiceVertices gs ∅ = Finset.univ.filter (PlainState gs) := by
  ext S
  rw [mem_choiceVertices_iff gs ∅ S hgs]
  simp [hcover]

lemma slotTotal_eq_plain_sum (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (f : Set Γ → ℝ) :
    slotTotal gs f ∅ = ∑ S ∈ Finset.univ.filter (PlainState gs), f S := by
  rw [slotTotal_eq_choice_sum gs ∅ f hgs,choiceVertices_plain gs hgs hcover]

end ChoiceEnumeration

section DefectEnumeration
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance46 : Fintype Γ] [sectionInstance47 : DecidableEq Γ]

omit sectionInstance46 sectionInstance47 in
lemma pairSupport_perm [Fintype Γ] [DecidableEq Γ] {ps qs : List (Γ × Γ)} (hp : ps.Perm qs) : pairSupport ps = pairSupport qs := by
  ext x
  simp only [pairSupport,Set.mem_ofPred_eq,hp.mem_iff]

lemma SlotsFresh.support_disjoint {ps : List (Γ × Γ)} {A : Set Γ} (h : SlotsFresh ps A) :
    Disjoint (pairSupport ps) A := by
  apply Set.disjoint_left.mpr
  rintro x ⟨p,hp,hx⟩ hxA
  rcases hx with rfl | rfl
  · exact (h.elements_fresh p hp).1 hxA
  · exact (h.elements_fresh p hp).2 hxA

lemma choiceVertices_defect (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (p r : Γ × Γ)
    (hp : p ∈ gs) (hr : r ∈ gs) (hpr : p ≠ r) :
    choiceVertices ((gs.erase p).erase r) {r.1,r.2} =
      Finset.univ.filter (DefectState gs p r) := by
  let ps := (gs.erase p).erase r
  have hperm : gs.Perm (p::r::ps) := slots_perm_two hp hr hpr
  have hslots := (SlotsFresh.perm hperm ∅).mp hgs
  have htail : SlotsFresh ps {r.1,r.2} := hslots.2.2.2.2.2.2.mono (by
    intro x hx
    simp only [Set.mem_insert_iff,Set.mem_singleton_iff,Set.mem_empty_iff_false,or_false] at hx ⊢
    tauto)
  have hno : p.1 ∉ pairSupport (r::ps) ∧ p.2 ∉ pairSupport (r::ps) := by
    exact ⟨fun hx => Set.disjoint_left.mp hslots.2.2.2.support_disjoint hx (Or.inl rfl),
      fun hx => Set.disjoint_left.mp hslots.2.2.2.support_disjoint hx (Or.inr (Or.inl rfl))⟩
  have htailSupport : ({r.1,r.2} : Set Γ) ∪ pairSupport ps = pairSupport (r::ps) := by
    rw [pairSupport_cons]
    ext x
    simp only [Set.mem_union,Set.mem_insert_iff,Set.mem_singleton_iff]
    tauto
  have hcover' : pairSupport (p::r::ps) = Set.univ := (pairSupport_perm hperm).symm.trans hcover
  ext S
  rw [mem_choiceVertices_iff _ _ _ htail,Finset.mem_filter]
  simp only [Finset.mem_univ,true_and]
  constructor
  · rintro ⟨hfix,hsupp,hsing⟩
    refine ⟨hp,hr,hpr,⟨?_,?_⟩,⟨hfix (Or.inl rfl),hfix (Or.inr rfl)⟩,?_⟩
    · intro hx; exact hno.1 (htailSupport ▸ hsupp hx)
    · intro hx; exact hno.2 (htailSupport ▸ hsupp hx)
    · intro t ht htp htr
      have ht' : t ∈ p::r::ps := hperm.mem_iff.mp ht
      simp only [List.mem_cons] at ht'
      exact hsing t (ht'.resolve_left htp |>.resolve_left htr)
  · intro hdef
    refine ⟨?_,?_,?_⟩
    · intro x hx
      rcases hx with rfl | hx
      · exact hdef.2.2.2.2.1.1
      · exact hx ▸ hdef.2.2.2.2.1.2
    · intro x hx
      have hh : x ∈ pairSupport (p::r::ps) := hcover' ▸ Set.mem_univ x
      rw [pairSupport_cons] at hh
      rcases hh with rfl | rfl | hh
      · exact False.elim (hdef.2.2.2.1.1 hx)
      · exact False.elim (hdef.2.2.2.1.2 hx)
      · exact htailSupport ▸ hh
    · intro t ht
      exact hdef.2.2.2.2.2 t (hperm.mem_iff.mpr (List.mem_cons_of_mem _ (List.mem_cons_of_mem _ ht)))
        (hslots.pair_ne_of_mem (List.mem_cons_of_mem _ ht)).symm
        (hslots.2.2.2.pair_ne_of_mem ht).symm

lemma slotDefect_eq_defect_sum (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (f : Set Γ → ℝ) (p r : Γ × Γ)
    (hp : p ∈ gs) (hr : r ∈ gs) (hpr : p ≠ r) :
    slotDefect gs f ∅ p r = ∑ S ∈ Finset.univ.filter (DefectState gs p r), f S := by
  have hslots := (SlotsFresh.perm (slots_perm_two hp hr hpr) ∅).mp hgs
  have htail : SlotsFresh ((gs.erase p).erase r) {r.1,r.2} := hslots.2.2.2.2.2.2.mono (by
    intro x hx
    simp only [Set.mem_insert_iff,Set.mem_singleton_iff,Set.mem_empty_iff_false,or_false] at hx ⊢
    tauto)
  unfold slotDefect
  simp only [insert_empty_eq]
  rw [slotTotal_eq_choice_sum _ _ f htail,choiceVertices_defect gs hgs hcover p r hp hr hpr]

end DefectEnumeration

section DefectTransport
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance48 : Fintype Γ] [sectionInstance49 : DecidableEq Γ]

def SamePair (p q : Γ × Γ) : Prop := q = p ∨ q = p.swap

end DefectTransport

section DefectMeans
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance50 : Fintype Γ] [sectionInstance51 : DecidableEq Γ]

def orientPair (p : Γ × Γ) (b : Bool) : Γ × Γ := if b then p.swap else p

end DefectMeans

section Observable
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance52 : Fintype Γ] [DecidableEq Γ]

noncomputable def chainStates (gs : List (Γ × Γ)) : Finset (Set Γ) :=
  Finset.univ.filter (ChainState gs)
noncomputable def defectTypes (gs : List (Γ × Γ)) : Finset ((Γ × Γ) × (Γ × Γ)) :=
  gs.toFinset.offDiag
noncomputable def chainWeight (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) (S : Set Γ) : ℝ :=
  chainMultiplier gs w S * f S
noncomputable def chainTotal (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) : ℝ :=
  ∑ S ∈ chainStates gs, chainWeight gs w f S
noncomputable def plainMean (gs : List (Γ × Γ)) (f H : Set Γ → ℝ) : ℝ :=
  slotTotal gs (fun S => f S*H S) ∅/slotTotal gs f ∅
noncomputable def defectMean (gs : List (Γ × Γ)) (f H : Set Γ → ℝ) (p r : Γ × Γ) : ℝ :=
  slotDefect gs (fun S => f S*H S) ∅ p r/slotDefect gs f ∅ p r
noncomputable def observableProjection (gs : List (Γ × Γ)) (f H : Set Γ → ℝ) (S : Set Γ) : ℝ :=
  if h : ∃ p r, DefectState gs p r S then defectMean gs f H h.choose h.choose_spec.choose else H S

omit sectionInstance52 in
lemma mem_defectTypes [Fintype Γ] (gs : List (Γ × Γ)) (pr : (Γ × Γ) × (Γ × Γ)) :
    pr ∈ defectTypes gs ↔ pr.1 ∈ gs ∧ pr.2 ∈ gs ∧ pr.1 ≠ pr.2 := by
  simp [defectTypes,Finset.mem_offDiag]

lemma chain_sum_decomposition (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (g : Set Γ → ℝ) :
    (∑ S ∈ chainStates gs, chainMultiplier gs w S*g S) =
      (∑ S ∈ Finset.univ.filter (PlainState gs),g S) +
      ∑ pr ∈ defectTypes gs, w pr.1 pr.2 *
        ∑ S ∈ Finset.univ.filter (DefectState gs pr.1 pr.2),g S := by
  simp only [chainStates,Finset.sum_filter,Finset.mul_sum]
  rw [Finset.sum_comm,← Finset.sum_add_distrib]
  apply Finset.sum_congr rfl
  intro S _
  by_cases hp : PlainState gs S
  · have hd : ∀ p r, ¬ DefectState gs p r S := fun p r hh => hh.not_plain hp
    simp [hp,ChainState,chainMultiplier_plain gs w S hp,hd]
  · by_cases hd : ∃ p r, DefectState gs p r S
    · obtain ⟨p,r,hpr⟩ := hd
      have hmem : (p,r) ∈ defectTypes gs := (mem_defectTypes gs _).mpr ⟨hpr.1,hpr.2.1,hpr.2.2.1⟩
      have hu : ∀ pr ∈ defectTypes gs, pr ≠ (p,r) → ¬ DefectState gs pr.1 pr.2 S := by
        intro pr _ hn hh
        have he := hh.unique hpr
        exact hn (Prod.ext he.1 he.2)
      rw [ite_eq_left (show ChainState gs S from Or.inr ⟨p,r,hpr⟩),ite_eq_right hp,zero_add,
        Finset.sum_eq_single (p,r)]
      · simp [hpr,chainMultiplier_defect gs w S hpr]
      · intro pr hm hn
        simp [hu pr hm hn]
      · exact fun hn => False.elim (hn hmem)
    · have hd' : ∀ p r, ¬ DefectState gs p r S := fun p r hh => hd ⟨p,r,hh⟩
      simp [ChainState,hp,hd']

lemma chainTotal_eq (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) :
    chainTotal gs w f = slotTotal gs f ∅ +
      ∑ pr ∈ defectTypes gs, w pr.1 pr.2*slotDefect gs f ∅ pr.1 pr.2 := by
  unfold chainTotal chainWeight
  rw [chain_sum_decomposition,← slotTotal_eq_plain_sum gs hgs hcover f]
  congr 1
  apply Finset.sum_congr rfl
  intro pr hm
  obtain ⟨hp,hr,hpr⟩ := (mem_defectTypes gs pr).mp hm
  rw [slotDefect_eq_defect_sum gs hgs hcover f pr.1 pr.2 hp hr hpr]

lemma chainTotal_ge_plain (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hf : ∀ S, 0 < f S) (hw : ∀ p r, 0 < w p r) :
    slotTotal gs f ∅ ≤ chainTotal gs w f := by
  rw [chainTotal_eq gs hgs hcover w f]
  apply le_add_of_nonneg_right
  apply Finset.sum_nonneg
  intro pr hm
  obtain ⟨hp,hr,hpr⟩ := (mem_defectTypes gs pr).mp hm
  exact mul_nonneg (hw _ _).le (slotDefect_pos gs f hf ∅ hgs pr.1 pr.2 hp hr hpr).le

lemma chainTotal_pos (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hf : ∀ S, 0 < f S) (hw : ∀ p r, 0 < w p r) :
    0 < chainTotal gs w f :=
  (slotTotal_pos gs f hf ∅ hgs).trans_le (chainTotal_ge_plain gs hgs hcover w f hf hw)

noncomputable def chainMean (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f H : Set Γ → ℝ) : ℝ :=
  (∑ S ∈ chainStates gs,chainWeight gs w f S*H S)/chainTotal gs w f
noncomputable def chainVariance (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f H : Set Γ → ℝ) : ℝ :=
  (∑ S ∈ chainStates gs,chainWeight gs w f S*(H S-chainMean gs w f H)^2)/chainTotal gs w f
noncomputable def chainDirichlet (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f H : Set Γ → ℝ) : ℝ :=
  restrictedEnergy (chainEdges gs) (chainConductance gs w f) H ∅ ∅/
    (2*(gs.length:ℝ)^2*chainTotal gs w f)

end Observable

section ChainGeometry
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance54 : Fintype Γ] [sectionInstance55 : DecidableEq Γ]

lemma pairSupport_ncard (ps : List (Γ × Γ)) (A : Set Γ) (hps : SlotsFresh ps A) :
    (A ∪ pairSupport ps).ncard = A.ncard+2*ps.length := by
  induction ps generalizing A with
  | nil => simp
  | cons p ps ih =>
    have he : A ∪ pairSupport (p::ps) = (insert p.1 (insert p.2 A)) ∪ pairSupport ps := by
      rw [pairSupport_cons]
      ext x
      simp only [Set.mem_union,Set.mem_insert_iff]
      tauto
    rw [he,ih _ hps.2.2.2,Set.ncard_insert_of_notMem (by
      simpa only [Set.mem_insert_iff,not_or] using And.intro hps.1 hps.2.1),
      Set.ncard_insert_of_notMem hps.2.2.1,List.length_cons]
    omega

lemma slots_ground_card (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) : Fintype.card Γ = 2*gs.length := by
  have h := pairSupport_ncard gs ∅ hgs
  simpa [hcover,Set.ncard_univ,Nat.card_eq_fintype_card] using h

lemma choiceVertices_ncard (ps : List (Γ × Γ)) (A S : Set Γ) (hps : SlotsFresh ps A)
    (hS : S ∈ choiceVertices ps A) : S.ncard = A.ncard+ps.length := by
  induction ps generalizing A with
  | nil =>
    simp only [choiceVertices,Finset.mem_singleton] at hS
    subst S
    simp
  | cons p ps ih =>
    rw [choiceVertices,Finset.mem_union] at hS
    rcases hS with hS | hS
    · have ht : SlotsFresh ps (insert p.1 A) := hps.2.2.2.mono (by
        intro x hx
        simp only [Set.mem_insert_iff] at hx ⊢
        tauto)
      rw [ih _ ht hS,Set.ncard_insert_of_notMem hps.2.1,List.length_cons]
      omega
    · have ht : SlotsFresh ps (insert p.2 A) := hps.2.2.2.mono (Set.subset_insert _ _)
      rw [ih _ ht hS,Set.ncard_insert_of_notMem hps.2.2.1,List.length_cons]
      omega

lemma PlainState.ncard {gs : List (Γ × Γ)} (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) {S : Set Γ} (hS : PlainState gs S) : S.ncard = gs.length := by
  have hm : S ∈ choiceVertices gs ∅ := by rw [choiceVertices_plain gs hgs hcover]; simp [hS]
  simpa using choiceVertices_ncard gs ∅ S hgs hm

lemma DefectState.ncard {gs : List (Γ × Γ)} (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) {p r : Γ × Γ} {S : Set Γ} (hS : DefectState gs p r S) :
    S.ncard = gs.length := by
  let ps := (gs.erase p).erase r
  have hperm : gs.Perm (p::r::ps) := slots_perm_two hS.1 hS.2.1 hS.2.2.1
  have hs := (SlotsFresh.perm hperm ∅).mp hgs
  have ht : SlotsFresh ps {r.1,r.2} := hs.2.2.2.2.2.2.mono (by
    intro x hx
    simp only [Set.mem_insert_iff,Set.mem_singleton_iff,Set.mem_empty_iff_false,or_false] at hx ⊢
    tauto)
  have hm : S ∈ choiceVertices ps {r.1,r.2} := by
    rw [choiceVertices_defect gs hgs hcover p r hS.1 hS.2.1 hS.2.2.1]
    simp [hS]
  have hc := choiceVertices_ncard ps {r.1,r.2} S ht hm
  have hpair : ({r.1,r.2} : Set Γ).ncard = 2 := Set.ncard_pair hs.2.2.2.1
  rw [hpair] at hc
  have hl := hperm.length_eq
  simp only [List.length_cons] at hl
  omega

lemma ChainState.ncard {gs : List (Γ × Γ)} (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) {S : Set Γ} (hS : ChainState gs S) : S.ncard = gs.length := by
  rcases hS with hS | ⟨p,r,hS⟩
  · exact hS.ncard hgs hcover
  · exact hS.ncard hgs hcover

omit sectionInstance54 sectionInstance55 in
lemma ExchangeAdjacent.symm [Fintype Γ] [DecidableEq Γ] {S T : Set Γ} (h : ExchangeAdjacent S T) : ExchangeAdjacent T S := by
  obtain ⟨A,a,b,hab,ha,hb,hS,hT⟩ := h
  exact ⟨A,b,a,hab.symm,hb,ha,hT,hS⟩
omit sectionInstance54 sectionInstance55 in
lemma ExchangeAdjacent.ne [Fintype Γ] [DecidableEq Γ] {S T : Set Γ} (h : ExchangeAdjacent S T) : S ≠ T := by
  obtain ⟨A,a,b,hab,ha,hb,rfl,rfl⟩ := h
  intro he
  have hh : a ∈ insert b A := he ▸ Set.mem_insert a A
  exact (Set.mem_insert_iff.mp hh).elim hab ha

omit sectionInstance54 sectionInstance55 in
lemma ExchangeAdjacent.move [Fintype Γ] [DecidableEq Γ] {S T : Set Γ} (h : ExchangeAdjacent S T) :
    ∃ a ∈ S, ∃ b ∈ Sᶜ, T = insert b (S\{a}) := by
  obtain ⟨A,a,b,hab,ha,hb,rfl,rfl⟩ := h
  refine ⟨a,Set.mem_insert _ _,b,?_,?_⟩
  · simpa only [Set.mem_compl_iff,Set.mem_insert_iff,not_or] using And.intro hab.symm hb
  · congr 1
    ext x
    simp only [Set.mem_sdiff,Set.mem_insert_iff,Set.mem_singleton_iff]
    constructor
    · intro hx
      exact ⟨Or.inr hx,fun he => ha (he ▸ hx)⟩
    · rintro ⟨hh,hn⟩
      exact hh.resolve_left hn

lemma exchange_neighbors_card (S : Set Γ) :
    (Finset.univ.filter (ExchangeAdjacent S)).card ≤ S.ncard*Sᶜ.ncard := by
  have hsub : Finset.univ.filter (ExchangeAdjacent S) ⊆
      (S.toFinset ×ˢ Sᶜ.toFinset).image (fun ab => insert ab.2 (S\{ab.1})) := by
    intro T hT
    obtain ⟨a,ha,b,hb,rfl⟩ := (Finset.mem_filter.mp hT).2.move
    exact Finset.mem_image.mpr ⟨(a,b),by simpa only [Finset.mem_product,Set.mem_toFinset] using And.intro ha hb,rfl⟩
  calc
    _ ≤ ((S.toFinset ×ˢ Sᶜ.toFinset).image (fun ab => insert ab.2 (S\{ab.1}))).card := Finset.card_le_card hsub
    _ ≤ (S.toFinset ×ˢ Sᶜ.toFinset).card := Finset.card_image_le
    _ = _ := by rw [Finset.card_product,← Set.ncard_eq_toFinset_card',← Set.ncard_eq_toFinset_card']

abbrev ChainIndex (gs : List (Γ × Γ)) := {S : Set Γ // ChainState gs S}

lemma chain_neighbors_card (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) (S : ChainIndex gs) :
    (Finset.univ.filter (fun T : ChainIndex gs => ExchangeAdjacent S.val T.val)).card ≤ gs.length^2 := by
  let e : ChainIndex gs → Set Γ := Subtype.val
  have he : Function.Injective e := Subtype.val_injective
  have hsub : (Finset.univ.filter (fun T : ChainIndex gs => ExchangeAdjacent S.val T.val)).image e ⊆
      Finset.univ.filter (ExchangeAdjacent S.val) := by
    intro T hT
    obtain ⟨U,hU,hUT⟩ := Finset.mem_image.mp hT
    subst T
    exact Finset.mem_filter.mpr ⟨Finset.mem_univ _,(Finset.mem_filter.mp hU).2⟩
  have hcompl : S.valᶜ.ncard = gs.length := by
    have hc := S.val.ncard_add_ncard_compl
    rw [Nat.card_eq_fintype_card,slots_ground_card gs hgs hcover,S.property.ncard hgs hcover] at hc
    omega
  calc
    _ = ((Finset.univ.filter (fun T : ChainIndex gs => ExchangeAdjacent S.val T.val)).image e).card :=
      (Finset.card_image_of_injective _ he).symm
    _ ≤ (Finset.univ.filter (ExchangeAdjacent S.val)).card := Finset.card_le_card hsub
    _ ≤ S.val.ncard*S.valᶜ.ncard := exchange_neighbors_card _
    _ = gs.length^2 := by rw [S.property.ncard hgs hcover,hcompl,pow_two]

end ChainGeometry

open scoped BigOperators Classical

structure FiniteChain (X : Type*) [Fintype X] where
  P : X → X → ℝ
  π : X → ℝ
  positive : ∀ x, 0 < π x
  normalized : ∑ x, π x = 1
  nonnegative : ∀ x y, 0 ≤ P x y
  stochastic : ∀ x, ∑ y, P x y = 1
  reversible : ∀ x y, π x*P x y = π y*P y x
  lazy : ∀ x, 1/2 ≤ P x x

namespace FiniteChain
variable {X : Type*} [sectionInstance56 : Fintype X] (C : FiniteChain X)

noncomputable def inner (f g : X → ℝ) : ℝ := ∑ x, C.π x*f x*g x
noncomputable def apply (f : X → ℝ) (x : X) : ℝ := ∑ y, C.P x y*f y
noncomputable def energy (f : X → ℝ) : ℝ := C.inner f (fun x => f x-C.apply f x)
noncomputable def iterate (k : ℕ) (f : X → ℝ) : X → ℝ := Nat.iterate C.apply k f

lemma inner_symm (f g : X → ℝ) : C.inner f g = C.inner g f := by
  unfold inner
  apply Finset.sum_congr rfl
  intro x _
  ring
lemma inner_sub_right (f g h : X → ℝ) : C.inner f (fun x => g x-h x) = C.inner f g-C.inner f h := by
  simp [inner,mul_sub,Finset.sum_sub_distrib]
lemma apply_selfAdjoint (f g : X → ℝ) : C.inner f (C.apply g) = C.inner (C.apply f) g := by
  unfold inner apply
  simp only [Finset.mul_sum,Finset.sum_mul]
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro y _
  apply Finset.sum_congr rfl
  intro x _
  have h := C.reversible x y
  calc
    _ = (C.π x*C.P x y)*(f x*g y) := by ring
    _ = (C.π y*C.P y x)*(f x*g y) := by rw [h]
    _ = _ := by ring

@[simp] lemma iterate_zero (f : X → ℝ) : C.iterate 0 f = f := rfl

noncomputable def partialResolvent (N : ℕ) (f : X → ℝ) (x : X) : ℝ :=
  ∑ k ∈ Finset.range N,C.iterate k f x

noncomputable def pathExpectation : ℕ → X → (List X → ℝ) → ℝ
  | 0,x,F => F [x]
  | N+1,x,F => ∑ y,C.P x y*pathExpectation N y (fun l => F (x::l))

noncomputable def pathSum (g : X → ℝ) (l : List X) : ℝ := (l.map g).sum
noncomputable def squareMoment (g : X → ℝ) (N : ℕ) (x : X) : ℝ :=
  if N=0 then 0 else C.pathExpectation (N-1) x (fun l => (pathSum g l)^2)

variable [DecidableEq X]

noncomputable def ofMoves (π : X → ℝ) (m : X → X → ℝ)
    (hπ : ∀ x,0 < π x) (hπsum : ∑ x,π x = 1)
    (hm : ∀ x y,0 ≤ m x y) (hrow : ∀ x,∑ y,m x y ≤ 1/2)
    (hrev : ∀ x y,π x*m x y = π y*m y x) : FiniteChain X where
  P x y := m x y+if x=y then 1-∑ z,m x z else 0
  π := π
  positive := hπ
  normalized := hπsum
  nonnegative x y := by
    split_ifs
    · linarith [hm x y,hrow x]
    · simpa using hm x y
  stochastic x := by simp [Finset.sum_add_distrib]
  reversible x y := by
    by_cases h : x=y
    · subst y; rfl
    · simp [h,Ne.symm h,hrev]
  lazy x := by simp only [ite_true]; linarith [hm x x,hrow x]

end FiniteChain
section Metropolis
open scoped BigOperators Classical
variable {Γ : Type*} [Fintype Γ] [sectionInstance59 : DecidableEq Γ]

lemma chainWeight_pos (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S) (S : Set Γ) :
    0 < chainWeight gs w f S := mul_pos (chainMultiplier_pos gs w hw S) (hf S)

omit sectionInstance59 in
lemma chain_sum_subtype [DecidableEq Γ] (gs : List (Γ × Γ)) (g : Set Γ → ℝ) :
    (∑ S ∈ chainStates gs,g S) = ∑ S : ChainIndex gs,g S.val := by
  exact Finset.sum_subtype _ (by intro S; simp [chainStates]) g

noncomputable def metropolisMove (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) (S T : ChainIndex gs) : ℝ :=
  if ExchangeAdjacent S.val T.val then
    min (chainWeight gs w f S.val) (chainWeight gs w f T.val)/
      (2*(gs.length:ℝ)^2*chainWeight gs w f S.val) else 0

lemma metropolisMove_nonneg (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ)
    (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S) (S T : ChainIndex gs) :
    0 ≤ metropolisMove gs w f S T := by
  have hS := chainWeight_pos gs w f hw hf S.val
  unfold metropolisMove
  split_ifs
  · exact div_nonneg (le_min (chainWeight_pos gs w f hw hf _).le (chainWeight_pos gs w f hw hf _).le)
      (by positivity)
  · rfl

lemma metropolisMove_row (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (hne : gs ≠ [])
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S) (S : ChainIndex gs) :
    ∑ T,metropolisMove gs w f S T ≤ 1/2 := by
  have hS := chainWeight_pos gs w f hw hf S.val
  have hn : (0:ℝ) < gs.length := by exact_mod_cast List.length_pos_iff.mpr hne
  have hinc (T : ChainIndex gs) : metropolisMove gs w f S T ≤
      if ExchangeAdjacent S.val T.val then 1/(2*(gs.length:ℝ)^2) else 0 := by
    unfold metropolisMove
    split_ifs
    · apply (div_le_iff₀ (by positivity : 0 < 2*(gs.length:ℝ)^2*chainWeight gs w f S.val)).mpr
      have he : 1/(2*(gs.length:ℝ)^2)*(2*(gs.length:ℝ)^2*chainWeight gs w f S.val) = chainWeight gs w f S.val := by
        field_simp
      rw [he]
      exact min_le_left _ _
    · rfl
  have hs := Finset.sum_le_sum (fun T (_ : T ∈ Finset.univ) => hinc T)
  have hc : ((Finset.univ.filter (fun T : ChainIndex gs => ExchangeAdjacent S.val T.val)).card:ℝ) ≤ (gs.length:ℝ)^2 := by
    exact_mod_cast chain_neighbors_card gs hgs hcover S
  have he : (gs.length:ℝ)^2*(1/(2*(gs.length:ℝ)^2)) = 1/2 := by field_simp
  calc
    _ ≤ _ := hs
    _ = ((Finset.univ.filter (fun T : ChainIndex gs => ExchangeAdjacent S.val T.val)).card:ℝ)*(1/(2*(gs.length:ℝ)^2)) := by
      rw [← Finset.sum_filter]; simp
    _ ≤ (gs.length:ℝ)^2*(1/(2*(gs.length:ℝ)^2)) := mul_le_mul_of_nonneg_right hc (by positivity)
    _ = _ := he

lemma metropolisMove_balanced (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S) (S T : ChainIndex gs) :
    (chainWeight gs w f S.val/chainTotal gs w f)*metropolisMove gs w f S T =
    (chainWeight gs w f T.val/chainTotal gs w f)*metropolisMove gs w f T S := by
  have hs := (chainWeight_pos gs w f hw hf S.val).ne'
  have ht := (chainWeight_pos gs w f hw hf T.val).ne'
  by_cases h : ExchangeAdjacent S.val T.val
  · simp only [metropolisMove,ite_eq_left h,ite_eq_left h.symm]
    rw [min_comm (chainWeight gs w f T.val)]
    field_simp
  · have hh : ¬ ExchangeAdjacent T.val S.val := fun hh => h hh.symm
    simp [metropolisMove,h,hh]

noncomputable def metropolisChain (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (hne : gs ≠ [])
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S) : FiniteChain (ChainIndex gs) :=
  FiniteChain.ofMoves (fun S => chainWeight gs w f S.val/chainTotal gs w f) (metropolisMove gs w f)
    (fun S => div_pos (chainWeight_pos gs w f hw hf S.val) (chainTotal_pos gs hgs hcover w f hf hw))
    (by rw [← Finset.sum_div,← chain_sum_subtype]; exact div_self (chainTotal_pos gs hgs hcover w f hf hw).ne')
    (metropolisMove_nonneg gs w f hw hf)
    (metropolisMove_row gs hgs hne hcover w f hw hf)
    (metropolisMove_balanced gs w f hw hf)

end Metropolis

namespace FiniteChain
variable {X : Type*} [Fintype X] (C : FiniteChain X)

noncomputable def mean (f : X → ℝ) : ℝ := ∑ x,C.π x*f x
noncomputable def variance (f : X → ℝ) : ℝ := C.inner (fun x => f x-C.mean f) (fun x => f x-C.mean f)

end FiniteChain
section ProjectionBridge
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance61 : Fintype Γ] [sectionInstance62 : DecidableEq Γ]

def ClassObservable (gs : List (Γ × Γ)) (G : Set Γ → ℝ) : Prop :=
  ∀ p r S T, DefectState gs p r S → DefectState gs p r T → G S = G T

noncomputable def extendChainFun (gs : List (Γ × Γ)) (H : ChainIndex gs → ℝ) (S : Set Γ) : ℝ :=
  if h : ChainState gs S then H ⟨S,h⟩ else 0

end ProjectionBridge

structure KilledKernel (B : Type*) [Fintype B] where
  Q : B → B → ℝ
  r : ℝ
  nonnegative : ∀ x y,0 ≤ Q x y
  rnonneg : 0 ≤ r
  rlt : r < 1
  rowbound : ∀ x,∑ y,Q x y ≤ r

namespace KilledKernel
open scoped BigOperators Classical
open Filter
variable {B : Type*} [Fintype B] (Q : KilledKernel B)
noncomputable def apply (f : B → ℝ) (x : B) : ℝ := ∑ y,Q.Q x y*f y
noncomputable def iterate (k : ℕ) (f : B → ℝ) : B → ℝ := Nat.iterate Q.apply k f
@[simp] lemma iterate_zero (f : B → ℝ) : Q.iterate 0 f = f := rfl
lemma iterate_succ (k : ℕ) (f : B → ℝ) : Q.iterate (k+1) f = Q.apply (Q.iterate k f) :=
  Function.iterate_succ_apply' Q.apply k f
lemma iterate_succ' (k : ℕ) (f : B → ℝ) : Q.iterate (k+1) f = Q.iterate k (Q.apply f) :=
  Function.iterate_succ_apply Q.apply k f
lemma apply_sub (f g : B → ℝ) : Q.apply (fun x => f x-g x) = fun x => Q.apply f x-Q.apply g x := by
  funext x
  simp [apply,mul_sub,Finset.sum_sub_distrib]
lemma apply_sum {J : Type*} (s : Finset J) (f : J → B → ℝ) :
    Q.apply (fun x => ∑ j ∈ s,f j x) = fun x => ∑ j ∈ s,Q.apply (f j) x := by
  funext x
  simp only [apply,Finset.mul_sum]
  exact Finset.sum_comm
lemma apply_nonneg (f : B → ℝ) (hf : ∀ x,0 ≤ f x) (x : B) : 0 ≤ Q.apply f x :=
  Finset.sum_nonneg (fun y _ => mul_nonneg (Q.nonnegative x y) (hf y))
lemma iterate_nonneg (k : ℕ) (f : B → ℝ) (hf : ∀ x,0 ≤ f x) (x : B) : 0 ≤ Q.iterate k f x := by
  induction k generalizing x with
  | zero => exact hf x
  | succ k ih =>
    rw [Q.iterate_succ]
    exact Q.apply_nonneg _ ih x

lemma apply_abs_bound (f : B → ℝ) (M : ℝ) (hM : 0 ≤ M) (hf : ∀ x,|f x| ≤ M) (x : B) :
    |Q.apply f x| ≤ Q.r*M := by
  calc
    _ ≤ ∑ y,|Q.Q x y*f y| := Finset.abs_sum_le_sum_abs _ _
    _ = ∑ y,Q.Q x y*|f y| := by simp_rw [abs_mul,abs_of_nonneg (Q.nonnegative x _) ]
    _ ≤ ∑ y,Q.Q x y*M := Finset.sum_le_sum (fun y _ => mul_le_mul_of_nonneg_left (hf y) (Q.nonnegative x y))
    _ = (∑ y,Q.Q x y)*M := (Finset.sum_mul _ _ _).symm
    _ ≤ Q.r*M := mul_le_mul_of_nonneg_right (Q.rowbound x) hM

lemma iterate_abs_bound (k : ℕ) (f : B → ℝ) (M : ℝ) (hM : 0 ≤ M) (hf : ∀ x,|f x| ≤ M) (x : B) :
    |Q.iterate k f x| ≤ Q.r^k*M := by
  induction k generalizing x with
  | zero => simpa using hf x
  | succ k ih =>
    rw [Q.iterate_succ,pow_succ']
    simpa only [mul_assoc] using Q.apply_abs_bound (Q.iterate k f) (Q.r^k*M)
      (mul_nonneg (pow_nonneg Q.rnonneg _) hM) ih x

lemma summable_iterate (f : B → ℝ) (x : B) : Summable (fun k => Q.iterate k f x) := by
  let M := ∑ y,|f y|
  have hM : 0 ≤ M := Finset.sum_nonneg (fun y _ => abs_nonneg _)
  have hf : ∀ y,|f y| ≤ M := fun y => Finset.single_le_sum (f := fun z => |f z|) (fun z _ => abs_nonneg _) (Finset.mem_univ y)
  exact (Summable.of_nonneg_of_le (fun k => abs_nonneg _) (fun k => Q.iterate_abs_bound k f M hM hf x)
    ((summable_geometric_of_lt_one Q.rnonneg Q.rlt).mul_right M)).of_abs

lemma iterate_tendsto_zero (f : B → ℝ) (x : B) : Tendsto (fun k => Q.iterate k f x) atTop (nhds 0) := by
  let M := ∑ y,|f y|
  have hM : 0 ≤ M := Finset.sum_nonneg (fun y _ => abs_nonneg _)
  have hf : ∀ y,|f y| ≤ M := fun y => Finset.single_le_sum (f := fun z => |f z|) (fun z _ => abs_nonneg _) (Finset.mem_univ y)
  have ht : Tendsto (fun k => Q.r^k*M) atTop (nhds 0) := by
    simpa using (tendsto_pow_atTop_nhds_zero_of_lt_one Q.rnonneg Q.rlt).mul_const M
  exact (tendsto_zero_iff_abs_tendsto_zero _).mpr (squeeze_zero (fun k => abs_nonneg _)
    (fun k => Q.iterate_abs_bound k f M hM hf x) ht)

noncomputable def green (f : B → ℝ) (x : B) : ℝ := ∑' k,Q.iterate k f x
lemma green_nonneg (f : B → ℝ) (hf : ∀ x,0 ≤ f x) (x : B) : 0 ≤ Q.green f x :=
  tsum_nonneg (fun k => Q.iterate_nonneg k f hf x)

lemma green_firstStep (f : B → ℝ) (x : B) : Q.green f x = f x+Q.apply (Q.green f) x := by
  unfold green
  rw [(Q.summable_iterate f x).tsum_eq_zero_add]
  simp only [Q.iterate_zero,Q.iterate_succ,apply]
  congr 1
  rw [Summable.tsum_finsetSum (fun y (_ : y ∈ Finset.univ) => (Q.summable_iterate f y).mul_left (Q.Q x y))]
  simp_rw [tsum_mul_left]

lemma iterate_sub (k : ℕ) (f g : B → ℝ) :
    Q.iterate k (fun x => f x-g x) = fun x => Q.iterate k f x-Q.iterate k g x := by
  induction k with
  | zero => rfl
  | succ k ih => rw [Q.iterate_succ,ih,Q.apply_sub,← Q.iterate_succ,← Q.iterate_succ]
lemma iterate_sum {J : Type*} (s : Finset J) (f : J → B → ℝ) (k : ℕ) :
    Q.iterate k (fun x => ∑ j ∈ s,f j x) = fun x => ∑ j ∈ s,Q.iterate k (f j) x := by
  induction k with
  | zero => rfl
  | succ k ih => rw [Q.iterate_succ,ih,Q.apply_sum]; simp_rw [← Q.iterate_succ]

lemma green_sum {J : Type*} (s : Finset J) (f : J → B → ℝ) (x : B) :
    Q.green (fun x => ∑ j ∈ s,f j x) x = ∑ j ∈ s,Q.green (f j) x := by
  unfold green
  simp_rw [Q.iterate_sum]
  exact Summable.tsum_finsetSum (fun j _ => Q.summable_iterate (f j) x)

lemma iterate_residual_sum (f : B → ℝ) (N : ℕ) (x : B) :
    (∑ k ∈ Finset.range N,Q.iterate k (fun y => f y-Q.apply f y) x) = f x-Q.iterate N f x := by
  induction N with
  | zero => simp
  | succ N ih =>
    rw [Finset.sum_range_succ,ih,Q.iterate_sub,← Q.iterate_succ']
    simp only
    ring

lemma green_residual (f : B → ℝ) (x : B) : Q.green (fun y => f y-Q.apply f y) x = f x := by
  have hs := (Q.summable_iterate (fun y => f y-Q.apply f y) x).tendsto_sum_tsum_nat
  simp_rw [Q.iterate_residual_sum] at hs
  have ht : Tendsto (fun N => f x-Q.iterate N f x) atTop (nhds (f x)) := by
    simpa using tendsto_const_nhds.sub (Q.iterate_tendsto_zero f x)
  exact tendsto_nhds_unique hs ht

end KilledKernel
namespace FiniteChain

section
open scoped BigOperators Classical
variable {X : Type*} [sectionInstance66 : Fintype X] (C : FiniteChain X) (A : X → Prop)

abbrev Inside := {x : X // A x}
abbrev Outside := {x : X // ¬ A x}

noncomputable def mergeFun (h : Inside A → ℝ) (b : Outside A → ℝ) (x : X) : ℝ :=
  if hx : A x then h ⟨x,hx⟩ else b ⟨x,hx⟩
omit sectionInstance66 in
@[simp] lemma mergeFun_inside [Fintype X] (h : Inside A → ℝ) (b : Outside A → ℝ) (x : Inside A) :
    mergeFun A h b x.val = h x := by simp [mergeFun,x.property]
omit sectionInstance66 in
@[simp] lemma mergeFun_outside [Fintype X] (h : Inside A → ℝ) (b : Outside A → ℝ) (x : Outside A) :
    mergeFun A h b x.val = b x := by simp [mergeFun,x.property]

variable (Q : KilledKernel (Outside A)) (hQ : ∀ x y : Outside A,Q.Q x y = C.P x.val y.val)

noncomputable def hitKernel (x : Outside A) (y : Inside A) : ℝ :=
  Q.green (fun z => C.P z.val y.val) x
lemma hitKernel_nonneg (x : Outside A) (y : Inside A) : 0 ≤ C.hitKernel A Q x y :=
  Q.green_nonneg _ (fun z => C.nonnegative z.val y.val) x

include hQ in
lemma exitRow (x : Outside A) : (∑ y : Inside A,C.P x.val y.val) = 1-Q.apply (fun _ => 1) x := by
  have hh := Fintype.sum_subtype_add_sum_subtype A (fun y => C.P x.val y)
  rw [C.stochastic] at hh
  have he : Q.apply (fun _ => 1) x = ∑ y : Outside A,C.P x.val y.val := by
    simp [KilledKernel.apply,hQ]
  rw [he]
  linarith

include hQ in
lemma hitKernel_row (x : Outside A) : ∑ y : Inside A,C.hitKernel A Q x y = 1 := by
  unfold hitKernel
  rw [← Q.green_sum Finset.univ]
  have he : (fun z : Outside A => ∑ y : Inside A,C.P z.val y.val) =
      fun z => 1-Q.apply (fun _ => 1) z := by
    funext z
    exact C.exitRow A Q hQ z
  rw [he,Q.green_residual]

include hQ in
lemma hitKernel_firstStep (x : Outside A) (y : Inside A) :
    C.hitKernel A Q x y = C.P x.val y.val+∑ z : Outside A,C.P x.val z.val*C.hitKernel A Q z y := by
  unfold hitKernel
  rw [Q.green_firstStep]
  simp only [KilledKernel.apply,hQ]

noncomputable def traceMatrix (x y : Inside A) : ℝ :=
  C.P x.val y.val+∑ z : Outside A,C.P x.val z.val*C.hitKernel A Q z y
noncomputable def traceApply (h : Inside A → ℝ) (x : Inside A) : ℝ :=
  ∑ y,C.traceMatrix A Q x y*h y

lemma traceMatrix_nonneg (x y : Inside A) : 0 ≤ C.traceMatrix A Q x y :=
  add_nonneg (C.nonnegative _ _) (Finset.sum_nonneg (fun z _ => mul_nonneg
    (C.nonnegative _ _) (C.hitKernel_nonneg A Q z y)))
lemma traceMatrix_lazy (x : Inside A) : 1/2 ≤ C.traceMatrix A Q x x := by
  exact (C.lazy x.val).trans (le_add_of_nonneg_right (Finset.sum_nonneg (fun z _ =>
    mul_nonneg (C.nonnegative _ _) (C.hitKernel_nonneg A Q z x))))
include hQ in
lemma traceMatrix_stochastic (x : Inside A) : ∑ y,C.traceMatrix A Q x y = 1 := by
  unfold traceMatrix
  rw [Finset.sum_add_distrib,Finset.sum_comm]
  simp_rw [← Finset.mul_sum,C.hitKernel_row A Q hQ,mul_one]
  rw [Fintype.sum_subtype_add_sum_subtype A,C.stochastic]

noncomputable def offExtension (h : Inside A → ℝ) (x : Outside A) : ℝ :=
  ∑ y : Inside A,C.hitKernel A Q x y*h y
noncomputable def harmonicExtension (h : Inside A → ℝ) : X → ℝ := mergeFun A h (C.offExtension A Q h)
@[simp] lemma harmonicExtension_inside (h : Inside A → ℝ) (x : Inside A) :
    C.harmonicExtension A Q h x.val = h x := mergeFun_inside _ _ _ _
@[simp] lemma harmonicExtension_outside (h : Inside A → ℝ) (x : Outside A) :
    C.harmonicExtension A Q h x.val = C.offExtension A Q h x := mergeFun_outside _ _ _ _

include hQ in
lemma offExtension_firstStep (h : Inside A → ℝ) (x : Outside A) :
    C.offExtension A Q h x = (∑ y : Inside A,C.P x.val y.val*h y)+
      ∑ z : Outside A,C.P x.val z.val*C.offExtension A Q h z := by
  unfold offExtension
  calc
    _ = ∑ y : Inside A,(C.P x.val y.val+∑ z : Outside A,C.P x.val z.val*C.hitKernel A Q z y)*h y := by
      apply Finset.sum_congr rfl
      intro y _
      rw [C.hitKernel_firstStep A Q hQ]
    _ = _ := ?_
  simp only [add_mul,Finset.sum_mul,Finset.sum_add_distrib]
  congr 1
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro z _
  simp_rw [mul_assoc,← Finset.mul_sum]

include hQ in
lemma harmonicExtension_harmonic (h : Inside A → ℝ) (x : Outside A) :
    C.apply (C.harmonicExtension A Q h) x.val = C.harmonicExtension A Q h x.val := by
  unfold apply
  rw [← Fintype.sum_subtype_add_sum_subtype A]
  simp only [C.harmonicExtension_inside,C.harmonicExtension_outside]
  exact (C.offExtension_firstStep A Q hQ h x).symm

lemma apply_harmonicExtension_inside (h : Inside A → ℝ) (x : Inside A) :
    C.apply (C.harmonicExtension A Q h) x.val = C.traceApply A Q h x := by
  unfold apply
  rw [← Fintype.sum_subtype_add_sum_subtype A]
  simp only [C.harmonicExtension_inside,C.harmonicExtension_outside]
  unfold traceApply traceMatrix offExtension
  simp_rw [add_mul,Finset.sum_mul,Finset.sum_add_distrib,Finset.mul_sum]
  congr 1
  rw [Finset.sum_comm]
  apply Finset.sum_congr rfl
  intro y _
  apply Finset.sum_congr rfl
  intro z _
  ring

include hQ in
lemma harmonic_inner_residual (h g : Inside A → ℝ) :
    C.inner (C.harmonicExtension A Q h)
      (fun x => C.harmonicExtension A Q g x-C.apply (C.harmonicExtension A Q g) x) =
    ∑ x : Inside A,C.π x.val*h x*(g x-C.traceApply A Q g x) := by
  unfold inner
  rw [← Fintype.sum_subtype_add_sum_subtype A]
  simp only [C.harmonicExtension_inside,C.apply_harmonicExtension_inside,
    C.harmonicExtension_harmonic A Q hQ,sub_self,mul_zero,Finset.sum_const_zero,add_zero]

end
section
open scoped BigOperators Classical
variable {X : Type*} [Fintype X] (C : FiniteChain X) (A : X → Prop)
variable (Q : KilledKernel (Outside A)) (hQ : ∀ x y : Outside A,Q.Q x y = C.P x.val y.val)

lemma residual_symmetric (f g : X → ℝ) :
    C.inner f (fun x => g x-C.apply g x) = C.inner g (fun x => f x-C.apply f x) := by
  rw [C.inner_sub_right,C.inner_sub_right,C.apply_selfAdjoint,C.inner_symm f g,
    C.inner_symm (C.apply f) g]

include hQ in
lemma traceMatrix_reversible (x y : Inside A) :
    C.π x.val*C.traceMatrix A Q x y = C.π y.val*C.traceMatrix A Q y x := by
  by_cases hxy : x = y
  · subst y; rfl
  have h := C.residual_symmetric (C.harmonicExtension A Q (fun z => if z=x then 1 else 0))
    (C.harmonicExtension A Q (fun z => if z=y then 1 else 0))
  rw [C.harmonic_inner_residual A Q hQ,C.harmonic_inner_residual A Q hQ] at h
  simp [traceApply,hxy,Ne.symm hxy] at h
  linarith

noncomputable def insideMass : ℝ := ∑ x : Inside A,C.π x.val
lemma insideMass_pos [Nonempty (Inside A)] : 0 < C.insideMass A := by
  apply Finset.sum_pos
  · intro x _; exact C.positive _
  · exact Finset.univ_nonempty

noncomputable def traceChain [Nonempty (Inside A)] : FiniteChain (Inside A) where
  P := C.traceMatrix A Q
  π := fun x => C.π x.val/C.insideMass A
  positive := fun x => div_pos (C.positive _) (C.insideMass_pos A)
  normalized := by rw [← Finset.sum_div]; exact div_self (C.insideMass_pos A).ne'
  nonnegative := C.traceMatrix_nonneg A Q
  stochastic := C.traceMatrix_stochastic A Q hQ
  reversible := by
    intro x y
    rw [div_mul_eq_mul_div,div_mul_eq_mul_div,C.traceMatrix_reversible A Q hQ]
  lazy := C.traceMatrix_lazy A Q

noncomputable def duration (x : Outside A) : ℝ := Q.green (fun _ => 1) x
noncomputable def returnCost (x : Inside A) : ℝ :=
  1+∑ y : Outside A,C.P x.val y.val*duration A Q y
end
section
open scoped BigOperators Classical
variable {X : Type*} [Fintype X] (C : FiniteChain X) (A : X → Prop)
variable (hExit : ∀ x : Outside A,∃ y : Inside A,0 < C.P x.val y.val)
noncomputable def exitProbability (x : Outside A) : ℝ := ∑ y : Inside A,C.P x.val y.val
lemma exitProbability_nonneg (x : Outside A) : 0 ≤ C.exitProbability A x :=
  Finset.sum_nonneg (fun y _ => C.nonnegative x.val y.val)
include hExit in
lemma exitProbability_pos (x : Outside A) : 0 < C.exitProbability A x := by
  obtain ⟨y,hy⟩ := hExit x
  exact hy.trans_le (Finset.single_le_sum (fun z _ => C.nonnegative _ _) (Finset.mem_univ y))
lemma exitProbability_le_one (x : Outside A) : C.exitProbability A x ≤ 1 := by
  have h := Fintype.sum_subtype_add_sum_subtype A (fun y => C.P x.val y)
  rw [C.stochastic] at h
  have hn : 0 ≤ ∑ y : Outside A,C.P x.val y.val := Finset.sum_nonneg (fun y _ => C.nonnegative _ _)
  change (∑ y : Inside A,C.P x.val y.val) ≤ 1
  linarith
noncomputable def killedChain : KilledKernel (Outside A) where
  Q := fun x y => C.P x.val y.val
  r := 1-∏ x : Outside A,C.exitProbability A x
  nonnegative := fun x y => C.nonnegative _ _
  rnonneg := sub_nonneg.mpr (Finset.prod_le_one (fun x _ => C.exitProbability_nonneg A x)
    (fun x _ => C.exitProbability_le_one A x))
  rlt := by
    have h : 0 < ∏ x : Outside A,C.exitProbability A x := Finset.prod_pos (fun x _ => C.exitProbability_pos A hExit x)
    linarith
  rowbound := by
    intro x
    have hprod : (∏ z : Outside A,C.exitProbability A z) ≤ C.exitProbability A x := by
      have h := Finset.prod_le_prod_of_subset_of_le_one (Finset.singleton_subset_iff.mpr (Finset.mem_univ x))
        (fun z (_ : z ∈ Finset.univ) => C.exitProbability_nonneg A z)
        (fun z _ _ => C.exitProbability_le_one A z)
      simpa only [Finset.prod_singleton] using h
    have hs := Fintype.sum_subtype_add_sum_subtype A (fun y => C.P x.val y)
    rw [C.stochastic] at hs
    change (∑ y : Inside A,C.P x.val y.val)+(∑ y : Outside A,C.P x.val y.val) = 1 at hs
    unfold exitProbability at hprod ⊢
    linarith
end
open scoped Classical BigOperators
variable {X : Type*} [Fintype X] (C : FiniteChain X)

noncomputable def pointDensity (x : X) (y : X) : ℝ := if y=x then 1/C.π x else 0
noncomputable def centeredPoint (x : X) (y : X) : ℝ := C.pointDensity x y-1
end FiniteChain
section TraceGeometry
open scoped Classical BigOperators
variable {Γ : Type*} [Fintype Γ] [sectionInstance71 : DecidableEq Γ]

lemma SlotsFresh.cross_ne {gs : List (Γ × Γ)} {A : Set Γ} (hgs : SlotsFresh gs A)
    {p r : Γ × Γ} (hp : p ∈ gs) (hr : r ∈ gs) (hpr : p ≠ r)
    {x y : Γ} (hx : x=p.1 ∨ x=p.2) (hy : y=r.1 ∨ y=r.2) : x ≠ y := by
  intro hxy
  have := hgs.pair_contains_unique hp hr hx (hxy ▸ hy)
  exact hpr this

lemma DefectState.plain_neighbor {gs : List (Γ × Γ)} (hgs : SlotsFresh gs ∅)
    {p r : Γ × Γ} {S : Set Γ} (hS : DefectState gs p r S) :
    ∃ T,PlainState gs T ∧ ExchangeAdjacent S T := by
  obtain ⟨hp,hr,hpr,he,hf,ht⟩ := hS
  let T := insert p.1 (S\{r.1})
  have hpne : p.1 ≠ p.2 := ((SlotsFresh.perm (List.perm_cons_erase hp) ∅).mp hgs).1
  have hrne : r.1 ≠ r.2 := ((SlotsFresh.perm (List.perm_cons_erase hr) ∅).mp hgs).1
  have hpr11 : p.1 ≠ r.1 := hgs.cross_ne hp hr hpr (Or.inl rfl) (Or.inl rfl)
  have hpr21 : p.2 ≠ r.1 := hgs.cross_ne hp hr hpr (Or.inr rfl) (Or.inl rfl)
  have hpr12 : p.1 ≠ r.2 := hgs.cross_ne hp hr hpr (Or.inl rfl) (Or.inr rfl)
  refine ⟨T,?_,?_⟩
  · intro t htm
    by_cases htp : t=p
    · subst t
      simp [PairSingle,T,hpne.symm,he.1,he.2]
    by_cases htr : t=r
    · subst t
      simp [PairSingle,T,hpr11.symm,hpr12.symm,hrne.symm,hf.2]
    have h₁ : t.1 ≠ p.1 := hgs.cross_ne htm hp htp (Or.inl rfl) (Or.inl rfl)
    have h₂ : t.2 ≠ p.1 := hgs.cross_ne htm hp htp (Or.inr rfl) (Or.inl rfl)
    have h₃ : t.1 ≠ r.1 := hgs.cross_ne htm hr htr (Or.inl rfl) (Or.inl rfl)
    have h₄ : t.2 ≠ r.1 := hgs.cross_ne htm hr htr (Or.inr rfl) (Or.inl rfl)
    simpa only [PairSingle,T,Set.mem_insert_iff,Set.mem_sdiff,Set.mem_singleton_iff,
      h₁,h₂,h₃,h₄,false_or,not_false_eq_true,and_true] using ht t htm htp htr
  · refine ⟨S\{r.1},r.1,p.1,hpr11.symm,by simp,?_,?_,rfl⟩
    · exact fun h => he.1 h.1
    · ext x
      simp only [Set.mem_insert_iff,Set.mem_sdiff,Set.mem_singleton_iff]
      constructor
      · intro hx
        by_cases h : x=r.1
        · exact Or.inl h
        · exact Or.inr ⟨hx,h⟩
      · rintro (rfl|⟨hx,_⟩)
        · exact hf.1
        · exact hx

abbrev PlainIndex (gs : List (Γ × Γ)) :=
  FiniteChain.Inside (fun S : ChainIndex gs => PlainState gs S.val)

omit sectionInstance71 in
lemma plainIndex_sum [DecidableEq Γ] (gs : List (Γ × Γ)) (g : Set Γ → ℝ) :
    (∑ S : PlainIndex gs,g S.val.val) = ∑ S ∈ Finset.univ.filter (PlainState gs),g S := by
  let e : PlainIndex gs ≃ {S : Set Γ // PlainState gs S} :=
    { toFun := fun S => ⟨S.val.val,S.property⟩
      invFun := fun S => ⟨⟨S.val,Or.inl S.property⟩,S.property⟩
      left_inv := by intro; rfl
      right_inv := by intro; rfl }
  calc
    _ = ∑ S : {S : Set Γ // PlainState gs S},g S.val :=
      Fintype.sum_equiv e _ _ (by intro S; rfl)
    _ = _ := (Finset.sum_subtype _ (by intro S; simp) g).symm

lemma plainIndex_nonempty (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs = Set.univ) : Nonempty (PlainIndex gs) := by
  have hz := slotTotal_pos gs (fun _ => (1:ℝ)) (by intro; norm_num) ∅ hgs
  rw [slotTotal_eq_plain_sum gs hgs hcover,← plainIndex_sum] at hz
  by_contra h
  have : IsEmpty (PlainIndex gs) := not_nonempty_iff.mp h
  simp at hz

lemma metropolis_plain_exit (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (hne : gs ≠ [])
    (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S)
    (S : FiniteChain.Outside (fun S : ChainIndex gs => PlainState gs S.val)) :
    ∃ T : PlainIndex gs,0 < (metropolisChain gs hgs hne hcover w f hw hf).P S.val T.val := by
  obtain ⟨p,r,hS⟩ := S.val.property.resolve_left S.property
  obtain ⟨T,hT,hST⟩ := hS.plain_neighbor hgs
  let t : PlainIndex gs := ⟨⟨T,Or.inl hT⟩,hT⟩
  refine ⟨t,?_⟩
  have hneST : S.val ≠ t.val := by intro h; exact hST.ne (congrArg Subtype.val h)
  change 0 < metropolisMove gs w f S.val t.val+if S.val=t.val then _ else _
  rw [ite_eq_right hneST,add_zero]
  unfold metropolisMove
  rw [ite_eq_left hST]
  have hn : (0:ℝ) < gs.length := by exact_mod_cast List.length_pos_iff.mpr hne
  exact div_pos (lt_min (chainWeight_pos gs w f hw hf _) (chainWeight_pos gs w f hw hf _))
    (mul_pos (mul_pos (by norm_num) (sq_pos_of_pos hn)) (chainWeight_pos gs w f hw hf _))
end TraceGeometry
section TracePoincare
open scoped Classical BigOperators
variable {Γ : Type*} [Fintype Γ] [DecidableEq Γ]
variable (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (hne : gs ≠ [])
  (hcover : pairSupport gs = Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
  (f : Set Γ → ℝ) (hw : ∀ p r,0 < w p r) (hf : ∀ S,0 < f S)

abbrev plainPredicate (gs : List (Γ × Γ)) (S : ChainIndex gs) := PlainState gs S.val
noncomputable def metropolisKilled : KilledKernel (FiniteChain.Outside (plainPredicate gs)) :=
  (metropolisChain gs hgs hne hcover w f hw hf).killedChain (plainPredicate gs)
    (metropolis_plain_exit gs hgs hne hcover w f hw hf)

noncomputable def metropolisTrace : FiniteChain (PlainIndex gs) := by
  letI := plainIndex_nonempty gs hgs hcover
  exact (metropolisChain gs hgs hne hcover w f hw hf).traceChain (plainPredicate gs)
    (metropolisKilled gs hgs hne hcover w f hw hf) (by intro _ _; rfl)

end TracePoincare

namespace KilledKernel
open scoped BigOperators Classical
variable {B : Type*} [Fintype B] (Q : KilledKernel B)
noncomputable def partialGreen (N : ℕ) (f : B → ℝ) (x : B) : ℝ :=
  ∑ k ∈ Finset.range N,Q.iterate k f x
lemma partialGreen_nonneg (N : ℕ) (f : B → ℝ) (hf : ∀ x,0 ≤ f x) (x : B) :
    0 ≤ Q.partialGreen N f x := Finset.sum_nonneg (fun k _ => Q.iterate_nonneg k f hf x)
lemma partialGreen_le (N : ℕ) (f : B → ℝ) (hf : ∀ x,0 ≤ f x) (x : B) :
    Q.partialGreen N f x ≤ Q.green f x :=
  (Q.summable_iterate f x).sum_le_tsum (Finset.range N) (fun k _ => Q.iterate_nonneg k f hf x)
end KilledKernel

namespace FiniteChain
open scoped BigOperators Classical
variable {X : Type*} [Fintype X] (C : FiniteChain X) (A : X → Prop)
  (Q : KilledKernel (Outside A)) (hQ : ∀ x y,Q.Q x y=C.P x.val y.val)

noncomputable def cappedTrace (N : ℕ) (x y : Inside A) : ℝ :=
  C.P x.val y.val + ∑ z : Outside A,C.P x.val z.val*
    Q.partialGreen N (fun t => C.P t.val y.val) z
lemma cappedTrace_nonneg (N : ℕ) (x y : Inside A) : 0 ≤ C.cappedTrace A Q N x y := by
  apply add_nonneg (C.nonnegative _ _)
  exact Finset.sum_nonneg (fun z _ => mul_nonneg (C.nonnegative _ _)
    (Q.partialGreen_nonneg N _ (fun t => C.nonnegative _ _) z))
lemma cappedTrace_le (N : ℕ) (x y : Inside A) : C.cappedTrace A Q N x y ≤ C.traceMatrix A Q x y := by
  unfold cappedTrace traceMatrix
  apply add_le_add_right
  apply Finset.sum_le_sum
  intro z _
  exact mul_le_mul_of_nonneg_left (Q.partialGreen_le N _ (fun t => C.nonnegative _ _) z) (C.nonnegative _ _)

include hQ in
lemma cappedTrace_substochastic (N : ℕ) (x : Inside A) : ∑ y,C.cappedTrace A Q N x y ≤ 1 := by
  calc
    _ ≤ ∑ y,C.traceMatrix A Q x y := Finset.sum_le_sum (fun y _ => C.cappedTrace_le A Q N x y)
    _ = 1 := C.traceMatrix_stochastic A Q hQ x

end FiniteChain

structure FiniteKernel (X Y : Type*) [Fintype Y] where
  P : X → Y → ℝ
  nonnegative : ∀ x y,0 ≤ P x y
  substochastic : ∀ x,∑ y,P x y ≤ 1
namespace FiniteKernel
open scoped BigOperators Classical
variable {X Y Z : Type*} [Fintype X] [Fintype Y] [Fintype Z]
noncomputable def identity : FiniteKernel X X where
  P x y := if x=y then 1 else 0
  nonnegative := by intros; split_ifs <;> norm_num
  substochastic := by intro x; simp
noncomputable def comp (K : FiniteKernel X Y) (L : FiniteKernel Y Z) : FiniteKernel X Z where
  P x z := ∑ y,K.P x y*L.P y z
  nonnegative := by intros; exact Finset.sum_nonneg (fun y _ => mul_nonneg (K.nonnegative _ _) (L.nonnegative _ _))
  substochastic := by
    intro x
    rw [Finset.sum_comm]
    simp_rw [← Finset.mul_sum]
    calc
      _ ≤ ∑ y,K.P x y*1 := Finset.sum_le_sum (fun y _ => mul_le_mul_of_nonneg_left (L.substochastic y) (K.nonnegative x y))
      _ ≤ 1 := by simp only [mul_one]; exact K.substochastic x
noncomputable def push (K : FiniteKernel X Y) (ν : X → ℝ) (y : Y) := ∑ x,ν x*K.P x y
noncomputable def power (K : FiniteKernel X X) : ℕ → FiniteKernel X X
  | 0 => identity
  | n+1 => (K.power n).comp K
end FiniteKernel

namespace FiniteChain

section
open scoped BigOperators Classical
variable {X : Type*} [Fintype X] (C : FiniteChain X)
noncomputable def kernel : FiniteKernel X X where
  P := C.P
  nonnegative := C.nonnegative
  substochastic := fun x => (C.stochastic x).le

variable (A : X → Prop) (Q : KilledKernel (Outside A)) (hQ : ∀ x y,Q.Q x y=C.P x.val y.val)
noncomputable def cappedTraceKernel (N : ℕ) : FiniteKernel (Inside A) (Inside A) where
  P := C.cappedTrace A Q N
  nonnegative := C.cappedTrace_nonneg A Q N
  substochastic := C.cappedTrace_substochastic A Q hQ N
end

section
open scoped BigOperators Classical
variable {X : Type*} [sectionInstance81 : Fintype X] (C : FiniteChain X)

noncomputable def pathProbability (ν : X → ℝ) (N : ℕ) (E : List X → Prop) : ℝ :=
  ∑ x,ν x*C.pathExpectation N x (fun l => if E l then 1 else 0)
end

open scoped BigOperators Classical
end FiniteChain

section SamplingObservables
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance85 : Fintype Γ] [DecidableEq Γ]
noncomputable def plainObservable (gs : List (Γ × Γ)) (g : Set Γ → ℝ) (S : Set Γ) : ℝ :=
  if PlainState gs S then g S else 0
noncomputable def defectIndicator (gs : List (Γ × Γ)) (p r : Γ × Γ) (S : Set Γ) : ℝ :=
  if DefectState gs p r S then 1 else 0
lemma plainObservable_mean (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f g : Set Γ → ℝ) : chainMean gs w f (plainObservable gs g) =
      slotTotal gs (fun S =>  f S*g S) ∅/chainTotal gs w f := by
  unfold chainMean
  congr 1
  simp only [chainWeight,mul_assoc]
  rw [chain_sum_decomposition,slotTotal_eq_plain_sum gs hgs hcover]
  have hplain : (∑ S with PlainState gs S,f S*plainObservable gs g S)=
      ∑ S with PlainState gs S,f S*g S := by
    apply Finset.sum_congr rfl
    intro S hS
    simp [plainObservable,(Finset.mem_filter.mp hS).2]
  rw [hplain]
  have hdef : ∀ pr ∈ defectTypes gs,(∑ S with DefectState gs pr.1 pr.2 S,f S*plainObservable gs g S)=0 := by
    intro pr hpr
    apply Finset.sum_eq_zero
    intro S hS
    simp [plainObservable,((Finset.mem_filter.mp hS).2).not_plain]
  rw [add_eq_left]
  apply Finset.sum_eq_zero
  intro pr hpr
  rw [hdef pr hpr,mul_zero]
lemma defectIndicator_mean (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f : Set Γ → ℝ) (p r : Γ × Γ) (hp : p∈gs) (hr : r∈gs) (hpr : p≠r) :
    chainMean gs w f (defectIndicator gs p r) =
      w p r*slotDefect gs f ∅ p r/chainTotal gs w f := by
  unfold chainMean
  congr 1
  simp only [chainWeight,mul_assoc]
  rw [chain_sum_decomposition,slotDefect_eq_defect_sum gs hgs hcover f p r hp hr hpr]
  have hplain : (∑ S with PlainState gs S,f S*defectIndicator gs p r S)=0 := by
    apply Finset.sum_eq_zero
    intro S hS
    have hh : ¬DefectState gs p r S := fun h =>  h.not_plain (Finset.mem_filter.mp hS).2
    simp [defectIndicator,hh]
  rw [hplain,zero_add]
  rw [Finset.sum_eq_single (p,r)]
  · congr 1
    apply Finset.sum_congr rfl
    intro S hS
    simp [defectIndicator,(Finset.mem_filter.mp hS).2]
  · intro pr hm hne
    have he : ∀ S,DefectState gs pr.1 pr.2 S → ¬DefectState gs p r S := by
      intro S hS h
      exact hne (Prod.ext (hS.unique h).1 (hS.unique h).2)
    have hz : (∑ S with DefectState gs pr.1 pr.2 S,f S*defectIndicator gs p r S)=0 := by
      apply Finset.sum_eq_zero
      intro S hS
      simp [defectIndicator,he S (Finset.mem_filter.mp hS).2]
    rw [hz,mul_zero]
  · intro h
    exact (h ((mem_defectTypes gs (p,r)).mpr ⟨hp,hr,hpr⟩)).elim

abbrev PhaseObservables (gs : List (Γ × Γ)) := Option {pr // pr∈defectTypes gs} ⊕ Unit
noncomputable def phaseObservable (gs : List (Γ × Γ)) (g : Set Γ → ℝ) :
    PhaseObservables gs → Set Γ → ℝ
  | .inl none => plainObservable gs (fun _ => 1)
  | .inl (some pr) => defectIndicator gs pr.val.1 pr.val.2
  | .inr _ => plainObservable gs g
end SamplingObservables

open Set

section PairedReduction
variable {α β : Type*}

def pairedMatroid (M₁ M₂ : Matroid α) : Matroid (α ⊕ α) := M₁.sum M₂✶

def transversal (A : Set α) : Set (α ⊕ α) := Sum.inl '' A ∪ Sum.inr '' Aᶜ

@[simp] lemma preimage_inl_transversal (A : Set α) :
    Sum.inl ⁻¹' transversal A = A := by
  simp [transversal, Set.preimage_image_eq _ Sum.inl_injective]

@[simp] lemma preimage_inr_transversal (A : Set α) :
    Sum.inr ⁻¹' transversal A = Aᶜ := by
  simp [transversal, 
    Set.preimage_image_eq _ Sum.inr_injective]

end PairedReduction

open scoped BigOperators Classical

noncomputable def annealRho (n : ℕ) : ℝ := 1 - 1 / (2 * (n : ℝ))
def annealLength (n b : ℕ) : ℕ := 2*n*(n+b)
noncomputable def annealTolerance (ε : ℝ) (L : ℕ) : ℝ := ε/(32*((L:ℝ)+1))

lemma annealRho_bounds (n : ℕ) (hn : 0<n) : 1/2 ≤ annealRho n ∧ annealRho n < 1 := by
  have hn' : (1:ℝ) ≤ n := by exact_mod_cast hn
  unfold annealRho
  have hh : 0 < 2*(n:ℝ) := by positivity
  constructor
  · have := (div_le_iff₀ hh).mpr (show (1:ℝ) ≤ (1/2)*(2*n) by nlinarith)
    linarith
  · have := div_pos (show (0:ℝ)<1 by norm_num) hh
    linarith
lemma annealRho_pow_lower (n : ℕ) (hn : 0<n) : 1/2 ≤ annealRho n ^ n := by
  have hn' : 0 < (n:ℝ) := by exact_mod_cast hn
  have h := one_add_mul_le_pow (a := -(1/(2*(n:ℝ)))) (by
    have hh := (annealRho_bounds n hn).1
    unfold annealRho at hh
    linarith) n
  have he : 1+(n:ℝ)*(-(1/(2*n))) = 1/2 := by field_simp; ring
  simpa only [he,annealRho,sub_eq_add_neg] using h
lemma annealStep_bounds (n d : ℕ) (hn : 0<n) (hd : d≤n) :
    1/2 ≤ annealRho n ^ d ∧ annealRho n ^ d ≤ 1 := by
  have hr := annealRho_bounds n hn
  have h0 : 0 ≤ annealRho n := by linarith
  exact ⟨(annealRho_pow_lower n hn).trans (pow_le_pow_of_le_one h0 hr.2.le hd),
    pow_le_one₀ h0 hr.2.le⟩

section CoefficientDrift
variable {X : Type*} [Fintype X]

noncomputable def deficiencyPartition (d : X → ℕ) (q : ℝ) : ℝ := ∑ x,q^(d x)
end CoefficientDrift

lemma estimated_ratio_bounds (η p u phat uhat : ℝ) (hη0 : 0≤η) (hη1 : η<1)
    (hp : 0<p) (hu : 0<u)
    (hphat : (1-η)*p ≤ phat ∧ phat ≤ (1+η)*p)
    (huhat : (1-η)*u ≤ uhat ∧ uhat ≤ (1+η)*u) :
    0<phat ∧ (1-η)/(1+η) ≤ (uhat/phat)/(u/p) ∧
      (uhat/phat)/(u/p) ≤ (1+η)/(1-η) := by
  have hp' : 0<phat := lt_of_lt_of_le (mul_pos (by linarith) hp) hphat.1
  have hu' : 0<uhat := lt_of_lt_of_le (mul_pos (by linarith) hu) huhat.1
  refine ⟨hp',?_,?_⟩
  · rw [le_div_iff₀ (div_pos hu hp),div_mul_div_comm,div_le_div_iff₀ (mul_pos (by linarith) hp) hp']
    have h1 := mul_le_mul_of_nonneg_left hphat.2 (mul_nonneg (by linarith : 0≤1-η) hu.le)
    have h2 := mul_le_mul_of_nonneg_right huhat.1 (mul_nonneg (by linarith : 0≤1+η) hp.le)
    nlinarith
  · rw [div_le_iff₀ (div_pos hu hp),div_mul_div_comm,
      div_le_div_iff₀ hp' (mul_pos (by linarith) hp)]
    have h1 := mul_le_mul_of_nonneg_left hphat.1 (mul_nonneg (by linarith : 0≤1+η) hu.le)
    have h2 := mul_le_mul_of_nonneg_right huhat.2 (mul_nonneg (by linarith : 0≤1-η) hp.le)
    nlinarith

lemma ratio_coarse_bounds (η : ℝ) (h0 : 0≤η) (h1 : η≤1/3) :
    1/2 ≤ (1-η)/(1+η) ∧ (1+η)/(1-η) ≤ 2 := by
  constructor
  · rw [le_div_iff₀ (by linarith : 0<1+η)]; linarith
  · rw [div_le_iff₀ (by linarith : 0<1-η)]; linarith

lemma learned_multiplier_good (w p0 pt phat0 phatt ideal nextIdeal η : ℝ)
    (hw : 0<w) (hp0 : 0<p0) (hpt : 0<pt) (_ : 0 < ideal)
    (hidentity : w*(p0/pt)=ideal) (hη0 : 0≤η) (hη1 : η≤1/3)
    (hphat0 : (1-η)*p0≤phat0 ∧ phat0≤(1+η)*p0)
    (hphatt : (1-η)*pt≤phatt ∧ phatt≤(1+η)*pt)
    (hdrift : ideal/2≤nextIdeal ∧ nextIdeal≤2*ideal) :
    nextIdeal/4 ≤ w*(phat0/phatt) ∧ w*(phat0/phatt)≤4*nextIdeal := by
  have hb := estimated_ratio_bounds η pt p0 phatt phat0 hη0 (by linarith) hpt hp0 hphatt hphat0
  have hc := ratio_coarse_bounds η hη0 hη1
  have hl := (le_div_iff₀ (div_pos hp0 hpt)).mp (hc.1.trans hb.2.1)
  have hu := (div_le_iff₀ (div_pos hp0 hpt)).mp (hb.2.2.trans hc.2)
  have hl' := mul_le_mul_of_nonneg_left hl hw.le
  have hu' := mul_le_mul_of_nonneg_left hu hw.le
  have hl'' : ideal/2 ≤ w*(phat0/phatt) := by nlinarith [hidentity]
  have hu'' : w*(phat0/phatt) ≤ 2*ideal := by nlinarith [hidentity]
  constructor <;> linarith

section Contamination
variable {X : Type*} [Fintype X]
noncomputable def zeroDeficiencyCount (d : X → ℕ) : ℕ := (Finset.univ.filter (fun x => d x=0)).card
end Contamination

namespace FiniteExperiment
open scoped BigOperators Classical
variable {X : Type*} [sectionInstance89 : Fintype X]

noncomputable def expect (K : List X → X → ℝ) : ℕ → List X → (List X → ℝ) → ℝ
  | 0,h,φ => φ h
  | t+1,h,φ => ∑ x,K h x*expect K t (h++[x]) φ
noncomputable def mass (K : List X → X → ℝ) (t : ℕ) (h : List X) : ℝ :=
  expect K t h (fun _ => 1)

noncomputable def restrict (P : List X → X → ℝ) (Good : List X → Prop) (h : List X) (x : X) : ℝ :=
  if Good (h++[x]) then P h x else 0
end FiniteExperiment
section PairedSlots
open scoped BigOperators Classical
variable {α : Type*} [sectionInstance90 : Fintype α] [sectionInstance91 : DecidableEq α]
def slotPair (i : α) : (α ⊕ α) × (α ⊕ α) := (Sum.inl i,Sum.inr i)
def pairedSlots (n : ℕ) : List ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) :=
  (List.finRange n).map slotPair
omit sectionInstance90 sectionInstance91 in
lemma slotPairList_fresh [Fintype α] [DecidableEq α] (l : List α) (hl : l.Nodup) (B : Set (α ⊕ α))
    (hB : ∀ i ∈ l,Sum.inl i ∉ B ∧ Sum.inr i ∉ B) : SlotsFresh (l.map slotPair) B := by
  induction l generalizing B with
  | nil => trivial
  | cons a l ih =>
    obtain ⟨ha,hl⟩ := List.nodup_cons.mp hl
    refine ⟨by simp [slotPair],(hB a (by simp)).1,(hB a (by simp)).2,?_⟩
    apply ih hl
    intro i hi
    have hn : i ≠ a := fun he => ha (he ▸ hi)
    have hb := hB i (by simp [hi])
    simpa [slotPair,hn] using hb
lemma pairedSlots_fresh (n : ℕ) : SlotsFresh (pairedSlots n) ∅ :=
  slotPairList_fresh _ (List.nodup_finRange n) ∅ (by intros; simp)
@[simp] lemma pairedSlots_length (n : ℕ) : (pairedSlots n).length = n := by simp [pairedSlots]
lemma pairedSlots_ne_nil (n : ℕ) (hn : 0<n) : pairedSlots n ≠ [] := by
  intro h; have hh := congrArg List.length h
  simp only [pairedSlots_length,List.length_nil] at hh
  omega
lemma pairedSlots_cover (n : ℕ) : pairSupport (pairedSlots n) = Set.univ := by
  ext x
  simp only [pairSupport,Set.mem_ofPred_eq,Set.mem_univ,iff_true]
  cases x with
  | inl i => exact ⟨slotPair i,by simp [pairedSlots],Or.inl rfl⟩
  | inr i => exact ⟨slotPair i,by simp [pairedSlots],Or.inr rfl⟩
lemma pairedSlots_plain (n : ℕ) (S : Set (Fin n ⊕ Fin n)) :
    PlainState (pairedSlots n) S ↔ ∀ i, (Sum.inl i ∈ S ↔ Sum.inr i ∉ S) := by
  simp only [PlainState,pairedSlots,List.mem_map,forall_exists_index,and_imp]
  constructor
  · intro h i
    have hh := h (slotPair i) i (by simp) rfl
    simp only [PairSingle,slotPair] at hh
    tauto
  · intro h p i hi he
    subst p
    have hh := h i
    simp only [PairSingle,slotPair]
    tauto
lemma transversal_plain (n : ℕ) (A : Set (Fin n)) : PlainState (pairedSlots n) (transversal A) := by
  rw [pairedSlots_plain]
  intro i
  have hl : Sum.inl i ∈ transversal A ↔ i ∈ A := by
    change i ∈ Sum.inl ⁻¹' transversal A ↔ _
    rw [preimage_inl_transversal]
  have hr : Sum.inr i ∈ transversal A ↔ i ∉ A := by
    change i ∈ Sum.inr ⁻¹' transversal A ↔ _
    rw [preimage_inr_transversal]; rfl
  rw [hl,hr,not_not]
lemma plain_eq_transversal (n : ℕ) (S : Set (Fin n ⊕ Fin n)) (hS : PlainState (pairedSlots n) S) :
    S = transversal (Sum.inl ⁻¹' S) := by
  rw [pairedSlots_plain] at hS
  ext x
  cases x with
  | inl i =>
    change i ∈ Sum.inl ⁻¹' S ↔ i ∈ Sum.inl ⁻¹' transversal (Sum.inl ⁻¹' S)
    rw [preimage_inl_transversal]
  | inr i =>
    change Sum.inr i ∈ S ↔ i ∈ Sum.inr ⁻¹' transversal (Sum.inl ⁻¹' S)
    rw [preimage_inr_transversal]
    change Sum.inr i ∈ S ↔ Sum.inl i ∉ S
    have hh := hS i
    tauto
noncomputable def plainEquivSubsets (n : ℕ) :
    {S : Set (Fin n ⊕ Fin n) // PlainState (pairedSlots n) S} ≃ Set (Fin n) where
  toFun S := Sum.inl ⁻¹' S.val
  invFun A := ⟨transversal A,transversal_plain n A⟩
  left_inv S := Subtype.ext (plain_eq_transversal n S.val S.property).symm
  right_inv A := preimage_inl_transversal A
lemma pairedPlain_card (n : ℕ) : Fintype.card {S : Set (Fin n ⊕ Fin n) // PlainState (pairedSlots n) S} = 2^n := by
  rw [Fintype.card_congr (plainEquivSubsets n),Fintype.card_set,Fintype.card_fin]
end PairedSlots
open scoped BigOperators Classical
noncomputable def subsetDeficiency {n : ℕ} (M₁ M₂ : Matroid (Fin n)) (A : Set (Fin n)) : ℕ :=
  n-((pairedMatroid M₁ M₂).eRk (transversal A)).toNat

noncomputable def commonSetFinsetEquiv {n : ℕ} (M₁ M₂ : Matroid (Fin n)) :
    {A : Set (Fin n) // M₁.IsBase A ∧ M₂.IsBase A} ≃
    {A : Finset (Fin n) // M₁.IsBase (A:Set (Fin n)) ∧ M₂.IsBase (A:Set (Fin n))} where
  toFun A := ⟨A.val.toFinset,by simpa only [Set.coe_toFinset] using A.property⟩
  invFun A := ⟨(A.val:Set (Fin n)),A.property⟩
  left_inv A := by apply Subtype.ext; exact Set.coe_toFinset _
  right_inv A := by apply Subtype.ext; simp
open scoped BigOperators Classical
section ActualAnnealing
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance93 : Fintype Γ] [sectionInstance94 : DecidableEq Γ]
lemma slotTotal_mono (ps : List (Γ × Γ)) (f g : Set Γ → ℝ)
    (h : ∀ S,f S ≤ g S) (A : Set Γ) : slotTotal ps f A ≤ slotTotal ps g A := by
  induction ps generalizing A with
  | nil => exact h A
  | cons p ps ih =>
    change coeffDeriv (pairDirection p) (slotTotal ps f) A ≤ coeffDeriv (pairDirection p) (slotTotal ps g) A
    unfold coeffDeriv
    apply Finset.sum_le_sum
    intro i _
    split_ifs
    · rfl
    · exact mul_le_mul_of_nonneg_left (ih _) (slotDirection_nonneg _ _)
lemma slotTotal_scale (ps : List (Γ × Γ)) (f : Set Γ → ℝ) (c : ℝ) (A : Set Γ) :
    slotTotal ps (fun S => c*f S) A=c*slotTotal ps f A := coeffDerivs_scale _ _ _ _
noncomputable def annealWeight (M : Matroid Γ) (n j : ℕ) : Set Γ → ℝ :=
  matroidWeight M n (annealRho n^j)
lemma annealWeight_pos (M : Matroid Γ) (n j : ℕ) (hn : 0<n) (S : Set Γ) :
    0<annealWeight M n j S :=
  matroidWeight_pos M n _ (pow_pos (lt_of_lt_of_le (by norm_num) (annealRho_bounds n hn).1) _) S
omit sectionInstance93 sectionInstance94 in
lemma annealWeight_step [Fintype Γ] [DecidableEq Γ] (M : Matroid Γ) (n j : ℕ) (_ : 0<n) (S : Set Γ) :
    annealWeight M n (j+1) S=annealWeight M n j S*annealRho n^(n-(M.eRk S).toNat) := by
  unfold annealWeight matroidWeight
  rw [pow_succ,mul_pow]
lemma annealWeight_drift (M : Matroid Γ) (n j : ℕ) (hn : 0<n) (S : Set Γ) :
    (1/2)*annealWeight M n j S ≤ annealWeight M n (j+1) S ∧
      annealWeight M n (j+1) S ≤ annealWeight M n j S := by
  rw [annealWeight_step M n j hn]
  have h := annealStep_bounds n (n-(M.eRk S).toNat) hn (Nat.sub_le _ _)
  have hp := (annealWeight_pos M n j hn S).le
  constructor
  · nlinarith [mul_nonneg hp (sub_nonneg.mpr h.1)]
  · nlinarith [mul_nonneg hp (sub_nonneg.mpr h.2)]
lemma anneal_slot_drift (ps : List (Γ × Γ)) (M : Matroid Γ) (n j : ℕ) (hn : 0<n) (A : Set Γ) :
    (1/2)*slotTotal ps (annealWeight M n j) A ≤ slotTotal ps (annealWeight M n (j+1)) A ∧
      slotTotal ps (annealWeight M n (j+1)) A ≤ slotTotal ps (annealWeight M n j) A := by
  constructor
  · rw [← slotTotal_scale]
    exact slotTotal_mono _ _ _ (fun S => (annealWeight_drift M n j hn S).1) _
  · exact slotTotal_mono _ _ _ (fun S => (annealWeight_drift M n j hn S).2) _
lemma anneal_ideal_drift (ps : List (Γ × Γ)) (hps : SlotsFresh ps ∅)
    (M : Matroid Γ) (n j : ℕ) (hn : 0<n) (p r : Γ × Γ)
    (hp : p∈ps) (hr : r∈ps) (hpr : p≠r) :
    (slotTotal ps (annealWeight M n j) ∅/slotDefect ps (annealWeight M n j) ∅ p r)/2  ≤
      slotTotal ps (annealWeight M n (j+1)) ∅/slotDefect ps (annealWeight M n (j+1)) ∅ p r ∧
    slotTotal ps (annealWeight M n (j+1)) ∅/slotDefect ps (annealWeight M n (j+1)) ∅ p r  ≤
      2*(slotTotal ps (annealWeight M n j) ∅/slotDefect ps (annealWeight M n j) ∅ p r) := by
  have h0 := slotTotal_pos ps _ (annealWeight_pos M n j hn) ∅ hps
  have h1 := slotTotal_pos ps _ (annealWeight_pos M n (j+1) hn) ∅ hps
  have hd0 := slotDefect_pos ps _ (annealWeight_pos M n j hn) ∅ hps p r hp hr hpr
  have hd1 := slotDefect_pos ps _ (annealWeight_pos M n (j+1) hn) ∅ hps p r hp hr hpr
  have ha := anneal_slot_drift ps M n j hn ∅
  have hb := anneal_slot_drift ((ps.erase p).erase r) M n j hn (insert r.1 (insert r.2 ∅))
  change (1/2)*slotDefect ps (annealWeight M n j) ∅ p r ≤ slotDefect ps (annealWeight M n (j+1)) ∅ p r ∧ slotDefect ps (annealWeight M n (j+1)) ∅ p r ≤ slotDefect ps (annealWeight M n j) ∅ p r at hb
  constructor
  · rw [div_div,div_le_div_iff₀ (mul_pos hd0 (by norm_num)) hd1]
    nlinarith [mul_nonneg h0.le (sub_nonneg.mpr hb.2),mul_nonneg hd0.le (sub_nonneg.mpr ha.1)]
  · rw [← mul_div_assoc,div_le_div_iff₀ hd1 hd0]
    nlinarith [mul_nonneg hd0.le (sub_nonneg.mpr ha.2),mul_nonneg h0.le (sub_nonneg.mpr hb.1)]

structure GoodMultipliers (gs : List (Γ × Γ)) (f : Set Γ → ℝ)
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) : Prop where
  positive : ∀ p r,0<w p r
  lower : ∀ p∈gs,∀ r∈gs,p≠r → slotTotal gs f ∅/(4*slotDefect gs f ∅ p r) ≤ w p r
  upper : ∀ p∈gs,∀ r∈gs,p≠r → w p r ≤ 4*slotTotal gs f ∅/slotDefect gs f ∅ p r

lemma plain_probability_mean (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (f : Set Γ → ℝ)
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) :
    chainMean gs w f (plainObservable gs (fun _ => 1))=slotTotal gs f ∅/chainTotal gs w f := by
  rw [plainObservable_mean gs hgs hcover]
  simp
lemma weight_mean_identity (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (f : Set Γ → ℝ) (hf : ∀ S,0<f S)
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (hw : ∀ p r,0<w p r)
    (p r : Γ × Γ) (hp : p∈gs) (hr : r∈gs) (hpr : p≠r) :
    w p r*(chainMean gs w f (plainObservable gs (fun _ => 1))/
      chainMean gs w f (defectIndicator gs p r))=slotTotal gs f ∅/slotDefect gs f ∅ p r := by
  rw [plain_probability_mean gs hgs hcover,defectIndicator_mean gs hgs hcover _ _ p r hp hr hpr]
  field_simp [(chainTotal_pos gs hgs hcover w f hf hw).ne',(hw p r).ne']

lemma plainIndex_card (n : ℕ) : Fintype.card (PlainIndex (pairedSlots n))=2^n := by
  let e : PlainIndex (pairedSlots n) ≃ {S : Set (Fin n ⊕ Fin n) // PlainState (pairedSlots n) S} :=
    { toFun := fun S => ⟨S.val.val,S.property⟩
      invFun := fun S => ⟨⟨S.val,Or.inl S.property⟩,S.property⟩
      left_inv := by intro S; rfl
      right_inv := by intro S; rfl }
  rw [Fintype.card_congr e,pairedPlain_card]
lemma slotTotal_one (ps : List (Γ × Γ)) (A : Set Γ) (hps : SlotsFresh ps A) :
    slotTotal ps (fun _ => 1) A=(2:ℝ)^ps.length := by
  induction ps generalizing A with
  | nil => rfl
  | cons p ps ih =>
    rw [slotTotal_cons p ps _ A hps.1 hps.2.1 hps.2.2.1]
    have hleft : SlotsFresh ps (insert p.1 A) := hps.2.2.2.mono (by intro i hi; simp only [Set.mem_insert_iff] at hi ⊢; tauto)
    have hright : SlotsFresh ps (insert p.2 A) := hps.2.2.2.mono (by intro i hi; simp only [Set.mem_insert_iff] at hi ⊢; tauto)
    rw [ih _ hleft,ih _ hright,List.length_cons,pow_succ]
    ring
lemma initial_ideal (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (p r : Γ × Γ)
    (hp : p∈gs) (hr : r∈gs) (hpr : p≠r) :
    slotTotal gs (fun _ => 1) ∅/slotDefect gs (fun _ => 1) ∅ p r=4 := by
  have hperm := slots_perm_two hp hr hpr
  have hh := (SlotsFresh.perm hperm ∅).mp hgs
  have hrest : SlotsFresh ((gs.erase p).erase r) (insert r.1 (insert r.2 ∅)) :=
    hh.2.2.2.2.2.2.mono (by intro i hi; simp only [Set.mem_insert_iff] at hi ⊢; tauto)
  have hlen := List.Perm.length_eq hperm
  simp only [List.length_cons] at hlen
  rw [slotTotal_one _ _ hgs,slotDefect,slotTotal_one _ _ hrest,hlen,pow_succ,pow_succ]
  have hp : (2:ℝ)^((gs.erase p).erase r).length≠0 := by positivity
  field_simp
  ; ring
lemma initial_good (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅) (M : Matroid Γ) (n : ℕ) :
    GoodMultipliers gs (annealWeight M n 0) (fun _ _ => 4) := by
  have he : annealWeight M n 0=(fun _ => 1) := by funext S; simp [annealWeight,matroidWeight]
  rw [he]
  refine ⟨by intros; norm_num,?_,?_⟩
  · intro p hp r hr hpr
    rw [mul_comm (4:ℝ),← div_div,initial_ideal gs hgs p r hp hr hpr]
    norm_num
  · intro p hp r hr hpr
    rw [mul_div_assoc,initial_ideal gs hgs p r hp hr hpr]
    norm_num

noncomputable def empirical (H : Set Γ → ℝ) (l : List (Set Γ)) : ℝ :=
  (l.map H).sum/(l.length:ℝ)
noncomputable def learnedMultipliers (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (l : List (Set Γ)) (p r : Γ × Γ) : ℝ :=
  if p∈gs ∧ r∈gs ∧ p≠r then
    w p r*(empirical (plainObservable gs (fun _ => 1)) l/empirical (defectIndicator gs p r) l)
  else w p r
noncomputable def empiricalRatio (gs : List (Γ × Γ)) (g : Set Γ → ℝ) (l : List (Set Γ)) : ℝ :=
  empirical (plainObservable gs g) l/empirical (plainObservable gs (fun _ => 1)) l

def AccuratePhase (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f g : Set Γ → ℝ) (η : ℝ) (l : List (Set Γ)) : Prop :=
  ∀ j : PhaseObservables gs,
    |empirical (phaseObservable gs g j) l-chainMean gs w f (phaseObservable gs g j)| ≤
      η*chainMean gs w f (phaseObservable gs g j)
lemma relative_error_bounds (x m η : ℝ) (h : |x-m| ≤ η*m) :
    (1-η)*m ≤ x ∧ x ≤ (1+η)*m := by
  obtain ⟨h1,h2⟩ := abs_le.mp h
  constructor <;> nlinarith
lemma accurate_plain (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f g : Set Γ → ℝ) (η : ℝ) (l : List (Set Γ)) (h : AccuratePhase gs w f g η l) :
    (1-η)*chainMean gs w f (plainObservable gs (fun _ => 1)) ≤ empirical (plainObservable gs (fun _ => 1)) l ∧
    empirical (plainObservable gs (fun _ => 1)) l ≤ (1+η)*chainMean gs w f (plainObservable gs (fun _ => 1)) :=
  relative_error_bounds _ _ _ (h (.inl none))
lemma accurate_defect (gs : List (Γ × Γ)) (w : (Γ × Γ) → (Γ × Γ) → ℝ)
    (f g : Set Γ → ℝ) (η : ℝ) (l : List (Set Γ)) (h : AccuratePhase gs w f g η l)
    (p r : Γ × Γ) (hp : p∈gs) (hr : r∈gs) (hpr : p≠r) :
    (1-η)*chainMean gs w f (defectIndicator gs p r) ≤ empirical (defectIndicator gs p r) l ∧
    empirical (defectIndicator gs p r) l ≤ (1+η)*chainMean gs w f (defectIndicator gs p r) :=
  relative_error_bounds _ _ _ (h (.inl (some ⟨(p,r),(mem_defectTypes gs (p,r)).mpr ⟨hp,hr,hpr⟩⟩)))

theorem successful_phase_learns (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (M : Matroid Γ) (n j : ℕ) (hn : 0<n)
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (hw : ∀ p r,0<w p r)
    (η : ℝ) (hη0 : 0 ≤ η) (hη1 : η ≤ 1/3) (l : List (Set Γ))
    (hacc : AccuratePhase gs w (annealWeight M n j)
      (fun S => annealRho n^(n-(M.eRk S).toNat)) η l) :
    GoodMultipliers gs (annealWeight M n (j+1)) (learnedMultipliers gs w l) := by
  let f := annealWeight M n j
  let g := fun S => annealRho n^(n-(M.eRk S).toNat)
  have hf := annealWeight_pos M n j hn
  have hΛ := chainTotal_pos gs hgs hcover w f hf hw
  have hp0 : 0<chainMean gs w f (plainObservable gs (fun _ => 1)) := by
    rw [plain_probability_mean gs hgs hcover]
    exact div_pos (slotTotal_pos gs f hf ∅ hgs) hΛ
  have hp0h := accurate_plain gs w f g η l hacc
  have hp0hp : 0<empirical (plainObservable gs (fun _ => 1)) l :=
    lt_of_lt_of_le (mul_pos (by linarith) hp0) hp0h.1
  have hpt : ∀ p∈gs,∀ r∈gs,p≠r → 0<chainMean gs w f (defectIndicator gs p r) := by
    intro p hp r hr hpr
    rw [defectIndicator_mean gs hgs hcover w f p r hp hr hpr]
    exact div_pos (mul_pos (hw p r) (slotDefect_pos gs f hf ∅ hgs p r hp hr hpr)) hΛ
  have hptp : ∀ p∈gs,∀ r∈gs,p≠r → 0<empirical (defectIndicator gs p r) l := by
    intro p hp r hr hpr
    exact lt_of_lt_of_le (mul_pos (by linarith) (hpt p hp r hr hpr))
      (accurate_defect gs w f g η l hacc p r hp hr hpr).1
  have hb : ∀ p∈gs,∀ r∈gs,p≠r →
      (slotTotal gs (annealWeight M n (j+1)) ∅/slotDefect gs (annealWeight M n (j+1)) ∅ p r)/4 ≤
        learnedMultipliers gs w l p r ∧
      learnedMultipliers gs w l p r ≤ 4*(slotTotal gs (annealWeight M n (j+1)) ∅/slotDefect gs (annealWeight M n (j+1)) ∅ p r) := by
    intro p hp r hr hpr
    rw [learnedMultipliers,ite_eq_left ⟨hp,hr,hpr⟩]
    exact learned_multiplier_good _ _ _ _ _ _ _ η (hw p r) hp0 (hpt p hp r hr hpr)
      (div_pos (slotTotal_pos gs f hf ∅ hgs) (slotDefect_pos gs f hf ∅ hgs p r hp hr hpr))
      (weight_mean_identity gs hgs hcover f hf w hw p r hp hr hpr) hη0 hη1 hp0h
      (accurate_defect gs w f g η l hacc p r hp hr hpr)
      (anneal_ideal_drift gs hgs M n j hn p r hp hr hpr)
  refine ⟨?_,?_,?_⟩
  · intro p r
    unfold learnedMultipliers
    split_ifs with h
    · exact mul_pos (hw p r) (div_pos hp0hp (hptp p h.1 r h.2.1 h.2.2))
    · exact hw p r
  · intro p hp r hr hpr
    simpa only [div_div,mul_comm] using (hb p hp r hr hpr).1
  · intro p hp r hr hpr
    simpa only [mul_div_assoc] using (hb p hp r hr hpr).2

end ActualAnnealing

section RestartBridge
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M : Matroid (Fin n ⊕ Fin n))
    (hE : M.E=Set.univ) (hR : M.eRank=(n:ℕ∞))
local notation "GS" => pairedSlots n
local notation "W" => (((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ℝ)
noncomputable def actualTrace (j : ℕ) (w : W) (hw : ∀ p r,0<w p r) : FiniteChain (PlainIndex GS) :=
  metropolisTrace GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
    w (annealWeight M n j) hw (annealWeight_pos M n j hn)
end RestartBridge

section ActualRestart
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M : Matroid (Fin n ⊕ Fin n))
local notation "GS" => pairedSlots n
local notation "W" => (((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ℝ)
noncomputable def actualCappedTrace (j : ℕ) (w : W) (hw : ∀ p r,0<w p r) (cap : ℕ) :
    FiniteKernel (PlainIndex GS) (PlainIndex GS) :=
  (metropolisChain GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
    w (annealWeight M n j) hw (annealWeight_pos M n j hn)).cappedTraceKernel (plainPredicate GS)
    (metropolisKilled GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
      w (annealWeight M n j) hw (annealWeight_pos M n j hn)) (by intros; rfl) cap
noncomputable def uniformPlain (_ : PlainIndex GS) : ℝ := by
  exact 1/(2:ℝ)^n
noncomputable def restartLaw (L cap : ℕ) (w : ℕ → W) (hw : ∀ j p r,0<w j p r) : ℕ → PlainIndex GS → ℝ
  | 0 => uniformPlain n
  | j+1 => ((actualCappedTrace n hn M (j+1) (w (j+1)) (hw (j+1)) cap).power
      (20*n^4*(n*(L+1)+2))).push (restartLaw L cap w hw j)
end ActualRestart
namespace FiniteChain
open scoped BigOperators Classical
variable {X : Type*} [sectionInstance101 : Fintype X]
universe u

def Sample (X : Type u) : ℕ → Type u
  | 0 => X
  | k+1 => X × Sample X k
instance sampleFintype (k : ℕ) : Fintype (Sample X k) := by
  induction k with
  | zero => exact inferInstanceAs (Fintype X)
  | succ k ih =>
    letI : Fintype (Sample X k) := ih
    exact inferInstanceAs (Fintype (X × Sample X k))
def Sample.toList : {k : ℕ} → Sample X k → List X
  | 0,x => [x]
  | _+1,(x,v) => x::v.toList
variable (C : FiniteChain X)
noncomputable def sampleMass (C : FiniteChain X) (ν : X → ℝ) : {k : ℕ} → Sample X k → ℝ
  | 0,x => ν x
  | _+1,(x,v) => ν x*sampleMass C (C.P x) v
end FiniteChain
section WarmLift
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance102 : Fintype Γ] [sectionInstance103 : DecidableEq Γ]
noncomputable def plainLift (gs : List (Γ × Γ)) (ν : PlainIndex gs → ℝ) (S : ChainIndex gs) : ℝ :=
  if h : PlainState gs S.val then ν ⟨S,h⟩ else 0
end WarmLift
section ActualHistory
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance105 : Fintype Γ] [DecidableEq Γ]
variable (gs : List (Γ × Γ))
noncomputable def historyWeights {X : Type*} (sample : X → List (Set Γ)) (h : List X) :
    (Γ × Γ) → (Γ × Γ) → ℝ :=
  h.foldl (fun w x => learnedMultipliers gs w (sample x)) (fun _ _ => 4)
omit sectionInstance105 in
lemma historyWeights_snoc [Fintype Γ] {X : Type*} (sample : X → List (Set Γ)) (h : List X) (x : X) :
    historyWeights gs sample (h++[x])=learnedMultipliers gs (historyWeights gs sample h) (sample x) := by
  simp [historyWeights,List.foldl_append]
variable (M : Matroid Γ) (n : ℕ) (η : ℝ)
inductive SuccessfulHistory {X : Type*} (sample : X → List (Set Γ)) : List X → Prop
  | nil : SuccessfulHistory sample []
  | snoc (h : List X) (x : X) (hh : SuccessfulHistory sample h)
      (hx : AccuratePhase gs (historyWeights gs sample h) (annealWeight M n h.length)
        (fun S => annealRho n^(n-(M.eRk S).toNat)) η (sample x)) : SuccessfulHistory sample (h++[x])
lemma successfulHistory_take {X : Type*} (sample : X → List (Set Γ)) (h : List X)
    (hh : SuccessfulHistory gs M n η sample h) (a : ℕ) :
    SuccessfulHistory gs M n η sample (h.take a) := by
  induction hh with
  | nil => simpa using SuccessfulHistory.nil (gs := gs) (M := M) (n := n) (η := η) (sample := sample)
  | snoc h x hh hx ih =>
    by_cases ha : a ≤ h.length
    · rw [List.take_append_of_le_length ha]
      exact ih
    · have he : a ≥ (h++[x]).length := by simp; omega
      rw [List.take_of_length_le he]
      exact SuccessfulHistory.snoc h x hh hx
lemma successfulHistory_good {X : Type*} (sample : X → List (Set Γ))
    (hgs : SlotsFresh gs ∅) (hcover : pairSupport gs=Set.univ) (hn : 0<n)
    (hη0 : 0 ≤ η) (hη1 : η ≤ 1/3) (h : List X) (hh : SuccessfulHistory gs M n η sample h) :
    GoodMultipliers gs (annealWeight M n h.length) (historyWeights gs sample h) := by
  induction hh with
  | nil => exact initial_good gs hgs M n
  | snoc h x hh hx ih =>
    rw [historyWeights_snoc,List.length_append,List.length_singleton]
    exact successful_phase_learns gs hgs hcover M n h.length hn (historyWeights gs sample h)
      ih.positive η hη0 hη1 (sample x) hx
end ActualHistory
section ActualPhase
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M : Matroid (Fin n ⊕ Fin n))
local notation "GS" => pairedSlots n
local notation "W" => (((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ℝ)
def restartCap (n L : ℕ) : ℕ := 2000*(L+1)^2*n^2*(20*n^4*(n*(L+1)+2))
variable (N : ℕ)
abbrev PhaseSample := FiniteChain.Sample (ChainIndex GS) (N-1)
def observedSets (v : PhaseSample n N) : List (Set (Fin n ⊕ Fin n)) := v.toList.map Subtype.val
noncomputable def storedWeights (h : List (PhaseSample n N)) (j : ℕ) : W :=
  historyWeights GS (observedSets n N) (h.take j)
def HistoryPositive (h : List (PhaseSample n N)) : Prop :=
  ∀ j ≤ h.length,∀ p r,0<storedWeights n N h j p r
lemma historyPositive_all (h : List (PhaseSample n N)) (hp : HistoryPositive n N h) (j : ℕ) (p r) :
    0<storedWeights n N h j p r := by
  by_cases hj : j ≤ h.length
  · exact hp j hj p r
  · have he : h.take j=h := List.take_of_length_le (by omega)
    simpa only [storedWeights,he,List.take_length] using hp h.length le_rfl p r
include hn in
lemma successfulHistory_positive (η : ℝ) (hη0 : 0 ≤ η) (hη1 : η ≤ 1/3)
    (h : List (PhaseSample n N)) (hh : SuccessfulHistory GS M n η (observedSets n N) h) :
    HistoryPositive n N h := by
  intro j hj
  exact (successfulHistory_good GS M n η (observedSets n N) (pairedSlots_fresh n) (pairedSlots_cover n)
    hn hη0 hη1 (h.take j) (successfulHistory_take GS M n η (observedSets n N) h hh j)).positive
variable (L : ℕ)
noncomputable def phaseStart (h : List (PhaseSample n N)) (hp : HistoryPositive n N h) : ChainIndex GS → ℝ :=
  plainLift GS (restartLaw n hn M L (restartCap n L) (storedWeights n N h) (historyPositive_all n N h hp) h.length)
noncomputable def phaseChain (h : List (PhaseSample n N)) (hp : HistoryPositive n N h) : FiniteChain (ChainIndex GS) :=
  metropolisChain GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
    (storedWeights n N h h.length) (annealWeight M n h.length) (hp h.length le_rfl)
    (annealWeight_pos M n h.length hn)
noncomputable def phaseKernel (h : List (PhaseSample n N)) (v : PhaseSample n N) : ℝ :=
  if hp : HistoryPositive n N h then
    (phaseChain n hn M N h hp).sampleMass (phaseStart n hn M N L h hp) v else 0
end ActualPhase
section HistoryEstimate
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance107 : Fintype Γ] [sectionInstance108 : DecidableEq Γ]
variable {X : Type*} (sample : X → List (Set Γ))
def historySample (h : List X) (j : ℕ) : List (Set Γ) := (h[j]?.map sample).getD []
end HistoryEstimate
section ActualHistoryEstimate
open scoped BigOperators Classical
variable (n : ℕ) (N L : ℕ) (M : Matroid (Fin n ⊕ Fin n))
noncomputable def historyEstimate (h : List (PhaseSample n N)) : ℝ :=
  (2:ℝ)^n * ∏ j ∈ Finset.range L,
    empiricalRatio (pairedSlots n) (fun S => annealRho n^(n-(M.eRk S).toNat))
      (historySample (observedSets n N) h j)
end ActualHistoryEstimate
section WitnessRestart
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n))
local notation "K" => pairedMatroid M₁ M₂
local notation "GS" => pairedSlots n
def Witnessed (x : PlainIndex GS) : Prop := n-((K).eRk x.val.val).toNat=0
variable (N : ℕ)
noncomputable def witnessKernel (L : ℕ) (h : List (PhaseSample n N)) (x : PlainIndex GS) : ℝ :=
  if hp : HistoryPositive n N h then
    restartLaw n hn K L (restartCap n L) (storedWeights n N h) (historyPositive_all n N h hp) h.length x
  else 0
end WitnessRestart
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (N L : ℕ)
local notation "K" => pairedMatroid M₁ M₂
local notation "GS" => pairedSlots n
noncomputable def guardedEstimate (h : List (PhaseSample n N)) (x : PlainIndex GS) : ℝ :=
  if Witnessed n M₁ M₂ x then historyEstimate n N L K h else 0
noncomputable def actualRealSuccess (ε : ℝ) : ℝ :=
  FiniteExperiment.expect (phaseKernel n hn K N L) L [] (fun h =>
    ∑ x,if (1-ε)*(commonBaseCount M₁ M₂:ℝ) ≤ guardedEstimate n M₁ M₂ N L h x ∧
      guardedEstimate n M₁ M₂ N L h x ≤ (1+ε)*(commonBaseCount M₁ M₂:ℝ)
      then witnessKernel n hn M₁ M₂ N L h x else 0)

end

section

open scoped BigOperators Classical
abbrev BitWord (h : ℕ) := Fin h → Bool

def wordEquiv : (h : ℕ) → BitWord h ≃ Fin (2^h)
  | 0 => { toFun := fun _ => 0
           invFun := fun _ => Fin.elim0
           left_inv := fun _ => Subsingleton.elim _ _
           right_inv := by
             intro i
             apply Fin.ext
             have hi : i.val < 1 := by exact i.isLt
             change 0=i.val
             omega }
  | h+1 => (Fin.consEquiv (fun _ : Fin (h+1) => Bool)).symm |>.trans
      ((Equiv.prodCongr finTwoEquiv.symm (wordEquiv h)).trans
        (finProdFinEquiv.trans (finCongr (by rw [pow_succ]; omega : 2*2^h=2^(h+1)))))
noncomputable def uniformAverage {X : Type*} [Fintype X] (f : X → ℝ) : ℝ :=
  (∑ x,f x)/(Fintype.card X:ℝ)

def boundedUniform (v h : ℕ) : (D : ℕ) → (Fin D → BitWord h) → Option (Fin v)
  | 0,_ => none
  | D+1,bits =>
      let x := wordEquiv h (bits 0)
      if hx : x.val < v then some ⟨x.val,hx⟩ else boundedUniform v h D (Fin.tail bits)
noncomputable def rejectionRate (v h : ℕ) : ℝ := (2^h-v:ℕ)/(2:ℝ)^h

def bitPrefix (h H : ℕ) (hh : h≤H) (w : BitWord H) : BitWord h :=
  fun i => w ⟨i.val,lt_of_lt_of_le i.isLt hh⟩

inductive DrawTree (A : Type*) where
  | leaf (a : A)
  | draw (v : ℕ) (next : Fin (v+1) → DrawTree A)
namespace DrawTree

section
open scoped BigOperators Classical
variable {A : Type*}
noncomputable def expectation (φ : A → ℝ) : DrawTree A → ℝ
  | .leaf a => φ a
  | .draw _ k => uniformAverage (fun i => expectation φ (k i))

def Fits (H : ℕ) : ℕ → DrawTree A → Prop
  | _,.leaf _ => True
  | 0,.draw _ _ => False
  | M+1,.draw v k => Nat.clog 2 (v+1)≤H ∧ ∀ i,Fits H M (k i)

def run (H D : ℕ) : (M : ℕ) → DrawTree A → (Fin M → Fin D → BitWord H) → Option A
  | _,.leaf a,_ => some a
  | 0,.draw _ _,_ => none
  | M+1,.draw v k,bits =>
    if hh : Nat.clog 2 (v+1)≤H then
      match boundedUniform (v+1) (Nat.clog 2 (v+1)) D
        (fun d => bitPrefix _ H hh (bits 0 d)) with
      | none => none
      | some a => run H D M (k a) (Fin.tail bits)
    else none

def optionValue (φ : A → ℝ) : Option A → ℝ
  | none => 0
  | some a => φ a
end
open scoped BigOperators Classical
variable {A B : Type*}
def bind : DrawTree A → (A → DrawTree B) → DrawTree B
  | .leaf a,k => k a
  | .draw v f,k => .draw v (fun i => (f i).bind k)
def map (f : A → B) (t : DrawTree A) : DrawTree B := t.bind (fun a => .leaf (f a))

def rationalCoin (a v : ℕ) : DrawTree Bool :=
  .draw v (fun i => .leaf (decide (i.val<a)))
def bernoulli (p : ℚ) : DrawTree Bool := rationalCoin p.num.natAbs (p.den-1)
end DrawTree

section MetropolisSampling
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance110 : Fintype Γ] [sectionInstance111 : DecidableEq Γ]
abbrev ExchangeChoice (S : Set Γ) := ↥S × ↥(Sᶜ)
def exchangedSet (S : Set Γ) (a : ExchangeChoice S) : Set Γ := insert a.2.val (S\{a.1.val})
lemma exchangeChoice_card (gs : List (Γ × Γ)) (hgs : SlotsFresh gs ∅)
    (hcover : pairSupport gs=Set.univ) (S : ChainIndex gs) :
    Fintype.card (ExchangeChoice S.val)=gs.length^2 := by
  rw [Fintype.card_prod]
  have hs : Fintype.card S.val=S.val.ncard := by rw [Set.ncard_eq_toFinset_card',Set.toFinset_card]
  have hc : Fintype.card ↥(S.valᶜ)=S.valᶜ.ncard := by rw [Set.ncard_eq_toFinset_card',Set.toFinset_card]
  rw [hs,hc]
  have ht := S.val.ncard_add_ncard_compl
  rw [Nat.card_eq_fintype_card,slots_ground_card gs hgs hcover,S.property.ncard hgs hcover] at ht
  have hh : S.valᶜ.ncard=gs.length := by omega
  rw [S.property.ncard hgs hcover,hh,pow_two]

noncomputable def metropolisAcceptance (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) (S T : ChainIndex gs) : ℝ :=
  min 1 (chainWeight gs w f T.val/chainWeight gs w f S.val)
noncomputable def exchangeOffer (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℝ) (f : Set Γ → ℝ) (S : ChainIndex gs)
    (a : ExchangeChoice S.val) (φ : ChainIndex gs → ℝ) : ℝ :=
  if ht : ChainState gs (exchangedSet S.val a) then
    let T : ChainIndex gs := ⟨exchangedSet S.val a,ht⟩
    metropolisAcceptance gs w f S T*φ T+(1-metropolisAcceptance gs w f S T)*φ S
  else φ S
end MetropolisSampling
section RationalSampling
open scoped BigOperators Classical
variable {Γ : Type*} [sectionInstance112 : Fintype Γ] [sectionInstance113 : DecidableEq Γ]
noncomputable def rationalMultiplier (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℚ) (S : Set Γ) : ℚ :=
  if h : ∃ p r,DefectState gs p r S then w h.choose h.choose_spec.choose else 1
noncomputable def rationalWeight (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℚ) (f : Set Γ → ℚ) (S : Set Γ) : ℚ :=
  rationalMultiplier gs w S*f S
noncomputable def rationalEmpirical (H : Set Γ → ℚ) (l : List (Set Γ)) : ℚ :=
  (l.map H).sum/(l.length:ℚ)
noncomputable def rationalPlain (gs : List (Γ × Γ)) (g : Set Γ → ℚ) (S : Set Γ) : ℚ :=
  if PlainState gs S then g S else 0
noncomputable def rationalDefect (gs : List (Γ × Γ)) (p r : Γ × Γ) (S : Set Γ) : ℚ :=
  if DefectState gs p r S then 1 else 0
noncomputable def rationalLearn (gs : List (Γ × Γ))
    (w : (Γ × Γ) → (Γ × Γ) → ℚ) (l : List (Set Γ)) (p r : Γ × Γ) : ℚ :=
  if p∈gs ∧ r∈gs ∧ p≠r then
    w p r*(rationalEmpirical (rationalPlain gs (fun _ => 1)) l/rationalEmpirical (rationalDefect gs p r) l)
  else w p r
noncomputable def rationalHistory {X : Type*} (gs : List (Γ × Γ))
    (sample : X → List (Set Γ)) (h : List X) : (Γ × Γ) → (Γ × Γ) → ℚ :=
  h.foldl (fun w x => rationalLearn gs w (sample x)) (fun _ _ => 4)

def rationalRho (n : ℕ) : ℚ := 1-1/(2*(n:ℚ))
noncomputable def rationalAnneal (M : Matroid Γ) (n j : ℕ) (S : Set Γ) : ℚ :=
  (rationalRho n^j)^(n-(M.eRk S).toNat)
noncomputable def rationalRatio (gs : List (Γ × Γ)) (g : Set Γ → ℚ) (l : List (Set Γ)) : ℚ :=
  rationalEmpirical (rationalPlain gs g) l / rationalEmpirical (rationalPlain gs (fun _ => 1)) l
end RationalSampling

namespace DrawTree
open scoped BigOperators Classical
noncomputable def uniformChoice (A : Type*) [Fintype A] [Nonempty A] : DrawTree A :=
  let h : Fintype.card A-1+1=Fintype.card A := by have hh := Fintype.card_pos (α := A); omega
  .draw (Fintype.card A-1) (fun i => .leaf ((Fintype.equivFin A).symm (Fin.cast h i)))
end DrawTree

section RationalMetropolis
open scoped BigOperators Classical
variable {Γ : Type*} [Fintype Γ] [sectionInstance115 : DecidableEq Γ]
variable (gs : List (Γ × Γ))
noncomputable def rationalAcceptance (w : (Γ × Γ) → (Γ × Γ) → ℚ) (f : Set Γ → ℚ)
    (S T : ChainIndex gs) : ℚ := min 1 (rationalWeight gs w f T.val/rationalWeight gs w f S.val)
noncomputable def rationalExchange (w : (Γ × Γ) → (Γ × Γ) → ℚ) (f : Set Γ → ℚ)
    (S : ChainIndex gs) (a : ExchangeChoice S.val) : DrawTree (ChainIndex gs) :=
  if ht : ChainState gs (exchangedSet S.val a) then
    let T : ChainIndex gs := ⟨exchangedSet S.val a,ht⟩
    (DrawTree.bernoulli (rationalAcceptance gs w f S T)).map (fun accept => if accept then T else S)
  else .leaf S
noncomputable def rationalMetropolis (hgs : SlotsFresh gs ∅) (hne : gs≠[])
    (hcover : pairSupport gs=Set.univ) (w : (Γ × Γ) → (Γ × Γ) → ℚ) (f : Set Γ → ℚ)
    (S : ChainIndex gs) : DrawTree (ChainIndex gs) :=
  letI : Nonempty (ExchangeChoice S.val) := Fintype.card_pos_iff.mp (by
    rw [exchangeChoice_card gs hgs hcover S]; exact pow_pos (List.length_pos_iff.mpr hne) _)
  (DrawTree.bernoulli (1/2)).bind (fun lazy => if lazy then .leaf S else
    (DrawTree.uniformChoice (ExchangeChoice S.val)).bind (rationalExchange gs w f S))
end RationalMetropolis
namespace DrawTree
open scoped BigOperators Classical
variable {A B D : Type*}
def optionBind (t : DrawTree (Option A)) (k : A → DrawTree (Option B)) : DrawTree (Option B) :=
  t.bind (fun z => match z with | none => .leaf none | some a => k a)
def pureSome (a : A) : DrawTree (Option A) := .leaf (some a)

def Matches [Fintype B] (t : A → DrawTree (Option B)) (P : A → B → ℝ) : Prop :=
  ∀ a φ,(t a).expectation (optionValue φ)=∑ b,P a b*φ b

def iterate (t : A → DrawTree (Option A)) : ℕ → A → DrawTree (Option A)
  | 0,a => pureSome a
  | k+1,a => (iterate t k a).optionBind t
end DrawTree

section DrawHitting
open scoped BigOperators Classical
variable {X : Type*} [sectionInstance116 : Fintype X]
variable (step : X → DrawTree X) (A : X → Prop)
noncomputable def hittingTree : ℕ → X → DrawTree (Option (FiniteChain.Inside A))
  | 0,_ => .leaf none
  | k+1,x => (step x).bind (fun y => if hy : A y then .leaf (some ⟨y,hy⟩) else hittingTree k y)
end DrawHitting
namespace DrawTree
open scoped BigOperators Classical
variable {X Y : Type*} [sectionInstance117 : Fintype X]
def sample (step : X → DrawTree X) : (k : ℕ) → X → DrawTree (FiniteChain.Sample X k)
  | 0,x => .leaf x
  | k+1,x => (step x).bind (fun y => (sample step k y).map (fun v => (x,v)))

def history (K : List X → DrawTree (Option X)) : ℕ → List X → DrawTree (Option (List X))
  | 0,h => pureSome h
  | k+1,h => (K h).optionBind (fun x => history K k (h++[x]))
end DrawTree
section DrawActual
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M : Matroid (Fin n ⊕ Fin n)) (N L : ℕ)
local notation "GS" => pairedSlots n
noncomputable def rationalStored (h : List (PhaseSample n N)) (j : ℕ) :=
  rationalHistory GS (observedSets n N) (h.take j)
noncomputable def actualStepTree (h : List (PhaseSample n N)) (j : ℕ) : ChainIndex GS → DrawTree (ChainIndex GS) :=
  rationalMetropolis GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
    (rationalStored n N h j) (rationalAnneal M n j)
noncomputable def actualTraceTree (h : List (PhaseSample n N)) (j cap : ℕ) :
    PlainIndex GS → DrawTree (Option (PlainIndex GS)) :=
  fun x => hittingTree (actualStepTree n hn M N h j) (plainPredicate GS) (cap+1) x.val
noncomputable def actualRestartTree (h : List (PhaseSample n N)) (cap : ℕ) : ℕ → DrawTree (Option (PlainIndex GS))
  | 0 =>
    letI := plainIndex_nonempty GS (pairedSlots_fresh n) (pairedSlots_cover n)
    (DrawTree.uniformChoice (PlainIndex GS)).map some
  | j+1 => (actualRestartTree h cap j).optionBind
      (DrawTree.iterate (actualTraceTree n hn M N h (j+1) cap) (20*n^4*(n*(L+1)+2)))
noncomputable def actualStartTree (h : List (PhaseSample n N)) : DrawTree (Option (ChainIndex GS)) :=
  (actualRestartTree n hn M N L h (restartCap n L) h.length).map (Option.map Subtype.val)
noncomputable def actualPhaseTree (h : List (PhaseSample n N)) : DrawTree (Option (PhaseSample n N)) :=
  if HistoryPositive n N h then
    (actualStartTree n hn M N L h).optionBind
      (fun x => (DrawTree.sample (actualStepTree n hn M N h h.length) (N-1) x).map some)
  else .leaf none
end DrawActual
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (N L : ℕ)
local notation "K" => pairedMatroid M₁ M₂
local notation "GS" => pairedSlots n
noncomputable def rationalEstimate (h : List (PhaseSample n N)) : ℚ :=
  (2:ℚ)^n * ∏ j ∈ Finset.range L,
    rationalRatio GS (fun S => rationalRho n^(n-((K).eRk S).toNat))
      (historySample (observedSets n N) h j)
noncomputable def rationalGuard (h : List (PhaseSample n N)) (x : PlainIndex GS) : ℚ :=
  if Witnessed n M₁ M₂ x then rationalEstimate n M₁ M₂ N L h else 0
noncomputable def rationalWitnessTree (h : List (PhaseSample n N)) : DrawTree (Option (PlainIndex GS)) :=
  if HistoryPositive n N h then actualRestartTree n hn K N L h (restartCap n L) h.length else .leaf none
noncomputable def rationalAlgorithmTree : DrawTree (Option ℚ) :=
  (DrawTree.history (actualPhaseTree n hn K N L) L []).optionBind (fun h =>
    (rationalWitnessTree n hn M₁ M₂ N L h).map (Option.map (rationalGuard n M₁ M₂ N L h)))

end

open scoped BigOperators Classical

def RatBound (q : ℚ) (B : ℕ) : Prop :=
  ∃ a b : ℕ,a≤B ∧ b≤B ∧ 0<b ∧ q=(a:ℚ)/(b:ℚ)

open scoped BigOperators Classical
def drawWordBound (n N L : ℕ) : ℕ := 2*(2+N*L+(2*n)*(n*L))+n^2+n+4

section ActualFits
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M : Matroid (Fin n ⊕ Fin n)) (N L : ℕ) (hN : 0<N)

def phaseDrawBound (n N L : ℕ) : ℕ := 1+3*L*(restartCap n L+1)*(20*n^4*(n*(L+1)+2))+3*N
end ActualFits

section AlgorithmFits
open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (N L : ℕ) (hN : 0<N)
def algorithmDrawBound (n N L : ℕ) : ℕ := phaseDrawBound n N L*(L+1)
end AlgorithmFits

namespace DrawTree
variable {A B : Type*}
def All (p : A → Prop) : DrawTree A → Prop
  | .leaf a => p a
  | .draw _ k => ∀ i,All p (k i)
end DrawTree
open scoped BigOperators Classical
abbrev FairBank (n N L : ℕ) :=
  Fin (algorithmDrawBound n N L) → Fin (algorithmDrawBound n N L+6) → BitWord (drawWordBound n N L)
noncomputable def fairOutput (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (N L : ℕ) (bits : FairBank n N L) : ℚ :=
  ((DrawTree.run (drawWordBound n N L) (algorithmDrawBound n N L+6) (algorithmDrawBound n N L)
    (rationalAlgorithmTree n hn M₁ M₂ N L) bits).join).getD 0
open scoped BigOperators Classical

def rationalMedian (l : List ℚ) : ℚ := (l.mergeSort (· ≤ ·)).getD (l.length/2) 0

def outsideInterval (a b x : ℚ) : Bool := decide (¬(a≤x ∧ x≤b))

def repetitionCount (δ : ℚ) : ℕ := 64*(δ.den.bits.length+1)

open scoped BigOperators Classical

def accuracyExponent (ε : ℚ) : ℕ := ε.den.bits.length+5

def sampleCount (n L : ℕ) (ε : ℚ) : ℕ := 10^14*(L+1)^3*n^14*⌈ε⁻¹⌉₊^2+1

noncomputable def amplifiedOutput (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (N L : ℕ) (δ : ℚ)
    (bits : Fin (repetitionCount δ) → FairBank n N L) : ℚ :=
  rationalMedian (List.ofFn (fun i => fairOutput n hn M₁ M₂ N L (bits i)))

abbrev FPRASBank (n : ℕ) (ε δ : ℚ) := Fin (repetitionCount δ) →
  FairBank n (sampleCount n (annealLength n (accuracyExponent ε)) ε) (annealLength n (accuracyExponent ε))
noncomputable def boundedFPRASOutput (n : ℕ) (hn : 0<n) (M₁ M₂ : Matroid (Fin n)) (ε δ : ℚ) (bits : FPRASBank n ε δ) : ℚ :=
  amplifiedOutput n hn M₁ M₂ (sampleCount n (annealLength n (accuracyExponent ε)) ε)
    (annealLength n (accuracyExponent ε)) δ bits

section GreedyOracle
variable {α : Type*} [sectionInstance120 : DecidableEq α]

def greedyOracle (O : Finset α → Bool) : List α → Finset α
  | [] => ∅
  | a::l => let I := greedyOracle O l
            if O (insert a I) then insert a I else I
end GreedyOracle

open scoped Classical

def queryFinset {n : ℕ} (O : IndependenceOracle n) (which : Bool) (I : Finset (Fin n)) : Bool :=
  O which (fun i => decide (i ∈ I))
def rankOracle {n : ℕ} (O : IndependenceOracle n) (which : Bool) (S : Finset (Fin n)) : ℕ :=
  (greedyOracle (queryFinset O which) ((List.finRange n).filter (fun i => decide (i ∈ S)))).card

open scoped Classical

def pairedDeficiencyOracle {n : ℕ} (r : ℕ) (O : IndependenceOracle n)
    (Sx Sy : Finset (Fin n)) : ℕ :=
  n+r-(rankOracle O false Sx+Sy.card+rankOracle O true Syᶜ)

section

open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (r : ℕ) (O : IndependenceOracle n) (N L : ℕ)
local notation "GS" => pairedSlots n
noncomputable def oracleDeficiency (S : Set (Fin n ⊕ Fin n)) : ℕ :=
  pairedDeficiencyOracle r O (Sum.inl ⁻¹' S).toFinset (Sum.inr ⁻¹' S).toFinset
noncomputable def oracleAnneal (j : ℕ) (S : Set (Fin n ⊕ Fin n)) : ℚ :=
  (rationalRho n^j)^(oracleDeficiency n r O S)
def rationalHistoryPositive (h : List (PhaseSample n N)) : Prop :=
  ∀ j ≤ h.length,∀ p q,0<rationalStored n N h j p q
noncomputable def oracleStepTree (h : List (PhaseSample n N)) (j : ℕ) : ChainIndex GS → DrawTree (ChainIndex GS) :=
  rationalMetropolis GS (pairedSlots_fresh n) (pairedSlots_ne_nil n hn) (pairedSlots_cover n)
    (rationalStored n N h j) (oracleAnneal n r O j)
noncomputable def oracleTraceTree (h : List (PhaseSample n N)) (j cap : ℕ) :
    PlainIndex GS → DrawTree (Option (PlainIndex GS)) :=
  fun x => hittingTree (oracleStepTree n hn r O N h j) (plainPredicate GS) (cap+1) x.val
noncomputable def oracleRestartTree (h : List (PhaseSample n N)) (cap : ℕ) : ℕ → DrawTree (Option (PlainIndex GS))
  | 0 =>
    letI := plainIndex_nonempty GS (pairedSlots_fresh n) (pairedSlots_cover n)
    (DrawTree.uniformChoice (PlainIndex GS)).map some
  | j+1 => (oracleRestartTree h cap j).optionBind
      (DrawTree.iterate (oracleTraceTree n hn r O N h (j+1) cap) (20*n^4*(n*(L+1)+2)))
noncomputable def oracleStartTree (h : List (PhaseSample n N)) : DrawTree (Option (ChainIndex GS)) :=
  (oracleRestartTree n hn r O N L h (restartCap n L) h.length).map (Option.map Subtype.val)
noncomputable def oraclePhaseTree (h : List (PhaseSample n N)) : DrawTree (Option (PhaseSample n N)) :=
  if rationalHistoryPositive n N h then
    (oracleStartTree n hn r O N L h).optionBind
      (fun x => (DrawTree.sample (oracleStepTree n hn r O N h h.length) (N-1) x).map some)
  else .leaf none
noncomputable def oracleEstimate (h : List (PhaseSample n N)) : ℚ :=
  (2:ℚ)^n * ∏ j ∈ Finset.range L,
    rationalRatio GS (fun S => rationalRho n^(oracleDeficiency n r O S))
      (historySample (observedSets n N) h j)
noncomputable def oracleGuard (h : List (PhaseSample n N)) (x : PlainIndex GS) : ℚ :=
  if oracleDeficiency n r O x.val.val=0 then oracleEstimate n r O N L h else 0
noncomputable def oracleWitnessTree (h : List (PhaseSample n N)) : DrawTree (Option (PlainIndex GS)) :=
  if rationalHistoryPositive n N h then oracleRestartTree n hn r O N L h (restartCap n L) h.length else .leaf none

noncomputable def oracleAlgorithmTree : DrawTree (Option ℚ) :=
  (DrawTree.history (oraclePhaseTree n hn r O N L) L []).optionBind (fun h =>
    (oracleWitnessTree n hn r O N L h).map (Option.map (oracleGuard n r O N L h)))

end

def bitResourceParameter (n : ℕ) (ε δ : ℚ) : ℕ :=
  1+n+ε.den.bits.length+δ.den.bits.length+⌈ε⁻¹⌉₊
def fairBitCount (n : ℕ) (ε δ : ℚ) : ℕ :=
  let L := annealLength n (accuracyExponent ε)
  let N := sampleCount n L ε
  repetitionCount δ * drawWordBound n N L * (algorithmDrawBound n N L+6) * algorithmDrawBound n N L

open scoped BigOperators Classical

open scoped BigOperators Classical

noncomputable def wordPlainEquiv (n : ℕ) : BitWord n ≃ PlainIndex (pairedSlots n) where
  toFun b := ⟨⟨transversal {i | b i=true},Or.inl (transversal_plain n _)⟩,transversal_plain n _⟩
  invFun S := fun i => decide (Sum.inl i∈S.val.val)
  left_inv b := by
    funext i
    change decide (Sum.inl i∈transversal {i | b i=true})=b i
    change decide (i∈Sum.inl ⁻¹' transversal {i | b i=true})=b i
    rw [preimage_inl_transversal]
    cases h : b i <;> simp [h]
  right_inv S := by
    apply Subtype.ext
    apply Subtype.ext
    change transversal {i | decide (Sum.inl i∈S.val.val)=true}=S.val.val
    have he : {i | decide (Sum.inl i∈S.val.val)=true}=Sum.inl ⁻¹' S.val.val := by
      ext i
      simp
    rw [he]
    exact (plain_eq_transversal n S.val.val S.property).symm

noncomputable def sortedMemberEquiv {Γ : Type*} [Fintype Γ] [LinearOrder Γ] (S : Set Γ) :
    Fin (Fintype.card S) ≃ S := by
  let c : ↥S.toFinset ≃ S :=
    { toFun := fun a => ⟨a.val,Set.mem_toFinset.mp a.property⟩
      invFun := fun a => ⟨a.val,Set.mem_toFinset.mpr a.property⟩
      left_inv := by intro a; rfl
      right_inv := by intro a; rfl }
  let e := (S.toFinset.orderIsoOfFin rfl).toEquiv.trans c
  have he : Fintype.card S=S.toFinset.card := by
    simpa only [Fintype.card_fin] using (Fintype.card_congr e).symm
  exact (finCongr he).trans e
noncomputable def canonicalPlainEnumeration (n : ℕ) :
    Fin (Fintype.card (PlainIndex (pairedSlots n))) ≃ PlainIndex (pairedSlots n) :=
  (finCongr (plainIndex_card n)).trans ((wordEquiv n).symm.trans (wordPlainEquiv n))
noncomputable def canonicalExchangeEnumeration (n : ℕ) (S : Set (Fin n ⊕ Fin n)) :
    Fin (Fintype.card (ExchangeChoice S)) ≃ ExchangeChoice S := by
  letI : LinearOrder (Fin n ⊕ Fin n) := LinearOrder.lift' (finSumFinEquiv : Fin n ⊕ Fin n ≃ Fin (n+n)) (Equiv.injective _)
  let e := finProdFinEquiv.symm.trans ((sortedMemberEquiv S).prodCongr (sortedMemberEquiv Sᶜ))
  have he : Fintype.card (ExchangeChoice S)=_ := (Fintype.card_congr e).symm
  exact (finCongr (by simpa only [Fintype.card_fin] using he)).trans e

section
namespace DrawTree
open scoped BigOperators Classical
variable {A B : Type*}

inductive Reindexed : DrawTree A → DrawTree A → Prop
  | leaf (a : A) : Reindexed (.leaf a) (.leaf a)
  | draw (v : ℕ) (k l : Fin (v+1) → DrawTree A) (e : Fin (v+1) ≃ Fin (v+1))
      (h : ∀ i,Reindexed (k i) (l (e i))) : Reindexed (.draw v k) (.draw v l)
noncomputable def uniformVia (A : Type*) [Fintype A] [Nonempty A]
    (e : Fin (Fintype.card A) ≃ A) : DrawTree A :=
  let h : Fintype.card A-1+1=Fintype.card A := by have hh := Fintype.card_pos (α := A); omega
  .draw (Fintype.card A-1) (fun i => .leaf (e (Fin.cast h i)))
end DrawTree

open scoped BigOperators Classical
variable (n : ℕ) (hn : 0<n) (r : ℕ) (O : IndependenceOracle n) (N L : ℕ)
local notation "GS" => pairedSlots n
noncomputable def canonicalMetropolis (w : ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) →
    ((Fin n ⊕ Fin n) × (Fin n ⊕ Fin n)) → ℚ) (f : Set (Fin n ⊕ Fin n) → ℚ)
    (S : ChainIndex GS) : DrawTree (ChainIndex GS) :=
  letI : Nonempty (ExchangeChoice S.val) := Fintype.card_pos_iff.mp (by
    rw [exchangeChoice_card GS (pairedSlots_fresh n) (pairedSlots_cover n),pairedSlots_length]; positivity)
  (DrawTree.bernoulli (1/2)).bind (fun lazy => if lazy then .leaf S else
    (DrawTree.uniformVia (ExchangeChoice S.val) (canonicalExchangeEnumeration n S.val)).bind
      (rationalExchange GS w f S))
noncomputable def canonicalStepTree (h : List (PhaseSample n N)) (j : ℕ) : ChainIndex GS → DrawTree (ChainIndex GS) :=
  canonicalMetropolis n hn (rationalStored n N h j) (oracleAnneal n r O j)
noncomputable def canonicalTraceTree (h : List (PhaseSample n N)) (j cap : ℕ) :
    PlainIndex GS → DrawTree (Option (PlainIndex GS)) :=
  fun x => hittingTree (canonicalStepTree n hn r O N h j) (plainPredicate GS) (cap+1) x.val
noncomputable def canonicalRestartTree (h : List (PhaseSample n N)) (cap : ℕ) : ℕ → DrawTree (Option (PlainIndex GS))
  | 0 =>
    letI := plainIndex_nonempty GS (pairedSlots_fresh n) (pairedSlots_cover n)
    (DrawTree.uniformVia (PlainIndex GS) (canonicalPlainEnumeration n)).map some
  | j+1 => (canonicalRestartTree h cap j).optionBind
      (DrawTree.iterate (canonicalTraceTree n hn r O N h (j+1) cap) (20*n^4*(n*(L+1)+2)))
noncomputable def canonicalStartTree (h : List (PhaseSample n N)) : DrawTree (Option (ChainIndex GS)) :=
  (canonicalRestartTree n hn r O N L h (restartCap n L) h.length).map (Option.map Subtype.val)
noncomputable def canonicalPhaseTree (h : List (PhaseSample n N)) : DrawTree (Option (PhaseSample n N)) :=
  if rationalHistoryPositive n N h then
    (canonicalStartTree n hn r O N L h).optionBind
      (fun x => (DrawTree.sample (canonicalStepTree n hn r O N h h.length) (N-1) x).map some)
  else .leaf none
noncomputable def canonicalWitnessTree (h : List (PhaseSample n N)) : DrawTree (Option (PlainIndex GS)) :=
  if rationalHistoryPositive n N h then canonicalRestartTree n hn r O N L h (restartCap n L) h.length else .leaf none
noncomputable def canonicalAlgorithmTree : DrawTree (Option ℚ) :=
  (DrawTree.history (canonicalPhaseTree n hn r O N L) L []).optionBind (fun h =>
    (canonicalWitnessTree n hn r O N L h).map (Option.map (oracleGuard n r O N L h)))

end

section CanonicalFair
open scoped BigOperators Classical
noncomputable def canonicalFairOutput (n : ℕ) (hn : 0<n) (r : ℕ) (O : IndependenceOracle n)
    (N L : ℕ) (bits : FairBank n N L) : ℚ :=
  ((DrawTree.run (drawWordBound n N L) (algorithmDrawBound n N L+6) (algorithmDrawBound n N L)
    (canonicalAlgorithmTree n hn r O N L) bits).join).getD 0
noncomputable def canonicalAmplifiedOutput (n : ℕ) (hn : 0<n) (r : ℕ) (O : IndependenceOracle n)
    (N L : ℕ) (δ : ℚ) (bits : Fin (repetitionCount δ) → FairBank n N L) : ℚ :=
  rationalMedian (List.ofFn (fun i => canonicalFairOutput n hn r O N L (bits i)))
noncomputable def canonicalBoundedOutput (n r : ℕ) (O : IndependenceOracle n)
    (ε δ : ℚ) (bits : FPRASBank n ε δ) : ℚ :=
  if hn : 0<n then
    canonicalAmplifiedOutput n hn r O (sampleCount n (annealLength n (accuracyExponent ε)) ε)
      (annealLength n (accuracyExponent ε)) δ bits
  else 1
end CanonicalFair

open scoped BigOperators Classical

open scoped BigOperators Classical

def bitMatrixEquiv (a b : ℕ) : BitWord (a*b) ≃ (Fin a → BitWord b) :=
  (Equiv.arrowCongr finProdFinEquiv.symm (Equiv.refl Bool)).trans
    (Equiv.curry (Fin a) (Fin b) Bool)
def fourBitEquiv (a b c d : ℕ) : BitWord (a*(b*(c*d))) ≃
    (Fin a → Fin b → Fin c → BitWord d) :=
  (bitMatrixEquiv a (b*(c*d))).trans
    (Equiv.piCongrRight (fun _ => (bitMatrixEquiv b (c*d)).trans
      (Equiv.piCongrRight (fun _ => bitMatrixEquiv c d))))

open scoped BigOperators Classical

section

variable {k n : ℕ}
def workConfig (R : Fin (k+8) → List Bool) (pc : Fin 6) (q c : ℕ) : MachineConfig k 5 :=
  ⟨R,pc,none,q,c⟩
def pairRegisters (R : Fin (k+8) → List Bool) (a b : Fin (k+8)) (xs ys : List Bool) :=
  Function.update (Function.update R a xs) b ys
def incrementProgram (a b : Fin (k+8)) : Program k 5 := fun pc =>
  match pc.val with
  | 0 => .pop a 1 1 2
  | 1 => .push a true 3
  | 2 => .push b false 0
  | 3 => .pop b 5 4 4
  | 4 => .push a false 3
  | _ => .halt
def leadingOnes : List Bool → ℕ
  | [] => 0
  | false::_ => 0
  | true::xs => leadingOnes xs+1
def carryTail : List Bool → List Bool
  | [] => [true]
  | false::xs => true::xs
  | true::xs => carryTail xs
def incrementBits (xs : List Bool) : List Bool := List.replicate (leadingOnes xs) false ++ carryTail xs

end

section

variable {k s t n : ℕ}
def Instruction.relabel (e : Fin (s+1) → Fin (t+1)) : Instruction k s → Instruction k t
  | .push i b j => .push i b (e j)
  | .pop i x y z => .pop i (e x) (e y) (e z)
  | .coin x y => .coin (e x) (e y)
  | .query w i x y => .query w i (e x) (e y)
  | .halt => .halt
def relabelConfig (e : Fin (s+1) → Fin (t+1)) (c : MachineConfig k s) : MachineConfig k t :=
  ⟨c.registers,e c.control,c.output,c.oracleCalls,c.bitOperations⟩
def firstLabel (s t : ℕ) (i : Fin (s+1)) : Fin (s+t+1) := ⟨i.val,by omega⟩
def secondLabel (s t : ℕ) (i : Fin (t+1)) : Fin (s+t+1) := ⟨s+i.val,by omega⟩
def lastLabel (s : ℕ) : Fin (s+1) := ⟨s,by omega⟩

def concatProgram (P : Program k s) (Q : Program k t) : Program k (s+t) := fun i =>
  if hi : i.val<s then (P ⟨i.val,by omega⟩).relabel (firstLabel s t)
  else (Q ⟨i.val-s,by omega⟩).relabel (secondLabel s t)
lemma concatProgram_second (P : Program k s) (Q : Program k t) (i : Fin (t+1)) :
    concatProgram P Q (secondLabel s t i)=(Q i).relabel (secondLabel s t) := by
  simp [concatProgram,secondLabel,show ¬s+i.val<s by omega]

def restartControl (c : MachineConfig k s) (j : Fin (t+1)) : MachineConfig k t :=
  ⟨c.registers,j,c.output,c.oracleCalls,c.bitOperations⟩

end

section
variable {k n : ℕ}
def tripleRegisters (R : Fin (k+8) → List Bool) (a b d : Fin (k+8)) (xs ys zs : List Bool) :=
  Function.update (pairRegisters R a b xs ys) d zs
def copyConfig (R : Fin (k+8) → List Bool) (pc : Fin 9) (q c : ℕ) : MachineConfig k 8 :=
  ⟨R,pc,none,q,c⟩
def copyProgram (a b d : Fin (k+8)) : Program k 8 := fun pc =>
  match pc.val with
  | 0 => .pop a 3 1 2
  | 1 => .push b false 0
  | 2 => .push b true 0
  | 3 => .pop b 8 4 5
  | 4 => .push a false 6
  | 5 => .push a true 7
  | 6 => .push d false 3
  | 7 => .push d true 3
  | _ => .halt

def sumDigit (x y c : Bool) : Bool := xor (xor x y) c
def sumCarry (x y c : Bool) : Bool := (x && y) || (x && c) || (y && c)
def addDigits (c : Bool) (xs ys : List Bool) : List Bool :=
  match xs,ys with
  | [],[] => if c then [true] else []
  | x::xs,[] => sumDigit x false c :: addDigits (sumCarry x false c) xs []
  | [],y::ys => sumDigit false y c :: addDigits (sumCarry false y c) [] ys
  | x::xs,y::ys => sumDigit x y c :: addDigits (sumCarry x y c) xs ys
termination_by xs.length+ys.length
def addCost (c : Bool) (xs ys : List Bool) : ℕ :=
  match xs,ys with
  | [],[] => if c then 3 else 2
  | x::xs,[] => 3+addCost (sumCarry x false c) xs []
  | [],y::ys => 3+addCost (sumCarry false y c) [] ys
  | x::xs,y::ys => 3+addCost (sumCarry x y c) xs ys
termination_by xs.length+ys.length

def addConfig (R : Fin (k+8) → List Bool) (pc : Fin 17) (q c : ℕ) : MachineConfig k 16 :=
  ⟨R,pc,none,q,c⟩
def addProgram (a b d : Fin (k+8)) : Program k 16 := fun pc =>
  match pc.val with
  | 0 => .pop a 2 3 4
  | 1 => .pop a 5 6 7
  | 2 => .pop b 12 8 9
  | 3 => .pop b 8 8 9
  | 4 => .pop b 9 9 10
  | 5 => .pop b 13 9 10
  | 6 => .pop b 9 9 10
  | 7 => .pop b 10 10 11
  | 8 => .push d false 0
  | 9 => .push d true 0
  | 10 => .push d false 1
  | 11 => .push d true 1
  | 12 => .pop d 16 14 15
  | 13 => .push d true 12
  | 14 => .push a false 12
  | 15 => .push a true 12
  | _ => .halt

def mulRegisters (R : Fin (k+8) → List Bool) (xs ys zs ws : List Bool) : Fin (k+8) → List Bool :=
  fun i => if i=0 then xs else if i=1 then ys else if i=2 then zs
    else if i=3 then [] else if i=4 then ws else R i

def mulBody : Program k 24 := concatProgram (copyProgram 0 3 4) (addProgram 2 4 3)
def mulBodyConfig (R : Fin (k+8) → List Bool) (pc : Fin 25) (q c : ℕ) : MachineConfig k 24 :=
  ⟨R,pc,none,q,c⟩
def mulBodyCost (xs zs : List Bool) : ℕ :=
  (5*xs.length+2)+(addCost false zs xs+2*(addDigits false zs xs).length+1)

def mulBodyLabel (i : Fin 25) : Fin 27 := ⟨i.val+1,by omega⟩
def mulProgram : Program k 26 := fun i =>
  if hi : i.val=0 then .pop 1 26 25 1
  else if hj : i.val<25 then
    ((mulBody : Program k 24) ⟨i.val-1,by omega⟩).relabel mulBodyLabel
  else if i.val=25 then .push 0 false 0 else .halt
def mulConfig (R : Fin (k+8) → List Bool) (pc : Fin 27) (q c : ℕ) : MachineConfig k 26 :=
  ⟨R,pc,none,q,c⟩

def mulDigits (xs ys zs : List Bool) : List Bool :=
  match ys with
  | [] => zs
  | bit::ys => mulDigits (false::xs) ys (if bit then addDigits false zs xs else zs)
def mulCost (xs ys zs : List Bool) : ℕ :=
  match ys with
  | [] => 1
  | bit::ys => 2+(if bit then mulBodyCost xs zs else 0)+
      mulCost (false::xs) ys (if bit then addDigits false zs xs else zs)

def coinWord : ℕ → (ℕ → Bool) → List Bool
  | 0,_ => []
  | m+1,bits => bits 1::coinWord m (fun i => bits (3+i))
def randomProgram (counter dest : Fin (k+8)) : Program k 4 := fun pc =>
  match pc.val with
  | 0 => .pop counter 4 1 1
  | 1 => .coin 2 3
  | 2 => .push dest false 0
  | 3 => .push dest true 0
  | _ => .halt

end

open scoped Classical

section

variable {k n : ℕ}
def wordOracle (which : Bool) (O : IndependenceOracle n) (l : List Bool) : Bool :=
  O which (fun i => l[i.val]?.getD false)
def wordGreedy (W : List Bool → Bool) : List Bool → List Bool → List Bool
  | [],ps => ps
  | b::xs,ps => wordGreedy W xs ((b && W (List.replicate xs.length false++true::ps))::ps)
def greedyTime (n : ℕ) : List Bool → ℕ
  | [] => 1
  | b::xs => (if b then 6*n+5 else 3)+greedyTime n xs
def greedyWork (n : ℕ) : List Bool → ℕ
  | [] => 1
  | b::xs => (if b then 8*n+5 else 3)+greedyWork n xs
def greedyCalls : List Bool → ℕ
  | [] => 0
  | b::xs => (if b then 1 else 0)+greedyCalls xs

def greedyRegisters (R : Fin (k+8) → List Bool) (xs zs ps qs : List Bool) : Fin (k+8) → List Bool :=
  fun i => if i=0 then xs else if i=1 then zs else if i=2 then ps else if i=3 then [] else if i=4 then qs else R i
def greedyConfig (R : Fin (k+8) → List Bool) (pc : Fin 27) (q c : ℕ) : MachineConfig k 26 :=
  ⟨R,pc,none,q,c⟩
def greedyCopy₁ (i : Fin 9) : Fin 27 := ⟨i.val+4,by omega⟩
def greedyCopy₂ (i : Fin 9) : Fin 27 := ⟨i.val+13,by omega⟩
def greedyProgram (which : Bool) : Program k 26 := fun pc =>
  if h₁ : 4≤pc.val ∧ pc.val<12 then
    ((copyProgram 2 3 4) ⟨pc.val-4,by omega⟩).relabel greedyCopy₁
  else if h₂ : 13≤pc.val ∧ pc.val<21 then
    ((copyProgram 1 3 4) ⟨pc.val-13,by omega⟩).relabel greedyCopy₂
  else match pc.val with
    | 0 => .pop 0 26 1 2
    | 1 => .pop 1 3 3 3
    | 2 => .pop 1 4 4 4
    | 3 => .push 2 false 0
    | 12 => .push 4 true 13
    | 21 => .query which 4 22 23
    | 22 => .pop 4 24 22 22
    | 23 => .pop 4 25 23 23
    | 24 => .push 2 false 0
    | 25 => .push 2 true 0
    | _ => .halt

end

section

variable {n : ℕ}
def wordSet (n : ℕ) (l : List Bool) : Set (Fin n) := {i | l[i.val]?.getD false=true}

end

section

variable {k n : ℕ}

def popcountBits : List Bool → List Bool → List Bool
  | [],ys => ys
  | b::xs,ys => popcountBits xs (if b then incrementBits ys else ys)
def popcountTime : List Bool → List Bool → ℕ
  | [],_ => 1
  | b::xs,ys => 1+(if b then 4*leadingOnes ys+3 else 0)+popcountTime xs (if b then incrementBits ys else ys)

def popcountConfig (R : Fin (k+8) → List Bool) (pc : Fin 7) (q c : ℕ) : MachineConfig k 6 :=
  ⟨R,pc,none,q,c⟩
def popcountLabel (i : Fin 6) : Fin 7 := if i.val<5 then ⟨i.val+1,by omega⟩ else 0
def popcountProgram : Program k 6 := fun pc =>
  if h0 : pc.val=0 then .pop 2 6 0 1
  else if h1 : pc.val<6 then
    ((incrementProgram 0 3) ⟨pc.val-1,by omega⟩).relabel popcountLabel
  else .halt

end

section
open scoped BigOperators Classical
variable {k n : ℕ}

def rankProgram (which : Bool) : Program k 32 := concatProgram (greedyProgram which) popcountProgram
def rankConfig (R : Fin (k+8) → List Bool) (pc : Fin 33) (q c : ℕ) : MachineConfig k 32 :=
  ⟨R,pc,none,q,c⟩
def rankBits (which : Bool) (O : IndependenceOracle n) (xs : List Bool) : List Bool :=
  popcountBits (wordGreedy (wordOracle which O) xs []) []
def rankTime (which : Bool) (O : IndependenceOracle n) (xs : List Bool) : ℕ :=
  greedyTime n xs+popcountTime (wordGreedy (wordOracle which O) xs []) []
def rankWork (which : Bool) (O : IndependenceOracle n) (xs : List Bool) : ℕ :=
  greedyWork n xs+popcountTime (wordGreedy (wordOracle which O) xs []) []

end

section

variable {k n : ℕ}
def compareBits : Bool → List Bool → List Bool → Bool
  | b,[],[] => b
  | b,[],y::ys => compareBits (if y then true else b) [] ys
  | b,x::xs,[] => compareBits (if x then false else b) xs []
  | b,x::xs,y::ys => compareBits (if x=y then b else y) xs ys

def compareConfig (R : Fin (k+8) → List Bool) (pc : Fin 10) (q c : ℕ) : MachineConfig k 9 :=
  ⟨R,pc,none,q,c⟩
def compareProgram (a b : Fin (k+8)) : Program k 9 := fun pc =>
  match pc.val with
  | 0 => .pop a 1 2 3
  | 1 => .pop b 8 0 4
  | 2 => .pop b 0 0 4
  | 3 => .pop b 0 0 0
  | 4 => .pop a 5 6 7
  | 5 => .pop b 9 4 4
  | 6 => .pop b 4 4 4
  | 7 => .pop b 0 0 4
  | _ => .halt

def subBorrow (x y c : Bool) : Bool := ((!x) && (y || c)) || (y && c)
def subDigits (c : Bool) (xs ys : List Bool) : List Bool :=
  match xs,ys with
  | [],[] => []
  | x::xs,[] => sumDigit x false c :: subDigits (subBorrow x false c) xs []
  | [],y::ys => sumDigit false y c :: subDigits (subBorrow false y c) [] ys
  | x::xs,y::ys => sumDigit x y c :: subDigits (subBorrow x y c) xs ys
termination_by xs.length+ys.length
def subCost (c : Bool) (xs ys : List Bool) : ℕ :=
  match xs,ys with
  | [],[] => 2
  | x::xs,[] => 3+subCost (subBorrow x false c) xs []
  | [],y::ys => 3+subCost (subBorrow false y c) [] ys
  | x::xs,y::ys => 3+subCost (subBorrow x y c) xs ys
termination_by xs.length+ys.length

def subConfig (R : Fin (k+8) → List Bool) (pc : Fin 17) (q c : ℕ) : MachineConfig k 16 :=
  ⟨R,pc,none,q,c⟩
def subProgram (a b d : Fin (k+8)) : Program k 16 := fun pc =>
  match pc.val with
  | 0 => .pop a 2 3 4
  | 1 => .pop a 5 6 7
  | 2 => .pop b 12 8 11
  | 3 => .pop b 8 8 11
  | 4 => .pop b 9 9 8
  | 5 => .pop b 12 11 10
  | 6 => .pop b 11 11 10
  | 7 => .pop b 8 8 11
  | 8 => .push d false 0
  | 9 => .push d true 0
  | 10 => .push d false 1
  | 11 => .push d true 1
  | 12 => .pop d 16 14 15
  | 13 => .halt
  | 14 => .push a false 12
  | 15 => .push a true 12
  | _ => .halt

end

section

variable {k n : ℕ}
def divRegisters (R : Fin (k+8) → List Bool) (xs ys zs ws as bs : List Bool) : Fin (k+8) → List Bool :=
  fun i => if i=0 then xs else if i=1 then ys else if i=2 then zs else if i=3 then ws
    else if i=4 then [] else if i=5 then as else if i=6 then bs else R i
def divCopies : Program k 16 := concatProgram (copyProgram 2 4 5) (copyProgram 1 4 6)
def divCheck : Program k 25 := concatProgram divCopies (compareProgram 5 6)
def divSubtract : Program k 24 := concatProgram (copyProgram 1 4 5) (subProgram 2 5 4)
def divCopiesConfig (R : Fin (k+8) → List Bool) (pc : Fin 17) (q c : ℕ) : MachineConfig k 16 := ⟨R,pc,none,q,c⟩
def divCheckConfig (R : Fin (k+8) → List Bool) (pc : Fin 26) (q c : ℕ) : MachineConfig k 25 := ⟨R,pc,none,q,c⟩
def divSubtractConfig (R : Fin (k+8) → List Bool) (pc : Fin 25) (q c : ℕ) : MachineConfig k 24 := ⟨R,pc,none,q,c⟩

def divCheckCost (ys zs : List Bool) : ℕ := (5*zs.length+2)+(5*ys.length+2)+(2*max zs.length ys.length+2)

def divSubtractCost (ys zs : List Bool) : ℕ := (5*ys.length+2)+(subCost false zs ys+2*(subDigits false zs ys).length+1)

def divisionCheckLabel (i : Fin 26) : Fin 54 :=
  if h : i.val<24 then ⟨3+i.val,by omega⟩ else if i.val=24 then 27 else 52
def divisionSubLabel (i : Fin 25) : Fin 54 := ⟨27+i.val,by omega⟩
def divisionProgram : Program k 53 := fun i =>
  if i.val=0 then .pop 0 53 1 2
  else if i.val=1 then .push 2 false 3
  else if i.val=2 then .push 2 true 3
  else if h : i.val<27 then
    ((divCheck : Program k 25) ⟨i.val-3,by omega⟩).relabel divisionCheckLabel
  else if h : i.val<51 then
    ((divSubtract : Program k 24) ⟨i.val-27,by omega⟩).relabel divisionSubLabel
  else if i.val=51 then .push 3 true 0
  else if i.val=52 then .push 3 false 0
  else .halt
def divisionConfig (R : Fin (k+8) → List Bool) (pc : Fin 54) (q c : ℕ) : MachineConfig k 53 :=
  ⟨R,pc,none,q,c⟩

def divisionRemainder (ys zs : List Bool) : List Bool :=
  if binaryValue zs<binaryValue ys then zs else subDigits false zs ys
def divisionDigit (ys zs : List Bool) : Bool := decide (binaryValue ys≤binaryValue zs)
def divisionDigitCost (ys zs : List Bool) : ℕ :=
  2+divCheckCost ys zs+(if binaryValue zs<binaryValue ys then 0 else divSubtractCost ys zs)+1

def divisionWords : List Bool → List Bool → List Bool → List Bool → List Bool × List Bool
  | [],_,zs,ws => (zs,ws)
  | x::xs,ys,zs,ws => divisionWords xs ys (divisionRemainder ys (x::zs)) (divisionDigit ys (x::zs)::ws)
def divisionCost : List Bool → List Bool → List Bool → ℕ
  | [],_,_ => 1
  | x::xs,ys,zs => divisionDigitCost ys (x::zs)+divisionCost xs ys (divisionRemainder ys (x::zs))

def streamValue : List Bool → ℕ → ℕ
  | [],a => a
  | x::xs,a => streamValue xs (2*a+(if x then 1 else 0))

end

variable {k n : ℕ}
def bitFrame : List Bool → List Bool
  | [] => [true]
  | b::bs => false::b::bitFrame bs

def framePackProgram (a d : Fin (k+8)) : Program k 5 := fun pc =>
  match pc.val with
  | 0 => .push d true 1
  | 1 => .pop a 5 2 3
  | 2 => .push d false 4
  | 3 => .push d true 4
  | 4 => .push d false 1
  | _ => .halt
def frameConfig (R : Fin (k+8) → List Bool) (pc : Fin 6) (q c : ℕ) : MachineConfig k 5 :=
  ⟨R,pc,none,q,c⟩

def frameUnpackProgram (a d : Fin (k+8)) : Program k 5 := fun pc =>
  match pc.val with
  | 0 => .pop a 5 1 4
  | 1 => .pop a 5 2 3
  | 2 => .push d false 0
  | 3 => .push d true 0
  | _ => .halt

end

namespace MachineCode
abbrev Registers (k : ℕ) := Fin (k+8) → List Bool
structure Block (k : ℕ) where
  size : ℕ
  code : Program k size
  exit_halt : code (lastLabel size) = .halt

def state {k s : ℕ} (R : Registers k) (pc : Fin (s+1)) (q w : ℕ) : MachineConfig k s :=
  ⟨R,pc,none,q,w⟩

def Runs {k n : ℕ} (p : Block k) (O : IndependenceOracle n) (bits : ℕ → Bool)
    (R R' : Registers k) (t q w : ℕ) : Prop :=
  ∀ q₀ w₀, execution p.code O (state R 0 q₀ w₀) bits t =
    state R' (lastLabel p.size) (q₀+q) (w₀+w)

def skip (k : ℕ) : Block k := ⟨0,fun _ => .halt,rfl⟩

def seq {k : ℕ} (p q : Block k) : Block k where
  size := p.size+q.size
  code := concatProgram p.code q.code
  exit_halt := by
    have he : lastLabel (p.size+q.size)=secondLabel p.size q.size (lastLabel q.size) := by
      apply Fin.ext; rfl
    rw [he,concatProgram_second,q.exit_halt]
    rfl

def push {k : ℕ} (a : Fin (k+8)) (b : Bool) : Block k where
  size := 1
  code := fun pc => if pc=0 then .push a b 1 else .halt
  exit_halt := by simp [lastLabel]

inductive Test (k : ℕ) where
  | pop (a : Fin (k+8)) (empty zero one : Bool)
  | query (which : Bool) (a : Fin (k+8))
  | coin

def Test.instruction {k s : ℕ} (test : Test k) (no yes : Fin (s+1)) : Instruction k s :=
  match test with
  | .pop a e z o => .pop a (if e then yes else no) (if z then yes else no) (if o then yes else no)
  | .query which a => .query which a no yes
  | .coin => .coin no yes

def Test.result {k n : ℕ} (test : Test k) (O : IndependenceOracle n) (bit : Bool)
    (R : Registers k) : Option (Bool × Registers k) :=
  match test with
  | .pop a e z o => match R a with
    | [] => some (e,R)
    | b::xs => some ((if b then o else z),Function.update R a xs)
  | .query which a => if (R a).length=n then
      some (O which (fun i => (R a)[i.val]?.getD false),R) else none
  | .coin => some (bit,R)

def Test.queries {k : ℕ} : Test k → ℕ
  | .query _ _ => 1
  | _ => 0

def Test.work {k : ℕ} (test : Test k) (n : ℕ) (R : Registers k) : ℕ :=
  match test with
  | .query _ a => (R a).length+n+1
  | _ => 1

def noLabel (s t : ℕ) (i : Fin (s+1)) : Fin (s+t+2) :=
  if i.val=s then lastLabel (s+t+1) else ⟨i.val+1,by omega⟩
def yesLabel (s t : ℕ) (i : Fin (t+1)) : Fin (s+t+2) :=
  if i.val=t then lastLabel (s+t+1) else ⟨s+i.val+1,by omega⟩

def fork {k : ℕ} (test : Test k) (p q : Block k) : Block k where
  size := p.size+q.size+1
  code := fun pc =>
    if pc.val=0 then test.instruction (noLabel p.size q.size 0) (yesLabel p.size q.size 0)
    else if h : pc.val≤p.size then
      (p.code ⟨pc.val-1,by omega⟩).relabel (noLabel p.size q.size)
    else if h : pc.val≤p.size+q.size then
      (q.code ⟨pc.val-p.size-1,by omega⟩).relabel (yesLabel p.size q.size)
    else .halt
  exit_halt := by simp [lastLabel,show ¬p.size+q.size+1≤p.size by omega]

end MachineCode

section MachineLoops
variable {k s n : ℕ}
def loopLabel (s : ℕ) (i : Fin (s+1)) : Fin (s+2) :=
  if h : i.val=s then 0 else ⟨i.val+1,by omega⟩
def repeatProgram (P : Program k s) (a : Fin (k+8)) : Program k (s+1) := fun pc =>
  if pc.val=0 then .pop a (lastLabel (s+1)) (loopLabel s 0) (loopLabel s 0)
  else if h : pc.val≤ s then (P ⟨pc.val-1,by omega⟩).relabel (loopLabel s)
  else .halt
def loopConfig (R : Fin (k+8) → List Bool) (pc : Fin (s+1)) (q c : ℕ) : MachineConfig k s :=
  ⟨R,pc,none,q,c⟩
end MachineLoops

section MachineMove
variable {k n : ℕ}
def reverseProgram (a d : Fin (k+8)) : Program k 3 := fun pc =>
  match pc.val with
  | 0 => .pop a 3 1 2
  | 1 => .push d false 0
  | 2 => .push d true 0
  | _ => .halt
def reverseConfig (R : Fin (k+8) → List Bool) (pc : Fin 4) (q c : ℕ) : MachineConfig k 3 :=
  ⟨R,pc,none,q,c⟩
def clearProgram (a : Fin (k+8)) : Program k 1 := fun pc => if pc.val=0 then .pop a 1 0 0 else .halt
def clearConfig (R : Fin (k+8) → List Bool) (pc : Fin 2) (q c : ℕ) : MachineConfig k 1 :=
  ⟨R,pc,none,q,c⟩
def moveProgram (a b d : Fin (k+8)) : Program k 6 := concatProgram (reverseProgram a b) (reverseProgram b d)
def moveConfig (R : Fin (k+8) → List Bool) (pc : Fin 7) (q c : ℕ) : MachineConfig k 6 :=
  ⟨R,pc,none,q,c⟩
end MachineMove

namespace MachineCode

def reverse {k : ℕ} (a d : Fin (k+8)) : Block k :=
  ⟨3,reverseProgram a d,rfl⟩
def clear {k : ℕ} (a : Fin (k+8)) : Block k :=
  ⟨1,clearProgram a,rfl⟩
def move {k : ℕ} (a b d : Fin (k+8)) : Block k :=
  ⟨6,moveProgram a b d,rfl⟩
def pack {k : ℕ} (a d : Fin (k+8)) : Block k :=
  ⟨5,framePackProgram a d,rfl⟩
def unpack {k : ℕ} (a d : Fin (k+8)) : Block k where
  size := 4
  code := fun pc => match pc.val with
    | 0 => .pop a 4 1 4
    | 1 => .pop a 4 2 3
    | 2 => .push d false 0
    | 3 => .push d true 0
    | _ => .halt
  exit_halt := rfl

def repeatBlock {k : ℕ} (p : Block k) (a : Fin (k+8)) : Block k where
  size := p.size+1
  code := repeatProgram p.code a
  exit_halt := by simp [repeatProgram,lastLabel]

abbrev Word := List Bool

def regs (x y z saved : Word) : Registers 1 := fun i =>
  if i=0 then x else if i=1 then y else if i=2 then z else if i=8 then saved else []
@[simp] lemma regs_zero (x y z s : Word) : regs x y z s 0=x := by simp [regs]
@[simp] lemma regs_one (x y z s : Word) : regs x y z s 1=y := by simp [regs]
@[simp] lemma regs_eight (x y z s : Word) : regs x y z s 8=s := by simp [regs]

def pair (x y : Word) : Word := bitFrame x.reverse++y

def Computes {n : ℕ} (p : Block 1) (O : IndependenceOracle n)
    (x y : Word) (t q w : ℕ) : Prop :=
  ∀ saved bits, Runs p O bits (regs x [] [] saved) (regs y [] [] saved) t q w

def Bounded {n : ℕ} (p : Block 1) (O : IndependenceOracle n) (x y : Word) (B : ℕ) : Prop :=
  ∃ t q w, t≤B ∧ q≤B ∧ w≤B ∧ Computes p O x y t q w

def copy {k : ℕ} (a b d : Fin (k+8)) : Block k := ⟨8,copyProgram a b d,rfl⟩

def duplicate : Block 1 := seq (copy 0 2 1) (pack 1 0)

def first : Block 1 := seq (unpack 0 1) (seq (clear 0) (move 1 2 0))
def second : Block 1 := seq (unpack 0 1) (clear 1)

def swap : Block 1 := seq (unpack 0 1) (seq (pack 0 1) (move 1 2 0))

def left (p : Block 1) : Block 1 :=
  seq (unpack 0 1) (seq (pack 0 8) (seq (move 1 2 0)
    (seq p (seq (unpack 8 1) (seq (pack 0 1) (move 1 2 0))))))

def right (p : Block 1) : Block 1 := seq swap (seq (left p) swap)

def parallel (p q : Block 1) : Block 1 := seq (left p) (right q)
def fanout (p q : Block 1) : Block 1 := seq duplicate (parallel p q)

def stripZeros : Word → Word
  | [] => []
  | false::xs => stripZeros xs
  | true::xs => true::xs
def stripCost : Word → ℕ
  | [] => 1
  | false::xs => 1+stripCost xs
  | true::_ => 2

def normalWord (x : Word) : Word := (stripZeros x.reverse).reverse

def stripCode {k : ℕ} (a : Fin (k+8)) : Block k where
  size := 2
  code := fun pc => match pc.val with
    | 0 => .pop a 2 0 1
    | 1 => .push a true 2
    | _ => .halt
  exit_halt := rfl

def normalCode : Block 1 := seq (reverse 0 1) (seq (stripCode 1) (reverse 1 0))
def normalCost (x : Word) : ℕ := 2*x.length+1+(stripCost x.reverse+(2*(stripZeros x.reverse).length+1))

def addBlock : Block 1 := ⟨16,addProgram 0 1 2,rfl⟩
def addTime (x y : Word) : ℕ := addCost false x y+2*(addDigits false x y).length+1

def addCode : Block 1 := seq (unpack 0 1) (seq addBlock normalCode)

def mulBlock : Block 1 := ⟨26,mulProgram,rfl⟩

def mulCode : Block 1 := seq (unpack 0 1) (seq mulBlock (seq (clear 0) (seq (move 2 1 0) normalCode)))
def mulTime (x y : Word) : ℕ :=
  3*x.length+1+mulCost y x []+(x.length+y.length+1)+(4*(mulDigits y x []).length+2)+normalCost (mulDigits y x [])

def prependCode : Word → Block 1
  | [] => skip 1
  | b::xs => seq (prependCode xs) (push 0 b)

def constCode (x : Word) : Block 1 := seq (clear 0) (prependCode x)

def selectCode (p q : Block 1) : Block 1 := seq (unpack 0 1) (fork (.pop 1 false false true) p q)

def queryCode (which : Bool) : Block 1 := fork (.query which 0) (constCode [false]) (constCode [true])

def quadRegs (x y z u s : Word) : Registers 1 := Function.update (regs x y z s) 3 u
@[simp] lemma quadRegs_three (x y z u s : Word) : quadRegs x y z u s 3=u := by simp [quadRegs]
@[simp] lemma update_quad_three (x y z u s v : Word) : Function.update (quadRegs x y z u s) 3 v=quadRegs x y z v s := by
  simp [quadRegs]

def divBlock : Block 1 := ⟨53,divisionProgram,rfl⟩

def rawDivCode : Block 1 := seq (unpack 0 1)
  (seq (seq (reverse 0 2) (move 2 3 0)) (seq divBlock
    (seq (clear 1) (seq (pack 2 3) (move 3 1 0)))))
def divResult (x y : Word) : Word × Word := divisionWords y.reverse x [] []
def rawDivTime (x y : Word) : ℕ :=
  4*x.length+6*y.length+11*(divResult x y).1.length+4*(divResult x y).2.length+13+divisionCost y.reverse x []

def divCode : Block 1 := seq rawDivCode (parallel normalCode normalCode)
def normalizedResult (x y : Word) : Word := pair (binaryValue (divResult x y).1).bits (binaryValue (divResult x y).2).bits

def cmpLabel (i : Fin 10) : Fin 11 := ⟨i.val,by omega⟩
def cmpBlock : Block 1 where
  size := 10
  code := fun pc => if h : pc.val < 8 then
      (compareProgram (1 : Fin 9) 0 ⟨pc.val,by omega⟩).relabel cmpLabel
    else if pc.val=8 then .push 0 false 10
    else if pc.val=9 then .push 0 true 10 else .halt
  exit_halt := rfl

def cmpCode : Block 1 := seq (unpack 0 1) cmpBlock

def words : List Word → Word
  | [] => []
  | x::xs => true :: pair x (words xs)

def listBody (p : Block 1) : Block 1 :=
  seq (unpack 1 2) (seq (pack 1 8) (seq (pack 2 0) (seq p (unpack 8 1))))

def foldCode (p : Block 1) : Block 1 := seq (unpack 0 1) (repeatBlock (listBody p) 1)

def reverseCode : Block 1 := seq (reverse 0 1) (move 1 2 0)

def boolListBlock : Block 1 where
  size := 7
  code := fun pc => match pc.val with
    | 0 => .pop 1 7 1 2
    | 1 => .push 0 true 3
    | 2 => .push 0 true 4
    | 3 => .push 0 false 5
    | 4 => .push 0 true 5
    | 5 => .push 0 false 6
    | 6 => .push 0 true 0
    | _ => .halt
  exit_halt := rfl

def boolListCode : Block 1 := seq (reverse 0 1) boolListBlock

def consWordCode : Block 1 := seq (unpack 0 1)
  (fork (.pop 1 false false true) (push 0 false) (push 0 true))

def consListCode : Block 1 := push 0 true

def appendCode : Block 1 := seq (unpack 0 1) (move 1 2 0)

def subBlock : Block 1 := ⟨16,subProgram 1 0 2,rfl⟩
def subTime (x y : Word) : ℕ := subCost false x y+2*(subDigits false x y).length+1

def subCode : Block 1 := seq (unpack 0 1) (seq subBlock (seq (move 1 2 0) normalCode))

def rankPrep : Block 1 where
  size := 5
  code := fun pc => match pc.val with
    | 0 => .pop 0 5 1 3
    | 1 => .push 2 false 2
    | 2 => .push 1 false 0
    | 3 => .push 2 true 4
    | 4 => .push 1 false 0
    | _ => .halt
  exit_halt := rfl

def readyRank (which : Bool) : Block 1 := ⟨32,rankProgram which,rfl⟩
def rankCode (which : Bool) : Block 1 := seq rankPrep (seq (move 2 3 0) (seq (readyRank which) normalCode))
def rankWord {n : ℕ} (O : IndependenceOracle n) (which : Bool) (x : Word) : ℕ :=
  binaryValue (rankBits which O x.reverse)

inductive DataType where
  | unit | bool | nat | word
  | prod : DataType → DataType → DataType
  | list : DataType → DataType

@[reducible] def Value : DataType → Type
  | .unit => Unit
  | .bool => Bool
  | .nat => ℕ
  | .word => Word
  | .prod a b => Value a × Value b
  | .list a => List (Value a)

def encode : {a : DataType} → Value a → Word
  | .unit,_ => []
  | .bool,b => [b]
  | .nat,m => m.bits
  | .word,w => w
  | .prod _ _,(x,y) => pair (encode x) (encode y)
  | .list _,xs => words (xs.map encode)

def width {a : DataType} (x : Value a) : ℕ := (encode x).length

inductive Expression : DataType → DataType → Type where
  | ident {a} : Expression a a
  | constant {a b} : Value b → Expression a b
  | compose {a b c} : Expression a b → Expression b c → Expression a c
  | both {a b c} : Expression a b → Expression a c → Expression a (.prod b c)
  | fst {a b} : Expression (.prod a b) a
  | snd {a b} : Expression (.prod a b) b
  | branch {a b} : Expression a b → Expression a b → Expression (.prod .bool a) b
  | fold {a b} : Expression (.prod a b) b → Expression (.prod (.list a) b) b
  | cons {a} : Expression (.prod a (.list a)) (.list a)
  | consBit : Expression (.prod .bool .word) .word
  | append : Expression (.prod .word .word) .word
  | reverse : Expression .word .word
  | toList : Expression .word (.list .bool)
  | toBits : Expression .nat .word
  | toNat : Expression .word .nat
  | add : Expression (.prod .nat .nat) .nat
  | sub : Expression (.prod .nat .nat) .nat
  | rank : Bool → Expression .word .nat
  | mul : Expression (.prod .nat .nat) .nat
  | less : Expression (.prod .nat .nat) .bool
  | divRem : Expression (.prod .nat .nat) (.prod .nat .nat)
  | query : Bool → Expression .word .bool

def compile : {a b : DataType} → Expression a b → Block 1
  | _,_,.ident => skip 1
  | _,_,.constant x => constCode (encode x)
  | _,_,.compose p q => seq (compile p) (compile q)
  | _,_,.both p q => fanout (compile p) (compile q)
  | _,_,.fst => first
  | _,_,.snd => second
  | _,_,.branch p q => selectCode (compile p) (compile q)
  | _,_,.fold p => foldCode (compile p)
  | _,_,.cons => consListCode
  | _,_,.consBit => consWordCode
  | _,_,.append => appendCode
  | _,_,.reverse => reverseCode
  | _,_,.toList => boolListCode
  | _,_,.toBits => skip 1
  | _,_,.toNat => normalCode
  | _,_,.add => addCode
  | _,_,.sub => subCode
  | _,_,.rank b => rankCode b
  | _,_,.mul => mulCode
  | _,_,.less => cmpCode
  | _,_,.divRem => divCode
  | _,_,.query b => queryCode b

def evalFold {a b : DataType} (step : Value (.prod a b) → Option (Value b × ℕ)) :
    List (Value a) → Value b → Option (Value b × ℕ)
  | [],z => some (z,0)
  | x::xs,z => do
    let (z',c) ← step (x,z)
    let (y,d) ← evalFold step xs z'
    pure (y,7+6*width x+6*width (a:=.list a) xs+c+d)

def evaluate {n : ℕ} (O : IndependenceOracle n) : {a b : DataType} → Expression a b →
    Value a → Option (Value b × ℕ)
  | _,_,.ident,x => some (x,0)
  | _,_,.constant y,x => some (y,width x+width y+1)
  | _,_,.compose p q,x => do
    let (y,c) ← evaluate O p x
    let (z,d) ← evaluate O q y
    pure (z,c+d)
  | _,_,.both p q,x => do
    let (y,c) ← evaluate O p x
    let (z,d) ← evaluate O q x
    pure ((y,z),43*width x+39*width y+18*width z+50+c+d)
  | _,_,.fst,(x,y) => some (x,7*width x+width y+4)
  | _,_,.snd,(x,y) => some (y,4*width x+2)
  | _,_,.branch p q,(b,x) => do
    let (y,c) ← (if b then evaluate O q x else evaluate O p x)
    pure (y,5+c)
  | _,_,.fold p,(xs,z) => do
    let (y,c) ← evalFold (evaluate O p) xs z
    pure (y,3*width xs+2+c)
  | _,_,.cons,(x,xs) => some (x::xs,1)
  | _,_,.consBit,(b,xs) => some (b::xs,6)
  | _,_,.append,(x,y) => some (x++y,7*x.length+3)
  | _,_,.reverse,x => some (x.reverse,6*x.length+3)
  | _,_,.toList,x => some (x,7*x.length+2)
  | _,_,.toBits,x => some (x.bits,0)
  | _,_,.toNat,x => some (binaryValue x,5*x.length+4)
  | _,_,.add,(x,y) => some (x+y,20*(x.bits.length+y.bits.length+1))
  | _,_,.sub,(x,y) => if y≤x then some (x-y,30*(x.bits.length+y.bits.length+1)) else none
  | _,_,.rank b,x => if x.length=n then some (rankWord O b x,100*(n+1)^2) else none
  | _,_,.mul,(x,y) => some (x*y,60*(x.bits.length+y.bits.length+1)^2)
  | _,_,.less,(x,y) => some (decide (x<y),5*(x.bits.length+y.bits.length+1))
  | _,_,.divRem,(x,y) => if x=0 then none else
      some ((y%x,y/x),300*(x.bits.length+y.bits.length+1)^2)
  | _,_,.query b,x => if x.length=n then
      some (O b (fun i => x[i.val]?.getD false),3*n+3) else none

def foldValue {A B : Type*} (step : A × B → Option B) : List A → B → Option B
  | [],z => some z
  | x::xs,z => (step (x,z)).bind (foldValue step xs)

def run {n : ℕ} (O : IndependenceOracle n) : {a b : DataType} → Expression a b → Value a → Option (Value b)
  | _,_,.ident,x => some x
  | _,_,.constant y,_ => some y
  | _,_,.compose p q,x => (run O p x).bind (run O q)
  | _,_,.both p q,x => do let y ← run O p x; let z ← run O q x; pure (y,z)
  | _,_,.fst,(x,_) => some x
  | _,_,.snd,(_,y) => some y
  | _,_,.branch p q,(b,x) => if b then run O q x else run O p x
  | _,_,.fold p,(xs,z) => foldValue (run O p) xs z
  | _,_,.cons,(x,xs) => some (x::xs)
  | _,_,.consBit,(b,xs) => some (b::xs)
  | _,_,.append,(x,y) => some (x++y)
  | _,_,.reverse,x => some x.reverse
  | _,_,.toList,x => some x
  | _,_,.toBits,x => some x.bits
  | _,_,.toNat,x => some (binaryValue x)
  | _,_,.add,(x,y) => some (x+y)
  | _,_,.sub,(x,y) => if y≤x then some (x-y) else none
  | _,_,.mul,(x,y) => some (x*y)
  | _,_,.less,(x,y) => some (decide (x<y))
  | _,_,.divRem,(x,y) => if x=0 then none else some (y%x,y/x)
  | _,_,.query b,x => if x.length=n then some (O b (fun i => x[i.val]?.getD false)) else none
  | _,_,.rank b,x => if x.length=n then some (rankWord O b x) else none

namespace Expression
infixr:80 " >>> " => compose

def onLeft {a b c} (p : Expression a b) : Expression (.prod a c) (.prod b c) :=
  both (fst >>> p) snd
def onRight {a b c} (p : Expression a b) : Expression (.prod c a) (.prod c b) :=
  both fst (snd >>> p)
def swap {a b} : Expression (.prod a b) (.prod b a) := both snd fst
def letIn {a b c} (p : Expression a b) (q : Expression (.prod a b) c) : Expression a c :=
  both ident p >>> q
def applyIf {a b} (test : Expression a .bool) (no yes : Expression a b) : Expression a b :=
  both test ident >>> branch no yes

def boolNot : Expression .bool .bool := both ident ident >>> branch (constant true) (constant false)
def boolAnd : Expression (.prod .bool .bool) .bool := branch (constant false) ident
def natEqZero : Expression .nat .bool := both ident (constant 1) >>> less
def natSub : Expression (.prod .nat .nat) .nat := applyIf less sub (constant 0)
def natDivRem : Expression (.prod .nat .nat) (.prod .nat .nat) :=
  applyIf (fst >>> natEqZero) divRem (both snd (constant 0))
def natDiv : Expression (.prod .nat .nat) .nat := natDivRem >>> snd
def natMod : Expression (.prod .nat .nat) .nat := natDivRem >>> fst

def listReverse {a} : Expression (.list a) (.list a) := both ident (constant []) >>> fold cons

def mapStep {a b c} (p : Expression (.prod a c) b) : Expression (.prod a (.prod (.list b) c)) (.prod (.list b) c) :=
  both
    (both (both fst (snd >>> snd) >>> p) (snd >>> fst) >>> cons)
    (snd >>> snd)
def mapWith {a b c} (p : Expression (.prod a c) b) : Expression (.prod (.list a) c) (.list b) :=
  both fst (both (constant []) snd) >>> fold (mapStep p) >>> fst >>> listReverse

def listLength {a} : Expression (.list a) .nat :=
  both ident (constant 0) >>> fold (both snd (constant 1) >>> add)
def wordLength : Expression .word .nat := toList >>> listLength

def wordRepeatStep : Expression (.prod .bool .word) .word :=
  letIn (snd >>> both ident ident >>> append)
    (both fst snd >>> both (fst >>> fst) snd >>> branch ident (both (constant false) ident >>> consBit))

def unary : Expression .nat .word :=
  both (toBits >>> reverse >>> toList) (constant []) >>> fold wordRepeatStep

end Expression

namespace Expression
variable {n : ℕ} (O : IndependenceOracle n)
@[simp] lemma run_boolNot (b : Bool) : run O boolNot b=some (!b) := by
  cases b <;> rfl
@[simp] lemma run_boolAnd (b c : Bool) : run O boolAnd (b,c)=some (b && c) := by
  cases b <;> rfl
@[simp] lemma run_natEqZero (m : ℕ) : run O natEqZero m=some (decide (m=0)) := by
  simp [natEqZero,run]
@[simp] lemma run_natSub (x y : ℕ) : run O natSub (x,y)=some (x-y) := by
  simp only [natSub,applyIf,run]
  by_cases h : x<y
  · simp [h,Nat.sub_eq_zero_of_le (Nat.le_of_lt h)]
  · simp [h,Nat.le_of_not_gt h]
@[simp] lemma run_natDivRem (x y : ℕ) : run O natDivRem (x,y)=some (y%x,y/x) := by
  simp only [natDivRem,applyIf,run,run_natEqZero]
  by_cases h : x=0 <;> simp [h]
@[simp] lemma run_natDiv (x y : ℕ) : run O natDiv (x,y)=some (y/x) := by
  change ((run O natDivRem (x,y)).bind fun p => some p.2)=some (y/x)
  rw [run_natDivRem]; rfl
@[simp] lemma run_natMod (x y : ℕ) : run O natMod (x,y)=some (y%x) := by
  change ((run O natDivRem (x,y)).bind fun p => some p.1)=some (y%x)
  rw [run_natDivRem]; rfl
end Expression

@[simp] lemma width_nil {a : DataType} : width (a:=.list a) []=0 := rfl

def FoldSafe {A B : Type*} (ok : A×B → Prop) (f : A×B → Option B) : List A → B → Prop
  | [],_ => True
  | x::xs,z => ok (x,z) ∧ ∀ y,f (x,z)=some y → FoldSafe ok f xs y

def Safe {n : ℕ} (O : IndependenceOracle n) (S : ℕ) : {a b : DataType} →
    Expression a b → Value a → Prop
  | _,_,e,x => width x≤S ∧ (∀ y,run O e x=some y → width y≤S) ∧
    match e,x with
    | .compose p q,x => Safe O S p x ∧ ∀ y,run O p x=some y → Safe O S q y
    | .both p q,x => Safe O S p x ∧ Safe O S q x
    | .branch p q,(b,x) => if b then Safe O S q x else Safe O S p x
    | .fold p,(xs,z) => FoldSafe (Safe O S p) (run O p) xs z
    | _,_ => True

def chargeBound : {a b : DataType} → Expression a b → ℕ → ℕ
  | _,_,.compose p q,T => 10000*(T+1)^2+chargeBound p T+chargeBound q T
  | _,_,.both p q,T => 10000*(T+1)^2+chargeBound p T+chargeBound q T
  | _,_,.branch p q,T => 10000*(T+1)^2+chargeBound p T+chargeBound q T
  | _,_,.fold p,T => 10000*(T+1)^2+(T+1)*(10000*(T+1)^2+chargeBound p T)
  | _,_,_,T => 10000*(T+1)^2

def chargeDegree : {a b : DataType} → Expression a b → ℕ
  | _,_,.compose p q => chargeDegree p+chargeDegree q+3
  | _,_,.both p q => chargeDegree p+chargeDegree q+3
  | _,_,.branch p q => chargeDegree p+chargeDegree q+3
  | _,_,.fold p => chargeDegree p+3
  | _,_,_ => 2

def chargeCoefficient : {a b : DataType} → Expression a b → ℕ
  | _,_,.compose p q => 10000+chargeCoefficient p+chargeCoefficient q
  | _,_,.both p q => 10000+chargeCoefficient p+chargeCoefficient q
  | _,_,.branch p q => 10000+chargeCoefficient p+chargeCoefficient q
  | _,_,.fold p => 20000+chargeCoefficient p
  | _,_,_ => 10000

inductive SizePolynomial where
  | constant : ℕ → SizePolynomial
  | input : SizePolynomial
  | add : SizePolynomial → SizePolynomial → SizePolynomial
  | mul : SizePolynomial → SizePolynomial → SizePolynomial
namespace SizePolynomial
def eval : SizePolynomial → ℕ → ℕ
  | .constant c,_ => c
  | .input,x => x
  | .add p q,x => p.eval x+q.eval x
  | .mul p q,x => p.eval x*q.eval x
def degree : SizePolynomial → ℕ
  | .constant _ => 0
  | .input => 1
  | .add p q | .mul p q => p.degree+q.degree
def coefficient : SizePolynomial → ℕ
  | .constant c => c
  | .input => 1
  | .add p q => p.coefficient+q.coefficient
  | .mul p q => p.coefficient*q.coefficient
def subst : SizePolynomial → SizePolynomial → SizePolynomial
  | .constant c,_ => .constant c
  | .input,q => q
  | .add p r,q => .add (p.subst q) (r.subst q)
  | .mul p r,q => .mul (p.subst q) (r.subst q)
end SizePolynomial

def Controlled {a b : DataType} (p : Expression a b) : Prop :=
  ∃ f : SizePolynomial,∀ {n : ℕ} (O : IndependenceOracle n) (x : Value a),Safe O (f.eval (width x)) p x

@[simp] lemma width_nat (x : ℕ) : width (a:=.nat) x=x.bits.length := rfl
@[simp] lemma width_word (x : Word) : width (a:=.word) x=x.length := rfl
@[simp] lemma width_bool (x : Bool) : width (a:=.bool) x=1 := rfl
@[simp] lemma width_unit (x : Unit) : width (a:=.unit) x=0 := rfl

def Atomic : {a b : DataType} → Expression a b → Prop
  | _,_,.compose _ _ | _,_,.both _ _ | _,_,.branch _ _ | _,_,.fold _ => False
  | _,_,_ => True

def constantWidth : {a b : DataType} → Expression a b → ℕ
  | _,_,.constant x => width x
  | _,_,_ => 0

def gcdStep (p : ℕ × ℕ) : ℕ × ℕ := if p.2=0 then p else (p.2,p.1%p.2)
def gcdSteps : ℕ → ℕ × ℕ → ℕ × ℕ
  | 0,p => p
  | k+1,p => gcdSteps k (gcdStep p)

namespace Expression
abbrev NN := DataType.prod .nat .nat

def sortPair : Expression NN NN := applyIf less ident swap
def gcdStepCode : Expression NN NN :=
  applyIf (snd >>> natEqZero) (both snd (swap >>> natMod)) ident

def gcdCode : Expression NN .nat :=
  sortPair >>> both
    (both (fst >>> toBits) (snd >>> toBits) >>> append >>> toList)
    ident >>> fold (snd >>> gcdStepCode) >>> fst
end Expression

open Expression

open Expression

def PolySafe {a b : DataType} (p : Expression a b) (R : ℕ → Value a → Prop) : Prop :=
  ∃ f : SizePolynomial,∀ {n : ℕ} (O : IndependenceOracle n) (S : ℕ) (x : Value a),
    R S x → Safe O (f.eval S) p x

def rationalPair (q : ℚ) : ℕ × ℕ := (q.num.natAbs,q.den)
def normalizePair (a b : ℕ) : ℕ × ℕ :=
  if b=0 then (0,1) else (a/Nat.gcd a b,b/Nat.gcd a b)

namespace Expression

def normalizeRat : Expression NN NN := applyIf (snd >>> natEqZero)
  (letIn gcdCode (both
    (both snd (fst >>> fst) >>> natDiv)
    (both snd (fst >>> snd) >>> natDiv)))
  (constant (0,1))

def ratCross : Expression (.prod NN NN) NN := both
  (both (fst >>> fst) (snd >>> snd) >>> mul)
  (both (snd >>> fst) (fst >>> snd) >>> mul)
def ratAdd : Expression (.prod NN NN) NN :=
  both (ratCross >>> add) (both (fst >>> snd) (snd >>> snd) >>> mul) >>> normalizeRat
def ratMul : Expression (.prod NN NN) NN :=
  both (both (fst >>> fst) (snd >>> fst) >>> mul)
       (both (fst >>> snd) (snd >>> snd) >>> mul) >>> normalizeRat
def ratDiv : Expression (.prod NN NN) NN := ratCross >>> normalizeRat
def ratLess : Expression (.prod NN NN) .bool := ratCross >>> less

def ratMinOne : Expression NN NN := applyIf (swap >>> less) ident (constant (1,1))

def iterateWord {a : DataType} (p : Expression a a) : Expression (.prod .word a) a :=
  onLeft toList >>> fold (snd >>> p)
def iterateNat {a : DataType} (p : Expression a a) : Expression (.prod .nat a) a :=
  onLeft unary >>> iterateWord p

def fromList : Expression (.list .bool) .word :=
  both (listReverse) (constant []) >>> fold consBit

def rangeStepCode : Expression (.prod .nat (.list .nat)) (.prod .nat (.list .nat)) :=
  both (both fst (constant 1) >>> add) (both fst snd >>> cons)
def rangeCode : Expression .nat (.list .nat) :=
  both ident (constant (0,[])) >>> iterateNat rangeStepCode >>> snd >>> listReverse

def rangeStep (p : ℕ × List ℕ) : ℕ × List ℕ := (p.1+1,p.1::p.2)

abbrev SplitState (a : DataType) := DataType.prod .nat (.prod (.list a) (.list a))
def splitStep {a : DataType} : Expression (.prod a (SplitState a)) (SplitState a) :=
  applyIf (snd >>> fst >>> natEqZero)
    (both (both (snd >>> fst) (constant 1) >>> natSub)
      (both (both fst (snd >>> snd >>> fst) >>> cons) (snd >>> snd >>> snd)))
    (both (snd >>> fst)
      (both (snd >>> snd >>> fst) (both fst (snd >>> snd >>> snd) >>> cons)))
def splitAtCode {a : DataType} : Expression (.prod (.list a) .nat) (.prod (.list a) (.list a)) :=
  both fst (both snd (constant ([],[]))) >>> fold splitStep >>> snd >>>
    both (fst >>> listReverse) (snd >>> listReverse)
def takeCode {a : DataType} : Expression (.prod (.list a) .nat) (.list a) := splitAtCode >>> fst
def dropCode {a : DataType} : Expression (.prod (.list a) .nat) (.list a) := splitAtCode >>> snd

def splitStepValue {a : DataType} (p : Value a × (ℕ × (List (Value a) × List (Value a)))) :=
  if p.2.1=0 then (0,(p.2.2.1,p.1::p.2.2.2)) else (p.2.1-1,(p.1::p.2.2.1,p.2.2.2))

def headD {a} : Expression (.prod (.list a) a) a := onLeft listReverse >>> fold fst
def getDCode {a} : Expression (.prod (.prod (.list a) .nat) a) a :=
  both (fst >>> dropCode) snd >>> headD

def boolOr : Expression (.prod .bool .bool) .bool := branch ident (constant true)
def natEqual : Expression NN .bool := both less (swap >>> less) >>> boolOr >>> boolNot

def listAll : Expression (.list .bool) .bool := both ident (constant true) >>> fold boolAnd

def filterStep {a b} (p : Expression (.prod a b) .bool) :
    Expression (.prod a (.prod (.list a) b)) (.prod (.list a) b) :=
  both (applyIf (both fst (snd >>> snd) >>> p) (snd >>> fst)
    (both fst (snd >>> fst) >>> cons)) (snd >>> snd)
def filterWith {a b} (p : Expression (.prod a b) .bool) :
    Expression (.prod (.list a) b) (.list a) :=
  both fst (both (constant []) snd) >>> fold (filterStep p) >>> fst >>> listReverse

def foldLast {A : Type*} : List A → A → A
  | [],d => d
  | x::xs,_ => foldLast xs x

def powStepCode : Expression (.prod .nat .nat) (.prod .nat .nat) := both fst mul
def powCode : Expression NN .nat :=
  both snd (both fst (constant 1)) >>> iterateNat powStepCode >>> snd

def ratPowCode : Expression (.prod NN .nat) NN :=
  both (both (fst >>> fst) snd >>> powCode) (both (fst >>> snd) snd >>> powCode) >>> normalizeRat
end Expression

open Expression

open Expression
def wordDouble (w : Word) (b : Bool) : Word := if b then false::(w++w) else w++w

abbrev StateData := DataType.prod .word .word

def representedState (n : ℕ) (p : Word×Word) : Set (Fin n⊕Fin n) :=
  {i | Sum.elim (fun j => j∈wordSet n p.1) (fun j => j∈wordSet n p.2) i}
@[simp] lemma representedState_inl (n : ℕ) (p : Word×Word) (i : Fin n) :
    Sum.inl i∈representedState n p ↔ p.1[i.val]?.getD false=true := Iff.rfl
@[simp] lemma representedState_inr (n : ℕ) (p : Word×Word) (i : Fin n) :
    Sum.inr i∈representedState n p ↔ p.2[i.val]?.getD false=true := Iff.rfl
@[simp] lemma representedState_preinl (n : ℕ) (p : Word×Word) :
    Sum.inl ⁻¹' representedState n p=wordSet n p.1 := rfl
@[simp] lemma representedState_preinr (n : ℕ) (p : Word×Word) :
    Sum.inr ⁻¹' representedState n p=wordSet n p.2 := rfl

namespace Expression

def wordGet : Expression (.prod .word .nat) .bool :=
  both (both (fst >>> toList) snd) (constant false) >>> getDCode
def wordNot : Expression .word .word :=
  toList >>> both ident (constant (b:=.unit) ()) >>> mapWith (c:=.unit) (fst >>> boolNot) >>> fromList
def wordCountStep : Expression (.prod .bool .nat) .nat := branch ident (both ident (constant 1) >>> add)
def wordCount : Expression .word .nat := toList >>> both ident (constant 0) >>> fold wordCountStep

def deficiencyCode : Expression (.prod .nat StateData) .nat :=
  both (both (snd >>> fst >>> wordLength) fst >>> add)
    (both (both (snd >>> fst >>> rank false) (snd >>> snd >>> wordCount) >>> add)
      (snd >>> snd >>> wordNot >>> rank true) >>> add) >>> natSub
end Expression

abbrev Optional (a : DataType) := DataType.prod .bool a
abbrev RandomRows := DataType.list .word

def optionData {a : DataType} (d : Value a) : Option (Value a) → Value (Optional a)
  | none => (false,d)
  | some x => (true,x)

namespace Expression
@[simp] lemma run_ident {n : ℕ} (O : IndependenceOracle n) {a : DataType} (x : Value a) :
  run O (ident (a:=a)) x=some x := rfl
@[simp] lemma run_fst {n : ℕ} (O : IndependenceOracle n) {a b : DataType} :
  run O (fst (a:=a) (b:=b))=(fun x => some x.1) := rfl
@[simp] lemma run_snd {n : ℕ} (O : IndependenceOracle n) {a b : DataType} :
  run O (snd (a:=a) (b:=b))=(fun x => some x.2) := rfl
@[simp] lemma run_constant {n : ℕ} (O : IndependenceOracle n) {a b : DataType} (x : Value a) (y : Value b) :
  run O (constant (a:=a) (b:=b) y) x=some y := rfl
@[simp] lemma run_reverse {n : ℕ} (O : IndependenceOracle n) (x : Word) :
  run O reverse x=some x.reverse := rfl
@[simp] lemma run_toNat {n : ℕ} (O : IndependenceOracle n) (x : Word) :
  run O toNat x=some (binaryValue x) := rfl
@[simp] lemma run_less {n : ℕ} (O : IndependenceOracle n) (x y : ℕ) :
  run O less (x,y)=some (decide (x<y)) := rfl

def wordTake : Expression (.prod .word .nat) .word := onLeft toList >>> takeCode >>> fromList

abbrev RejectState := DataType.prod NN (Optional .nat)
def rejectStep : Expression (.prod .word RejectState) RejectState :=
  both (snd >>> fst)
    (letIn (both fst (snd >>> fst >>> snd) >>> wordTake >>> reverse >>> toNat)
      (applyIf (both snd (fst >>> snd >>> fst >>> fst) >>> less)
        (fst >>> snd >>> snd) (both (constant true) snd)))
def rejectCode : Expression (.prod RandomRows NN) (Optional .nat) :=
  both (fst >>> listReverse) (both snd (constant (false,0))) >>> fold rejectStep >>> snd

def rejectValue (v k : ℕ) : List Word → Option ℕ
  | [] => none
  | x::xs => let z := binaryValue (x.take k).reverse
      if z<v then some z else rejectValue v k xs
end Expression

abbrev BankData := DataType.list RandomRows
abbrev Bank := List (List Word)
lemma rejectValue_lt (v h : ℕ) (xs : List Word) {i : ℕ}
    (hi : Expression.rejectValue v h xs=some i) : i<v := by
  induction xs with
  | nil => cases hi
  | cons x xs ih =>
    simp only [Expression.rejectValue] at hi
    split_ifs at hi with hx
    · cases hi; exact hx
    · exact ih hi

def bankEval {A : Type*} : DrawTree A → Bank → Option A × Bank
  | .leaf a,bank => (some a,bank)
  | .draw _ _,[] => (none,[])
  | .draw v k,rows::bank =>
    match hi : Expression.rejectValue (v+1) (Nat.clog 2 (v+1)) rows with
    | none => (none,bank)
    | some i => bankEval (k ⟨i,rejectValue_lt _ _ _ hi⟩) bank

def bankEncode {H D M : ℕ} (bits : Fin M → Fin D → BitWord H) : Bank :=
  List.ofFn (fun m => List.ofFn (fun d => List.ofFn (bits m d)))

open Expression

def zeroValue : (a : DataType) → Value a
  | .unit => ()
  | .bool => false
  | .nat => 0
  | .word => []
  | .prod a b => (zeroValue a,zeroValue b)
  | .list _ => []
def optionalValue (a : DataType) : Option (Value a) → Value (Optional a) := optionData (zeroValue a)

abbrev Effect (a b : DataType) := Expression (.prod a BankData) (.prod (Optional b) BankData)
namespace Expression

def clogCode : Expression .nat .nat := both ident (constant 1) >>> natSub >>> toBits >>> wordLength

end Expression
open Expression
namespace Effect

def pure {a b} (p : Expression a b) : Effect a b := both (both (constant true) (fst >>> p)) snd

def bind {a b c} (p : Effect a b) (q : Effect b c) : Effect a c :=
  p >>> applyIf (fst >>> fst)
    (both (constant (optionalValue c none)) snd)
    (both (fst >>> snd) snd >>> q)

def keep {a b} (p : Effect a b) : Effect a (.prod a b) :=
  _root_.OAI.CommonBasesFPRAS.MachineCode.Expression.letIn p (both
    (applyIf (snd >>> fst >>> fst) (constant (optionalValue (.prod a b) none))
      (both (constant true) (both (fst >>> fst) (snd >>> fst >>> snd))))
    (snd >>> snd))

def letIn {a b c} (p : Effect a b) (q : Effect (.prod a b) c) : Effect a c :=
  bind (keep p) q

def draw : Effect .nat .nat :=
  both
    (both (snd >>> both ident (constant []) >>> headD) (both fst (fst >>> clogCode)) >>> rejectCode)
    (snd >>> both ident (constant 1) >>> dropCode)

def Realizes {n : ℕ} (O : IndependenceOracle n) {A : Type*} {a b : DataType}
    (p : Effect a b) (encode : A → Value b) (x : Value a) (t : DrawTree A) : Prop :=
  ∀ bank, run O p (x,bank)=some
    (optionalValue b ((bankEval t bank).1.map encode),(bankEval t bank).2)

def map {a b c} (p : Effect a b) (q : Expression b c) : Effect a c := bind p (pure q)
def precompose {a b c} (p : Expression a b) (q : Effect b c) : Effect a c := onLeft p >>> q

def choose {a b} (test : Expression a .bool) (p q : Effect a b) : Effect a b :=
  applyIf (fst >>> test) p q

end Effect

def iterateM {A : Type*} (f : A → Option A) : ℕ → A → Option A
  | 0,x => some x
  | k+1,x => (f x).bind (iterateM f k)
namespace Effect

def loopStep {a} (p : Effect a a) : Expression (.prod (Optional a) BankData) (.prod (Optional a) BankData) :=
  applyIf (fst >>> fst) ident (both (fst >>> snd) snd >>> p)
def repeatCode {a} (p : Effect a a) : Effect (.prod .nat a) a :=
  both (fst >>> fst) (both (both (constant (b:=.bool) true) (fst >>> snd)) snd) >>> iterateNat (loopStep p)

def treeRepeat {A : Type*} (f : A → DrawTree A) : ℕ → A → DrawTree A
  | 0,x => .leaf x
  | k+1,x => (f x).bind (treeRepeat f k)

end Effect
end MachineCode

namespace MachineCode
open Expression
namespace Effect

def RealizesLaw {n : ℕ} (O : IndependenceOracle n) {A : Type*} {a b : DataType}
    (p : Effect a b) (encode : A → Value b) (x : Value a) (t : DrawTree A) : Prop :=
  ∃ u, DrawTree.Reindexed t u ∧ Realizes O p encode x u
end Effect
end MachineCode

namespace MachineCode

open Expression

def optionEncode {A : Type*} {a} (e : A → Value a) (x : Option A) : Value (Optional a) :=
  optionalValue a (x.map e)
@[simp] lemma optionEncode_none {A : Type*} {a} (e : A → Value a) :
    optionEncode e none=(false,zeroValue a) := rfl
@[simp] lemma optionEncode_some {A : Type*} {a} (e : A → Value a) (x : A) :
    optionEncode e (some x)=(true,e x) := rfl
namespace Effect

def someCode {a b} (p : Effect a b) : Effect a (Optional b) :=
  map p (both (constant true) ident)
def noneCode {a b} : Effect a (Optional b) := pure (constant (optionalValue b none))
def optionContinue {a b} (q : Effect a (Optional b)) : Effect (Optional a) (Optional b) :=
  choose fst noneCode (precompose snd q)
def optionBind {a b c} (p : Effect a (Optional b)) (q : Effect b (Optional c)) : Effect a (Optional c) :=
  bind p (optionContinue q)

def iterateCode {a} (p : Effect a (Optional a)) : Effect (.prod .nat a) (Optional a) :=
  precompose (both fst (both (constant true) snd)) (repeatCode (optionContinue p))
end Effect

open scoped Classical
noncomputable def stateEncode {n : ℕ} (S : Set (Fin n⊕Fin n)) : Value StateData :=
  (List.ofFn (fun i => decide (Sum.inl i∈S)),List.ofFn (fun i => decide (Sum.inr i∈S)))
@[simp] lemma stateEncode_left_length {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    (stateEncode S).1.length=n := List.length_ofFn
@[simp] lemma stateEncode_right_length {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    (stateEncode S).2.length=n := List.length_ofFn
@[simp] lemma stateEncode_repr {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    representedState n (stateEncode S)=S := by
  ext i
  cases i <;> simp [representedState,stateEncode,wordSet]
lemma stateEncode_get_left {n : ℕ} (S : Set (Fin n⊕Fin n)) (i : Fin n) :
    (stateEncode S).1[i.val]?.getD false=decide (Sum.inl i∈S) := by simp [stateEncode]

namespace Expression

def boolXor : Expression (.prod .bool .bool) .bool := branch ident boolNot

def singleAt : Expression (.prod .nat StateData) .bool :=
  both (both (snd >>> fst) fst >>> wordGet) (both (snd >>> snd) fst >>> wordGet) >>> boolXor
def fullAt : Expression (.prod .nat StateData) .bool :=
  both (both (snd >>> fst) fst >>> wordGet) (both (snd >>> snd) fst >>> wordGet) >>> boolAnd
def emptyAt : Expression (.prod .nat StateData) .bool :=
  both (both (snd >>> fst) fst >>> wordGet) (both (snd >>> snd) fst >>> wordGet) >>> boolOr >>> boolNot

def singleValue (i : ℕ) (S : Word×Word) : Bool := xor (S.1[i]?.getD false) (S.2[i]?.getD false)
def fullValue (i : ℕ) (S : Word×Word) : Bool := (S.1[i]?.getD false) && (S.2[i]?.getD false)
def emptyValue (i : ℕ) (S : Word×Word) : Bool := !(S.1[i]?.getD false || S.2[i]?.getD false)

def plainValue (S : Word×Word) : Bool := ((List.range S.1.length).map (fun i => singleValue i S)).all id
def plainCode : Expression StateData .bool :=
  both (fst >>> wordLength >>> rangeCode) ident >>> mapWith singleAt >>> listAll
end Expression

abbrev DefectInput := DataType.prod Expression.NN StateData
namespace Expression

def defectAt : Expression (.prod .nat DefectInput) .bool :=
  applyIf (both fst (snd >>> fst >>> fst) >>> natEqual)
    (applyIf (both fst (snd >>> fst >>> snd) >>> natEqual)
      (both fst (snd >>> snd) >>> singleAt) (both fst (snd >>> snd) >>> fullAt))
    (both fst (snd >>> snd) >>> emptyAt)
def defectAtValue (i : ℕ) (p : (ℕ×ℕ)×(Word×Word)) : Bool :=
  if i=p.1.1 then emptyValue i p.2 else if i=p.1.2 then fullValue i p.2 else singleValue i p.2

def defectValue (p r : ℕ) (S : Word×Word) : Bool :=
  decide (p≠r) && ((List.range S.1.length).map (fun i => defectAtValue i ((p,r),S))).all id
def defectCode : Expression DefectInput .bool :=
  both (fst >>> natEqual >>> boolNot)
    (both (snd >>> fst >>> wordLength >>> rangeCode) ident >>> mapWith defectAt >>> listAll) >>> boolAnd
end Expression

abbrev SampleData := DataType.list StateData
abbrev HistoryData := DataType.list SampleData
namespace Expression

def countWith {a b} (p : Expression (.prod a b) .bool) : Expression (.prod (.list a) b) .nat :=
  filterWith p >>> listLength

def countPlainCode : Expression SampleData .nat :=
  both ident (constant (b:=.unit) ()) >>> countWith (fst >>> plainCode)
def countDefectCode : Expression (.prod SampleData NN) .nat := countWith (swap >>> defectCode)
def countPlainValue (xs : List (Word×Word)) : ℕ := xs.countP plainValue
def countDefectValue (xs : List (Word×Word)) (p r : ℕ) : ℕ := xs.countP (fun S => defectValue p r S)

def learningRatioCode : Expression (.prod SampleData NN) NN :=
  both (fst >>> countPlainCode) countDefectCode >>> normalizeRat
def learningRatio (xs : List (Word×Word)) (p r : ℕ) : ℚ := (countPlainValue xs:ℚ)/countDefectValue xs p r

def weightStep : Expression (.prod SampleData (.prod NN NN)) (.prod NN NN) :=
  both (snd >>> fst)
    (applyIf (snd >>> fst >>> natEqual)
      (both (snd >>> snd) (both fst (snd >>> fst) >>> learningRatioCode) >>> ratMul)
      (snd >>> snd))
def weightUpdate (p r : ℕ) (xs : List (Word×Word)) (q : ℚ) : ℚ :=
  if p=r then q else q*learningRatio xs p r

def historyWeightValue (p r : ℕ) (h : List (List (Word×Word))) : ℚ :=
  h.foldl (fun q xs => weightUpdate p r xs q) 4
def historyWeightCode : Expression (.prod HistoryData NN) NN :=
  both fst (both snd (constant (b:=NN) (4,1))) >>> fold weightStep >>> snd
end Expression

noncomputable def sampleEncode {n : ℕ} (xs : List (Set (Fin n⊕Fin n))) : Value SampleData := xs.map stateEncode
noncomputable def historyEncode {n : ℕ} (h : List (List (Set (Fin n⊕Fin n)))) : Value HistoryData := h.map sampleEncode

namespace Expression

def listAppend {a} : Expression (.prod (.list a) (.list a)) (.list a) :=
  onLeft listReverse >>> fold cons

def productStep {a b} : Expression (.prod a (.prod (.list (.prod a b)) (.list b)))
    (.prod (.list (.prod a b)) (.list b)) :=
  both (both (snd >>> fst) (both (snd >>> snd) fst >>> mapWith swap) >>> listAppend) (snd >>> snd)
def productCode {a b} : Expression (.prod (.list a) (.list b)) (.list (.prod a b)) :=
  both fst (both (constant []) snd) >>> fold productStep >>> fst

def indexPairs : Expression .nat (.list NN) := both rangeCode rangeCode >>> productCode
end Expression

open Expression

open Expression
open Expression
open scoped Classical
namespace Effect

def phaseRepeat {A : Type*} (f : ℕ → A → DrawTree (Option A)) : ℕ → A → DrawTree (Option A)
  | 0,x => .leaf (some x)
  | k+1,x => (phaseRepeat f k x).optionBind (f (k+1))

def hitStepCode {a} (step : Effect a a) (test : Expression a .bool) : Effect (.prod a .bool) (.prod a .bool) :=
  choose snd (map (precompose fst step) (both ident test)) (pure ident)
def hitFinish {a} : Expression (.prod a .bool) (Optional a) :=
  applyIf snd (constant (optionalValue a none)) (both (constant true) fst)
def hittingCode {a} (step : Effect a a) (test : Expression a .bool) : Effect (.prod .nat a) (Optional a) :=
  precompose (both fst (both snd (constant false))) (map (repeatCode (hitStepCode step test)) hitFinish)

abbrev SearchState (X : Type*) (P : X → Prop) := X ⊕ FiniteChain.Inside P
noncomputable def searchStep {X : Type*} (step : X → DrawTree X) (P : X → Prop) :
    SearchState X P → DrawTree (SearchState X P)
  | .inl x => (step x).map (fun y => if hy : P y then .inr ⟨y,hy⟩ else .inl y)
  | .inr y => .leaf (.inr y)
def searchFinish {X : Type*} {P : X → Prop} : SearchState X P → Option (FiniteChain.Inside P)
  | .inl _ => none
  | .inr y => some y
def searchEncode {X : Type*} {P : X → Prop} {a} (e : X → Value a) : SearchState X P → Value (.prod a .bool)
  | .inl x => (e x,false)
  | .inr y => (e y.val,true)

end Effect

open Expression
namespace Expression

def optionMapCode {a b} (p : Expression a b) : Expression (Optional a) (Optional b) :=
  applyIf fst (constant (optionalValue b none)) (both (constant true) (snd >>> p))
end Expression
namespace Effect

def withContext {a b c} (p : Effect (.prod c a) b) : Effect (.prod c a) (.prod c b) :=
  letIn p (pure (both (fst >>> fst) snd))
end Effect

open scoped Classical
namespace Expression

def defectPairsValue (S : Word×Word) : List (ℕ×ℕ) :=
  ((List.range S.1.length) ×ˢ (List.range S.1.length)).filter (fun pr => defectValue pr.1 pr.2 S)
def defectPairsCode : Expression StateData (.list NN) :=
  both (fst >>> wordLength >>> indexPairs) ident >>> filterWith defectCode

def chainValue (S : Word×Word) : Bool := plainValue S || decide ((defectPairsValue S).length≠0)
def chainCode : Expression StateData .bool :=
  both plainCode (defectPairsCode >>> listLength >>> natEqZero >>> boolNot) >>> boolOr

def multiplierValue (h : List (List (Word×Word))) (S : Word×Word) : ℚ :=
  if (defectPairsValue S).length=0 then 1 else
    historyWeightValue ((defectPairsValue S).headD (0,0)).1 ((defectPairsValue S).headD (0,0)).2 h
def multiplierCode : Expression (.prod HistoryData StateData) NN :=
  letIn (snd >>> defectPairsCode)
    (applyIf (snd >>> listLength >>> natEqZero)
      (both (fst >>> fst) (both snd (constant (0,0)) >>> headD) >>> historyWeightCode)
      (constant (1,1)))
end Expression

namespace Expression

@[simp] lemma run_append {n : ℕ} (O : IndependenceOracle n) (S : Word×Word) :
    run O append S=some (S.1++S.2) := rfl

def updateAt : Expression (.prod .nat (.prod .word (.prod .nat .bool))) .bool :=
  applyIf (both fst (snd >>> snd >>> fst) >>> natEqual)
    (both (snd >>> fst) fst >>> wordGet) (snd >>> snd >>> snd)
def updateWord : Expression (.prod .word (.prod .nat .bool)) .word :=
  both (fst >>> wordLength >>> rangeCode) ident >>> mapWith updateAt >>> fromList

def splitWord : Expression (.prod .word .nat) StateData :=
  onLeft toList >>> splitAtCode >>> both (fst >>> fromList) (snd >>> fromList)

def exchangeWord : Expression (.prod .word NN) .word :=
  letIn (both fst (both (snd >>> fst) (constant false)) >>> updateWord)
    (both snd (both (fst >>> snd >>> snd) (constant true)) >>> updateWord)

def exchangeSetCode : Expression (.prod StateData NN) StateData :=
  both (both (fst >>> append) snd >>> exchangeWord) (fst >>> fst >>> wordLength) >>> splitWord
end Expression

open scoped Classical
noncomputable def combinedEncode {n : ℕ} (S : Set (Fin n⊕Fin n)) : Word := (stateEncode S).1++(stateEncode S).2
def position {n : ℕ} (i : Fin n⊕Fin n) : ℕ := (finSumFinEquiv i).val
@[simp] lemma combinedEncode_length {n : ℕ} (S : Set (Fin n⊕Fin n)) : (combinedEncode S).length=n+n := by
  simp [combinedEncode]
lemma combinedEncode_get {n : ℕ} (S : Set (Fin n⊕Fin n)) (i : Fin n⊕Fin n) :
    (combinedEncode S)[position i]?.getD false=decide (i∈S) := by
  cases i with
  | inl i =>
    simp only [combinedEncode,position,finSumFinEquiv,Equiv.coe_fn_mk]
    rw [List.getElem?_append_left (by simp)]
    exact stateEncode_get_left S i
  | inr i => simp [combinedEncode,stateEncode,position,finSumFinEquiv,i.isLt]

open scoped Classical
namespace Expression

def rhoCode : Expression .nat NN := applyIf natEqZero
  (letIn (both ident (constant 2) >>> mul)
    (both (both snd (constant 1) >>> natSub) snd >>> normalizeRat))
  (constant (1,1))

abbrev WeightContext := DataType.prod NN HistoryData
abbrev WeightInput := DataType.prod WeightContext StateData

def annealCode : Expression WeightInput NN :=
  both (both (snd >>> fst >>> wordLength >>> rhoCode) (fst >>> fst >>> snd) >>> ratPowCode)
    (both (fst >>> fst >>> fst) snd >>> deficiencyCode) >>> ratPowCode
def weightCode : Expression WeightInput NN :=
  both (both (fst >>> snd) snd >>> multiplierCode) annealCode >>> ratMul
end Expression

open Expression
namespace Effect

def bernoulliCode : Effect NN .bool :=
  letIn (precompose snd draw) (pure (both snd (fst >>> fst) >>> less))
end Effect

open Expression
open scoped Classical
namespace Expression
abbrev AcceptanceInput := DataType.prod WeightContext (.prod StateData StateData)
def acceptanceCode : Expression AcceptanceInput NN :=
  both (both fst (snd >>> snd) >>> weightCode) (both fst (snd >>> fst) >>> weightCode) >>>
    ratDiv >>> ratMinOne
end Expression

open scoped Classical
namespace Expression

def memberPositions (w : Word) : List ℕ := (List.range w.length).filter (fun i => w[i]?.getD false)
def membersCode : Expression .word (.list .nat) :=
  both (wordLength >>> rangeCode) ident >>> filterWith (swap >>> wordGet)
lemma memberPositions_nodup (w : Word) : (memberPositions w).Nodup :=
  List.nodup_range.filter _
end Expression

lemma mem_memberPositions_source {n : ℕ} (S : Set (Fin n⊕Fin n)) (i : Fin n⊕Fin n) :
    position i∈Expression.memberPositions (combinedEncode S) ↔ i∈S := by
  simp only [Expression.memberPositions,List.mem_filter,List.mem_range,combinedEncode_length,
    combinedEncode_get,decide_eq_true_eq]
  exact and_iff_right (finSumFinEquiv i).isLt

noncomputable def memberPositionEquiv {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    {i : ℕ // i∈Expression.memberPositions (combinedEncode S)} ≃ S where
  toFun i := by
    have hi : i.val<n+n := by
      have hh := (List.mem_filter.mp i.property).1
      simpa only [List.mem_range,combinedEncode_length] using hh
    let j := finSumFinEquiv.symm ⟨i.val,hi⟩
    have hj : position j=i.val := by simp [position,j]
    exact ⟨j,(mem_memberPositions_source S j).mp (hj.symm ▸ i.property)⟩
  invFun i := ⟨position i.val,(mem_memberPositions_source S i.val).mpr i.property⟩
  left_inv i := by apply Subtype.ext; simp [position]
  right_inv i := by apply Subtype.ext; simp [position]

open Expression
namespace Effect

def uniformList {a} : Effect (.list a) a :=
  letIn (precompose listLength draw) (pure (both (both fst snd) (constant (zeroValue a)) >>> getDCode))

end Effect

open scoped Classical
open Expression
lemma combinedEncode_compl {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    combinedEncode Sᶜ=(combinedEncode S).map Bool.not := by
  simp [combinedEncode,stateEncode,List.map_append,List.map_ofFn,Function.comp_def]

def exchangePositions (S : Word×Word) : List (ℕ×ℕ) :=
  memberPositions (S.1++S.2) ×ˢ memberPositions ((S.1++S.2).map Bool.not)
def Expression.exchangePositionsCode : Expression StateData (.list NN) :=
  append >>> both membersCode (wordNot >>> membersCode) >>> productCode
lemma exchangePositions_nodup (S : Word×Word) : (exchangePositions S).Nodup := by
  exact List.Nodup.product (memberPositions_nodup _) (memberPositions_nodup _)

lemma memberPositionEquiv_val {n : ℕ} (S : Set (Fin n⊕Fin n))
    (i : {i : ℕ // i∈memberPositions (combinedEncode S)}) :
    position (memberPositionEquiv S i).val=i.val := by simp [memberPositionEquiv,position]

noncomputable def exchangePositionEquiv {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    {p : ℕ×ℕ // p∈exchangePositions (stateEncode S)} ≃ ExchangeChoice S where
  toFun p :=
    let hp : p.val∈memberPositions (combinedEncode S) ×ˢ memberPositions (combinedEncode Sᶜ) := by
      rw [combinedEncode_compl]
      exact p.property
    (memberPositionEquiv S ⟨p.val.1,(List.mem_product.mp hp).1⟩,
      memberPositionEquiv Sᶜ ⟨p.val.2,(List.mem_product.mp hp).2⟩)
  invFun p := ⟨(position p.1.val,position p.2.val),by
    change (position p.1.val, position p.2.val)∈
      memberPositions (combinedEncode S) ×ˢ memberPositions ((combinedEncode S).map Bool.not)
    rw [←combinedEncode_compl]
    exact List.mem_product.mpr ⟨(mem_memberPositions_source S _).mpr p.1.property,
      (mem_memberPositions_source Sᶜ _).mpr p.2.property⟩⟩
  left_inv p := by apply Subtype.ext; simp [memberPositionEquiv_val]
  right_inv p := by
    apply Prod.ext <;> apply Subtype.ext <;> simp [memberPositionEquiv,position]

noncomputable def exchangeListEquiv {n : ℕ} (S : Set (Fin n⊕Fin n)) :
    Fin (exchangePositions (stateEncode S)).length ≃ ExchangeChoice S :=
  (exchangePositions_nodup (stateEncode S)).getEquiv.trans (exchangePositionEquiv S)

namespace Effect

def proposal : Effect StateData NN := precompose exchangePositionsCode uniformList
end Effect

section
open Expression
open scoped Classical
namespace Effect
abbrev OfferInput := DataType.prod WeightInput StateData

def acceptancePrep : Expression OfferInput AcceptanceInput :=
  both (fst >>> fst) (both (fst >>> snd) snd)
def offerCore : Effect OfferInput StateData :=
  choose (snd >>> chainCode) (pure (fst >>> snd))
    (letIn (precompose (acceptancePrep >>> acceptanceCode) bernoulliCode)
      (pure (applyIf snd (fst >>> fst >>> snd) (fst >>> snd))))
def offerCode : Effect (.prod WeightInput NN) StateData :=
  precompose (both fst (both (fst >>> snd) snd >>> exchangeSetCode)) offerCore

def activeStepCode : Effect WeightInput StateData :=
  letIn (precompose snd proposal) offerCode

def stepCode : Effect WeightInput StateData :=
  letIn (precompose (constant (1,2)) bernoulliCode)
    (choose snd (precompose fst activeStepCode) (pure (fst >>> snd)))
end Effect

open Effect
end

section
open Expression
open scoped Classical

namespace Expression

def paddedBitsCode : Expression NN .word :=
  both (fst >>> rangeCode) (snd >>> toBits) >>> mapWith (swap >>> wordGet) >>> fromList >>> reverse

def plainDecodeCode : Expression NN StateData := paddedBitsCode >>> both ident wordNot
end Expression
namespace Effect
noncomputable local instance instNonemptyPlainIndexSumFinPairedSlots {n : ℕ} : Nonempty (PlainIndex (pairedSlots n)) :=
  plainIndex_nonempty _ (pairedSlots_fresh n) (pairedSlots_cover n)

def plainCode : Effect .nat StateData :=
  letIn (precompose (both (constant 2) ident >>> powCode) draw) (pure plainDecodeCode)

def storedStepPrep : Expression WeightInput WeightInput :=
  both (both (fst >>> fst) (both (fst >>> snd) (fst >>> fst >>> snd) >>> takeCode)) snd

def storedStepCode : Effect WeightInput StateData := precompose storedStepPrep stepCode

def traceCode : Effect (.prod .nat WeightInput) (Optional StateData) :=
  map (hittingCode (withContext storedStepCode) (snd >>> Expression.plainCode)) (optionMapCode snd)
end Effect

open Effect
end

section
open Expression
namespace Effect

def withOptionContext {a b c} (p : Effect (.prod c a) (Optional b)) : Effect (.prod c a) (Optional (.prod c b)) :=
  letIn p (pure (applyIf (snd >>> fst) (constant (optionalValue (.prod c b) none))
    (both (constant true) (both (fst >>> fst) (snd >>> snd)))))
end Effect

open Expression
open scoped Classical
abbrev RestartContext : DataType := .prod (.prod .nat HistoryData) (.prod .nat .nat)
abbrev RestartState : DataType := .prod (.prod RestartContext .nat) StateData
abbrev RestartInput : DataType := .prod .nat (.prod .nat RestartContext)
namespace Effect

def traceAtPrep : Expression RestartState (.prod .nat WeightInput) :=
  both (fst >>> fst >>> snd >>> fst >>> (both ident (constant 1) >>> add))
    (both (both (both (fst >>> fst >>> fst >>> fst) (fst >>> snd))
      (fst >>> fst >>> fst >>> snd)) snd)
def traceAtCode : Effect RestartState (Optional StateData) := precompose traceAtPrep traceCode

def restartTickPrep : Expression RestartState (.prod .nat RestartState) :=
  both (fst >>> fst >>> snd >>> snd)
    (both (both (fst >>> fst) (fst >>> snd >>> (both ident (constant 1) >>> add))) snd)
def restartTick : Effect RestartState (Optional RestartState) :=
  precompose restartTickPrep (iterateCode (withOptionContext traceAtCode))

def restartPrep : Expression (.prod RestartInput StateData) (.prod .nat (Optional RestartState)) :=
  both (fst >>> snd >>> fst) (both (constant true)
    (both (both (fst >>> snd >>> snd) (constant 0)) snd))
def restartCode : Effect RestartInput (Optional StateData) :=
  map (letIn (precompose fst plainCode)
    (precompose restartPrep (repeatCode (optionContinue restartTick)))) (optionMapCode snd)
end Effect

open Effect
open Effect
end

open Expression
open scoped Classical
namespace Expression

def allRange {a} (p : Expression (.prod .nat a) .bool) : Expression (.prod .nat a) .bool :=
  both (fst >>> rangeCode) snd >>> mapWith p >>> listAll
end Expression
namespace Expression

def pairPositiveCode : Expression (.prod HistoryData NN) .bool :=
  historyWeightCode >>> fst >>> natEqZero >>> boolNot

def slotRowCode : Expression (.prod .nat (.prod .nat HistoryData)) .bool :=
  both (snd >>> fst) (both fst (snd >>> snd)) >>>
    allRange (both (snd >>> snd) (both (snd >>> fst) fst) >>> pairPositiveCode)
def slotsPositiveCode : Expression (.prod .nat HistoryData) .bool :=
  both fst ident >>> allRange slotRowCode
def prefixPositiveCode : Expression (.prod .nat (.prod .nat HistoryData)) .bool :=
  both (snd >>> fst) (both (snd >>> snd) fst >>> takeCode) >>> slotsPositiveCode
def historyPositiveCode : Expression (.prod .nat HistoryData) .bool :=
  both (snd >>> listLength >>> (both ident (constant 1) >>> add)) ident >>> allRange prefixPositiveCode

end Expression

end MachineCode

namespace MachineCode

open Expression
namespace Effect

def sampleTick {a} (p : Effect a a) : Effect (.prod (.list a) a) (.prod (.list a) a) :=
  letIn (precompose snd p) (pure (both (both (fst >>> snd) (fst >>> fst) >>> cons) snd))
def sampleFinish {a} : Expression (.prod (.list a) a) (.list a) :=
  both (fst >>> listReverse) (both snd (constant []) >>> cons) >>> listAppend
def sampleCode {a} (p : Effect a a) : Effect (.prod .nat a) (.list a) :=
  precompose (both fst (both (constant []) snd)) (map (repeatCode (sampleTick p)) sampleFinish)

def sampleTickTree {X : Type*} (f : X → DrawTree X) (s : List X × X) : DrawTree (List X × X) :=
  (f s.2).map (fun y => (s.2::s.1,y))
end Effect

section
open Expression
open scoped Classical
abbrev AlgorithmParams : DataType := .prod NN (.prod NN NN)
abbrev PhaseInput : DataType := .prod AlgorithmParams HistoryData
def params (n r N L : ℕ) : Value AlgorithmParams :=
  ((n,r),((N,L),(restartCap n L,20*n^4*(n*(L+1)+2))))
namespace Expression
def mapEach {a b} (p : Expression a b) : Expression (.list a) (.list b) :=
  both ident (constant (b:=.unit) ()) >>> mapWith (fst >>> p)
end Expression
namespace Effect
def retainOption {a b} (p : Effect a (Optional b)) : Effect a (Optional (.prod a b)) :=
  letIn p (pure (applyIf (snd >>> fst) (constant (optionalValue (.prod a b) none))
    (both (constant true) (both fst (snd >>> snd)))))

def phaseRestartPrep : Expression PhaseInput RestartInput :=
  both (fst >>> fst >>> fst) (both (snd >>> listLength)
    (both (both (fst >>> fst >>> snd) snd) (fst >>> snd >>> snd)))
def phaseRestartCode : Effect PhaseInput (Optional StateData) := precompose phaseRestartPrep restartCode
def startCode : Effect PhaseInput (Optional StateData) := map phaseRestartCode (optionMapCode ident)
def phaseStepPrep : Expression (.prod PhaseInput StateData) WeightInput :=
  both (both (both (fst >>> fst >>> fst >>> snd) (fst >>> snd >>> listLength)) (fst >>> snd)) snd
def phaseStepCode : Effect (.prod PhaseInput StateData) StateData := precompose phaseStepPrep storedStepCode

def phaseSamplePrep : Expression (.prod PhaseInput StateData) (.prod .nat (.prod PhaseInput StateData)) :=
  both (both (fst >>> fst >>> snd >>> fst >>> fst) (constant 1) >>> natSub) ident
def phaseSampleCode : Effect (.prod PhaseInput StateData) SampleData :=
  precompose phaseSamplePrep (map (sampleCode (withContext phaseStepCode)) (mapEach snd))
def positiveTest : Expression PhaseInput .bool := both (fst >>> fst >>> fst) snd >>> historyPositiveCode
def phaseCode : Effect PhaseInput (Optional SampleData) :=
  choose positiveTest noneCode (optionBind (retainOption startCode) (someCode phaseSampleCode))
def witnessCode : Effect PhaseInput (Optional StateData) := choose positiveTest noneCode phaseRestartCode
end Effect
open Effect
end

open Expression
namespace Expression

def ratSumCode : Expression (.list NN) NN := both ident (constant (b:=NN) (0,1)) >>> fold ratAdd
def ratProductCode : Expression (.list NN) NN := both ident (constant (b:=NN) (1,1)) >>> fold ratMul

def natRatCode : Expression .nat NN := both ident (constant 1) >>> normalizeRat
end Expression

def pairHeight (p : ℕ × ℕ) : ℕ := max 1 (max p.1.bits.length p.2.bits.length)

open Expression

open Expression
def weightSmall (S : ℕ) (x : Value WeightInput) : Prop :=
  width x≤S ∧ x.1.1.1≤S ∧ x.1.1.2≤S

abbrev OraclePredicate (a : DataType) := (n : ℕ) → IndependenceOracle n → ℕ → Value a → Prop

def OracleSafe {a b : DataType} (p : Expression a b) (R : OraclePredicate a) : Prop :=
  ∃ f : SizePolynomial,∀ {n : ℕ} (O : IndependenceOracle n) S x,R n O S x → Safe O (f.eval S) p x

namespace Effect
def bankScope {a : DataType} (R : OraclePredicate a) : OraclePredicate (.prod a BankData) :=
  fun n O S x => R n O S x.1 ∧ width (a:=BankData) x.2≤S
end Effect
end MachineCode

namespace MachineCode

open Expression

open Expression
abbrev Attribute (a : DataType) := ℕ → Value a → Prop
def Attribute.Mono {a : DataType} (R : Attribute a) : Prop :=
  ∀ S T,S≤T → ∀ x,R S x → R T x
def AttrSafe {a b : DataType} (p : Expression a b) (R : Attribute a) : Prop :=
  PolySafe p (fun S x => width x≤S ∧ R S x)
namespace Effect
abbrev EffectSafe {a b : DataType} (p : Effect a b) (R : Attribute a) :=
  AttrSafe p (fun S x => R S x.1)
end Effect

namespace Effect

def optionScope {a : DataType} (R : OraclePredicate a) : OraclePredicate (Optional a) :=
  fun n O S x => x=optionalValue a none ∨ x.1=true ∧ R n O S x.2
end Effect

def contextAttr (S : ℕ) (x : Value WeightContext) : Prop := x.1.1≤S ∧ x.1.2≤S
def weightAttr (S : ℕ) (x : Value WeightInput) : Prop := contextAttr S x.1
def acceptanceAttr (S : ℕ) (x : Value AcceptanceInput) : Prop := contextAttr S x.1
namespace Effect
def offerAttr (S : ℕ) (x : Value OfferInput) : Prop := weightAttr S x.1
def pairOfferAttr (S : ℕ) (x : Value (.prod WeightInput NN)) : Prop := weightAttr S x.1
end Effect

open scoped Classical

open Expression
open Effect
open scoped Classical

structure SamplerModel (n : ℕ) (O : IndependenceOracle n) where
  M₁ : Matroid (Fin n)
  M₂ : Matroid (Fin n)
  exact : ExactOracles M₁ M₂ O
  ground₁ : M₁.E=Set.univ
  ground₂ : M₂.E=Set.univ
  positive : 0<n
  rank : ℕ
  samples : ℕ
  phases : ℕ
namespace SamplerModel
variable {n : ℕ} {O : IndependenceOracle n}
def tau (m : SamplerModel n O) := 20*n^4*(n*(m.phases+1)+2)
def cap (m : SamplerModel n O) := restartCap n m.phases
def Bounded (m : SamplerModel n O) (S : ℕ) : Prop :=
  n≤S ∧ m.rank≤S ∧ m.samples≤S ∧ m.phases≤S ∧ m.cap≤S ∧ m.tau≤S
abbrev Hist (m : SamplerModel n O) := List (PhaseSample n m.samples)
noncomputable def histValue (m : SamplerModel n O) (history : m.Hist) : Value HistoryData :=
  historyEncode (history.map (observedSets n m.samples))
noncomputable def weightContext (m : SamplerModel n O) (h : m.Hist) (j : ℕ) : Value WeightContext :=
  ((m.rank,j),m.histValue h)
noncomputable def weightValue (m : SamplerModel n O) (h : m.Hist) (j : ℕ)
    (x : ChainIndex (pairedSlots n)) : Value WeightInput := (m.weightContext h j,stateEncode x.val)
noncomputable def restartContext (m : SamplerModel n O) (h : m.Hist) : Value RestartContext :=
  ((m.rank,m.histValue h),(m.cap,m.tau))
noncomputable def restartValue (m : SamplerModel n O) (h : m.Hist) (j : ℕ)
    (x : PlainIndex (pairedSlots n)) : Value RestartState := ((m.restartContext h,j),stateEncode x.val.val)
noncomputable def phaseValue (m : SamplerModel n O) (h : m.Hist) : Value PhaseInput :=
  (params n m.rank m.samples m.phases,m.histValue h)
end SamplerModel

def sourceWidthPoly (c : ℕ) : SizePolynomial :=
  .mul (.constant c) (.mul (.add .input (.constant 1))
    (.mul (.add .input (.constant 1)) (.add .input (.constant 1))))

def weightScope : OraclePredicate WeightInput := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ j,j≤S ∧ ∃ x : ChainIndex (pairedSlots n),z=m.weightValue h j x
end MachineCode

namespace MachineCode

open Expression
open scoped Classical
namespace Effect

abbrev SearchData := DataType.prod WeightInput .bool
def searchScope : OraclePredicate SearchData := fun n O S x => weightScope n O S x.1
def searchInputScope : OraclePredicate (.prod .nat SearchData) := fun n O S x =>
  x.1≤S+1 ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ j,j≤S ∧ ∃ u : SearchState (ChainIndex (pairedSlots n)) (plainPredicate (pairedSlots n)),
      x.2=searchEncode (m.weightValue h j) u

def traceScope : OraclePredicate (.prod .nat WeightInput) := fun n O S x =>
  x.1≤S+1 ∧ weightScope n O S x.2
end Effect

open Expression

open Expression
open scoped Classical
namespace Effect
def restartScope : OraclePredicate RestartState := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ j,j≤S ∧ ∃ x : PlainIndex (pairedSlots n),z=m.restartValue h j x

def traceRepeatScope : OraclePredicate (.prod .nat (Optional RestartState)) := fun n O S z =>
  z.1≤S ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ j,j≤S ∧ ∃ x : PlainIndex (pairedSlots n),z.2=optionEncode (m.restartValue h j) (some x)
def restartOptWidth : SizePolynomial :=
  .add (sourceWidthPoly 200) (.constant (width (zeroValue RestartState)+3))

def iterateTraceScope : OraclePredicate (.prod .nat RestartState) := fun n O S z =>
  z.1≤S ∧ restartScope n O S z.2
end Effect

open Expression
open scoped Classical
namespace SamplerModel
noncomputable def restartInput {n : ℕ} {O : IndependenceOracle n} (m : SamplerModel n O)
    (h : m.Hist) (k : ℕ) : Value RestartInput := (n,(k,m.restartContext h))
end SamplerModel
namespace Effect
def restartRepeatScope : OraclePredicate (.prod .nat (Optional RestartState)) := fun n O S z =>
  z.1≤S ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ x : PlainIndex (pairedSlots n),z.2=optionEncode (m.restartValue h 0) (some x)
def restartInputScope : OraclePredicate RestartInput := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ k,k≤S ∧ z=m.restartInput h k
def restartAfterPlainScope : OraclePredicate (.prod RestartInput StateData) := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ k,k≤S ∧ ∃ x : PlainIndex (pairedSlots n),z=(m.restartInput h k,stateEncode x.val.val)
end Effect

namespace SamplerModel
noncomputable def phaseState {n : ℕ} {O : IndependenceOracle n} (m : SamplerModel n O)
    (h : m.Hist) (x : ChainIndex (pairedSlots n)) : Value (.prod PhaseInput StateData) :=
  (m.phaseValue h,stateEncode x.val)
end SamplerModel
namespace Effect
def phaseScope : OraclePredicate PhaseInput := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧ z=m.phaseValue h
def phaseStateScope : OraclePredicate (.prod PhaseInput StateData) := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ x : ChainIndex (pairedSlots n),z=m.phaseState h x

abbrev SampleNodeData := DataType.prod PhaseInput StateData
abbrev SampleAccData := DataType.prod (.list SampleNodeData) SampleNodeData
noncomputable def sampleAccValue {n : ℕ} {O : IndependenceOracle n} (m : SamplerModel n O)
    (h : m.Hist) (s : List (ChainIndex (pairedSlots n)) × ChainIndex (pairedSlots n)) : Value SampleAccData :=
  (s.1.map (m.phaseState h),m.phaseState h s.2)
def sampleAccWidth : SizePolynomial :=
  .add (.mul (.constant 2) (.mul .input (.add (.mul (.constant 2) (sourceWidthPoly 210)) (.constant 2))))
    (.add (.constant 1) (sourceWidthPoly 210))
def sampleAccScope : OraclePredicate SampleAccData := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ acc : List (ChainIndex (pairedSlots n)),acc.length≤S ∧
    ∃ x : ChainIndex (pairedSlots n),z=sampleAccValue m h (acc,x)
def sampleRepeatScope : OraclePredicate (.prod .nat SampleAccData) := fun n O S z =>
  z.1≤S ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ ∃ h : m.Hist,h.length≤S ∧
    ∃ x : ChainIndex (pairedSlots n),z.2=sampleAccValue m h ([],x)
def sampleInputScope : OraclePredicate (.prod .nat SampleNodeData) := fun n O S z =>
  z.1≤S ∧ phaseStateScope n O S z.2
end Effect

open Expression

open Expression
open scoped Classical

section
open Expression
open scoped Classical
namespace Expression

def weightedPlainCode : Expression (.prod .nat StateData) NN :=
  applyIf (snd >>> plainCode) (constant (0,1))
    (both (both (both fst (constant 1)) (constant [])) snd >>> annealCode)
def weightedListCode : Expression (.prod .nat SampleData) (.list NN) :=
  both snd fst >>> mapWith (Expression.swap >>> weightedPlainCode)
def weightedMeanCode : Expression (.prod .nat SampleData) NN :=
  letIn weightedListCode (both (snd >>> ratSumCode) (fst >>> snd >>> listLength >>> natRatCode) >>> ratDiv)
def plainMeanCode : Expression (.prod .nat SampleData) NN :=
  both (snd >>> countPlainCode) (snd >>> listLength) >>> normalizeRat
def ratioCode : Expression (.prod .nat SampleData) NN := both weightedMeanCode plainMeanCode >>> ratDiv
end Expression
end

section
open Expression
namespace Effect

def historyTickCode {a c} (p : Effect (.prod c (.list a)) (Optional a)) :
    Effect (.prod c (.list a)) (Optional (.prod c (.list a))) :=
  map (retainOption p) (optionMapCode (both (fst >>> fst)
    (both (fst >>> snd) (both snd (constant []) >>> cons) >>> listAppend)))
def historyTickTree {X : Type*} (K : List X → DrawTree (Option X)) (h : List X) : DrawTree (Option (List X)) :=
  (K h).map (Option.map (fun x => h++[x]))

def historyCode {a c} (p : Effect (.prod c (.list a)) (Optional a)) :
    Effect (.prod .nat (.prod c (.list a))) (Optional (.prod c (.list a))) := iterateCode (historyTickCode p)
end Effect

open Expression
open scoped BigOperators Classical
namespace Expression

def historyAtCode : Expression (.prod HistoryData .nat) SampleData := both ident (constant []) >>> getDCode
def ratioAtCode : Expression (.prod .nat PhaseInput) NN :=
  both (snd >>> fst >>> fst >>> snd) (both (snd >>> snd) fst >>> historyAtCode) >>> ratioCode

def estimateCode : Expression PhaseInput NN :=
  both (both (constant (b:=.nat) 2) (fst >>> fst >>> fst : Expression PhaseInput .nat) >>> Expression.powCode >>> natRatCode)
    (both (fst >>> snd >>> fst >>> snd >>> rangeCode) ident >>> mapWith ratioAtCode >>> ratProductCode) >>> ratMul

def guardCode : Expression (.prod PhaseInput StateData) NN :=
  applyIf (both (fst >>> fst >>> fst >>> snd) snd >>> deficiencyCode >>> natEqZero)
    (constant (0,1)) (fst >>> estimateCode)
end Expression

end

section
open Expression
open Effect
open scoped Classical
namespace Effect

def finalGuardCode : Effect PhaseInput (Optional NN) :=
  map (retainOption witnessCode) (optionMapCode guardCode)
def algorithmHistoryCode : Effect AlgorithmParams (Optional PhaseInput) :=
  precompose (both (snd >>> fst >>> snd) (both ident (constant []))) (historyCode phaseCode)
def algorithmCode : Effect AlgorithmParams (Optional NN) := optionBind algorithmHistoryCode finalGuardCode
end Effect
end

open Expression
open scoped Classical
namespace Effect
noncomputable def phaseOptWidth : SizePolynomial :=
  .add (sourceWidthPoly 100) (.constant (width (zeroValue PhaseInput)+3))

def historyRepeatScope : OraclePredicate (.prod .nat (Optional PhaseInput)) := fun n O S z =>
  z.1≤S ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ z.2=optionEncode m.phaseValue (some [])
def historyInputScope : OraclePredicate (.prod .nat PhaseInput) := fun n O S z =>
  z.1≤S ∧ ∃ (m : SamplerModel n O),m.Bounded S ∧ z.2=m.phaseValue []
def algorithmScope : OraclePredicate AlgorithmParams := fun n O S z =>
  ∃ (m : SamplerModel n O),m.Bounded S ∧ z=params n m.rank m.samples m.phases
end Effect

open Expression
def estimatorAttr (S : ℕ) (z : Value PhaseInput) : Prop :=
  z.1.1.1≤S ∧ z.1.1.2≤S ∧ z.1.2.1.2≤S

open Expression Effect

open Expression
open scoped Classical

open Expression

namespace Expression
abbrev InsertState := DataType.prod NN (.prod (.list NN) (.list NN))
def insertStep : Expression (.prod NN InsertState) InsertState :=
  both (snd >>> fst) (both (both fst (snd >>> snd >>> fst) >>> Expression.cons)
    (applyIf (both fst (snd >>> fst) >>> ratLess)
      (both (snd >>> fst) (both fst (snd >>> snd >>> fst) >>> Expression.cons) >>> Expression.cons)
      (both fst (snd >>> snd >>> snd) >>> Expression.cons)))
def insertInit : Expression (.prod NN (.list NN)) InsertState :=
  both fst (both (constant (b:=.list NN) [])
    (both fst (constant (b:=.list NN) []) >>> Expression.cons (a:=NN)))
def insertRatCode : Expression (.prod NN (.list NN)) (.list NN) :=
  both (snd >>> listReverse) insertInit >>> fold insertStep >>> snd >>> snd
def sortRatCode : Expression (.list NN) (.list NN) :=
  both listReverse (constant []) >>> fold insertRatCode
def medianCode : Expression (.list NN) NN :=
  both (both sortRatCode (listLength >>> both (constant 2) ident >>> natDiv)) (constant (0,1)) >>> getDCode

end Expression

namespace Expression

def optionalRatCode : Expression (Optional NN) NN := applyIf fst (constant (0,1)) snd
def singleCode : Expression (.prod AlgorithmParams BankData) NN :=
  Effect.algorithmCode >>> fst >>> applyIf fst (constant (0,1)) (snd >>> optionalRatCode)
def amplifiedCode : Expression (.prod AlgorithmParams (.list BankData)) NN :=
  swap >>> mapWith (swap >>> singleCode) >>> medianCode
end Expression

open Expression Effect
def amplificationScope : OraclePredicate (.prod AlgorithmParams (.list BankData)) :=
  fun n O S z => algorithmScope n O S z.1 ∧ width (a:=.list BankData) z.2≤S

open Expression
abbrev FrozenData := DataType.prod NN (.prod NN NN)
def frozenValue (n r : ℕ) (ε δ : ℚ) : Value FrozenData :=
  ((n,r),((ε.num.natAbs,ε.den),(δ.num.natAbs,δ.den)))
namespace Expression

def natPow : ℕ → Expression .nat .nat
  | 0 => constant 1
  | k+1 => both ident (natPow k) >>> mul

def sumE {a} (p q : Expression a .nat) : Expression a .nat := both p q >>> add
def prodE {a} (p q : Expression a .nat) : Expression a .nat := both p q >>> mul

def ceilDivCode : Expression NN .nat :=
  both fst (both (sumE snd fst) (constant 1) >>> natSub) >>> divRem >>> snd

def lengthNatCode : Expression .nat .nat := toBits >>> toList >>> listLength

def annealLengthCode : Expression NN .nat :=
  prodE (prodE (constant 2) fst) (sumE fst (sumE snd (constant 5)))

def sampleCountCode : Expression (.prod .nat NN) .nat :=
  sumE (prodE (prodE (prodE (constant (10^14))
    (sumE (snd >>> fst) (constant 1) >>> natPow 3)) (fst >>> natPow 14))
      (snd >>> snd >>> natPow 2)) (constant 1)

def tauCode : Expression NN .nat :=
  prodE (prodE (constant 20) (fst >>> natPow 4))
    (sumE (prodE fst (sumE snd (constant 1))) (constant 2))
def capCode : Expression NN .nat :=
  prodE (prodE (prodE (constant 2000) (sumE snd (constant 1) >>> natPow 2))
    (fst >>> natPow 2)) tauCode

def chunkValues {α : Type*} (m h : ℕ) (xs : List α) : List (List α) :=
  (List.range m).map (fun i => (xs.drop (i*h)).take h)
def chunkStep {a : DataType} : Expression (.prod .nat (.prod (.list a) .nat)) (.list a) :=
  both (both (snd >>> fst) (prodE fst (snd >>> snd)) >>> dropCode) (snd >>> snd) >>> takeCode
def chunksCode {a : DataType} : Expression (.prod (.list a) NN) (.list (.list a)) :=
  both ((snd >>> fst) >>> rangeCode) (both fst (snd >>> snd)) >>> mapWith chunkStep
end Expression
open Expression Effect
namespace Expression
def frozenN : Expression FrozenData .nat := fst >>> fst
def frozenL : Expression FrozenData .nat :=
  both frozenN (snd >>> fst >>> snd >>> lengthNatCode) >>> annealLengthCode
def frozenE : Expression FrozenData .nat := snd >>> fst >>> ceilDivCode
def frozenSamples : Expression FrozenData .nat :=
  both frozenN (both frozenL frozenE) >>> sampleCountCode
def frozenParams : Expression FrozenData AlgorithmParams :=
  both fst (both (both frozenSamples frozenL)
    (both (both frozenN frozenL >>> capCode) (both frozenN frozenL >>> tauCode)))
def frozenReps : Expression FrozenData .nat :=
  prodE (constant 64) (sumE (snd >>> snd >>> snd >>> lengthNatCode) (constant 1))

abbrev Recipe3 := DataType.prod .nat NN
def drawsCode : Expression Recipe3 .nat :=
  prodE (sumE (sumE (constant 1)
     (prodE (prodE (prodE (constant 3) (snd >>> snd))
        (sumE (both fst (snd >>> snd) >>> capCode) (constant 1)))
          (both fst (snd >>> snd) >>> tauCode)))
      (prodE (constant 3) (snd >>> fst))) (sumE (snd >>> snd) (constant 1))
def wordBoundCode : Expression Recipe3 .nat :=
  sumE (sumE (sumE
    (prodE (constant 2) (sumE (sumE (constant 2) (prodE (snd >>> fst) (snd >>> snd)))
      (prodE (prodE (constant 2) fst) (prodE fst (snd >>> snd)))))
    (fst >>> natPow 2)) fst) (constant 4)
def frozenRecipe3 : Expression FrozenData Recipe3 := both frozenN (both frozenSamples frozenL)
def frozenDraws : Expression FrozenData .nat := frozenRecipe3 >>> drawsCode
def frozenWordBound : Expression FrozenData .nat := frozenRecipe3 >>> wordBoundCode
abbrev BankShape := DataType.prod .nat NN
abbrev PreparedData := DataType.prod AlgorithmParams BankShape
def frozenPrepared : Expression FrozenData PreparedData :=
  both frozenParams (both frozenReps (both frozenDraws frozenWordBound))
def bitCountCode : Expression BankShape .nat :=
  prodE (prodE (prodE fst (snd >>> snd)) (sumE (snd >>> fst) (constant 6))) (snd >>> fst)
def frozenBitCount : Expression FrozenData .nat :=
  frozenPrepared >>> snd >>> bitCountCode

abbrev Shape3 := DataType.prod .nat NN
abbrev Shape4 := DataType.prod .nat Shape3

def rowsCode : Expression (.prod (.list .bool) NN) RandomRows :=
  chunksCode >>> mapEach fromList
def bankCode : Expression (.prod (.list .bool) Shape3) BankData :=
  both (both fst (both (snd >>> fst) (prodE (snd >>> snd >>> fst) (snd >>> snd >>> snd))) >>> chunksCode)
    (snd >>> snd) >>> mapWith rowsCode
def banksCode : Expression (.prod (.list .bool) Shape4) (.list BankData) :=
  both (both fst (both (snd >>> fst) (prodE (snd >>> snd >>> fst)
      (prodE (snd >>> snd >>> snd >>> fst) (snd >>> snd >>> snd >>> snd)))) >>> chunksCode)
    (snd >>> snd) >>> mapWith bankCode
def shape4Code : Expression BankShape Shape4 :=
  both fst (both (snd >>> fst) (both (sumE (snd >>> fst) (constant 6)) (snd >>> snd)))
def reshapeCode : Expression (.prod .word BankShape) (.list BankData) :=
  both (fst >>> toList) (snd >>> shape4Code) >>> banksCode

def preambleCode : Expression FrozenData (.prod .word PreparedData) :=
  both (frozenBitCount >>> unary) frozenPrepared
abbrev RawData := DataType.prod .word PreparedData
abbrev AmplifyInput := DataType.prod AlgorithmParams (.list BankData)
def reshapePrepared : Expression RawData AmplifyInput :=
  both (snd >>> fst) (both fst (snd >>> snd) >>> reshapeCode)
def finishCode : Expression AmplifyInput NN :=
  applyIf (fst >>> fst >>> fst >>> natEqZero) amplifiedCode (constant (1,1))
def postambleCode : Expression RawData NN := reshapePrepared >>> finishCode
end Expression
def rawScope : OraclePredicate RawData := fun n O S z =>
  width z≤S ∧ z.2.2.1≤S ∧ z.2.2.2.1+6≤S ∧
    z.2.1.1.1=n ∧ (0<n → algorithmScope n O S z.2.1)
def finishScope : OraclePredicate AmplifyInput := fun n O S z =>
  width z≤S ∧ z.1.1.1=n ∧ (0<n → algorithmScope n O S z.1)

def coinPush {k : ℕ} (a : Fin (k+8)) : Block k := fork .coin (push a false) (push a true)

def sampledWord : ℕ → (ℕ → Bool) → Word
  | 0,_ => []
  | m+1,bits => sampledWord m (fun i => bits (3+i)) ++ [bits 1]
lemma sampledWord_length (m : ℕ) (bits : ℕ → Bool) : (sampledWord m bits).length=m := by
  induction m generalizing bits with
  | zero => rfl
  | succ m ih => simp [sampledWord,ih]
def sixRegs (a b c d e f : Word) : Registers 1 := fun i =>
  match i.val with
  | 0 => a | 1 => b | 2 => c | 3 => d | 4 => e | 5 => f | _ => []
def inputPack : Block 1 :=
  seq (pack 4 5) (seq (pack 2 3) (seq (pack 3 5) (seq (pack 0 1)
    (seq (pack 1 5) (move 5 2 0)))))
def inputTime (a b c d e f : Word) : ℕ :=
  (3*e.length+2)+((3*c.length+2)+((3*(pair c d).length+2)+((3*a.length+2)+
    ((3*(pair a b).length+2)+(4*(pair (pair a b) (pair (pair c d) (pair e f))).length+2)))))

open Expression
def preparedValue (n r : ℕ) (ε δ : ℚ) : Value PreparedData :=
  let L := annealLength n (accuracyExponent ε)
  let N := sampleCount n L ε
  (params n r N L,(repetitionCount δ,(algorithmDrawBound n N L,drawWordBound n N L)))
def rawValue (n r : ℕ) (ε δ : ℚ) (w : BitWord (fairBitCount n ε δ)) : Value RawData :=
  (List.ofFn w,preparedValue n r ε δ)

open Expression
namespace SizePolynomial
def pow (p : SizePolynomial) : ℕ → SizePolynomial
  | 0 => .constant 1
  | k+1 => .mul (pow p k) p
end SizePolynomial

def masterParameter (n r : ℕ) (ε δ : ℚ) : ℕ :=
  1+n+inputLength n r ε δ+⌈ε⁻¹⌉₊+Nat.clog 2 ⌈δ⁻¹⌉₊
def atomPoly : SizePolynomial := .mul (.constant (10^22)) (.pow (.add .input (.constant 1)) 24)
def tauPoly : SizePolynomial :=
  .mul (.mul (.constant 20) (atomPoly.pow 4))
    (.add (.mul atomPoly (.add atomPoly (.constant 1))) (.constant 2))
def capPoly : SizePolynomial :=
  .mul (.mul (.mul (.constant 2000) ((SizePolynomial.add atomPoly (.constant 1)).pow 2)) (atomPoly.pow 2)) tauPoly
def fairPoly : SizePolynomial := .mul (.constant (10^66)) ((SizePolynomial.add .input (.constant 1)).pow 73)
def sourceEnvelope : SizePolynomial :=
  .add (.add (.add (.add atomPoly tauPoly) capPoly) fairPoly) (.constant 6)
def scopeEnvelope : SizePolynomial := .mul (.constant 100) (.add sourceEnvelope (.constant 1))

open Expression Effect

open Expression

def compiledMajorant {a b : DataType} (p : Expression a b) (f : SizePolynomial) : SizePolynomial :=
  .mul (.constant (chargeCoefficient p)) ((SizePolynomial.add
    (.add f .input) (.constant 1)).pow (chargeDegree p))

def outRegs (a b : Word) : Registers 1 := fun i => if i=6 then a else if i=7 then b else []
def outputPack : Block 1 := seq (unpack 0 6) (move 0 1 7)
def outputTime (a b : Word) : ℕ := (3*a.length+1)+(4*b.length+2)

def pipeline (pre post : Block 1) : Block 1 :=
  seq inputPack (seq pre (seq (unpack 0 1) (seq (repeatBlock (coinPush 2) 1)
    (seq (pack 2 0) (seq post outputPack)))))
def pipelineTime (i pre f post a b : ℕ) : ℕ :=
  i+(pre+((3*f+1)+((3*f+1)+((3*f+2)+(post+((3*a+1)+(4*b+2)))))))

def physicalWord (start m : ℕ) (bits : ℕ → Bool) : BitWord m :=
  fun i => bits (start+3*(m-1-i.val)+1)
def physicalPosition (start m B : ℕ) (h : start+3*m+1≤B) (i : Fin m) : Fin B :=
  ⟨start+3*(m-1-i.val)+1,by have hi := i.isLt; omega⟩

def terminalState (p : Block 1) (x y : Word) (q w : ℕ) : MachineConfig 1 p.size :=
  ⟨outRegs x y,lastLabel p.size,some ((binaryValue x:ℚ)/(binaryValue y:ℚ)),q,w+x.length+y.length+1⟩

def pipelineBudget (i F cp cq W : ℕ) : ℕ := i+cp+9*F+cq+5*W+10

open Expression

def totalResourcePoly (fp fq : SizePolynomial) : SizePolynomial :=
  .mul (.constant 200) (.add (.add (.add sourceEnvelope (compiledMajorant preambleCode fp))
    (compiledMajorant postambleCode fq)) (.add fq (.constant 1)))

def fprasBlock : Block 1 := pipeline (compile preambleCode) (compile postambleCode)

open Expression Effect
open scoped Classical
noncomputable def treeBankOutput (t : DrawTree (Option ℚ)) (bank : Bank) : ℚ :=
  (bankEval t bank).1.join.getD 0
noncomputable def treeBanksOutput (t : DrawTree (Option ℚ)) (banks : List Bank) : ℚ :=
  rationalMedian (banks.map (treeBankOutput t))

open Expression

def bankReshapeEquiv {a : ℕ} (R M H : ℕ) (h : a=R*(M*((M+6)*H))) :
    BitWord a ≃ (Fin R → Fin M → Fin (M+6) → BitWord H) :=
  (Equiv.arrowCongr (finCongr h) (Equiv.refl Bool)).trans (fourBitEquiv R M (M+6) H)
lemma reshape_count (n : ℕ) (ε δ : ℚ) :
    let L := annealLength n (accuracyExponent ε)
    let N := sampleCount n L ε
    let M := algorithmDrawBound n N L
    let H := drawWordBound n N L
    fairBitCount n ε δ=repetitionCount δ*(M*((M+6)*H)) := by
  change repetitionCount δ * drawWordBound n (sampleCount n (annealLength n (accuracyExponent ε)) ε)
    (annealLength n (accuracyExponent ε)) * _ * _ = _
  ac_rfl

def reshapeWordEquiv (n : ℕ) (ε δ : ℚ) : BitWord (fairBitCount n ε δ) ≃ FPRASBank n ε δ :=
  bankReshapeEquiv (repetitionCount δ)
    (algorithmDrawBound n (sampleCount n (annealLength n (accuracyExponent ε)) ε) (annealLength n (accuracyExponent ε)))
    (drawWordBound n (sampleCount n (annealLength n (accuracyExponent ε)) ε) (annealLength n (accuracyExponent ε)))
    (reshape_count n ε δ)

open Expression
open scoped Classical

def OutputCertificate {n : ℕ} (r : ℕ) (M₁ M₂ : Matroid (Fin n))
    (O : IndependenceOracle n) (ε δ : ℚ)
    (v : BitWord (fairBitCount n ε δ) → ℚ) : Prop :=
  (∀ w,run O postambleCode (rawValue n r ε δ w)=some (rationalPair (v w))) ∧
  (∀ w,0≤v w ∧ (commonBaseCount M₁ M₂=0 → v w=0)) ∧
  (∀ φ : ℚ → ℝ,uniformAverage (fun w => φ (v w))=
    uniformAverage (fun banks : FPRASBank n ε δ => φ (canonicalBoundedOutput n r O ε δ banks)))

open Expression

open scoped Classical

end MachineCode
end CommonBasesFPRAS

open CommonBasesFPRAS CommonBasesFPRAS.MachineCode

end OAI
end
Source
OpenAI, Approximate counting of common bases of two matroids, OpenAI Math Release, September 23, 2026, https://github.com/openai/math (result family 114; paper: https://github.com/openai/math/blob/adc7f1241b42e322a6451854ab7e4b4c146bf78a/preprints/Approximate-counting-of-common-bases-of-two-matroids-September-23-2026/main.pdf; Apache License 2.0); definitions carved from lean/OAI/Combinatorics/MatroidCounting at commit adc7f12

View graph

Get started

Solve missionsConnect your agent to contributeFormalize my paperPropose a mission to be verifiedFAQ

About Prove2Me

Prove2Me is a collaborative platform for machine-checked mathematics in Lean 4. Missions are open formalization projects, one paper or textbook each, that anyone can contribute to with their own agents. Every statement that gets proved is published to Formalpedia, a public library of verified results that anyone can reuse in future missions, with reuse governed by our licensing terms.

How Prove2Me worksResearch paper
SKILL.mdTourFAQContactTerms
© 2026 Prove2Me