diff --git a/Complexity.lean b/Complexity.lean index c6efd2bc35..975c21d89e 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -921,12 +921,20 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CoeffSchem public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedContraction +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedLift public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContraction +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionMachine +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionPolyTime +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionStep public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeCall +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChild +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChildPolyTime public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeRecovery public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator @@ -1127,6 +1135,7 @@ public import Complexity.Computability.Complexity.Classes.CountingSimulation.Adm public import Complexity.Computability.Complexity.Classes.CountingSimulation.Assembly public import Complexity.Computability.Complexity.Classes.CountingSimulation.BlockNetworks public import Complexity.Computability.Complexity.Classes.CountingSimulation.Containments +public import Complexity.Computability.Complexity.Classes.CountingSimulation.FastAlgorithm public import Complexity.Computability.Complexity.Classes.CountingSimulation.Guarded public import Complexity.Computability.Complexity.Classes.CountingSimulation.Interfaces public import Complexity.Computability.Complexity.Classes.DP diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean index cbf09baa78..8168cafce7 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean @@ -8,7 +8,7 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit -public import Complexity.Computability.GenericRAM.Native.StackRecursion +public import Complexity.Computability.GenericRAM.Native.StackRecursionTree public import Complexity.Computability.PolyTime.GaloisRingArithmetic public import Complexity.Foundations.Data.BitString.List.Binary public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Words @@ -83,8 +83,6 @@ contraction algorithm, with implicit packet tensors as descriptors. ## Main results -* `Cslib.GenericRAM.StackRecursion.Scheme.Loop.counted`: a call whose children are indexed by - a counter. * `Cslib.Circuits.Frontier.DescribedNetwork.eval_sumRoot`: direct summation evaluates to `sumValue`, with `2 ^ m (k + 1) + 1` nodes and two open calls. * `Cslib.Circuits.Frontier.SumOps.IsHom.map_sumValue`: under a homomorphism, the value is the @@ -100,43 +98,6 @@ contraction algorithm, with implicit packet tensors as descriptors. @[expose] public section -namespace Cslib.GenericRAM.StackRecursion.Scheme - -open Finset - -variable {X A Y : Type} {R : Scheme X A Y} {I : Invariant X A Y} - -/-- **A call with counted children.** If, from the accumulators `acc c`, the call `x` asks for the -children `child c` for `c < bound` and then stops, and the children evaluate to `val c`, then its -loop from `acc 0` ends at `acc bound`, with the children's nodes and at most `H` levels. -/ -theorem Loop.counted {x : X} {acc : ℕ → A} {child : ℕ → X} {val : ℕ → Y} {nodes height : ℕ → ℕ} - (bound H : ℕ) (hnext : ∀ c < bound, R.next x (acc c) = some (child c)) - (hdone : R.next x (acc bound) = none) - (habsorb : ∀ c < bound, R.absorb x (acc c) (val c) = acc (c + 1)) - (heval : ∀ c < bound, R.Eval I (child c) (val c) (nodes c) (height c)) - (hframe : ∀ c < bound, I.frame x (acc c)) (hH : ∀ c < bound, height c ≤ H) : - ∃ h ≤ H, R.Loop I x (acc 0) (acc bound) (∑ c ∈ range bound, nodes c) h := by - -- From any start `s ≤ bound`. - have key : ∀ k s, s + k = bound → - ∃ h ≤ H, R.Loop I x (acc s) (acc bound) (∑ c ∈ Ico s bound, nodes c) h := by - intro k - induction k with - | zero => - intro s hs - subst hs - exact ⟨0, Nat.zero_le _, by simpa using Loop.done hdone⟩ - | succ k ih => - intro s hs - obtain ⟨h, hh, hl⟩ := ih (s + 1) (by omega) - have hs' : s < bound := by omega - have hl' := Loop.child (hframe s hs') (hnext s hs') (heval s hs') - (by rw [habsorb s hs']; exact hl) - refine ⟨max (height s) h, max_le (hH s hs') hh, ?_⟩ - rwa [sum_eq_sum_Ico_succ_bot hs'] - simpa using key bound 0 (by omega) - -end Cslib.GenericRAM.StackRecursion.Scheme - namespace Cslib.Circuits.Frontier open Finset Cslib.BitString Cslib.GenericRAM Cslib.GenericRAM.StackRecursion Turing.MultiTapeTM diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedLift.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedLift.lean new file mode 100644 index 0000000000..657d320f7e --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedLift.lean @@ -0,0 +1,488 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ReductionLift +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.VertexDesc +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.PolyTime.GaloisRingUnits +import Complexity.Foundations.NumberTheory.Padics.AdjoinRootDigitLift +import Mathlib.Data.List.GetD +import Mathlib.Data.List.Sort + +/-! +# A described network over `O` + +The contraction recursion of [OpenAI2026ETH] (§D.4, pp. 57–64) is proved correct over the valuation +ring `O = ℤ_(2)[U] / (f)`, while the native algorithm carries networks of descriptors +(`DescribedNetwork VertexDesc`) and computes with coefficient lists in +`A_p = (ℤ / 2 ^ p)[U] / (f)`. This file gives the network over `O` that a described network stands +for. + +* **Lifts.** A coefficient list `c` lifts to `∑ i < e, c_i U ^ i` in `O` (`GaloisRingArith.liftO`), + and reducing the lift modulo `2 ^ p` gives the value of `c` in `A_p` + (`GaloisRingArith.reduction_liftO`). This is the canonical lifting of representatives of §D.4.1 + (p. 58): a retained vertex is read through the lift of its entry at every later precision. + Lifts of distinct lists with binary coordinates differ by a unit + (`GaloisRingArith.isUnit_liftO_sub`); in particular the binary digits of `0, …, n - 1` are + interpolation points when `n ≤ 2 ^ e` (`GaloisRingArith.pairwise_isUnit_liftO_sub_digits`). +* **Rows first.** A described network is *rows-first bipartite* at `r` + (`DescribedNetwork.IsRowsFirstBipartite`) when every port names an edge and every edge is a port + of exactly one of the first `r` vertices, once, and of exactly one of the others, once. The ends + of the edges are then computed by listing the vertices at each edge + (`DescribedNetwork.incident`, `DescribedNetwork.edgeEnds`), in polynomial time + (`DescribedNetwork.computableInPolyTime_edgeEnds`). +* **The network** (`DescribedNetwork.toNetwork`). A rows-first described network and an evaluator + give a Boolean tensor network on `Fin k` vertices and `Fin m` edges with these ends, whose + tensor at a vertex is the evaluator at its descriptor and the bits on its ports. It has the + contraction of the described network (`DescribedNetwork.contraction_toNetwork`). Over `O`, with + the lifts of the entries, its contraction reduces modulo `2 ^ p` to the contraction of the + values in `A_p` (`DescribedNetwork.reduction_contraction_toNetwork`). + +## Main definitions + +* `Cslib.GaloisRingArith.liftO`: the lift of a coefficient list to `O`. +* `Cslib.Circuits.Frontier.DescribedNetwork.incident`, + `Cslib.Circuits.Frontier.DescribedNetwork.edgeEnds`: the ends of the edges. +* `Cslib.Circuits.Frontier.DescribedNetwork.toNetwork`: the network of a described network. + +## Main results + +* `Cslib.GaloisRingArith.reduction_liftO`, `Cslib.GaloisRingArith.mk_liftO`: reducing a lift. +* `Cslib.GaloisRingArith.isUnit_liftO_sub`, + `Cslib.GaloisRingArith.pairwise_isUnit_liftO_sub_digits`: binary lists as interpolation points. +* `Cslib.Circuits.Frontier.DescribedNetwork.incident_eq`: the vertices at an edge. +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction_toNetwork`, + `Cslib.Circuits.Frontier.DescribedNetwork.reduction_contraction_toNetwork`: the contraction. +* `Cslib.Circuits.Frontier.DescribedNetwork.computableInPolyTime_edgeEnds`: polynomial time. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1 (pp. 57–58) and + §D.4.6 (pp. 63–64). +-/ + +public section + +open Polynomial AdjoinRoot Finset + +/-! ### Lifts of coefficient lists -/ + +namespace AdjoinRoot + +/-- `ℤ_(2)` lies in the valuation ring `O` of `ℚ[U] / (f)`. -/ +theorem algebraMap_mem_integer_padicValuation {f : ℤ[X]} (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod 2)))) : + ∀ c ∈ Valuation.integer (Rat.padicValuation 2), + algebraMap ℚ (AdjoinRoot (f.map (Int.castRingHom ℚ))) c ∈ + (padicValuation 2 hf hirr).integer := fun c hc => by + rw [Valuation.mem_integer_iff, padicValuation_algebraMap] + exact hc + +end AdjoinRoot + +namespace Cslib.GaloisRingArith + +variable {fl : List ℕ} (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + +/-- The class of `U` in the valuation ring `O = ℤ_(2)[U] / (f)`, `f = monicOf fl`. -/ +noncomputable def rootO : (padicValuation 2 (monic_monicOf fl) hirr).integer := + ⟨map (Int.castRingHom ℚ) (monicOf fl) _ dvd_rfl (root _), + (Valuation.mem_integer_iff _ _).mpr (padicValuation_map_le_one _ hirr _)⟩ + +/-- **The lift of a coefficient list to `O`**: `∑ i < e, c_i U ^ i`, with `e = fl.length`. -/ +noncomputable def liftO (c : List ℕ) : (padicValuation 2 (monic_monicOf fl) hirr).integer := + ∑ i ∈ range fl.length, ((c.getD i 0 : ℕ) : (padicValuation 2 (monic_monicOf fl) hirr).integer) * + rootO hirr ^ i + +/-- Reduction sends `U` to `U`. -/ +theorem reduction_rootO (p : ℕ) : + reduction (monic_monicOf fl) hirr p (rootO hirr) = root _ := by + rw [rootO, reduction_toRat, map_root] + +/-- **Reducing a lift modulo `2 ^ p` gives the value in `A_p`.** -/ +theorem reduction_liftO (p : ℕ) (c : List ℕ) : + reduction (monic_monicOf fl) hirr p (liftO hirr c) = val p fl c := by + simp only [liftO, val, map_sum, map_mul, map_natCast, map_pow, reduction_rootO] + +/-- **The class of a lift modulo `2 ^ p`** is the image of the value in `A_p`. -/ +theorem mk_liftO (p : ℕ) (c : List ℕ) : + Ideal.Quotient.mk _ (liftO hirr c) = + Cslib.Circuits.Frontier.reductionLift (monic_monicOf fl) hirr p (val p fl c) := by + rw [← reduction_liftO hirr p, Cslib.Circuits.Frontier.reductionLift_reduction] + +/-! ### Binary lists as interpolation points -/ + +/-- A lift of a list with binary coordinates is a digit lift. -/ +private theorem liftO_eq_integerDigitLift {c : List ℕ} (hc : ∀ i < fl.length, c.getD i 0 < 2) : + liftO hirr c = AdjoinRoot.integerDigitLift (monic_monicOf fl) hirr + fun i => ((c.getD i 0 : ℕ) : ZMod 2) := by + apply Subtype.ext + rw [AdjoinRoot.coe_integerDigitLift, Fin.sum_univ_eq_sum_range + (fun i => ((((c.getD i 0 : ℕ) : ZMod 2)).val : AdjoinRoot ((monicOf fl).map + (Int.castRingHom ℚ))) * AdjoinRoot.root _ ^ i), natDegree_monicOf, liftO] + push_cast + refine Finset.sum_congr rfl fun i hi => ?_ + rw [ZMod.val_natCast, Nat.mod_eq_of_lt (hc i (mem_range.mp hi))] + exact congrArg (_ * · ^ i) (AdjoinRoot.map_root _ _ _ _) + +/-- **Lifts of distinct binary lists differ by a unit.** If two lists have binary coordinates +below `e = fl.length` and differ at one of them, their lifts to `O` differ by a unit: they are +digit lifts (`AdjoinRoot.isUnit_integerDigitLift_sub`). -/ +theorem isUnit_liftO_sub {c c' : List ℕ} (hc : ∀ i < fl.length, c.getD i 0 < 2) + (hc' : ∀ i < fl.length, c'.getD i 0 < 2) + (hne : ∃ i < fl.length, c.getD i 0 ≠ c'.getD i 0) : + IsUnit (liftO hirr c - liftO hirr c') := by + rw [liftO_eq_integerDigitLift hirr hc, liftO_eq_integerDigitLift hirr hc'] + refine AdjoinRoot.isUnit_integerDigitLift_sub _ hirr fun h => ?_ + obtain ⟨i, hi, hne⟩ := hne + have h2 := congrArg ZMod.val (congrFun h ⟨i, by rwa [natDegree_monicOf]⟩) + rw [ZMod.val_natCast, ZMod.val_natCast, Nat.mod_eq_of_lt (hc i hi), + Nat.mod_eq_of_lt (hc' i hi)] at h2 + exact hne h2 + +/-- Coordinate `i < e` of the binary digits of `k` below `e`. -/ +theorem getD_map_testBit (e k i : ℕ) (hi : i < e) : + ((List.range e).map fun j => if k.testBit j then 1 else 0).getD i 0 = + if k.testBit i then 1 else 0 := by + rw [List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, List.getElem_range] + +/-- **Binary-digit points are interpolation points.** The lists of the binary digits below +`e = fl.length` of `0, …, n - 1`, for `n ≤ 2 ^ e`, have lifts to `O` with unit differences. -/ +theorem pairwise_isUnit_liftO_sub_digits {n : ℕ} (hn : n ≤ 2 ^ fl.length) : + Pairwise fun i j : Fin ((List.range n).map fun k => + (List.range fl.length).map fun j => if k.testBit j then 1 else 0).length => + IsUnit (liftO hirr (((List.range n).map fun k => + (List.range fl.length).map fun j => if k.testBit j then 1 else 0).getD i []) - + liftO hirr (((List.range n).map fun k => + (List.range fl.length).map fun j => if k.testBit j then 1 else 0).getD j [])) := by + intro i j hij + have hi : (i : ℕ) < n := by simpa using i.2 + have hj : (j : ℕ) < n := by simpa using j.2 + rw [List.getD_eq_getElem _ _ i.2, List.getD_eq_getElem _ _ j.2, List.getElem_map, + List.getElem_map, List.getElem_range, List.getElem_range] + have hbin (k : ℕ) : ∀ b < fl.length, + ((List.range fl.length).map fun j => if k.testBit j then 1 else 0).getD b 0 < 2 := by + intro b hb + rw [getD_map_testBit _ _ _ hb] + split <;> omega + refine isUnit_liftO_sub hirr (hbin _) (hbin _) ?_ + by_contra hall + simp only [not_exists, not_and, not_not] at hall + refine hij (Fin.ext (Nat.eq_of_testBit_eq fun b => ?_)) + rcases lt_or_ge b fl.length with hb | hb + · have := hall b hb + rw [getD_map_testBit _ _ _ hb, getD_map_testBit _ _ _ hb] at this + revert this + cases (i : ℕ).testBit b <;> cases (j : ℕ).testBit b <;> simp + · have h2 : 2 ^ fl.length ≤ 2 ^ b := Nat.pow_le_pow_right (by norm_num) hb + rw [Nat.testBit_lt_two_pow (by omega), Nat.testBit_lt_two_pow (by omega)] + +end Cslib.GaloisRingArith + +namespace Cslib.Circuits.Frontier + +/-! ### Lists of lists with one occurrence -/ + +/-- **One occurrence among the lists.** If an element occurs once in total among a list of +lists, exactly one of the lists contains it, once. -/ +theorem exists_unique_of_sum_count_eq_one {Ls : List (List ℕ)} {j : ℕ} + (h : (Ls.map fun l => l.count j).sum = 1) : + ∃ i, ∃ hi : i < Ls.length, Ls[i].count j = 1 ∧ + ∀ i' (hi' : i' < Ls.length), j ∈ Ls[i'] → i' = i := by + induction Ls with + | nil => simp at h + | cons l Ls ih => + rw [List.map_cons, List.sum_cons] at h + by_cases hj : j ∈ l + · have hl : l.count j = 1 := by + have := List.count_pos_iff.mpr hj + omega + have h0 : (Ls.map fun l => l.count j).sum = 0 := by omega + refine ⟨0, by simp, by simpa using hl, fun i' hi' hm => ?_⟩ + rcases i' with _ | i' + · rfl + · exfalso + have hi'' : i' < Ls.length := by simpa using hi' + simp only [List.getElem_cons_succ] at hm + have hpos := List.count_pos_iff.mpr hm + have hle : Ls[i'].count j ≤ (Ls.map fun l => l.count j).sum := + List.le_sum_of_mem (List.mem_map.mpr ⟨_, List.getElem_mem _, rfl⟩) + omega + · have hl : l.count j = 0 := List.count_eq_zero.mpr hj + obtain ⟨i, hi, hc, hu⟩ := ih (by omega) + refine ⟨i + 1, by simpa using hi, by simpa using hc, fun i' hi' hm => ?_⟩ + rcases i' with _ | i' + · exact absurd hm (by simpa using hj) + · simp only [List.getElem_cons_succ] at hm + rw [hu i' (by simpa using hi') hm] + +namespace DescribedNetwork + +variable {T : Type} (D : DescribedNetwork T) + +/-! ### Rows first -/ + +/-- Being rows-first depends only on the ports, not on the descriptors. -/ +theorem isRowsFirstBipartite_map {T' : Type} (f : T → T') (r : ℕ) : + (⟨D.numEdges, D.vertices.map fun v => (v.1, f v.2)⟩ : + DescribedNetwork T').IsRowsFirstBipartite r ↔ + D.IsRowsFirstBipartite r := by + simp only [IsRowsFirstBipartite, List.forall_mem_map, ← List.map_take, ← List.map_drop, + List.map_map, Function.comp_def] + +/-- The vertices at the edge `j`: those having `j` among their ports, in increasing order. -/ +@[expose] def incident (j : ℕ) : List ℕ := + (List.range D.vertices.length).filter fun v => j ∈ (D.vertices.map Prod.fst).getD v [] + +/-- **The ends of the edges**: for each edge, its first and its second vertex. -/ +@[expose] def edgeEnds : List (ℕ × ℕ) := + (List.range D.numEdges).map fun j => ((D.incident j).getD 0 0, (D.incident j).getD 1 0) + +variable {D} + +theorem getD_map_fst (v : ℕ) (hv : v < D.vertices.length) : + (D.vertices.map Prod.fst).getD v [] = D.vertices[v].1 := by + rw [List.getD_eq_getElem _ _ (by simpa using hv), List.getElem_map] + +/-- **The two ends of an edge** of a rows-first network: a row `s < r` and a column `t ≥ r`, which +are the only vertices with the edge among their ports, each once. -/ +theorem exists_ends {r : ℕ} (hD : D.IsRowsFirstBipartite r) {j : ℕ} (hj : j < D.numEdges) : + ∃ s t, ∃ hs : s < D.vertices.length, ∃ ht : t < D.vertices.length, s < r ∧ r ≤ t ∧ + D.vertices[s].1.count j = 1 ∧ D.vertices[t].1.count j = 1 ∧ + ∀ v (hv : v < D.vertices.length), j ∈ D.vertices[v].1 → v = s ∨ v = t := by + obtain ⟨hrow', hcol'⟩ := hD.2 j hj + have hrow : (((D.vertices.map Prod.fst).take r).map fun l => l.count j).sum = 1 := by + rw [← List.map_take, List.map_map] + exact hrow' + have hcol : (((D.vertices.map Prod.fst).drop r).map fun l => l.count j).sum = 1 := by + rw [← List.map_drop, List.map_map] + exact hcol' + obtain ⟨s, hs, hsc, hsu⟩ := exists_unique_of_sum_count_eq_one hrow + obtain ⟨t, ht, htc, htu⟩ := exists_unique_of_sum_count_eq_one hcol + simp only [List.length_take, List.length_map, List.length_drop] at hs ht + have hs' : s < D.vertices.length := by omega + have hsr : s < r := by omega + have ht' : r + t < D.vertices.length := by omega + refine ⟨s, r + t, hs', ht', hsr, by omega, ?_, ?_, fun v hv hm => ?_⟩ + · simpa using hsc + · simpa using htc + · by_cases hvr : v < r + · left + refine hsu v (by simp; omega) ?_ + simpa [List.getElem_take] using hm + · right + have := htu (v - r) (by simp; omega) (by + simp only [List.getElem_map, List.getElem_drop] + convert hm using 3 + omega) + omega + +/-- **The vertices at an edge** of a rows-first network are its row and its column. -/ +theorem incident_eq {r : ℕ} (hD : D.IsRowsFirstBipartite r) {j : ℕ} (hj : j < D.numEdges) : + ∃ s t, ∃ hs : s < D.vertices.length, ∃ ht : t < D.vertices.length, s < r ∧ r ≤ t ∧ + D.vertices[s].1.count j = 1 ∧ D.vertices[t].1.count j = 1 ∧ D.incident j = [s, t] := by + obtain ⟨s, t, hs, ht, hsr, hrt, hsc, htc, hu⟩ := exists_ends hD hj + refine ⟨s, t, hs, ht, hsr, hrt, hsc, htc, ?_⟩ + have hsorted : (D.incident j).Pairwise (· < ·) := + (List.pairwise_lt_range).filter _ + have hmem : ∀ v, v ∈ D.incident j ↔ v ∈ [s, t] := by + intro v + simp only [incident, List.mem_filter, List.mem_range, decide_eq_true_eq, List.mem_cons, + List.not_mem_nil, or_false] + constructor + · rintro ⟨hv, hm⟩ + rw [getD_map_fst v hv] at hm + exact hu v hv hm + · rintro (rfl | rfl) + · refine ⟨hs, ?_⟩ + rw [getD_map_fst _ hs] + exact List.count_pos_iff.mp (by omega) + · refine ⟨ht, ?_⟩ + rw [getD_map_fst _ ht] + exact List.count_pos_iff.mp (by omega) + have hst : [s, t].Pairwise (· < ·) := by simp; omega + exact hsorted.eq_of_mem_iff hst hmem + +/-- The first vertex at an edge of a rows-first network is a vertex. -/ +theorem getD_incident_zero_lt {r : ℕ} (hD : D.IsRowsFirstBipartite r) {j : ℕ} + (hj : j < D.numEdges) : (D.incident j).getD 0 0 < D.vertices.length := by + obtain ⟨s, t, hs, ht, -, -, -, -, h⟩ := incident_eq hD hj + rw [h] + exact hs + +/-- The second vertex at an edge of a rows-first network is a vertex. -/ +theorem getD_incident_one_lt {r : ℕ} (hD : D.IsRowsFirstBipartite r) {j : ℕ} + (hj : j < D.numEdges) : (D.incident j).getD 1 0 < D.vertices.length := by + obtain ⟨s, t, hs, ht, -, -, -, -, h⟩ := incident_eq hD hj + rw [h] + exact ht + +/-! ### The network of a described network -/ + +/-- The row of an edge of a rows-first network. -/ +@[expose] def srcFin {r : ℕ} (hD : D.IsRowsFirstBipartite r) (j : Fin D.numEdges) : + Fin D.vertices.length := + ⟨(D.incident j).getD 0 0, getD_incident_zero_lt hD j.2⟩ + +/-- The column of an edge of a rows-first network. -/ +@[expose] def tgtFin {r : ℕ} (hD : D.IsRowsFirstBipartite r) (j : Fin D.numEdges) : + Fin D.vertices.length := + ⟨(D.incident j).getD 1 0, getD_incident_one_lt hD j.2⟩ + +/-- **The network of a rows-first described network**, on `Fin k` vertices and `Fin m` edges: the +edge `j` runs from its row to its column, and the tensor of a vertex is the evaluator at its +descriptor and the bits on its ports. -/ +@[expose] noncomputable def toNetwork {r : ℕ} (hD : D.IsRowsFirstBipartite r) {R : Type*} + (eval : T → List Bool → R) : + TensorNetwork R Bool (Fin D.vertices.length) (Fin D.numEdges) where + src := D.srcFin hD + tgt := D.tgtFin hD + tensor v x := eval D.vertices[v].2 (wordBits (List.ofFn x) D.vertices[v].1) + tensor_congr v x y hxy := by + congr 1 + refine List.map_congr_left fun i hi => ?_ + have hi' : i < D.numEdges := hD.1 _ (List.getElem_mem _) i hi + obtain ⟨s, t, hs, ht, -, -, -, -, hinc⟩ := incident_eq hD hi' + have hv : (v : ℕ) ∈ D.incident i := by + simp only [incident, List.mem_filter, List.mem_range, decide_eq_true_eq] + exact ⟨v.2, by rw [getD_map_fst _ v.2]; exact hi⟩ + rw [List.getD_eq_getElem _ _ (by simpa using hi'), + List.getD_eq_getElem _ _ (by simpa using hi'), + List.getElem_ofFn, List.getElem_ofFn] + refine hxy ⟨i, hi'⟩ ?_ + rw [hinc] at hv + simp only [List.mem_cons, List.not_mem_nil, or_false] at hv + rcases hv with hv | hv + · left + exact Fin.ext (by simp [srcFin, hinc, hv]) + · right + exact Fin.ext (by simp [tgtFin, hinc, hv]) + +variable {r : ℕ} (hD : D.IsRowsFirstBipartite r) + +@[simp] theorem toNetwork_src {R : Type*} (eval : T → List Bool → R) : + (D.toNetwork hD eval).src = D.srcFin hD := rfl + +@[simp] theorem toNetwork_tgt {R : Type*} (eval : T → List Bool → R) : + (D.toNetwork hD eval).tgt = D.tgtFin hD := rfl + +/-- The edges run from the rows. -/ +theorem srcFin_lt (j : Fin D.numEdges) : (D.srcFin hD j : ℕ) < r := by + obtain ⟨s, t, -, -, hsr, -, -, -, h⟩ := incident_eq hD j.2 + simp [srcFin, h, hsr] + +/-- The edges end at the columns. -/ +theorem le_tgtFin (j : Fin D.numEdges) : r ≤ (D.tgtFin hD j : ℕ) := by + obtain ⟨s, t, -, -, -, hrt, -, -, h⟩ := incident_eq hD j.2 + simp [tgtFin, h, hrt] + +/-- A vertex has an edge among its ports exactly when it is an end of the edge. -/ +theorem mem_ports_iff (v : Fin D.vertices.length) (j : Fin D.numEdges) : + (j : ℕ) ∈ D.vertices[v].1 ↔ D.srcFin hD j = v ∨ D.tgtFin hD j = v := by + obtain ⟨s, t, hs, ht, -, -, hsc, htc, hinc⟩ := incident_eq hD j.2 + have hmem : (v : ℕ) ∈ D.incident j ↔ (j : ℕ) ∈ D.vertices[v].1 := by + simp only [incident, List.mem_filter, List.mem_range, decide_eq_true_eq] + rw [getD_map_fst _ v.2] + exact ⟨fun h => h.2, fun h => ⟨v.2, h⟩⟩ + rw [← hmem, hinc] + simp only [List.mem_cons, List.not_mem_nil, or_false, Fin.ext_iff, srcFin, tgtFin, hinc, + List.getD_cons_zero, List.getD_cons_succ] + exact ⟨fun h => h.imp Eq.symm Eq.symm, fun h => h.imp Eq.symm Eq.symm⟩ + +/-- **The ends of the edges** are the native `edgeEnds`. -/ +theorem edgeEnds_eq : + D.edgeEnds = List.ofFn fun j => ((D.srcFin hD j : ℕ), (D.tgtFin hD j : ℕ)) := by + rw [edgeEnds, ← List.map_coe_finRange_eq_range, List.map_map, List.ofFn_eq_map] + rfl + +/-- **The network has the contraction of the described network.** -/ +theorem contraction_toNetwork {R : Type*} [CommSemiring R] (eval : T → List Bool → R) : + (D.toNetwork hD eval).contraction = D.contraction eval := by + simp only [TensorNetwork.contraction, contraction] + refine sum_congr rfl fun x _ => ?_ + rw [← List.ofFn_getElem_eq_map, List.prod_ofFn] + rfl + +/-- The network of a described network commutes with maps of the entries. -/ +theorem toNetwork_map {R S : Type*} [CommSemiring R] [CommSemiring S] (eval : T → List Bool → R) + (ψ : R →+* S) : (D.toNetwork hD eval).map ψ = D.toNetwork hD fun t bs => ψ (eval t bs) := + rfl + +/-- **Over `O`, the contraction reduces to the contraction in `A_p`.** For the network with the +lifts of the entries of the descriptors, reduction modulo `2 ^ p` of its contraction is the +contraction of the described network with the values of the entries in `A_p`. -/ +theorem reduction_contraction_toNetwork {fl : List ℕ} + (hirr : Irreducible ((GaloisRingArith.monicOf fl).map (Int.castRingHom (ZMod 2)))) + (eval : T → List Bool → List ℕ) (p : ℕ) : + reduction (GaloisRingArith.monic_monicOf fl) hirr p + (D.toNetwork hD fun t bs => GaloisRingArith.liftO hirr (eval t bs)).contraction = + D.contraction fun t bs => GaloisRingArith.val p fl (eval t bs) := by + rw [contraction_toNetwork, map_contraction] + simp only [GaloisRingArith.reduction_liftO] + +/-! ### Polynomial time -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {T : Type} (encT : T ↪ List Bool) + +/-- **The vertices at an edge are polynomial-time**, with the edge in unary. -/ +theorem computableInPolyTime_incident : + ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => q.1.incident q.2) + (encPair (encDescribed encT) encUnary) (encList encUnary) := by + have hV : ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => q.1.vertices) + (encPair (encDescribed encT) encUnary) (encList (encPair (encList encNat) encT)) := + vertices_polyTime.comp (ComputableInPolyTime.fst _ _) + have hP : ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => q.1.vertices.map Prod.fst) + (encPair (encDescribed encT) encUnary) (encList (encList encNat)) := + ComputableInPolyTime.mapWithInput (f := fun _ v => v.1) hV + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)) + have hrange : ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => + List.range q.1.vertices.length) (encPair (encDescribed encT) encUnary) (encList encUnary) := + ComputableInPolyTime.range.comp ((ComputableInPolyTime.length_list _).comp hV) + have hports : ComputableInPolyTime (fun z : (DescribedNetwork T × ℕ) × ℕ => + (z.1.1.vertices.map Prod.fst).getD z.2 []) + (encPair (encPair (encDescribed encT) encUnary) encUnary) (encList encNat) := + (ComputableInPolyTime.listGetD (encA := encList encNat) []).comp + ((hP.comp (ComputableInPolyTime.fst _ _)).prod (ComputableInPolyTime.snd _ _)) + have hj : ComputableInPolyTime (fun z : (DescribedNetwork T × ℕ) × ℕ => z.1.2) + (encPair (encPair (encDescribed encT) encUnary) encUnary) encNat := + ComputableInPolyTime.unary_to_binary.comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.fst _ _)) + exact ComputableInPolyTime.filterWithInput + (test := fun q v => decide (q.2 ∈ (q.1.vertices.map Prod.fst).getD v [])) hrange + (ComputableInPolyTime.decide_mem hports hj) + +/-- **The ends of the edges are polynomial-time.** -/ +theorem computableInPolyTime_edgeEnds : + ComputableInPolyTime (fun D : DescribedNetwork T => D.edgeEnds) (encDescribed encT) + (encList (encPair encNat encNat)) := by + have hinc : ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => q.1.incident q.2) + (encPair (encDescribed encT) encUnary) (encList encUnary) := + computableInPolyTime_incident encT + have hget (i : ℕ) : ComputableInPolyTime (fun q : DescribedNetwork T × ℕ => + (q.1.incident q.2).getD i 0) (encPair (encDescribed encT) encUnary) encNat := + ComputableInPolyTime.unary_to_binary.comp + ((ComputableInPolyTime.listGetD (encA := encUnary) 0).comp + (hinc.prod (ComputableInPolyTime.const i))) + exact ComputableInPolyTime.mapWithInput + (f := fun D j => ((D.incident j).getD 0 0, (D.incident j).getD 1 0)) + (ComputableInPolyTime.range.comp numEdges_polyTime) ((hget 0).prod (hget 1)) + +end PolyTime + +end DescribedNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean index 313760ebfc..a48aad9bc6 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean @@ -37,9 +37,9 @@ entry zero. Its size measures are the number of edges, the number of vertices, the maximum number of ports of a vertex (`ExplicitTensorNetwork.maxDegree`) and the total length of the tables -(`ExplicitTensorNetwork.tableLength`). A network is *rows-first bipartite* with `r` rows -(`ExplicitTensorNetwork.IsRowsFirstBipartite`) when every edge is a port of exactly one of its -first `r` vertices and of exactly one of the others. +(`ExplicitTensorNetwork.tableLength`). A described network is *rows-first bipartite* with `r` +rows (`DescribedNetwork.IsRowsFirstBipartite`) when every port names an edge and every edge is a +port of exactly one of its first `r` vertices and of exactly one of the others. **Writing a network out** (`TensorNetwork.toExplicit`). Given numberings of the vertices and the edges of a Boolean tensor network on finite types, list the edges at each vertex in increasing @@ -55,6 +55,8 @@ one end of every edge below `r` and the other at or above it gives a rows-first the descriptors of its vertices. * `Cslib.Circuits.Frontier.DescribedNetwork.contraction`: its contraction, for an evaluator of the descriptors. +* `Cslib.Circuits.Frontier.DescribedNetwork.IsRowsFirstBipartite`: the bipartition with the first + `r` vertices as rows. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork`: an explicit Boolean tensor network, with tables as descriptors. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.entry`, @@ -63,8 +65,6 @@ one end of every edge below `r` and the other at or above it gives a rows-first * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.contraction`: the contraction. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.maxDegree`, `Cslib.Circuits.Frontier.ExplicitTensorNetwork.tableLength`: size measures. -* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.IsRowsFirstBipartite`: the bipartition with the - first `r` vertices as rows. * `Cslib.Circuits.Frontier.TensorNetwork.explicitPorts`, `Cslib.Circuits.Frontier.TensorNetwork.explicitTable`, `Cslib.Circuits.Frontier.TensorNetwork.toExplicit`: writing a Boolean tensor network out. @@ -137,6 +137,18 @@ theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R rw [map_list_prod, List.map_map] rfl +/-- **Rows-first bipartite**: the first `r` vertices are the *rows* and the others the *columns*, +every port names an edge, and every edge is a port of the rows exactly once and of the columns +exactly once. So every edge joins a row to a column. -/ +def IsRowsFirstBipartite (d : DescribedNetwork T) (r : ℕ) : Prop := + (∀ v ∈ d.vertices, ∀ i ∈ v.1, i < d.numEdges) ∧ + ∀ i < d.numEdges, ((d.vertices.take r).map fun v => v.1.count i).sum = 1 ∧ + ((d.vertices.drop r).map fun v => v.1.count i).sum = 1 + +instance (d : DescribedNetwork T) (r : ℕ) : Decidable (d.IsRowsFirstBipartite r) := by + unfold IsRowsFirstBipartite + infer_instance + end DescribedNetwork /-- An *explicit Boolean tensor network* over `R`: a network whose descriptors are *tables*. The @@ -190,18 +202,6 @@ theorem length_le_maxDegree {v : List ℕ × List R} (hv : v ∈ d.vertices) : v.1.length ≤ d.maxDegree := d.maxDegree_le_iff.mp le_rfl v hv -/-- **Rows-first bipartite**: the first `r` vertices are the *rows* and the others the *columns*, -every port names an edge, and every edge is a port of the rows exactly once and of the columns -exactly once. So every edge joins a row to a column. -/ -def IsRowsFirstBipartite (r : ℕ) : Prop := - (∀ v ∈ d.vertices, ∀ i ∈ v.1, i < d.numEdges) ∧ - ∀ i < d.numEdges, ((d.vertices.take r).map fun v => v.1.count i).sum = 1 ∧ - ((d.vertices.drop r).map fun v => v.1.count i).sum = 1 - -instance (r : ℕ) : Decidable (d.IsRowsFirstBipartite r) := by - unfold IsRowsFirstBipartite - infer_instance - end ExplicitTensorNetwork private theorem getD_ofFn {n : ℕ} (x : Fin n → Bool) (i : ℕ) : diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContraction.lean new file mode 100644 index 0000000000..aab15a930b --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContraction.lean @@ -0,0 +1,557 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildCursor +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeRecovery +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ScheduledRecursion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.VertexDesc +public import Complexity.Computability.GenericRAM.Native.StackRecursionTree +public import Complexity.Computability.PolyTime.PacketPacking +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChild +import Complexity.Computability.PolyTime.GaloisRingSeries +import Mathlib.Data.List.GetD + +/-! +# The native contraction recursion as a stack recursion + +The recursion of Theorem D.1 of [OpenAI2026ETH] (Appendix D.4.7, p. 64), as a depth-first stack +recursion (`Cslib.GenericRAM.StackRecursion.Scheme`) over *calls*. A call +(`FastContraction.Call`) is a described network (`DescribedNetwork VertexDesc`) whose first `r` +vertices are its rows, with the schedule's scale `s` (the number of edges of the root, since the +machine sees no other size), its depth `d`, an edge bound, its precision `p` and the coefficients +`fl` of the modulus `f`. + +**Active calls and leaves.** A call with `M` edges is *active* (`FastContraction.active`) when the +schedule makes it an active call (`ContractionSchedule.callParams`, with `a = slotBits s d`, +`u = taper s d` and `h = ⌈3 M / u⌉`), its parameters satisfy the hypotheses of Lemma D.5, its +degrees are at most `degreeBound s`, the `M (2 L - 1) + 1` interpolation points are distinct +residues (`n ≤ 2 ^ e` for `e = deg f`), and `M` is below its edge bound. Every other call is a leaf, +evaluated by direct summation over the coefficient lists of `A_p` (`FastContraction.leafValue`). +A child receives its parent's number of edges as its bound, so the recursion terminates, as +`TensorNetwork.contractRec` does; a child that is not smaller is a leaf. + +**An active call** walks the children `(t, k, β, g)` with the cursor of +`Cslib.Circuits.Frontier.ChildCursor`, over the nodes `ts` of positive multiplicity +(`FastContraction.nodes`), the points, the mode tuples of weight below `p_t` and the `64` groups. +The child at a cursor state is given by a child builder `mk`, which computes the child's rows and +network (the native child of §D.4.6). The call absorbs the children's values with the operations of +`A_N` at the common precision `N = p + Δ'` (`FastContraction.wide`), where +`Δ' = (a + 1) 2 ^ (a + 1) h u + 8 h 2 ^ a + 8 M (a + 1)` bounds both the shift of Lemma D.5 and +every node precision `p_t - p`; it then places the sums at the rows `ts[i]` of the table of native +recovery (`FastContraction.table`) and recovers its value with +`Cslib.Circuits.Frontier.NativeRecovery.recover`. + +**Values and derivations.** The values of the scheme are tagged with the depth. +`FastContraction.value` is the value of a call by well-founded recursion on the edge bound, and +`FastContraction.exists_eval` derives it in the scheme, so a run returning a value tagged `0` from a +root at depth `0` returns `value` of the root, for every fuel (`FastContraction.eq_value_of_run`). +**Correctness** (`FastContraction.val_value`): if the step of an active call is correct +(`FastContraction.StepCorrect`, the correctness of one call from the correctness of its children, +through Lemma D.5) and a well-formedness predicate passes to the children, then every well-formed +call computes the contraction of its network in `A_p`. + +## Main definitions + +* `Cslib.Circuits.Frontier.FastContraction.Call`: a call. +* `Cslib.Circuits.Frontier.FastContraction.active`: whether a call is active. +* `Cslib.Circuits.Frontier.FastContraction.scheme`: the stack recursion. +* `Cslib.Circuits.Frontier.FastContraction.value`: the value of a call. +* `Cslib.Circuits.Frontier.FastContraction.StepCorrect`: the correctness of one active call. + +## Main results + +* `Cslib.Circuits.Frontier.FastContraction.pow_slots_le_edges_of_active`: an active call has at + least `64 ^ a` edges. +* `Cslib.Circuits.Frontier.FastContraction.exists_eval`: derivations of the values. +* `Cslib.Circuits.Frontier.FastContraction.eq_value_of_run`: a tagged value returned by the run is + the root's value. +* `Cslib.Circuits.Frontier.FastContraction.val_table_runLoop`: the table of an active call holds, + at each node's precision, the sums over the mode tuples of the products of the children's + values. +* `Cslib.Circuits.Frontier.FastContraction.val_value`: correctness from the step. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.5–D.4.7 + (pp. 62–64). +-/ + +public section + +namespace Cslib.Circuits.Frontier.FastContraction + +open GaloisRingArith ChildCursor NativeRecovery ContractionSchedule NativeChild Cslib.GenericRAM + Cslib.GenericRAM.StackRecursion + +/-- The parameters of a call: the scale `s`, the depth `d`, the edge bound, the precision `p`, the +coefficients `fl` of the modulus and the number `r` of rows. -/ +abbrev Params : Type := ℕ × ℕ × ℕ × ℕ × List ℕ × ℕ + +/-- **A call** of the recursion: its parameters and its network, whose first `r` vertices are its +rows. -/ +abbrev Call : Type := Params × DescribedNetwork VertexDesc + +namespace Call + +variable (x : Call) + +/-- The scale `s` of the schedule. -/ +abbrev scale : ℕ := x.1.1 +/-- The depth. -/ +abbrev depth : ℕ := x.1.2.1 +/-- The edge bound: the call is a leaf unless it has fewer edges. -/ +abbrev bound : ℕ := x.1.2.2.1 +/-- The precision `p`. -/ +abbrev prec : ℕ := x.1.2.2.2.1 +/-- The coefficients of the modulus `f = monicOf fl`. -/ +abbrev fl : List ℕ := x.1.2.2.2.2.1 +/-- The number of rows. -/ +abbrev rows : ℕ := x.1.2.2.2.2.2 +/-- The network. -/ +abbrev net : DescribedNetwork VertexDesc := x.2 +/-- The number `M` of edges. -/ +abbrev edges : ℕ := x.2.numEdges + +end Call + +/-! ### The data of a call -/ + +variable (x : Call) + +/-- The packets have `2 ^ a` slots, `a = slotBits s d`. -/ +@[expose] def slots : ℕ := slotBits x.scale x.depth + +/-- The number `u = taper s d` of levels of positive multiplicity. -/ +@[expose] def levels : ℕ := taper x.scale x.depth + +/-- The multiplicity step `h = ⌈3 M / u⌉`. -/ +@[expose] def step : ℕ := taperStep x.scale x.depth x.edges + +/-- The number `n = M (2 L - 1) + 1` of interpolation points. -/ +@[expose] def numPoints : ℕ := x.edges * (2 * 2 ^ slots x - 1) + 1 + +/-- **Whether a call is active**: the schedule makes it an active call, its parameters satisfy +Lemma D.5 (`1 ≤ u ≤ M`, `u ≤ a`), its degrees are at most `degreeBound s`, its interpolation +points are distinct residues (`n ≤ 2 ^ e`), and it has fewer edges than its bound. -/ +@[expose] def active : Bool := + decide (leafSize x.scale < x.edges ∧ 1 ≤ levels x ∧ levels x ≤ x.edges ∧ levels x ≤ slots x ∧ + (∀ v ∈ x.net.vertices, v.1.length ≤ degreeBound x.scale) ∧ numPoints x ≤ 2 ^ x.fl.length ∧ + x.edges < x.bound) + +/-- An active call has fewer edges than its bound. -/ +theorem edges_lt_bound_of_active {x : Call} (h : active x = true) : x.edges < x.bound := by + simp only [active, decide_eq_true_eq] at h + exact h.2.2.2.2.2.2 + +/-- The level `v₂ (t)` of the node of grid index `k`, `t = k - 2 ^ a`. -/ +@[expose] def level (k : ℕ) : ℕ := twoVal (slots x + 1) (k - 2 ^ slots x + (2 ^ slots x - k)) + +/-- **The nodes of positive multiplicity**, as increasing grid indices `k < 2 ^ (a + 1)`, +`k ≠ 2 ^ a`; the node of index `k` is `NativeRecovery.node a k = k - 2 ^ a`. -/ +@[expose] def nodes : List ℕ := + (List.range (2 ^ (slots x + 1))).filter fun k => + decide (k ≠ 2 ^ slots x ∧ 0 < mult (slots x) (levels x) (step x) k) + +/-- The precision `p_t = nodePrecision p h a M (v₂ t)` of the `i`-th node. -/ +@[expose] def nodePrec (i : ℕ) : ℕ := + nodePrecision x.prec (step x) (slots x) x.edges (level x ((nodes x).getD i 0)) + +/-- The number of rows capped at the number of vertices. The packing and the child builder use it +in place of the number of rows: it changes nothing for a rows-first network with an edge +(`FastContraction.rowCap_eq`), and it keeps them polynomial-time on every call, since the number +of rows is stored in binary and the packing lists one word per row. -/ +@[expose] def rowCap : ℕ := min x.rows x.net.vertices.length + +/-- The cap on the rows is inactive when there are at most as many rows as vertices. -/ +theorem rowCap_eq {x : Call} (h : x.rows ≤ x.net.vertices.length) : rowCap x = x.rows := + min_eq_left h + +/-- **The packing of Lemma D.3**, natively (`nativeParentPacking`, `r = 64`), with the rows +capped (`FastContraction.rowCap`). -/ +@[expose] def packing : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ) := + nativeParentPacking (rowCap x) (slots x) x.net + +/-- The packing is the native packing of the rows when there are at most as many rows as +vertices. -/ +theorem packing_eq {x : Call} (h : x.rows ≤ x.net.vertices.length) : + packing x = nativeParentPacking x.rows (slots x) x.net := by + rw [packing, rowCap_eq h] + +/-- **The weights of the modes of the `i`-th node**, packet-major over `(q, y₀, b)` with +`y₀ < 2 ^ (v₂ t + 1)` and `b < p_t`: mode `(q, y₀, b)` has weight `b + 1`. -/ +@[expose] def weights (i : ℕ) : List ℕ := + modeWeights (packing x).2.1 (2 ^ (level x ((nodes x).getD i 0) + 1)) (nodePrec x i) + +/-- The shift `Δ' = (a + 1) 2 ^ (a + 1) h u + 8 h 2 ^ a + 8 M (a + 1)`, which bounds the shift +`(a + 1) ∑_t q_t` of Lemma D.5 and every `p_t - p`. -/ +@[expose] def shift : ℕ := + (slots x + 1) * 2 ^ (slots x + 1) * step x * levels x + 8 * step x * 2 ^ slots x + + 8 * x.edges * (slots x + 1) + +/-- The common precision `N = p + Δ'` of the accumulator. -/ +@[expose] def wide : ℕ := x.prec + shift x + +/-- The binary digits of `k`, as a coefficient list of length `e`: the lift of the `k`-th +residue. -/ +@[expose] def digits (e k : ℕ) : List ℕ := (List.range e).map fun i => if k.testBit i then 1 else 0 + +/-- **The interpolation points**: the lifts of the residues `0, …, n - 1`. -/ +@[expose] def points : List (List ℕ) := (List.range (numPoints x)).map (digits x.fl.length) + +/-- **The table of native recovery** from the stored values, node-major and point-minor: row +`ts[j]` holds the `j`-th block of `n` values, and the other rows are empty. -/ +@[expose] def table (V : List (List ℕ)) : List (List (List ℕ)) := + (List.range (2 ^ (slots x + 1))).map fun k => + if k ∈ nodes x then (V.drop ((nodes x).idxOf k * numPoints x)).take (numPoints x) else [] + +/-- The cursor's list of children. -/ +@[expose] def children : List State := + cursorList (weights x) (nodePrec x) (nodes x).length (numPoints x) 64 + +/-- The accumulator after absorbing the values `vals` of the children in order. -/ +@[expose] def runLoop (vals : State → List ℕ) : Acc (List ℕ) := + runAcc (weights x) (nodePrec x) 64 (mul (wide x) x.fl) (add (wide x) x.fl) (one x.fl) + (zero x.fl) vals (children x) (one x.fl, zero x.fl, []) + +/-- **The value of an active call** from its final accumulator: native recovery from the table of +the stored values. -/ +@[expose] def finishValue (acc : Acc (List ℕ)) : List ℕ := + recover x.prec (shift x) x.fl (slots x) (levels x) (step x) (points x) (table x acc.2.2) + +/-- **The value of a leaf**: direct summation in `A_p`. -/ +@[expose] def leafValue : List ℕ := + DescribedNetwork.sumValue SumOps.galois VertexDesc.entry (x.prec, x.fl) x.net + +/-! ### The scheme -/ + +variable (mk : Call → State → ℕ × DescribedNetwork VertexDesc) + +/-- **The child call** at a cursor state `c = (i, k, β, g)`: the rows and the network given by the +child builder `mk`, at depth `d + 1`, with the parent's number of edges as its bound and the +node's precision `p_i`. -/ +@[expose] def child (c : State) : Call := + ((x.scale, x.depth + 1, x.edges, nodePrec x c.1, x.fl, (mk x c).1), (mk x c).2) + +/-- The accumulator of a call: the cursor state of the next child, if any, and the accumulator of +the cursor. -/ +abbrev Frame : Type := Option State × Acc (List ℕ) + +/-- **The recursion as a stack recursion**, with values tagged by the depth. -/ +@[expose] def scheme : Scheme Call Frame (ℕ × List ℕ) where + isLeaf x := !active x + leaf x := (x.depth, leafValue x) + start x := (some (cursorStart (weights x)), (one x.fl, zero x.fl, [])) + next x a := a.1.map (child x mk) + absorb x a y := match a.1 with + | none => a + | some c => (cursorNext (weights x) (nodePrec x) (nodes x).length (numPoints x) 64 c, + absorbAt (weights x) (nodePrec x) 64 (mul (wide x) x.fl) (add (wide x) x.fl) (one x.fl) + (zero x.fl) c a.2 y.2) + finish x a := (x.depth, finishValue x a.2) + +/-- **The value of a call**, by well-founded recursion on the edge bound: the leaf value, or the +recovery from the values of the children. -/ +@[expose] def value (x : Call) : List ℕ := + if _h : active x = true then + finishValue x (runLoop x fun c => value (child x mk c)) + else leafValue x +termination_by x.bound +decreasing_by exact edges_lt_bound_of_active _h + +theorem value_of_active {x : Call} (h : active x = true) : + value mk x = finishValue x (runLoop x fun c => value mk (child x mk c)) := by + rw [value, dite_eq_left h] + +theorem value_of_not_active {x : Call} (h : active x = false) : value mk x = leafValue x := by + rw [value, dite_eq_right (by simp [h])] + +/-! ### Derivations -/ + +section Walk + +variable {x} + +/-- Every node precision is positive at a call with an edge. -/ +theorem nodePrec_pos (hM : 1 ≤ x.edges) (i : ℕ) : 0 < nodePrec x i := by + unfold nodePrec nodePrecision + have : 0 < 8 * x.edges * (slots x + 1) := by positivity + omega + +/-- Every weight is positive. -/ +theorem weights_pos (i : ℕ) : ∀ w ∈ weights x i, 0 < w := by + intro w hw + simp only [weights, modeWeights, List.mem_map] at hw + obtain ⟨j, -, rfl⟩ := hw + omega + +/-- An active call has an edge. -/ +theorem one_le_edges_of_active (h : active x = true) : 1 ≤ x.edges := by + simp only [active, decide_eq_true_eq] at h + omega + +/-- **An active call has at least `64 ^ a` edges**, the hypothesis `r ^ a ≤ M` of the native packing +(`PacketPacking.nativePacking_eq`), which `active` implies: with `λ = ⌊log₂ s⌋`, `1 ≤ u` forces +`λ ≥ 1`, `a ≤ λ / 32768` (`ContractionSchedule.slotBits_le`), and +`M > leafSize s = ⌊s / ⌊√λ⌋⌋ ≥ 2 ^ (6 ⌊λ / 32768⌋)`, since `⌊√λ⌋ ≤ 2 ^ ((λ + 1) / 2)`. -/ +theorem pow_slots_le_edges_of_active (h : active x = true) : 64 ^ slots x ≤ x.edges := by + have h' := h + simp only [active, decide_eq_true_eq] at h' + obtain ⟨hleaf, hu, -, -, -, -, -⟩ := h' + set L := Nat.log 2 x.scale with hL + have hL1 : 1 ≤ L := by + have : levels x = L / 2 ^ (17 + x.depth) := rfl + rcases Nat.eq_zero_or_pos L with h0 | h0 + · rw [this, h0, Nat.zero_div] at hu; omega + · exact h0 + have hs0 : x.scale ≠ 0 := by + intro h0; rw [hL, h0, Nat.log_zero_right] at hL1; omega + have hsL : 2 ^ L ≤ x.scale := Nat.pow_log_le_self 2 hs0 + have ha : slots x ≤ L / 32768 := slotBits_le _ _ + -- `⌊√λ⌋ ≤ 2 ^ ((λ + 1) / 2)`. + have hsq : Nat.sqrt L ≤ 2 ^ ((L + 1) / 2) := by + have h1 : Nat.sqrt L ^ 2 ≤ (2 ^ ((L + 1) / 2)) ^ 2 := by + calc Nat.sqrt L ^ 2 ≤ L := Nat.sqrt_le' L + _ ≤ 2 ^ L := Nat.lt_two_pow_self.le + _ ≤ 2 ^ (2 * ((L + 1) / 2)) := Nat.pow_le_pow_right (by norm_num) (by omega) + _ = (2 ^ ((L + 1) / 2)) ^ 2 := by rw [mul_comm, pow_mul] + exact (Nat.pow_le_pow_iff_left (by norm_num)).mp h1 + have hsqpos : 0 < Nat.sqrt L := Nat.sqrt_pos.mpr hL1 + have h64 : 64 ^ slots x ≤ 2 ^ (6 * (L / 32768)) := by + calc 64 ^ slots x = 2 ^ (6 * slots x) := by rw [pow_mul]; norm_num + _ ≤ 2 ^ (6 * (L / 32768)) := Nat.pow_le_pow_right (by norm_num) (by omega) + have hprod : 64 ^ slots x * Nat.sqrt L ≤ x.scale := by + calc 64 ^ slots x * Nat.sqrt L ≤ 2 ^ (6 * (L / 32768)) * 2 ^ ((L + 1) / 2) := + Nat.mul_le_mul h64 hsq + _ = 2 ^ (6 * (L / 32768) + (L + 1) / 2) := (pow_add _ _ _).symm + _ ≤ 2 ^ L := Nat.pow_le_pow_right (by norm_num) (by omega) + _ ≤ x.scale := hsL + have hle : 64 ^ slots x ≤ leafSize x.scale := + (Nat.le_div_iff_mul_le hsqpos).mpr hprod + omega + +/-- An active call has a node of positive multiplicity: the index `2 ^ a + 1`, of level `0`. -/ +theorem nodes_ne_nil_of_active (h : active x = true) : nodes x ≠ [] := by + have h' := h + simp only [active, decide_eq_true_eq] at h' + obtain ⟨-, hu, huM, hua, -, -, -⟩ := h' + have hL : 2 ≤ 2 ^ slots x := + (pow_one 2).symm.le.trans (Nat.pow_le_pow_right (by norm_num) (hu.trans hua)) + have hlt : 2 ^ slots x + 1 < 2 ^ (slots x + 1) := by rw [pow_succ]; linarith + have hk : 2 ^ slots x + 1 ∈ nodes x := by + simp only [nodes, List.mem_filter, List.mem_range, decide_eq_true_eq] + refine ⟨hlt, Nat.succ_ne_self _, ?_⟩ + rw [mult_eq _ _ hlt] + have h3 : 3 * x.edges ≤ step x * levels x := le_taperStep_mul hu + have hh : 0 < step x := by + rcases Nat.eq_zero_or_pos (step x) with h0 | h0 + · rw [h0] at h3; omega + · exact h0 + have hv : padicValInt 2 (node (slots x) (2 ^ slots x + 1)) = 0 := by + simp [node] + simp only [taperMult, hv, Nat.sub_zero] + positivity + intro hn + rw [hn] at hk + simp at hk + +end Walk + +/-- The frames of an active call along its children: the cursor at the `c`-th child, with the +values `vals` of the earlier children absorbed. -/ +@[expose] def frameAt (vals : State → List ℕ) (c : ℕ) : Frame := + ((children x)[c]?, runAcc (weights x) (nodePrec x) 64 (mul (wide x) x.fl) (add (wide x) x.fl) + (one x.fl) (zero x.fl) vals ((children x).take c) (one x.fl, zero x.fl, [])) + +/-- **The loop of an active call walks its children**: from `start`, the `c`-th frame asks for the +child at the `c`-th cursor state, absorbing it gives the next frame, and the last frame stops. -/ +theorem walk {x : Call} (h : active x = true) (vals : State → List ℕ) : + frameAt x vals 0 = (scheme mk).start x ∧ + (∀ c < (children x).length, (scheme mk).next x (frameAt x vals c) = + some (child x mk ((children x).getD c (0, 0, [], 0)))) ∧ + (scheme mk).next x (frameAt x vals (children x).length) = none ∧ + (∀ c < (children x).length, (scheme mk).absorb x (frameAt x vals c) + (x.depth + 1, vals ((children x).getD c (0, 0, [], 0))) = frameAt x vals (c + 1)) ∧ + runLoop x vals = (frameAt x vals (children x).length).2 := by + have hM := one_le_edges_of_active h + have hT : 0 < (nodes x).length := List.length_pos_iff.mpr (nodes_ne_nil_of_active h) + obtain ⟨hpos, h0, hstep, hlast⟩ := cursorList_walk (ws := weights x) (ps := nodePrec x) + (T := (nodes x).length) (n := numPoints x) (r := 64) (by norm_num) (by simp [numPoints]) + (nodePrec_pos hM) weights_pos hT + unfold frameAt runLoop children + refine ⟨?_, fun c hc => ?_, ?_, fun c hc => ?_, ?_⟩ + · simp only [scheme, List.take_zero, List.getElem?_eq_getElem hpos, h0, runAcc, List.foldl_nil] + · simp only [scheme, List.getElem?_eq_getElem hc, Option.map_some, List.getD_eq_getElem _ _ hc] + · simp only [scheme, List.getElem?_eq_none le_rfl, Option.map_none] + · simp only [scheme, List.getElem?_eq_getElem hc, List.getD_eq_getElem _ _ hc] + refine Prod.ext ?_ ?_ + · by_cases hc' : c + 1 < (cursorList (weights x) (nodePrec x) (nodes x).length + (numPoints x) 64).length + · simp only [List.getElem?_eq_getElem hc'] + exact hstep c hc' + · have hc'' : c = (cursorList (weights x) (nodePrec x) (nodes x).length (numPoints x) + 64).length - 1 := by omega + simp only [List.getElem?_eq_none (show (cursorList (weights x) (nodePrec x) + (nodes x).length (numPoints x) 64).length ≤ c + 1 by omega)] + subst hc'' + exact hlast + · rw [List.take_add_one, List.getElem?_eq_getElem hc] + simp only [runAcc, Option.toList_some, List.foldl_append, List.foldl_cons, List.foldl_nil] + · simp only [List.take_length] + +/-- The invariant of the derivation from a root `x₀` at depth `0`: the only node at depth `0` is +the root, and a value tagged `0` is the root's value. -/ +@[expose] def inv (x₀ : Call) : Invariant Call Frame (ℕ × List ℕ) := + ⟨fun x => x.depth = 0 → x = x₀, fun _ _ => True, fun y => y.1 = 0 → y.2 = value mk x₀⟩ + +/-- **Derivations of the values.** Every call satisfying the invariant of a root `x₀` has a +derivation of its tagged value. -/ +theorem exists_eval (x₀ : Call) (x : Call) (hx : (inv mk x₀).node x) : + ∃ n h, (scheme mk).Eval (inv mk x₀) x (x.depth, value mk x) n h := by + refine Scheme.exists_eval_of_measure (R := scheme mk) (I := inv mk x₀) Call.bound + (fun x => (x.depth, value mk x)) (fun x _ hl => ?_) (fun x _ hl => ?_) (fun x hx h0 => ?_) x hx + · have hl' : active x = false := by simpa [scheme] using hl + simp [scheme, value_of_not_active mk hl'] + · have hl' : active x = true := by simpa [scheme] using hl + obtain ⟨h0, hnext, hdone, habs, hfin⟩ := walk mk hl' fun c => value mk (child x mk c) + refine ⟨(children x).length, frameAt x fun c => value mk (child x mk c), + fun c => child x mk ((children x).getD c (0, 0, [], 0)), h0, hnext, hdone, + fun c hc => habs c hc, + fun c _ => ⟨trivial, fun h => absurd h (by simp [child]), edges_lt_bound_of_active hl'⟩, ?_⟩ + simp only [scheme, Prod.mk.injEq, true_and] + rw [value_of_active mk hl', hfin] + · simp only at h0 + rw [hx h0] + +/-- **A tagged value returned by the run is the root's value**, for any fuel: if the root is at +depth `0` and the run returns `(0, y)`, then `y` is the value of the root. -/ +theorem eq_value_of_run {J : Type} {fuel : J → ℕ} {root : J → Call} {a : J} {y : List ℕ} + (hroot : (root a).depth = 0) (hrun : (scheme mk).run fuel root a = some (0, y)) : + y = value mk (root a) := by + obtain ⟨n, h, he⟩ := exists_eval mk (root a) (root a) fun _ => rfl + rw [hroot] at he + exact Scheme.value_of_run_eq_some he hrun rfl + +/-! ### The stored values -/ + +/-- An entry of a concatenation of blocks of `n` values. -/ +theorem getElem_flatMap_range {α : Type*} (f : ℕ → ℕ → α) {T n t k : ℕ} (ht : t < T) + (hk : k < n) : + ((List.range T).flatMap fun t => (List.range n).map (f t))[t * n + k]'(by + simp only [List.length_flatMap, List.length_map, List.length_range, List.map_const', + List.sum_replicate, smul_eq_mul] + nlinarith) = f t k := by + induction T with + | zero => omega + | succ T ih => + simp only [List.range_succ, List.flatMap_append, List.flatMap_singleton] + have hlen : ((List.range T).flatMap fun t => (List.range n).map (f t)).length = T * n := by + simp + rcases Nat.lt_or_ge t T with htT | htT + · rw [List.getElem_append_left (by rw [hlen]; nlinarith)] + exact ih htT + · have htT' : t = T := by omega + subst htT' + rw [List.getElem_append_right (by rw [hlen]; omega)] + simp [hlen] + +variable {x} + +/-- Every node precision is at most the common precision `N = p + Δ'`. -/ +theorem nodePrec_le_wide (i : ℕ) : nodePrec x i ≤ wide x := by + unfold nodePrec nodePrecision wide shift + have : 2 ^ (slots x - level x ((nodes x).getD i 0)) ≤ 2 ^ slots x := + Nat.pow_le_pow_right (by norm_num) (Nat.sub_le _ _) + have : 8 * step x * 2 ^ (slots x - level x ((nodes x).getD i 0)) ≤ 8 * step x * 2 ^ slots x := + Nat.mul_le_mul_left _ this + omega + +/-- **The values stored by an active call.** At the `j`-th node of positive multiplicity and the +`i`-th point, the table of native recovery holds, at the node's precision `p_j`, the sum over the +mode tuples of weight below `p_j` of the products over the `64` groups of the children's values: +the hypothesis of the one-call correctness of Lemma D.5 at that node. -/ +theorem val_table_runLoop (h : active x = true) (vals : State → List ℕ) {j : ℕ} + (hj : j < (nodes x).length) {i : ℕ} (hi : i < numPoints x) : + val (nodePrec x j) x.fl + (((table x (runLoop x vals).2.2).getD ((nodes x)[j]) []).getD i []) = + ((Cslib.BudgetTuples.budgetTuples (weights x j) (nodePrec x j)).map fun β => + ((List.range 64).map fun g => val (nodePrec x j) x.fl (vals (j, i, β, g))).prod).sum := by + have hM := one_le_edges_of_active h + obtain ⟨qs, hqs, hrun⟩ := runAcc_cursorList (ws := weights x) (ps := nodePrec x) + (T := (nodes x).length) (n := numPoints x) (r := 64) (mul (wide x) x.fl) (add (wide x) x.fl) + (one x.fl) (zero x.fl) (val (wide x) x.fl) (fun a b => val_mul a b) (fun a b => val_add a b) + val_one val_zero (by norm_num) (nodePrec_pos hM) weights_pos vals + have hloop : (runLoop x vals).2.2 = qs := by rw [runLoop, children, hrun] + rw [hloop] + -- The row of the `j`-th node is its block. + have hnodup : (nodes x).Nodup := (List.nodup_range).filter _ + have hmem : (nodes x)[j] ∈ nodes x := List.getElem_mem hj + have hlt : (nodes x)[j] < 2 ^ (slots x + 1) := by + have := hmem + simp only [nodes, List.mem_filter, List.mem_range] at this + exact this.1 + have hidx : (nodes x).idxOf (nodes x)[j] = j := hnodup.idxOf_getElem j hj + have hqlen : qs.length = (nodes x).length * numPoints x := by + have := congrArg List.length hqs + simpa [List.length_flatMap, List.sum_replicate] using this + have hpos : j * numPoints x + i < qs.length := by rw [hqlen]; nlinarith + have hrow : ((table x qs).getD ((nodes x)[j]) []).getD i [] = qs[j * numPoints x + i] := by + have htab : (table x qs).getD ((nodes x)[j]) [] = + (qs.drop (j * numPoints x)).take (numPoints x) := by + rw [table, List.getD_eq_getElem _ _ (by simp [hlt])] + simp [hmem, hidx] + rw [htab, List.getD_eq_getElem _ _ (by simp; omega), List.getElem_take, List.getElem_drop] + rw [hrow] + -- Its value at the common precision is the sum, which reduces to the node's precision. + have hφ : val (wide x) x.fl qs[j * numPoints x + i] = + sumAt (weights x) (nodePrec x) 64 (val (wide x) x.fl) vals j i := by + have h1 := congrArg (fun l => l[j * numPoints x + i]?) hqs + simp only [List.getElem?_map, List.getElem?_eq_getElem hpos, Option.map_some] at h1 + rw [List.getElem?_eq_getElem (by simpa [hqlen] using hpos), + getElem_flatMap_range (fun t k => sumAt (weights x) (nodePrec x) 64 (val (wide x) x.fl) + vals t k) hj hi] at h1 + exact Option.some.inj h1 + have hle := nodePrec_le_wide (x := x) j + rw [← reduce_val hle, hφ, sumAt, map_list_sum, List.map_map] + congr 1 + refine List.map_congr_left fun β _ => ?_ + simp only [Function.comp_def, map_list_prod, List.map_map, reduce_val] + +/-! ### Correctness -/ + +/-- **The correctness of one active call** for a well-formedness predicate `WF`: at a well-formed +active call, if the values `vals` of the children are their contractions in `A_{p_i}`, then the +recovered value is the contraction of the call's network in `A_p`. -/ +@[expose] def StepCorrect (WF : Call → Prop) : Prop := + ∀ x, WF x → active x = true → ∀ vals : State → List ℕ, + (∀ c ∈ children x, val (nodePrec x c.1) x.fl (vals c) = + (mk x c).2.contraction fun t bs => val (nodePrec x c.1) x.fl (VertexDesc.entry t bs)) → + val x.prec x.fl (finishValue x (runLoop x vals)) = + x.net.contraction fun t bs => val x.prec x.fl (VertexDesc.entry t bs) + +/-- **Well-formedness passes to the children** of an active call. -/ +@[expose] def WFChildren (WF : Call → Prop) : Prop := + ∀ x, WF x → active x = true → ∀ c ∈ children x, WF (child x mk c) + +/-- **Correctness of the recursion.** If the step of an active call is correct and +well-formedness passes to the children, every well-formed call computes the contraction of its +network in `A_p`. -/ +theorem val_value {WF : Call → Prop} (hstep : StepCorrect mk WF) (hwf : WFChildren mk WF) + (x : Call) (hx : WF x) : + val x.prec x.fl (value mk x) = + x.net.contraction fun t bs => val x.prec x.fl (VertexDesc.entry t bs) := by + induction hb : x.bound using Nat.strong_induction_on generalizing x with + | _ b ih => + cases ha : active x + · rw [value_of_not_active mk ha] + exact DescribedNetwork.val_sumValue_galois VertexDesc.entry (x.prec, x.fl) x.net + · rw [value_of_active mk ha] + refine hstep x hx ha _ fun c hc => ?_ + exact ih _ (hb ▸ edges_lt_bound_of_active ha) (child x mk c) (hwf x hx ha c hc) rfl + +end Cslib.Circuits.Frontier.FastContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean new file mode 100644 index 0000000000..f93e9b6050 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionMachine.lean @@ -0,0 +1,445 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContraction +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction +import Complexity.Computability.PolyTime.GaloisRingSeries +import Complexity.Foundations.FieldTheory.Finite.Trinomial +import Mathlib.Data.List.GetD +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.SqrtLog +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Sum +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPower +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends + +/-! +# The native machine of the fast contraction algorithm + +The stack recursion of `Cslib.Circuits.Frontier.FastContraction.scheme` as one native machine for +the contraction modulo `2 ^ P` of an explicit network `(r, G)`, with the precision `P` in binary. + +**The root.** The precision is first capped at the encoded length of the network +(`ExplicitTensorNetwork.capPrecision`), which does not change the residue. The root call +(`FastContraction.root`) has the scale `s = m`, the number of edges of `G`, depth `0`, edge bound +`m + 1`, the capped precision `p`, the tables of `G` as descriptors (`FastContraction.rootNet`) and +the modulus `f = X ^ (2 ⬝ 3 ^ t) + X ^ (3 ^ t) + 1` (`FastContraction.modulus`), irreducible modulo +`2` (`Cslib.irreducible_trinomial`), whose `2 ⬝ 3 ^ t > 4 ⌊log₂ m⌋ + 2` binary digits give the +interpolation points of every call of the schedule. + +**The fuel and the fallback.** The number of calls of the recursion depends, through the schedule's +constant, on the ratio of the precision to the number of edges, which the machine does not know in +advance. So the machine runs the recursion for a fixed clock `2 ^ (K₀ (m + p) / √⌊log₂ (m + p)⌋)` +(`FastContraction.fuel`), and returns the constant coordinate of the root's value when the run has +returned a value tagged with depth `0`, and direct summation otherwise +(`FastContraction.fastValue`). +A value tagged `0` is the root's value for every fuel (`FastContraction.eq_value_of_run`), so the +result is correct on every well-formed input, whatever the fuel +(`FastContraction.fastValue_eq`); the clock matters only for the running time. + +## Main definitions + +* `Cslib.Circuits.Frontier.FastContraction.encCall`, `encFrame`, `encValue`: the encodings. +* `Cslib.Circuits.Frontier.FastContraction.leafCertificate`: direct summation at the leaves. +* `Cslib.Circuits.Frontier.FastContraction.modulus`, `rootNet`, `root`, `fuel`: the root call and + the clock. +* `Cslib.Circuits.Frontier.FastContraction.fastValue`: the result of the machine. +* `Cslib.Circuits.Frontier.FastContraction.fastCertificate`: the machine, given polynomial-time + components of the scheme. + +## Main results + +* `Cslib.Circuits.Frontier.FastContraction.irreducible_modulus`: the modulus is irreducible modulo + `2`. +* `Cslib.Circuits.Frontier.FastContraction.val_rootNet`: the root computes the contraction of `G`. +* `Cslib.Circuits.Frontier.FastContraction.getD_mod_of_val_eq`: reading off the constant + coordinate. +* `Cslib.Circuits.Frontier.FastContraction.fastValue_eq`: the machine is correct on well-formed + inputs, for every fuel. +* `Cslib.Circuits.Frontier.FastContraction.root_polyTime`, + `Cslib.Circuits.Frontier.FastContraction.fuel_polyTime`: the root and the clock in polynomial + time. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.7 (p. 64). +-/ + +public section + +namespace Cslib.Circuits.Frontier.FastContraction + +open GaloisRingArith ChildCursor Cslib.GenericRAM Cslib.GenericRAM.StackRecursion + Turing.MultiTapeTM + +/-! ### Encodings -/ + +/-- The encoding of the parameters of a call: the scale, the depth, the edge bound and the +precision in unary, the coefficients of the modulus in binary and the number of rows in +binary. -/ +abbrev encParams : Params ↪ List Bool := + encPair encUnary (encPair encUnary (encPair encUnary (encPair encUnary + (encPair (encList encNat) encNat)))) + +/-- The encoding of a call: its parameters and its network. -/ +abbrev encCall : Call ↪ List Bool := + encPair encParams (DescribedNetwork.encDescribed VertexDesc.enc) + +/-- The encoding of a frame: the cursor state and the accumulator, in binary. -/ +abbrev encFrame : Frame ↪ List Bool := + encPair (encOption ChildCursor.encState) + (encPair (encList encNat) (encPair (encList encNat) (encList (encList encNat)))) + +/-- The encoding of a tagged value: the depth in unary and the coefficient list in binary. -/ +abbrev encValue : ℕ × List ℕ ↪ List Bool := encPair encUnary (encList encNat) + +/-! ### The leaves -/ + +/-- The depth of a call is polynomial-time. -/ +theorem depth_polyTime : ComputableInPolyTime Call.depth encCall encUnary := by + unfold Call.depth; fun_prop + +/-- The input of direct summation at a call, `((p, fl), D)`, is polynomial-time. -/ +theorem leafInput_polyTime : + ComputableInPolyTime (fun x : Call => ((x.prec, x.fl), x.net)) encCall + (DescribedNetwork.encInput (encPair encUnary (encList encNat)) VertexDesc.enc) := by + unfold Call.prec Call.fl Call.net; fun_prop + +variable (mk : Call → State → ℕ × DescribedNetwork VertexDesc) + +/-- **The leaf certificate**: direct summation over the coefficient lists of `A_p`, with the native +evaluator of the vertex descriptors, tagged with the depth. -/ +noncomputable def leafCertificate : NativeCertificate (scheme mk).leaf encCall encValue := + ((NativeCertificate.ofPolyTime depth_polyTime).prod + ((DescribedNetwork.contractionCertificate VertexDesc.entry SumOps.galois_polyTime + VertexDesc.entryCertificate).comp (NativeCertificate.ofPolyTime leafInput_polyTime))).congr + fun _ => rfl + +/-! ### The root -/ + +/-- The exponent `t` of the modulus, with `2 ^ (t + 1) > 4 ⌊log₂ m⌋ + 2`. -/ +@[expose] def modulusExp (m : ℕ) : ℕ := Nat.log 2 (4 * Nat.log 2 m + 2) + +/-- **The modulus** `X ^ (2 ⬝ 3 ^ t) + X ^ (3 ^ t) + 1`, by its coefficients below the leading +one. -/ +@[expose] def modulus (m : ℕ) : List ℕ := + (List.range (2 * 3 ^ modulusExp m)).map fun i => + if i = 0 ∨ i = 3 ^ modulusExp m then 1 else 0 + +/-- The modulus has `2 ⬝ 3 ^ t` coefficients. -/ +theorem length_modulus (m : ℕ) : (modulus m).length = 2 * 3 ^ modulusExp m := by + simp [modulus] + +/-- The modulus has a coefficient. -/ +theorem modulus_ne_nil (m : ℕ) : modulus m ≠ [] := by + rw [← List.length_pos_iff, length_modulus] + positivity + +/-- **The modulus is the trinomial** `X ^ (2 ⬝ 3 ^ t) + X ^ (3 ^ t) + 1` over `ℤ`. -/ +theorem monicOf_modulus (m : ℕ) : + monicOf (modulus m) = Polynomial.X ^ (2 * 3 ^ modulusExp m) + + Polynomial.X ^ (3 ^ modulusExp m) + 1 := by + set t := modulusExp m + have h3 : 0 < 3 ^ t := by positivity + have hsum : ∑ i ∈ Finset.range (2 * 3 ^ t), + Polynomial.C (((modulus m).getD i 0 : ℕ) : ℤ) * Polynomial.X ^ i = + Polynomial.X ^ (3 ^ t) + 1 := by + have hget (i : ℕ) (hi : i < 2 * 3 ^ t) : + (modulus m).getD i 0 = if i = 0 ∨ i = 3 ^ t then 1 else 0 := by + rw [List.getD_eq_getElem _ _ (by rw [length_modulus]; exact hi)] + simp [modulus, t] + rw [Finset.sum_congr rfl fun i hi => by rw [hget i (Finset.mem_range.mp hi)]] + rw [Finset.sum_eq_add (0 : ℕ) (3 ^ t) (by omega) (fun i _ hi => by + simp only [hi.1, hi.2, or_self, ↓reduceIte, Nat.cast_zero, map_zero, zero_mul]) + (fun h => absurd (Finset.mem_range.mpr (by omega)) h) + (fun h => absurd (Finset.mem_range.mpr (by omega)) h)] + simp [add_comm] + rw [monicOf, length_modulus, hsum, add_assoc] + +/-- **The modulus is irreducible modulo `2`**: its reduction is `Cslib.trinomial t`. -/ +theorem irreducible_modulus (m : ℕ) : + Irreducible ((monicOf (modulus m)).map (Int.castRingHom (ZMod 2))) := by + rw [monicOf_modulus] + simpa [Polynomial.map_add, Polynomial.map_pow, Polynomial.map_X, Polynomial.map_one, + Cslib.trinomial] using Cslib.irreducible_trinomial (modulusExp m) + +/-- **The tables of an explicit network as vertex descriptors.** -/ +@[expose] def rootNet (G : ExplicitTensorNetwork ℕ) : DescribedNetwork VertexDesc := + ⟨G.numEdges, G.vertices.map fun v => (v.1, .inl v.2)⟩ + +/-- **The root call** of a capped input `(p, r, G)`: scale `m`, depth `0`, edge bound `m + 1`, +precision `p`, the modulus and the tables of `G`. -/ +@[expose] def root (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) : Call := + ((q.2.2.numEdges, 0, q.2.2.numEdges + 1, q.1, modulus q.2.2.numEdges, q.2.1), rootNet q.2.2) + +/-- The constant of the clock. -/ +@[expose] def fuelConst : ℕ := 2 ^ 22 + +/-- **The clock** of a capped input `(p, r, G)`: `2 ^ (K₀ (m + p) / √⌊log₂ (m + p)⌋)`. -/ +@[expose] def fuel (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) : ℕ := + 2 ^ (fuelConst * (q.2.2.numEdges + q.1) / Nat.sqrt (Nat.log 2 (q.2.2.numEdges + q.1))) + +/-- **The root computes the contraction of `G`**: in `A_p`, the contraction of the tables read as +constants is the image of the contraction of `G`. -/ +theorem val_rootNet (p : ℕ) (fl : List ℕ) (G : ExplicitTensorNetwork ℕ) : + (rootNet G).contraction (fun t bs => val p fl (VertexDesc.entry t bs)) = + (G.contraction : Ring p fl) := by + rw [← DescribedNetwork.contraction_entry] + have h := DescribedNetwork.map_contraction (Nat.castRingHom (Ring p fl)) + ExplicitTensorNetwork.entry G + simp only [Nat.coe_castRingHom] at h + rw [h] + simp only [DescribedNetwork.contraction, rootNet, List.map_map] + refine Finset.sum_congr rfl fun x _ => ?_ + congr 1 + refine List.map_congr_left fun v _ => ?_ + simp only [Function.comp_apply, VertexDesc.val_entry_inl] + +/-- **Reading off the constant coordinate.** If a coefficient list has the value of a natural +number `k` in `A_p`, with `f` of positive degree, its constant coordinate is `k` modulo `2 ^ p`. -/ +theorem getD_mod_of_val_eq {p : ℕ} {fl y : List ℕ} {k : ℕ} (hfl : fl ≠ []) + (h : val p fl y = (k : Ring p fl)) : y.getD 0 0 % 2 ^ p = k % 2 ^ p := by + rw [← GaloisRingArith.val_singleton] at h + simpa using GaloisRingArith.mod_eq_of_val_eq h (List.length_pos_iff.mpr hfl) + +/-! ### The root and the clock in polynomial time -/ + +/-- The exponent of the modulus is polynomial-time, in unary from unary. -/ +theorem modulusExp_polyTime : ComputableInPolyTime modulusExp encUnary encUnary := by + have hb := ComputableInPolyTime.unary_to_binary + have hlog := ComputableInPolyTime.binary_log_two.comp hb + have harg := ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.const 4) hlog) + (ComputableInPolyTime.const 2) + exact ComputableInPolyTime.binary_log_two.comp (hb.comp harg) + +/-- `3 ^ t ≤ (4 ⌊log₂ m⌋ + 2) ^ 2` for the exponent `t` of the modulus. -/ +theorem three_pow_modulusExp_le (m : ℕ) : 3 ^ modulusExp m ≤ (4 * Nat.log 2 m + 2) ^ 2 := by + have h := Nat.pow_log_le_self 2 (show 4 * Nat.log 2 m + 2 ≠ 0 by omega) + calc 3 ^ modulusExp m ≤ 4 ^ modulusExp m := Nat.pow_le_pow_left (by norm_num) _ + _ = (2 ^ modulusExp m) ^ 2 := by rw [← pow_mul, mul_comm, pow_mul]; norm_num + _ ≤ (4 * Nat.log 2 m + 2) ^ 2 := Nat.pow_le_pow_left h _ + +/-- **The modulus is polynomial-time**, from the number of edges in unary. -/ +theorem modulus_polyTime : ComputableInPolyTime modulus encUnary (encList encNat) := by + have hb := ComputableInPolyTime.unary_to_binary + have hlog := ComputableInPolyTime.binary_log_two.comp hb + have hE : ComputableInPolyTime (fun m => 3 ^ modulusExp m) encUnary encUnary := by + refine ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_pow.comp ((ComputableInPolyTime.const 3).prod + modulusExp_polyTime)) + ((ComputableInPolyTime.unary_pow 2).comp (ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.const 4) hlog) + (ComputableInPolyTime.const 2))) three_pow_modulusExp_le + have hrange := ComputableInPolyTime.range.comp + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.const 2) hE) + have hi := ComputableInPolyTime.snd encUnary encUnary + have htest : ComputableInPolyTime (fun q : ℕ × ℕ => decide (q.2 = 0 ∨ q.2 = 3 ^ modulusExp q.1)) + (encPair encUnary encUnary) encBool := + ComputableInPolyTime.decide_or + (ComputableInPolyTime.decide_eq' hi (ComputableInPolyTime.const 0)) + (ComputableInPolyTime.decide_eq' hi (hE.comp (ComputableInPolyTime.fst _ _))) + have hbody : ComputableInPolyTime (fun q : ℕ × ℕ => + if q.2 = 0 ∨ q.2 = 3 ^ modulusExp q.1 then 1 else 0) (encPair encUnary encUnary) encNat := + (ComputableInPolyTime.ite htest + (ComputableInPolyTime.const 1 : ComputableInPolyTime (fun _ : ℕ × ℕ => (1 : ℕ)) + (encPair encUnary encUnary) encNat) + (ComputableInPolyTime.const 0)).of_enc_eq fun q => by simp only [decide_eq_true_eq] + unfold modulus + exact ComputableInPolyTime.mapWithInput + (f := fun m i => if i = 0 ∨ i = 3 ^ modulusExp m then 1 else 0) hrange hbody + +/-- The number of edges of a capped input, in unary. -/ +private theorem edges_polyTime : + ComputableInPolyTime (fun q : ℕ × ℕ × ExplicitTensorNetwork ℕ => q.2.2.numEdges) + ExplicitTensorNetwork.encContractionInput encUnary := + ExplicitTensorNetwork.numEdges_polyTime + +/-- The network of a capped input. -/ +private theorem network_polyTime : + ComputableInPolyTime (fun q : ℕ × ℕ × ExplicitTensorNetwork ℕ => q.2.2) + ExplicitTensorNetwork.encContractionInput ExplicitTensorNetwork.encExplicitNetwork := + (ComputableInPolyTime.snd encNat ExplicitTensorNetwork.encExplicitNetwork).comp + (ComputableInPolyTime.snd encUnary (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + +/-- **The tables as descriptors are polynomial-time.** -/ +theorem rootNet_polyTime : ComputableInPolyTime rootNet ExplicitTensorNetwork.encExplicitNetwork + (DescribedNetwork.encDescribed VertexDesc.enc) := by + have hpair : ComputableInPolyTime (DescribedNetwork.equivProd (T := List ℕ)) + ExplicitTensorNetwork.encExplicitNetwork + (encPair encUnary (encList (encPair (encList encNat) (encList encNat)))) := + ComputableInPolyTime.of_eq fun _ => rfl + have hm := (ComputableInPolyTime.fst _ _).comp hpair + have hvs := (ComputableInPolyTime.snd _ _).comp hpair + have hv : ComputableInPolyTime (fun q : ExplicitTensorNetwork ℕ × (List ℕ × List ℕ) => + (q.2.1, (Sum.inl q.2.2 : VertexDesc))) + (encPair ExplicitTensorNetwork.encExplicitNetwork (encPair (encList encNat) (encList encNat))) + (encPair (encList encNat) VertexDesc.enc) := + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)).prod + ((ComputableInPolyTime.sumInl _ _).comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _))) + have hmap := ComputableInPolyTime.mapWithInput (f := fun _ v => (v.1, (Sum.inl v.2 : VertexDesc))) + hvs hv + have hout : ComputableInPolyTime (DescribedNetwork.equivProd (T := VertexDesc)).symm + (encPair encUnary (encList (encPair (encList encNat) VertexDesc.enc))) + (DescribedNetwork.encDescribed VertexDesc.enc) := + ComputableInPolyTime.of_eq fun _ => rfl + exact (hout.comp (hm.prod hmap)).of_enc_eq fun _ => rfl + +/-- **The root call is polynomial-time**, from a capped input. -/ +theorem root_polyTime : ComputableInPolyTime root ExplicitTensorNetwork.encContractionInput + encCall := by + have hp := ComputableInPolyTime.fst encUnary (encPair encNat + ExplicitTensorNetwork.encExplicitNetwork) + have hr := (ComputableInPolyTime.fst encNat ExplicitTensorNetwork.encExplicitNetwork).comp + (ComputableInPolyTime.snd encUnary (encPair encNat ExplicitTensorNetwork.encExplicitNetwork)) + exact (edges_polyTime.prod ((ComputableInPolyTime.const 0).prod + ((ComputableInPolyTime.unary_add' edges_polyTime (ComputableInPolyTime.const 1)).prod + (hp.prod ((modulus_polyTime.comp edges_polyTime).prod hr))))).prod + (rootNet_polyTime.comp network_polyTime) + +/-- **The clock is polynomial-time**, from a capped input. -/ +theorem fuel_polyTime : ComputableInPolyTime fuel ExplicitTensorNetwork.encContractionInput + encNat := + (ComputableInPolyTime.unary_two_pow_div_sqrt_log_two fuelConst).comp + (ComputableInPolyTime.unary_add' edges_polyTime + (ComputableInPolyTime.fst encUnary (encPair encNat + ExplicitTensorNetwork.encExplicitNetwork))) + +/-! ### The result -/ + +/-- **The result of the machine** on an input `(P, r, G)`: with the capped precision `p`, the +constant coordinate of a returned root value modulo `2 ^ p` if the run returns a value tagged +`0`, and direct summation otherwise. -/ +@[expose] noncomputable def fastValue (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) : ℕ := + ((scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q)).elim + (q.2.2.contraction % 2 ^ q.1) + fun v => if v.1 = 0 then v.2.getD 0 0 % 2 ^ (ExplicitTensorNetwork.capPrecision q).1 + else q.2.2.contraction % 2 ^ q.1 + +/-- **The machine is correct on well-formed inputs**, for every fuel. If the step of an active +call is correct and well-formedness passes to the children, then on every input whose root is +well-formed the result is the contraction modulo `2 ^ P`. -/ +theorem fastValue_eq {WF : Call → Prop} (hstep : StepCorrect mk WF) (hwf : WFChildren mk WF) + (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) + (hroot : WF (root (ExplicitTensorNetwork.capPrecision q))) : + fastValue mk q = q.2.2.contraction % 2 ^ q.1 := by + unfold fastValue + rcases hrun : (scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q) with _ | ⟨t, y⟩ + · rfl + · simp only [Option.elim_some] + split_ifs with ht + · subst ht + have hy := eq_value_of_run mk (fuel := fuel) (root := root) rfl hrun + have hval : val (ExplicitTensorNetwork.capPrecision q).1 + (modulus (ExplicitTensorNetwork.capPrecision q).2.2.numEdges) y = + (rootNet (ExplicitTensorNetwork.capPrecision q).2.2).contraction fun t bs => + val (ExplicitTensorNetwork.capPrecision q).1 + (modulus (ExplicitTensorNetwork.capPrecision q).2.2.numEdges) + (VertexDesc.entry t bs) := hy ▸ val_value mk hstep hwf _ hroot + rw [val_rootNet] at hval + rw [getD_mod_of_val_eq (modulus_ne_nil _) hval] + exact ExplicitTensorNetwork.contraction_mod_two_pow_min q.1 q.2.2 + · rfl + +/-! ### The machine -/ + +/-- The encoding of an input `(P, r, G)`: the precision and the number of rows in binary, and the +network. -/ +abbrev encInput : ℕ × ℕ × ExplicitTensorNetwork ℕ ↪ List Bool := + encPair encNat (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) + +variable {mk} + +/-- **The clocked run** from a capped input, as one native machine, given polynomial-time +components of the scheme. -/ +@[expose] noncomputable def runCertificate + (hpoly : (scheme mk).PolyTime encCall encFrame encValue) : + NativeCertificate ((scheme mk).run fuel root) ExplicitTensorNetwork.encContractionInput + (encOption encValue) := + Scheme.certificate hpoly (leafCertificate mk) (NativeCertificate.ofPolyTime fuel_polyTime) + (NativeCertificate.ofPolyTime root_polyTime) + +/-- The constant coordinate of a value modulo `2 ^ p`, for the capped precision `p`. -/ +theorem decode_polyTime : + ComputableInPolyTime (fun q : (ℕ × ℕ × ExplicitTensorNetwork ℕ) × (ℕ × List ℕ) => + q.2.2.getD 0 0 % 2 ^ (ExplicitTensorNetwork.capPrecision q.1).1) + (encPair encInput encValue) encNat := by + have hcap := (ComputableInPolyTime.fst _ _).comp + (ExplicitTensorNetwork.capPrecision_polyTime.comp (ComputableInPolyTime.fst encInput encValue)) + have hy := (ComputableInPolyTime.snd encUnary (encList encNat)).comp + (ComputableInPolyTime.snd encInput encValue) + exact GaloisRingArith.computableInPolyTime_mod_two_pow + ((ComputableInPolyTime.listGetD (encA := encNat) 0).comp + (hy.prod (ComputableInPolyTime.const 0))) + hcap + +/-- Whether a value is tagged `0`. -/ +theorem tag_polyTime : + ComputableInPolyTime (fun q : (ℕ × ℕ × ExplicitTensorNetwork ℕ) × (ℕ × List ℕ) => + decide (q.2.1 = 0)) (encPair encInput encValue) encBool := + ComputableInPolyTime.decide_eq' ((ComputableInPolyTime.fst encUnary (encList encNat)).comp + (ComputableInPolyTime.snd encInput encValue)) (ComputableInPolyTime.const 0) + +/-- **The fast machine**: cap the precision and run the recursion; on a value tagged `0`, read +off its constant coordinate, and otherwise sum directly. It computes `fastValue`. -/ +@[expose] noncomputable def fastCertificate + (hpoly : (scheme mk).PolyTime encCall encFrame encValue) : + NativeCertificate (fastValue mk) encInput encNat := + (NativeCertificate.option + ((runCertificate hpoly).comp + (NativeCertificate.ofPolyTime ExplicitTensorNetwork.capPrecision_polyTime)) + ExplicitTensorNetwork.directContractionBinaryCertificate + (NativeCertificate.branch (NativeCertificate.ofPolyTime tag_polyTime) + (NativeCertificate.ofPolyTime decode_polyTime) + (ExplicitTensorNetwork.directContractionBinaryCertificate.comp + (NativeCertificate.ofPolyTime (ComputableInPolyTime.fst encInput encValue))))).congr + fun q => by + simp only [fastValue, Function.comp_apply] + cases (scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q) with + | none => rfl + | some v => simp only [Option.elim_some, decide_eq_true_eq] + +/-- The time of the fast machine, unfolded: the capped run twice, the input, and the decoding or +direct summation. -/ +theorem fastCertificate_time (hpoly : (scheme mk).PolyTime encCall encFrame encValue) + (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) : + (fastCertificate hpoly).time q = + 2 * ((NativeCertificate.ofPolyTime ExplicitTensorNetwork.capPrecision_polyTime).time q + + (runCertificate hpoly).time (ExplicitTensorNetwork.capPrecision q)) + + (encInput q).length + + ((scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q)).elim + (ExplicitTensorNetwork.directContractionBinaryCertificate.time q) fun v => + (NativeCertificate.ofPolyTime tag_polyTime).time (q, v) + + if decide (v.1 = 0) = true then + (NativeCertificate.ofPolyTime decode_polyTime).time (q, v) + else (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst encInput encValue)).time (q, v) + + ExplicitTensorNetwork.directContractionBinaryCertificate.time q := rfl + +/-- The space of the fast machine, unfolded. -/ +theorem fastCertificate_space (hpoly : (scheme mk).PolyTime encCall encFrame encValue) + (q : ℕ × ℕ × ExplicitTensorNetwork ℕ) : + (fastCertificate hpoly).space q = + 2 * ((NativeCertificate.ofPolyTime ExplicitTensorNetwork.capPrecision_polyTime).space q + + (runCertificate hpoly).space (ExplicitTensorNetwork.capPrecision q)) + + (encInput q).length + + ((scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q)).elim + (ExplicitTensorNetwork.directContractionBinaryCertificate.space q) fun v => + (NativeCertificate.ofPolyTime tag_polyTime).space (q, v) + + if decide (v.1 = 0) = true then + (NativeCertificate.ofPolyTime decode_polyTime).space (q, v) + else (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst encInput encValue)).space (q, v) + + ExplicitTensorNetwork.directContractionBinaryCertificate.space q := rfl + +end Cslib.Circuits.Frontier.FastContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionPolyTime.lean new file mode 100644 index 0000000000..4d83637e21 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionPolyTime.lean @@ -0,0 +1,872 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionMachine +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChildPolyTime +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionStep +import Complexity.Computability.PolyTime.BudgetTuples +import Complexity.Computability.PolyTime.GaloisRingArithmetic +import Complexity.Computability.PolyTime.GaloisRingSeries +import Mathlib.Data.List.GetD +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Div +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.SqrtLog +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Sqrt +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Option +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryDivision +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPower +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeRadix + +/-! +# The components of the native contraction recursion in polynomial time + +The scheme `Cslib.Circuits.Frontier.FastContraction.scheme mk` of the contraction recursion of +[OpenAI2026ETH] has polynomial-time components (`Cslib.GenericRAM.StackRecursion.Scheme.PolyTime`) +for the encodings `FastContraction.encCall`, `encFrame` and `encValue` whenever the child builder +`mk` is polynomial-time (`FastContraction.scheme_polyTime`). The native child builder is +(`FastContraction.nativeMk_polyTime`), so the scheme of `FastContraction.nativeMk` has +polynomial-time components (`FastContraction.scheme_nativeMk_polyTime`). + +**Every call.** The components compute the data of a call on every call and every frame, not only +on the active calls and the cursor's states. The scale `s`, the depth, the edge bound, the +precision and the number `M` of edges are in unary, so the schedule's quantities are polynomial in +the input: +* `a = slotBits s d ≤ ⌊log₂ s⌋ / 32768`, so `2 ^ a ≤ s + 1` + (`ContractionSchedule.two_pow_slotBits_le`); +* the multiplicity step `h = ⌈3 M / u⌉` is at most `3 M + u`; +* every level is at most `a + 1` (`FastContraction.level_le`), so a node has at most `4 (s + 1)` + tiles; +* the native packing has at most `M (2 ^ a) ^ 12` packets + (`NativeChild.numPackets_nativeParentPacking_le`), which bounds the mode weights and the + cursor's exponent tuples. + +The number of rows and the cursor's exponents are stored in binary, so the packing and the child +builder cap them, at the number of vertices (`FastContraction.rowCap`) and at the node's precision. +The caps change nothing on well-formed active calls (`FastContraction.nativeMk_eq`). + +## Main results + +* `Cslib.Circuits.Frontier.ContractionSchedule.taper_polyTime`, + `Cslib.Circuits.Frontier.ContractionSchedule.slotBits_polyTime`, + `Cslib.Circuits.Frontier.ContractionSchedule.taperStep_polyTime`, + `Cslib.Circuits.Frontier.ContractionSchedule.leafSize_polyTime`, + `Cslib.Circuits.Frontier.ContractionSchedule.degreeBound_polyTime`: the schedule. +* `Cslib.Circuits.Frontier.FastContraction.active_polyTime`, `nodePrec_polyTime`, + `packets_polyTime`, `weights_polyTime`, `points_polyTime`, `table_polyTime`: the data of a call. +* `Cslib.Circuits.Frontier.FastContraction.scheme_polyTime`: the components of the scheme, for a + polynomial-time child builder. +* `Cslib.Circuits.Frontier.FastContraction.nativeMk_polyTime`, + `Cslib.Circuits.Frontier.FastContraction.scheme_nativeMk_polyTime`: the native child builder and + the components of its scheme. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH]. +-/ + +public section + +open Turing.MultiTapeTM + +namespace Cslib.Circuits.Frontier.ContractionSchedule + +/-! ### The schedule -/ + +/-- `⌊log₂ s⌋` in unary from `s` in unary. -/ +private theorem log_polyTime : ComputableInPolyTime (Nat.log 2) encUnary encUnary := + ComputableInPolyTime.binary_log_two.comp ComputableInPolyTime.unary_to_binary + +/-- **The taper is polynomial-time**, in unary from the scale and the depth in unary. -/ +theorem taper_polyTime : ComputableInPolyTime (fun q : ℕ × ℕ => taper q.1 q.2) + (encPair encUnary encUnary) encUnary := by + have hlog := log_polyTime.comp (ComputableInPolyTime.fst encUnary encUnary) + exact ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_div.comp ((ComputableInPolyTime.unary_to_binary.comp hlog).prod + (ComputableInPolyTime.binary_two_pow.comp (ComputableInPolyTime.unary_add' + (ComputableInPolyTime.const 17) (ComputableInPolyTime.snd encUnary encUnary))))) + hlog fun q => Nat.div_le_self _ _ + +/-- A sum over `Finset.range` as the sum of a list. -/ +private theorem sum_range_eq_list (f : ℕ → ℕ) (n : ℕ) : + ∑ j ∈ Finset.range n, f j = ((List.range n).map f).sum := by + induction n with + | zero => simp + | succ n ih => + rw [Finset.sum_range_succ, List.range_succ, List.map_append, List.sum_append, ih] + simp + +/-- **The number of label coordinates is polynomial-time**, in unary from the scale and the depth +in unary. -/ +theorem slotBits_polyTime : ComputableInPolyTime (fun q : ℕ × ℕ => slotBits q.1 q.2) + (encPair encUnary encUnary) encUnary := by + have hd := ComputableInPolyTime.snd encUnary encUnary + have hlog := log_polyTime.comp (ComputableInPolyTime.fst encUnary encUnary) + have hterm : ComputableInPolyTime (fun p : (ℕ × ℕ) × ℕ => taper p.1.1 p.2) + (encPair (encPair encUnary encUnary) encUnary) encNat := + ComputableInPolyTime.unary_to_binary.comp (taper_polyTime.comp + (((ComputableInPolyTime.fst encUnary encUnary).comp (ComputableInPolyTime.fst _ _)).prod + (ComputableInPolyTime.snd _ _))) + have hterms : ComputableInPolyTime (fun q : ℕ × ℕ => (List.range (q.2 + 1)).map (taper q.1)) + (encPair encUnary encUnary) (encList encNat) := + ComputableInPolyTime.mapWithInput (f := fun q j => taper q.1 j) + (ComputableInPolyTime.range.comp + (ComputableInPolyTime.unary_add' hd (ComputableInPolyTime.const 1))) hterm + have hsum := (ComputableInPolyTime.of_tidy ComputableTidilyInPolyTime.binary_sum).comp hterms + have hval (q : ℕ × ℕ) : slotBits q.1 q.2 = 2 * ((List.range (q.2 + 1)).map (taper q.1)).sum := by + simp only [slotBits, prefixBits, sum_range_eq_list] + refine (ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_mul' (ComputableInPolyTime.const 2) hsum) hlog + fun q => ?_).of_enc_eq fun q => ?_ + · simp only [Function.comp_apply, ← hval] + exact (slotBits_le q.1 q.2).trans (Nat.div_le_self _ _) + · simp only [Function.comp_apply, hval] + +/-- `2 ^ a ≤ s + 1` for the number `a = slotBits s d` of label coordinates. -/ +theorem two_pow_slotBits_le (s d : ℕ) : 2 ^ slotBits s d ≤ s + 1 := by + have ha := slotBits_le s d + rcases Nat.eq_zero_or_pos s with rfl | hs + · simp only [Nat.log_zero_right, Nat.zero_div, Nat.le_zero] at ha + simp [ha] + · calc 2 ^ slotBits s d ≤ 2 ^ Nat.log 2 s := + Nat.pow_le_pow_right (by norm_num) (ha.trans (Nat.div_le_self _ _)) + _ ≤ s := Nat.pow_log_le_self 2 hs.ne' + _ ≤ s + 1 := Nat.le_succ s + +/-- **The multiplicity step is polynomial-time**, in unary from the scale, the depth and the number +of edges in unary. -/ +theorem taperStep_polyTime : + ComputableInPolyTime (fun q : ℕ × ℕ × ℕ => taperStep q.1 q.2.1 q.2.2) + (encPair encUnary (encPair encUnary encUnary)) encUnary := by + have hs := ComputableInPolyTime.fst encUnary (encPair encUnary encUnary) + have hr := ComputableInPolyTime.snd encUnary (encPair encUnary encUnary) + have hd := (ComputableInPolyTime.fst encUnary encUnary).comp hr + have hM := (ComputableInPolyTime.snd encUnary encUnary).comp hr + have hu := taper_polyTime.comp (hs.prod hd) + exact ComputableInPolyTime.unary_div.comp ((ComputableInPolyTime.unary_sub' + (ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 3) hM) hu) (ComputableInPolyTime.const 1)).prod hu) + +/-- **The leaf size is polynomial-time**, in unary from the scale in unary. -/ +theorem leafSize_polyTime : ComputableInPolyTime leafSize encUnary encUnary := + ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_div.comp (ComputableInPolyTime.unary_to_binary.prod + (ComputableInPolyTime.binary_sqrt' (ComputableInPolyTime.unary_to_binary.comp log_polyTime)))) + (ComputableInPolyTime.id' (encIn := encUnary)) fun _ => Nat.div_le_self _ _ + +/-- **The degree bound is polynomial-time**: `2 ^ (9 ⌊log₂ s⌋ / 10 + 2)` in binary from `s` in +unary. -/ +theorem degreeBound_polyTime : ComputableInPolyTime degreeBound encUnary encNat := by + have hb := ComputableInPolyTime.unary_to_binary + have hlog := ComputableInPolyTime.binary_log_two.comp hb + have he : ComputableInPolyTime (fun m => 9 * Nat.log 2 m / 10 + 2) encUnary encUnary := + ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_add.comp ((ComputableInPolyTime.binary_div.comp + ((ComputableInPolyTime.binary_mul' (ComputableInPolyTime.const 9) (hb.comp hlog)).prod + (ComputableInPolyTime.const 10))).prod (ComputableInPolyTime.const 2))) + (ComputableInPolyTime.unary_add' hlog (ComputableInPolyTime.const 2)) fun m => by + simp only [Function.comp_apply]; omega + exact ComputableInPolyTime.binary_two_pow.comp he + +end Cslib.Circuits.Frontier.ContractionSchedule + +namespace Cslib.Circuits.Frontier.FastContraction + +open GaloisRingArith ChildCursor NativeRecovery ContractionSchedule NativeChild + Cslib.GenericRAM Cslib.GenericRAM.StackRecursion + +/-! ### The parameters of a call -/ + +/-- The scale is polynomial-time. -/ +theorem scale_polyTime : ComputableInPolyTime Call.scale encCall encUnary := by + unfold Call.scale; fun_prop + +/-- The edge bound is polynomial-time. -/ +theorem bound_polyTime : ComputableInPolyTime Call.bound encCall encUnary := by + unfold Call.bound; fun_prop + +/-- The precision is polynomial-time. -/ +theorem prec_polyTime : ComputableInPolyTime Call.prec encCall encUnary := by + unfold Call.prec; fun_prop + +/-- The coefficients of the modulus are polynomial-time. -/ +theorem fl_polyTime : ComputableInPolyTime Call.fl encCall (encList encNat) := by + unfold Call.fl; fun_prop + +/-- The number of rows is polynomial-time. -/ +theorem rows_polyTime : ComputableInPolyTime Call.rows encCall encNat := by + unfold Call.rows; fun_prop + +/-- The network is polynomial-time. -/ +theorem net_polyTime : + ComputableInPolyTime Call.net encCall (DescribedNetwork.encDescribed VertexDesc.enc) := by + unfold Call.net; fun_prop + +/-- The number of edges is polynomial-time, in unary. -/ +theorem edges_polyTime : ComputableInPolyTime Call.edges encCall encUnary := + DescribedNetwork.numEdges_polyTime.comp net_polyTime + +/-- The vertices are polynomial-time. -/ +theorem vertices_polyTime : ComputableInPolyTime (fun x : Call => x.net.vertices) encCall + (encList (encPair (encList encNat) VertexDesc.enc)) := + DescribedNetwork.vertices_polyTime.comp net_polyTime + +/-! ### The schedule of a call -/ + +/-- The number of label coordinates is polynomial-time. -/ +theorem slots_polyTime : ComputableInPolyTime slots encCall encUnary := + slotBits_polyTime.comp (scale_polyTime.prod depth_polyTime) + +/-- `2 ^ a ≤ s + 1` at every call. -/ +theorem two_pow_slots_le (x : Call) : 2 ^ slots x ≤ x.scale + 1 := two_pow_slotBits_le _ _ + +/-- The number `2 ^ a` of slots is polynomial-time, in unary. -/ +theorem twoPowSlots_polyTime : ComputableInPolyTime (fun x => 2 ^ slots x) encCall encUnary := + ComputableInPolyTime.binary_to_unary_bounded + (ComputableInPolyTime.binary_two_pow.comp slots_polyTime) + (ComputableInPolyTime.unary_add' scale_polyTime (ComputableInPolyTime.const 1)) + two_pow_slots_le + +/-- The number `2 ^ (a + 1)` of grid indices is polynomial-time, in unary. -/ +theorem twoPowSlotsSucc_polyTime : + ComputableInPolyTime (fun x => 2 ^ (slots x + 1)) encCall encUnary := + (ComputableInPolyTime.unary_add' twoPowSlots_polyTime twoPowSlots_polyTime).of_enc_eq fun x => by + rw [pow_succ, mul_two] + +/-- The number of levels is polynomial-time. -/ +theorem levels_polyTime : ComputableInPolyTime levels encCall encUnary := + taper_polyTime.comp (scale_polyTime.prod depth_polyTime) + +/-- The multiplicity step is polynomial-time. -/ +theorem step_polyTime : ComputableInPolyTime step encCall encUnary := + taperStep_polyTime.comp (scale_polyTime.prod (depth_polyTime.prod edges_polyTime)) + +/-- The number of interpolation points is polynomial-time, in unary. -/ +theorem numPoints_polyTime : ComputableInPolyTime numPoints encCall encUnary := + ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_mul' edges_polyTime + (ComputableInPolyTime.unary_sub' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 2) twoPowSlots_polyTime) (ComputableInPolyTime.const 1))) + (ComputableInPolyTime.const 1) + +/-- The shift `Δ'` is polynomial-time, in unary. -/ +theorem shift_polyTime : ComputableInPolyTime shift encCall encUnary := + ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.unary_add' slots_polyTime (ComputableInPolyTime.const 1)) + twoPowSlotsSucc_polyTime) step_polyTime) levels_polyTime) + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 8) step_polyTime) twoPowSlots_polyTime)) + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 8) edges_polyTime) + (ComputableInPolyTime.unary_add' slots_polyTime (ComputableInPolyTime.const 1))) + +/-- The common precision `N = p + Δ'` is polynomial-time, in unary. -/ +theorem wide_polyTime : ComputableInPolyTime wide encCall encUnary := + ComputableInPolyTime.unary_add' prec_polyTime shift_polyTime + +/-! ### Nodes, levels and precisions -/ + +/-- The capped valuation `twoVal b d` is at most `b`. -/ +private theorem twoVal_le (b d : ℕ) : twoVal b d ≤ b := by + unfold twoVal + split_ifs + · exact Nat.zero_le _ + · exact (List.length_filter_le _ _).trans (List.length_range (n := b)).le + +/-- Every level is at most `a + 1`. -/ +theorem level_le (x : Call) (k : ℕ) : level x k ≤ slots x + 1 := twoVal_le _ _ + +/-- The level of a grid index is polynomial-time, in unary. -/ +theorem level_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => level q.1 q.2) (encPair encCall encUnary) + encUnary := by + have hx := ComputableInPolyTime.fst encCall encUnary + have hk := ComputableInPolyTime.snd encCall encUnary + have hL := twoPowSlots_polyTime.comp hx + have h := computableInPolyTime_twoVal + (ComputableInPolyTime.unary_add' (slots_polyTime.comp hx) (ComputableInPolyTime.const 1)) + (ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_sub' hk hL) + (ComputableInPolyTime.unary_sub' hL hk)) + exact h + +/-- The nodes of positive multiplicity are polynomial-time, as unary grid indices. -/ +theorem nodes_polyTime : ComputableInPolyTime nodes encCall (encList encUnary) := by + have hx := ComputableInPolyTime.fst encCall encUnary + have hk := ComputableInPolyTime.snd encCall encUnary + have hL := twoPowSlots_polyTime.comp hx + have hmult := computableInPolyTime_mult (slots_polyTime.comp hx) hL (levels_polyTime.comp hx) + (step_polyTime.comp hx) hk + have htest : ComputableInPolyTime (fun q : Call × ℕ => + decide (q.2 ≠ 2 ^ slots q.1 ∧ 0 < mult (slots q.1) (levels q.1) (step q.1) q.2)) + (encPair encCall encUnary) encBool := + (ComputableInPolyTime.bool_and' (ComputableInPolyTime.bool_not.comp + (ComputableInPolyTime.decide_eq' hk hL)) + (ComputableInPolyTime.unary_lt' (ComputableInPolyTime.const 0) hmult)).of_enc_eq fun q => by + simp only [Function.comp_apply, Bool.decide_and, decide_not] + have h := ComputableInPolyTime.filterWithInput + (test := fun (x : Call) k => + decide (k ≠ 2 ^ slots x ∧ 0 < mult (slots x) (levels x) (step x) k)) + (ComputableInPolyTime.range.comp twoPowSlotsSucc_polyTime) htest + exact h + +/-- The grid index of the `i`-th node is polynomial-time, in unary. -/ +theorem node_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => (nodes q.1).getD q.2 0) (encPair encCall encUnary) + encUnary := by + have h := ComputableInPolyTime.listGetDWithInput + (nodes_polyTime.comp (ComputableInPolyTime.fst encCall encUnary)) + (ComputableInPolyTime.snd encCall encUnary) 0 + exact h + +/-- The level of the `i`-th node is polynomial-time, in unary. -/ +theorem nodeLevel_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => level q.1 ((nodes q.1).getD q.2 0)) + (encPair encCall encUnary) encUnary := by + have h := level_polyTime.comp ((ComputableInPolyTime.fst encCall encUnary).prod node_polyTime) + exact h + +/-- The number `2 ^ (v₂ t + 1)` of tiles of the `i`-th node is polynomial-time, in unary. -/ +theorem tiles_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => 2 ^ (level q.1 ((nodes q.1).getD q.2 0) + 1)) + (encPair encCall encUnary) encUnary := by + have hv := ComputableInPolyTime.binary_two_pow.comp + (ComputableInPolyTime.unary_add' nodeLevel_polyTime (ComputableInPolyTime.const 1)) + have hb := ComputableInPolyTime.unary_mul' (ComputableInPolyTime.const 4) + (twoPowSlots_polyTime.comp (ComputableInPolyTime.fst encCall encUnary)) + have h := ComputableInPolyTime.binary_to_unary_bounded hv hb fun q => by + simp only [Function.comp_apply] + calc 2 ^ (level q.1 ((nodes q.1).getD q.2 0) + 1) ≤ 2 ^ (slots q.1 + 2) := + Nat.pow_le_pow_right (by norm_num) + (by have := level_le q.1 ((nodes q.1).getD q.2 0); omega) + _ = 4 * 2 ^ slots q.1 := by rw [pow_add]; ring + exact h + +/-- **The precision of the `i`-th node is polynomial-time**, in unary. -/ +theorem nodePrec_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => nodePrec q.1 q.2) (encPair encCall encUnary) + encUnary := by + have hx := ComputableInPolyTime.fst encCall encUnary + have hpow : ComputableInPolyTime + (fun q : Call × ℕ => 2 ^ (slots q.1 - level q.1 ((nodes q.1).getD q.2 0))) + (encPair encCall encUnary) encUnary := by + have h := ComputableInPolyTime.binary_to_unary_bounded (ComputableInPolyTime.binary_two_pow.comp + (ComputableInPolyTime.unary_sub' (slots_polyTime.comp hx) nodeLevel_polyTime)) + (twoPowSlots_polyTime.comp hx) + fun q => Nat.pow_le_pow_right (by norm_num) (Nat.sub_le _ _) + exact h + have h := ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_add' (prec_polyTime.comp hx) + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 8) (step_polyTime.comp hx)) hpow)) + (ComputableInPolyTime.unary_mul' (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 8) (edges_polyTime.comp hx)) + (ComputableInPolyTime.unary_add' (slots_polyTime.comp hx) (ComputableInPolyTime.const 1))) + exact h + +/-! ### The packing and the weights -/ + +/-- The capped number of rows is polynomial-time, in unary. -/ +theorem rowCap_polyTime : ComputableInPolyTime rowCap encCall encUnary := + (ComputableInPolyTime.binary_min_unary.comp + (((ComputableInPolyTime.length_list _).comp vertices_polyTime).prod rows_polyTime)).of_enc_eq + fun x => by simp only [Function.comp_apply, rowCap, min_comm] + +/-- **The packing is polynomial-time** on every call. -/ +theorem packing_polyTime : ComputableInPolyTime packing encCall PacketPacking.encNativePacking := + computableInPolyTime_nativeParentPacking rowCap_polyTime slots_polyTime net_polyTime + +/-- **The number of packets is polynomial-time**, in unary, on every call: there are at most +`M (2 ^ a) ^ 12 ≤ M (s + 1) ^ 12` of them (`NativeChild.numPackets_nativeParentPacking_le`). -/ +theorem packets_polyTime : + ComputableInPolyTime (fun x : Call => (packing x).2.1) encCall encUnary := + computableInPolyTime_numPackets_nativeParentPacking rowCap_polyTime slots_polyTime + twoPowSlots_polyTime net_polyTime + +/-- **The weights of the modes of the `i`-th node are polynomial-time.** -/ +theorem weights_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => weights q.1 q.2) (encPair encCall encUnary) + (encList encNat) := by + have hx := ComputableInPolyTime.fst encCall encUnary + have hp := nodePrec_polyTime + have hlen := ComputableInPolyTime.unary_mul' (packets_polyTime.comp hx) + (ComputableInPolyTime.unary_mul' tiles_polyTime hp) + have hj := ComputableInPolyTime.snd (encPair encCall encUnary) encUnary + have hpj := hp.comp (ComputableInPolyTime.fst (encPair encCall encUnary) encUnary) + have hf : ComputableInPolyTime (fun r : (Call × ℕ) × ℕ => r.2 % nodePrec r.1.1 r.1.2 + 1) + (encPair (encPair encCall encUnary) encUnary) encNat := by + have h := ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.unary_add' + (ComputableInPolyTime.unary_mod.comp (hj.prod hpj)) (ComputableInPolyTime.const 1)) + exact h + have h := ComputableInPolyTime.mapWithInput + (f := fun (q : Call × ℕ) j => j % nodePrec q.1 q.2 + 1) + (ComputableInPolyTime.range.comp hlen) hf + exact h + +/-- The number of modes of the `i`-th node is polynomial-time, in unary. -/ +theorem weightsLength_polyTime : + ComputableInPolyTime (fun q : Call × ℕ => (weights q.1 q.2).length) (encPair encCall encUnary) + encUnary := by + have h := (ComputableInPolyTime.length_list _).comp weights_polyTime + exact h + +/-! ### Points and the table -/ + +/-- The binary digits are polynomial-time, from the length and the number in unary. -/ +theorem digits_polyTime : + ComputableInPolyTime (fun q : ℕ × ℕ => digits q.1 q.2) (encPair encUnary encUnary) + (encList encNat) := by + have hk := (ComputableInPolyTime.snd encUnary encUnary).comp + (ComputableInPolyTime.fst (encPair encUnary encUnary) encUnary) + have hf : ComputableInPolyTime (fun r : (ℕ × ℕ) × ℕ => if r.1.2.testBit r.2 then 1 else 0) + (encPair (encPair encUnary encUnary) encUnary) encNat := by + have h := ComputableInPolyTime.ite (encMid := encNat) (ComputableInPolyTime.binary_testBit + (ComputableInPolyTime.unary_to_binary.comp hk) (ComputableInPolyTime.snd _ _)) + (ComputableInPolyTime.const 1) (ComputableInPolyTime.const 0) + exact h + have h := ComputableInPolyTime.mapWithInput + (f := fun (q : ℕ × ℕ) i => if q.2.testBit i then 1 else 0) + (ComputableInPolyTime.range.comp (ComputableInPolyTime.fst encUnary encUnary)) hf + exact h + +/-- **The interpolation points are polynomial-time.** -/ +theorem points_polyTime : ComputableInPolyTime points encCall (encList (encList encNat)) := by + have hf : ComputableInPolyTime (fun r : Call × ℕ => digits r.1.fl.length r.2) + (encPair encCall encUnary) (encList encNat) := by + have h := digits_polyTime.comp ((((ComputableInPolyTime.length_list _).comp fl_polyTime).comp + (ComputableInPolyTime.fst encCall encUnary)).prod (ComputableInPolyTime.snd _ _)) + exact h + have h := ComputableInPolyTime.mapWithInput (f := fun (x : Call) k => digits x.fl.length k) + (ComputableInPolyTime.range.comp numPoints_polyTime) hf + exact h + +/-- **The table of native recovery is polynomial-time**, from a call and the stored values. -/ +theorem table_polyTime : + ComputableInPolyTime (fun q : Call × List (List ℕ) => table q.1 q.2) + (encPair encCall (encList (encList encNat))) (encList (encList (encList encNat))) := by + let e := encPair encCall (encList (encList encNat)) + have hx := ComputableInPolyTime.fst encCall (encList (encList encNat)) + have hx' := hx.comp (ComputableInPolyTime.fst e encUnary) + have hV' := (ComputableInPolyTime.snd encCall (encList (encList encNat))).comp + (ComputableInPolyTime.fst e encUnary) + have hk := ComputableInPolyTime.snd e encUnary + have hn := numPoints_polyTime.comp hx' + have hmem := ComputableInPolyTime.decide_mem (nodes_polyTime.comp hx') hk + have hidx := ComputableInPolyTime.idxOfWithInput (nodes_polyTime.comp hx') hk + have hdrop : ComputableInPolyTime (fun r : (Call × List (List ℕ)) × ℕ => + r.1.2.drop ((nodes r.1.1).idxOf r.2 * numPoints r.1.1)) (encPair e encUnary) + (encList (encList encNat)) := + .of_tidy (ComputableTidilyInPolyTime.listDropWithInput hV'.tidy + (ComputableInPolyTime.unary_mul' hidx hn).tidy) + have htake : ComputableInPolyTime (fun r : (Call × List (List ℕ)) × ℕ => + (r.1.2.drop ((nodes r.1.1).idxOf r.2 * numPoints r.1.1)).take (numPoints r.1.1)) + (encPair e encUnary) (encList (encList encNat)) := + .of_tidy (ComputableTidilyInPolyTime.listTakeWithInput hdrop.tidy hn.tidy) + have hf : ComputableInPolyTime (fun r : (Call × List (List ℕ)) × ℕ => + if r.2 ∈ nodes r.1.1 then + (r.1.2.drop ((nodes r.1.1).idxOf r.2 * numPoints r.1.1)).take (numPoints r.1.1) + else []) (encPair e encUnary) (encList (encList encNat)) := by + have h := ComputableInPolyTime.ite hmem htake (ComputableInPolyTime.const []) + refine h.of_enc_eq fun r => ?_ + simp only [Function.comp_apply, decide_eq_true_eq] + have h := ComputableInPolyTime.mapWithInput (f := fun (q : Call × List (List ℕ)) k => + if k ∈ nodes q.1 then + (q.2.drop ((nodes q.1).idxOf k * numPoints q.1)).take (numPoints q.1) else []) + (ComputableInPolyTime.range.comp (twoPowSlotsSucc_polyTime.comp hx)) hf + exact h + +/-! ### Active calls -/ + +/-- Whether a call is active, as a conjunction of tests. -/ +theorem active_eq (x : Call) : active x = + (decide (leafSize x.scale < x.edges) && decide (1 ≤ levels x) && decide (levels x ≤ x.edges) && + decide (levels x ≤ slots x) && + x.net.vertices.all (fun v => decide (v.1.length ≤ degreeBound x.scale)) && + decide (numPoints x ≤ 2 ^ x.fl.length) && decide (x.edges < x.bound)) := by + simp only [active, Bool.decide_and, List.all_eq, decide_eq_true_eq, Bool.and_assoc] + +/-- **Whether a call is active is polynomial-time.** -/ +theorem active_polyTime : ComputableInPolyTime active encCall encBool := by + have hdeg : ComputableInPolyTime (fun x : Call => + x.net.vertices.all (fun v => decide (v.1.length ≤ degreeBound x.scale))) encCall encBool := + ComputableInPolyTime.allWithInput vertices_polyTime + (ComputableInPolyTime.binary_le' (ComputableInPolyTime.unary_to_binary.comp + ((ComputableInPolyTime.length_list _).comp ((ComputableInPolyTime.fst _ _).comp + (ComputableInPolyTime.snd _ _)))) + (degreeBound_polyTime.comp (scale_polyTime.comp (ComputableInPolyTime.fst _ _)))) + have h := ComputableInPolyTime.bool_and' (ComputableInPolyTime.bool_and' + (ComputableInPolyTime.bool_and' (ComputableInPolyTime.bool_and' + (ComputableInPolyTime.bool_and' (ComputableInPolyTime.bool_and' + (ComputableInPolyTime.unary_lt' (leafSize_polyTime.comp scale_polyTime) edges_polyTime) + (ComputableInPolyTime.unary_le' (ComputableInPolyTime.const 1) levels_polyTime)) + (ComputableInPolyTime.unary_le' levels_polyTime edges_polyTime)) + (ComputableInPolyTime.unary_le' levels_polyTime slots_polyTime)) hdeg) + (ComputableInPolyTime.binary_le' + (ComputableInPolyTime.unary_to_binary.comp numPoints_polyTime) + (ComputableInPolyTime.binary_two_pow.comp + ((ComputableInPolyTime.length_list _).comp fl_polyTime)))) + (ComputableInPolyTime.unary_lt' edges_polyTime bound_polyTime) + refine h.of_enc_eq fun x => ?_ + rw [active_eq] + rfl + +/-! ### The components of the scheme -/ + +section Scheme + +variable (mk : Call → State → ℕ × DescribedNetwork VertexDesc) + +/-- **The leaf test is polynomial-time.** -/ +theorem isLeaf_polyTime : ComputableInPolyTime (scheme mk).isLeaf encCall encBool := by + have h := ComputableInPolyTime.bool_not.comp active_polyTime + exact h + +/-- **The initial accumulator is polynomial-time.** -/ +theorem start_polyTime : ComputableInPolyTime (scheme mk).start encCall encFrame := by + have hlen : ComputableInPolyTime (fun x : Call => (weights x 0).length) encCall encUnary := by + have h := weightsLength_polyTime.comp + ((ComputableInPolyTime.id' (encIn := encCall)).prod (ComputableInPolyTime.const 0)) + exact h + have hst : ComputableInPolyTime (fun x : Call => cursorStart (weights x)) encCall + (encPair encUnary (encPair encUnary (encPair (encList encNat) encUnary))) := + (ComputableInPolyTime.const 0).prod ((ComputableInPolyTime.const 0).prod + ((ComputableInPolyTime.replicate hlen (ComputableInPolyTime.const 0)).prod + (ComputableInPolyTime.const 0))) + have hst' : ComputableInPolyTime (fun x : Call => cursorStart (weights x)) encCall + ChildCursor.encState := hst + have h : ComputableInPolyTime (scheme mk).start encCall + (encPair (encOption ChildCursor.encState) + (encPair (encList encNat) (encPair (encList encNat) (encList (encList encNat))))) := + ((ComputableInPolyTime.optionSome _).comp hst').prod + ((computableInPolyTime_one fl_polyTime).prod ((computableInPolyTime_zero fl_polyTime).prod + (ComputableInPolyTime.const []))) + exact h + +/-- The node index of a cursor state is polynomial-time. -/ +private theorem stateNode_polyTime : + ComputableInPolyTime (fun c : State => c.1) ChildCursor.encState encUnary := by + unfold ChildCursor.encState; fun_prop + +/-- The point index of a cursor state is polynomial-time. -/ +private theorem statePoint_polyTime : + ComputableInPolyTime (fun c : State => c.2.1) ChildCursor.encState encUnary := by + unfold ChildCursor.encState; fun_prop + +/-- The exponents of a cursor state are polynomial-time, in binary. -/ +private theorem stateExps_polyTime : + ComputableInPolyTime (fun c : State => c.2.2.1) ChildCursor.encState (encList encNat) := by + unfold ChildCursor.encState; fun_prop + +/-- The group of a cursor state is polynomial-time. -/ +private theorem stateGroup_polyTime : + ComputableInPolyTime (fun c : State => c.2.2.2) ChildCursor.encState encUnary := by + unfold ChildCursor.encState; fun_prop + +variable {mk} + +/-- **The child call at a cursor state is polynomial-time**, given a polynomial-time builder. -/ +theorem child_polyTime + (hmk : ComputableInPolyTime (fun q : Call × State => mk q.1 q.2) + (encPair encCall ChildCursor.encState) + (encPair encNat (DescribedNetwork.encDescribed VertexDesc.enc))) : + ComputableInPolyTime (fun q : Call × State => child q.1 mk q.2) + (encPair encCall ChildCursor.encState) encCall := by + have hx := ComputableInPolyTime.fst encCall ChildCursor.encState + have ht := stateNode_polyTime.comp (ComputableInPolyTime.snd encCall ChildCursor.encState) + have hprec : ComputableInPolyTime (fun q : Call × State => nodePrec q.1 q.2.1) + (encPair encCall ChildCursor.encState) encUnary := by + have h := nodePrec_polyTime.comp (hx.prod ht) + exact h + have h : ComputableInPolyTime (fun q : Call × State => child q.1 mk q.2) + (encPair encCall ChildCursor.encState) + (encPair (encPair encUnary (encPair encUnary (encPair encUnary (encPair encUnary + (encPair (encList encNat) encNat))))) (DescribedNetwork.encDescribed VertexDesc.enc)) := + ((scale_polyTime.comp hx).prod ((ComputableInPolyTime.unary_add' (depth_polyTime.comp hx) + (ComputableInPolyTime.const 1)).prod ((edges_polyTime.comp hx).prod (hprec.prod + ((fl_polyTime.comp hx).prod ((ComputableInPolyTime.fst _ _).comp hmk)))))).prod + ((ComputableInPolyTime.snd _ _).comp hmk) + exact h + +/-- **Child generation is polynomial-time**, given a polynomial-time builder. -/ +theorem next_polyTime + (hmk : ComputableInPolyTime (fun q : Call × State => mk q.1 q.2) + (encPair encCall ChildCursor.encState) + (encPair encNat (DescribedNetwork.encDescribed VertexDesc.enc))) : + ComputableInPolyTime (fun p : Call × Frame => (scheme mk).next p.1 p.2) + (encPair encCall encFrame) (encOption encCall) := by + have hc : ComputableInPolyTime (fun p : Call × Frame => p.2.1) (encPair encCall encFrame) + (encOption ChildCursor.encState) := + (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd encCall encFrame) + have hbody : ComputableInPolyTime (fun r : (Call × Frame) × State => some (child r.1.1 mk r.2)) + (encPair (encPair encCall encFrame) ChildCursor.encState) (encOption encCall) := by + have h := (ComputableInPolyTime.optionSome encCall).comp ((child_polyTime hmk).comp + (((ComputableInPolyTime.fst encCall encFrame).comp (ComputableInPolyTime.fst _ _)).prod + (ComputableInPolyTime.snd _ _))) + exact h + have h := ComputableInPolyTime.optionElimWithInput + (body := fun (p : Call × Frame) (c : State) => some (child p.1 mk c)) hc + (ComputableInPolyTime.const (encOut := encOption encCall) none) hbody + refine h.of_enc_eq fun p => ?_ + rcases p with ⟨x, _ | c, acc⟩ <;> rfl + +variable (mk) + +/-- The inputs of `absorb` at a cursor state: the call, its frame, the child's value and the +state. -/ +private abbrev AbsorbIn : Type := ((Call × Frame) × (ℕ × List ℕ)) × State + +/-- The encoding of `AbsorbIn`. -/ +private abbrev encAbsorbIn : AbsorbIn ↪ List Bool := + encPair (encPair (encPair encCall encFrame) encValue) ChildCursor.encState + +/-- The call of an input of `absorb`. -/ +private theorem absorbCall_polyTime : + ComputableInPolyTime (fun r : AbsorbIn => r.1.1.1) encAbsorbIn encCall := + (ComputableInPolyTime.fst encCall encFrame).comp ((ComputableInPolyTime.fst _ encValue).comp + (ComputableInPolyTime.fst _ ChildCursor.encState)) + +/-- The state of an input of `absorb`. -/ +private theorem absorbState_polyTime : + ComputableInPolyTime (fun r : AbsorbIn => r.2) encAbsorbIn ChildCursor.encState := + ComputableInPolyTime.snd _ _ + +/-- **The cursor step of `absorb` is polynomial-time.** -/ +private theorem absorbCursor_polyTime : + ComputableInPolyTime (fun r : AbsorbIn => cursorNext (weights r.1.1.1) (nodePrec r.1.1.1) + (nodes r.1.1.1).length (numPoints r.1.1.1) 64 r.2) encAbsorbIn (encOption encState) := by + have hx := absorbCall_polyTime + have hc := absorbState_polyTime + have ht := stateNode_polyTime.comp hc + have hT := (ComputableInPolyTime.length_list _).comp (nodes_polyTime.comp hx) + have hn := numPoints_polyTime.comp hx + have hwt : ComputableInPolyTime (fun r : AbsorbIn => weights r.1.1.1 r.2.1) encAbsorbIn + (encList encNat) := by + have h := weights_polyTime.comp (hx.prod ht) + exact h + have hpt : ComputableInPolyTime (fun r : AbsorbIn => nodePrec r.1.1.1 r.2.1) encAbsorbIn + encNat := by + have h := ComputableInPolyTime.unary_to_binary.comp (nodePrec_polyTime.comp (hx.prod ht)) + exact h + have hnext : ComputableInPolyTime (fun r : AbsorbIn => (weights r.1.1.1 (r.2.1 + 1)).length) + encAbsorbIn encUnary := by + have h := weightsLength_polyTime.comp + (hx.prod (ComputableInPolyTime.unary_add' ht (ComputableInPolyTime.const 1))) + exact h + have hin : ComputableInPolyTime (fun r : AbsorbIn => + (((((nodes r.1.1.1).length, numPoints r.1.1.1, (64 : ℕ)), (weights r.1.1.1 r.2.1, + nodePrec r.1.1.1 r.2.1, (weights r.1.1.1 (r.2.1 + 1)).length)), r.2) : StepInput)) + encAbsorbIn (encPair (encPair (encPair encUnary (encPair encUnary encUnary)) + (encPair (encList encNat) (encPair encNat encUnary))) ChildCursor.encState) := + ((hT.prod (hn.prod (ComputableInPolyTime.const 64))).prod (hwt.prod (hpt.prod hnext))).prod hc + have hin' : ComputableInPolyTime (fun r : AbsorbIn => + (((((nodes r.1.1.1).length, numPoints r.1.1.1, (64 : ℕ)), (weights r.1.1.1 r.2.1, + nodePrec r.1.1.1 r.2.1, (weights r.1.1.1 (r.2.1 + 1)).length)), r.2) : StepInput)) + encAbsorbIn encStepInput := hin + refine (computableInPolyTime_cursorStep.comp hin').of_enc_eq fun r => ?_ + rw [cursorNext_eq_cursorStep] + rfl + +/-- **The accumulator step of `absorb` is polynomial-time.** -/ +private theorem absorbAcc_polyTime : + ComputableInPolyTime (fun r : AbsorbIn => absorbAt (weights r.1.1.1) (nodePrec r.1.1.1) 64 + (mul (wide r.1.1.1) r.1.1.1.fl) (add (wide r.1.1.1) r.1.1.1.fl) (one r.1.1.1.fl) + (zero r.1.1.1.fl) r.2 r.1.1.2.2 r.1.2.2) encAbsorbIn + (encPair (encList encNat) (encPair (encList encNat) (encList (encList encNat)))) := by + have hx := absorbCall_polyTime + have hc := absorbState_polyTime + have ht := stateNode_polyTime.comp hc + have hN := wide_polyTime.comp hx + have hfl := fl_polyTime.comp hx + have hacc : ComputableInPolyTime (fun r : AbsorbIn => r.1.1.2.2) encAbsorbIn + (encPair (encList encNat) (encPair (encList encNat) (encList (encList encNat)))) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd encCall encFrame).comp + ((ComputableInPolyTime.fst _ encValue).comp + (ComputableInPolyTime.fst _ ChildCursor.encState))) + have hprod := (ComputableInPolyTime.fst _ _).comp hacc + have hsum := (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp hacc) + have hstored := (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp hacc) + have hy : ComputableInPolyTime (fun r : AbsorbIn => r.1.2.2) encAbsorbIn (encList encNat) := + (ComputableInPolyTime.snd encUnary (encList encNat)).comp ((ComputableInPolyTime.snd _ _).comp + (ComputableInPolyTime.fst _ ChildCursor.encState)) + have hpy := computableInPolyTime_mul hN hfl hprod hy + have hnew := computableInPolyTime_add hN hfl hsum hpy + have hone := computableInPolyTime_one hfl + have hzero := computableInPolyTime_zero hfl + have hlast : ComputableInPolyTime (fun r : AbsorbIn => + (BudgetTuples.budgetNext (weights r.1.1.1 r.2.1) (nodePrec r.1.1.1 r.2.1) r.2.2.2.1).isNone) + encAbsorbIn encBool := by + have hwt : ComputableInPolyTime (fun r : AbsorbIn => weights r.1.1.1 r.2.1) encAbsorbIn + (encList encNat) := by + have h := weights_polyTime.comp (hx.prod ht) + exact h + have hpt : ComputableInPolyTime (fun r : AbsorbIn => nodePrec r.1.1.1 r.2.1) encAbsorbIn + encNat := by + have h := ComputableInPolyTime.unary_to_binary.comp (nodePrec_polyTime.comp (hx.prod ht)) + exact h + have hin : ComputableInPolyTime (fun r : AbsorbIn => + (weights r.1.1.1 r.2.1, nodePrec r.1.1.1 r.2.1, r.2.2.2.1)) encAbsorbIn + (encPair (encList encNat) (encPair encNat (encList encNat))) := + hwt.prod (hpt.prod (stateExps_polyTime.comp hc)) + have hin' : ComputableInPolyTime (fun r : AbsorbIn => + (weights r.1.1.1 r.2.1, nodePrec r.1.1.1 r.2.1, r.2.2.2.1)) encAbsorbIn + BudgetTuples.encBudgetInput := hin + have h := ComputableInPolyTime.bool_not.comp (ComputableInPolyTime.optionIsSome + (BudgetTuples.computableInPolyTime_budgetNext.comp hin')) + refine h.of_enc_eq fun r => ?_ + simp only [Function.comp_apply] + cases BudgetTuples.budgetNext (weights r.1.1.1 r.2.1) (nodePrec r.1.1.1 r.2.1) r.2.2.2.1 <;> rfl + have hg : ComputableInPolyTime (fun r : AbsorbIn => decide (r.2.2.2.2 + 1 < 64)) encAbsorbIn + encBool := + ComputableInPolyTime.unary_lt' (ComputableInPolyTime.unary_add' (stateGroup_polyTime.comp hc) + (ComputableInPolyTime.const 1)) (ComputableInPolyTime.const 64) + have h := ComputableInPolyTime.ite hg (hpy.prod (hsum.prod hstored)) + (ComputableInPolyTime.ite hlast (hone.prod (hzero.prod (ComputableInPolyTime.listAppend' hstored + (ComputableInPolyTime.cons' hnew (ComputableInPolyTime.const []))))) + (hone.prod (hnew.prod hstored))) + refine h.of_enc_eq fun r => ?_ + simp only [absorbAt, decide_eq_true_eq, Function.comp_apply] + +/-- **Absorbing a child's value is polynomial-time.** -/ +theorem absorb_polyTime : + ComputableInPolyTime + (fun p : (Call × Frame) × (ℕ × List ℕ) => (scheme mk).absorb p.1.1 p.1.2 p.2) + (encPair (encPair encCall encFrame) encValue) encFrame := by + have hc : ComputableInPolyTime (fun p : (Call × Frame) × (ℕ × List ℕ) => p.1.2.1) + (encPair (encPair encCall encFrame) encValue) (encOption ChildCursor.encState) := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd encCall encFrame).comp + (ComputableInPolyTime.fst _ encValue)) + have hfallback : ComputableInPolyTime (fun p : (Call × Frame) × (ℕ × List ℕ) => p.1.2) + (encPair (encPair encCall encFrame) encValue) encFrame := + (ComputableInPolyTime.snd encCall encFrame).comp (ComputableInPolyTime.fst _ encValue) + have hbody : ComputableInPolyTime (fun r : AbsorbIn => + ((cursorNext (weights r.1.1.1) (nodePrec r.1.1.1) (nodes r.1.1.1).length (numPoints r.1.1.1) + 64 r.2, absorbAt (weights r.1.1.1) (nodePrec r.1.1.1) 64 + (mul (wide r.1.1.1) r.1.1.1.fl) (add (wide r.1.1.1) r.1.1.1.fl) (one r.1.1.1.fl) + (zero r.1.1.1.fl) r.2 r.1.1.2.2 r.1.2.2) : Frame)) encAbsorbIn encFrame := + absorbCursor_polyTime.prod (absorbAcc_polyTime) + have h := ComputableInPolyTime.optionElimWithInput + (body := fun (p : (Call × Frame) × (ℕ × List ℕ)) (c : State) => + ((cursorNext (weights p.1.1) (nodePrec p.1.1) (nodes p.1.1).length (numPoints p.1.1) 64 c, + absorbAt (weights p.1.1) (nodePrec p.1.1) 64 (mul (wide p.1.1) p.1.1.fl) + (add (wide p.1.1) p.1.1.fl) (one p.1.1.fl) (zero p.1.1.fl) c p.1.2.2 p.2.2) : Frame)) + hc hfallback hbody + refine h.of_enc_eq fun p => ?_ + rcases p with ⟨⟨x, _ | c, acc⟩, y⟩ <;> rfl + +/-- **The value of a call is polynomial-time.** -/ +theorem finish_polyTime : + ComputableInPolyTime (fun p : Call × Frame => (scheme mk).finish p.1 p.2) + (encPair encCall encFrame) encValue := by + have hx := ComputableInPolyTime.fst encCall encFrame + have hV : ComputableInPolyTime (fun p : Call × Frame => p.2.2.2.2) (encPair encCall encFrame) + (encList (encList encNat)) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd encCall encFrame))) + have htab : ComputableInPolyTime (fun p : Call × Frame => table p.1 p.2.2.2.2) + (encPair encCall encFrame) (encList (encList (encList encNat))) := by + have h := table_polyTime.comp (hx.prod hV) + exact h + have hrec := computableInPolyTime_recover (prec_polyTime.comp hx) (shift_polyTime.comp hx) + (fl_polyTime.comp hx) (slots_polyTime.comp hx) (twoPowSlots_polyTime.comp hx) + (levels_polyTime.comp hx) (step_polyTime.comp hx) (points_polyTime.comp hx) htab + have h := (depth_polyTime.comp hx).prod hrec + exact h + +variable {mk} + +/-- **The components of the scheme are polynomial-time**, for a polynomial-time child builder. -/ +theorem scheme_polyTime + (hmk : ComputableInPolyTime (fun q : Call × State => mk q.1 q.2) + (encPair encCall ChildCursor.encState) + (encPair encNat (DescribedNetwork.encDescribed VertexDesc.enc))) : + (scheme mk).PolyTime encCall encFrame encValue := + ⟨isLeaf_polyTime mk, start_polyTime mk, next_polyTime hmk, absorb_polyTime mk, + finish_polyTime mk⟩ + +end Scheme + +/-! ### The native child builder -/ + +/-- **The native child builder is polynomial-time** on every call and cursor state +(`NativeChild.computableInPolyTime_nativeChild_nativeParentPacking`). The rows are capped at the +number of vertices and the exponents at the node's precision, so both are polynomial in unary; the +number `2 ^ a` of slots is at most `s + 1` and the number `2 ^ (v₂ t + 1)` of tiles at most +`4 (s + 1)`. -/ +theorem nativeMk_polyTime : + ComputableInPolyTime (fun q : Call × State => nativeMk q.1 q.2) + (encPair encCall ChildCursor.encState) + (encPair encNat (DescribedNetwork.encDescribed VertexDesc.enc)) := by + have hx := ComputableInPolyTime.fst encCall ChildCursor.encState + have hc := ComputableInPolyTime.snd encCall ChildCursor.encState + have ht := stateNode_polyTime.comp hc + have hj := statePoint_polyTime.comp hc + have hβ := stateExps_polyTime.comp hc + have hg := ComputableInPolyTime.unary_to_binary.comp (stateGroup_polyTime.comp hc) + have hr := rowCap_polyTime.comp hx + have ha := slots_polyTime.comp hx + have hL := twoPowSlots_polyTime.comp hx + have hfl := fl_polyTime.comp hx + have hD := net_polyTime.comp hx + have hi : ComputableInPolyTime (fun q : Call × State => level q.1 ((nodes q.1).getD q.2.1 0)) + (encPair encCall ChildCursor.encState) encUnary := by + have h := nodeLevel_polyTime.comp (hx.prod ht) + exact h + have hT : ComputableInPolyTime + (fun q : Call × State => 2 ^ (level q.1 ((nodes q.1).getD q.2.1 0) + 1)) + (encPair encCall ChildCursor.encState) encUnary := by + have h := tiles_polyTime.comp (hx.prod ht) + exact h + have hpt : ComputableInPolyTime (fun q : Call × State => nodePrec q.1 q.2.1) + (encPair encCall ChildCursor.encState) encUnary := by + have h := nodePrec_polyTime.comp (hx.prod ht) + exact h + have hnode : ComputableInPolyTime + (fun q : Call × State => node (slots q.1) ((nodes q.1).getD q.2.1 0)) + (encPair encCall ChildCursor.encState) encIntBin := by + have h := ComputableInPolyTime.intBin_subNatNat.comp + ((ComputableInPolyTime.unary_to_binary.comp (node_polyTime.comp (hx.prod ht))).prod + (ComputableInPolyTime.unary_to_binary.comp hL)) + refine h.of_enc_eq fun q => ?_ + simp only [Function.comp_apply, node, Int.subNatNat_eq_coe, Nat.cast_pow, Nat.cast_ofNat] + have hζ : ComputableInPolyTime (fun q : Call × State => (points q.1).getD q.2.2.1 []) + (encPair encCall ChildCursor.encState) (encList encNat) := by + have h := ComputableInPolyTime.listGetDWithInput (points_polyTime.comp hx) hj [] + exact h + have hcap : ComputableInPolyTime + (fun q : Call × State => q.2.2.2.1.map (min · (nodePrec q.1 q.2.1))) + (encPair encCall ChildCursor.encState) (encList encUnary) := by + have hf : ComputableInPolyTime (fun r : (Call × State) × ℕ => min r.2 (nodePrec r.1.1 r.1.2.1)) + (encPair (encPair encCall ChildCursor.encState) encNat) encUnary := by + have h := ComputableInPolyTime.binary_min_unary.comp + ((hpt.comp (ComputableInPolyTime.fst _ _)).prod (ComputableInPolyTime.snd _ _)) + refine h.of_enc_eq fun r => ?_ + simp only [Function.comp_apply, min_comm] + have h := ComputableInPolyTime.mapWithInput + (f := fun (q : Call × State) (b : ℕ) => min b (nodePrec q.1 q.2.1)) hβ hf + exact h + have hrows := computableInPolyTime_childRows_nativeParentPacking hr ha hi hD hg + have hchild := computableInPolyTime_nativeChild_nativeParentPacking hfl ha hL hi hT hpt hnode hζ + hr hD hcap hg + have h := hrows.prod hchild + exact h + +/-- **The components of the native contraction recursion are polynomial-time**: the hypothesis +`hpoly` of `CountingSimulation.contractionAlgorithmOfNative`. -/ +theorem scheme_nativeMk_polyTime : (scheme nativeMk).PolyTime encCall encFrame encValue := + scheme_polyTime nativeMk_polyTime + +end Cslib.Circuits.Frontier.FastContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionStep.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionStep.lean new file mode 100644 index 0000000000..ea5822fb2d --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/FastContractionStep.lean @@ -0,0 +1,240 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContraction +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeCall +import Mathlib.Data.List.GetD + +/-! +# The step of the native contraction recursion + +The child builder of the recursion of `Cslib.Circuits.Frontier.FastContraction.scheme` is the native +child (`FastContraction.nativeMk`: `NativeChild.childRows` and `NativeChild.nativeChild` at the +cursor state, with the rows and the exponents capped so that it is polynomial-time on every state), +and a call is well-formed (`FastContraction.WF`) when its modulus is irreducible modulo `2` and its +network is rows-first. The step of an active call is then correct +(`FastContraction.stepCorrect_nativeMk`): its interpolation points, the binary digits of +`0, …, n - 1`, have lifts with unit differences (`FastContraction.pairwise_isUnit_points`), and the +values the call stores are the sums of (D.16) at each node (`FastContraction.val_table_runLoop`), +which is the hypothesis of the correctness of one native call +(`NativeCall.val_recover_eq_contraction`, [OpenAI2026ETH], §D.4.4–§D.4.7). + +## Main definitions + +* `Cslib.Circuits.Frontier.FastContraction.nativeMk`: the native child builder. +* `Cslib.Circuits.Frontier.FastContraction.WF`: well-formed calls. + +## Main results + +* `Cslib.Circuits.Frontier.FastContraction.pairwise_isUnit_points`: the points have lifts with unit + differences. +* `Cslib.Circuits.Frontier.FastContraction.nativeMk_eq`: the caps of the builder are inactive on + well-formed active calls. +* `Cslib.Circuits.Frontier.FastContraction.stepCorrect_nativeMk`: the step is correct. +* `Cslib.Circuits.Frontier.FastContraction.wfChildren_nativeMk`: well-formedness passes to the + children. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.4–§D.4.7 (pp. 61–64). +-/ + +public section + +namespace Cslib.Circuits.Frontier.FastContraction + +open Finset GaloisRingArith ChildCursor NativeRecovery ContractionSchedule NativeChild + +/-- **The native child builder**: at the cursor state `(j, i, β, g)`, the rows and the native +child of group `g` at the `j`-th node, the `i`-th point and the exponents `β`. The rows are capped +at the number of vertices (`FastContraction.rowCap`) and the exponents at the node's precision +`p_j`. On a well-formed active call and the cursor's states neither cap changes anything +(`FastContraction.nativeMk_eq`); on every call and state they keep the builder polynomial-time, +since the number of rows and the exponents are stored in binary but the child lists one word per +row and stores the exponents in unary. -/ +@[expose] def nativeMk (x : Call) (c : State) : ℕ × DescribedNetwork VertexDesc := + (childRows (rowCap x) (level x ((nodes x).getD c.1 0)) x.net (packing x) c.2.2.2, + nativeChild x.fl (slots x) (level x ((nodes x).getD c.1 0)) (nodePrec x c.1) + (node (slots x) ((nodes x).getD c.1 0)) ((points x).getD c.2.1 []) (rowCap x) x.net + (packing x) (c.2.2.1.map (min · (nodePrec x c.1))) c.2.2.2) + +/-- **A well-formed call**: its modulus is irreducible modulo `2` and its network is rows-first. -/ +@[expose] def WF (x : Call) : Prop := + Irreducible ((monicOf x.fl).map (Int.castRingHom (ZMod 2))) ∧ + x.net.IsRowsFirstBipartite x.rows + +variable {x : Call} + +/-- A rows-first network with an edge has at most as many rows as vertices. -/ +theorem rows_le_of_isRowsFirstBipartite {D : DescribedNetwork VertexDesc} {r : ℕ} + (hD : D.IsRowsFirstBipartite r) (hM : 1 ≤ D.numEdges) : r ≤ D.vertices.length := by + by_contra h + have h1 := (hD.2 0 hM).2 + rw [List.drop_of_length_le (by omega)] at h1 + simp at h1 + +/-- The cap of `nativeMk` leaves a tuple of weight below `p` unchanged, for positive weights. -/ +theorem map_min_of_mem_budgetTuples {ws : List ℕ} (hw : ∀ w ∈ ws, 0 < w) {p : ℕ} {β : List ℕ} + (hβ : β ∈ BudgetTuples.budgetTuples ws p) : β.map (min · p) = β := by + calc β.map (min · p) = β.map id := List.map_congr_left fun b hb => by + have := BudgetTuples.lt_of_mem_budgetTuples hw hβ hb + simp only [id] + omega + _ = β := List.map_id β + +/-- **The caps of `nativeMk` are inactive** at a well-formed active call and a state of its cursor: +the builder is the native child of the rows and the exponents of the state. -/ +theorem nativeMk_eq (hx : WF x) (ha : active x = true) {c : State} (hc : c ∈ children x) : + nativeMk x c = + (childRows x.rows (level x ((nodes x).getD c.1 0)) x.net + (nativeParentPacking x.rows (slots x) x.net) c.2.2.2, + nativeChild x.fl (slots x) (level x ((nodes x).getD c.1 0)) (nodePrec x c.1) + (node (slots x) ((nodes x).getD c.1 0)) ((points x).getD c.2.1 []) x.rows x.net + (nativeParentPacking x.rows (slots x) x.net) c.2.2.1 c.2.2.2) := by + have hr := rows_le_of_isRowsFirstBipartite hx.2 (one_le_edges_of_active ha) + have hβ : c.2.2.1 ∈ BudgetTuples.budgetTuples (weights x c.1) (nodePrec x c.1) := by + simp only [children, cursorList, nodeBlock, pointBlock, groupBlock, List.mem_flatMap, + List.mem_range, List.mem_map] at hc + obtain ⟨j, -, i, -, β, hβ, g, -, rfl⟩ := hc + exact hβ + rw [nativeMk, packing_eq hr, rowCap_eq hr, map_min_of_mem_budgetTuples (weights_pos _) hβ] + +/-- The level of a node index is the `2`-adic valuation of its node. -/ +theorem level_eq {k : ℕ} (hk : k < 2 ^ (slots x + 1)) : + level x k = padicValInt 2 (node (slots x) k) := by + have : ((k : ℤ) - 2 ^ slots x).natAbs = k - 2 ^ slots x + (2 ^ slots x - k) := by + rw [show (2 : ℤ) ^ slots x = ((2 ^ slots x : ℕ) : ℤ) by push_cast; rfl] + omega + rw [level, node, padicValInt, this, twoVal_eq (by rw [pow_succ] at hk ⊢; omega)] + +/-- The tapered multiplicities sum to at most `2 ^ (a + 1) h u`. -/ +theorem sum_taperMult_le (a u h : ℕ) : + ∑ t ∈ recoveryNodes a, taperMult u h t ≤ 2 ^ (a + 1) * (h * u) := by + rw [sum_recoveryNodes] + calc ∑ k ∈ range (2 ^ (a + 1)), (if k = 2 ^ a then 0 else taperMult u h (node a k)) + ≤ ∑ _k ∈ range (2 ^ (a + 1)), h * u := sum_le_sum fun k _ => by + split_ifs + · exact Nat.zero_le _ + · exact Nat.mul_le_mul_left _ (Nat.sub_le _ _) + _ = 2 ^ (a + 1) * (h * u) := by rw [sum_const, card_range, smul_eq_mul] + +/-- The children of an active call include every state of a node, a point, a tuple of weight +below the node's precision and a group. -/ +theorem mem_children {j i : ℕ} {β : List ℕ} {g : ℕ} (hj : j < (nodes x).length) + (hi : i < numPoints x) (hβ : β ∈ BudgetTuples.budgetTuples (weights x j) (nodePrec x j)) + (hg : g < 64) : (j, i, β, g) ∈ children x := by + simp only [children, cursorList, nodeBlock, pointBlock, groupBlock, List.mem_flatMap, + List.mem_range, List.mem_map] + exact ⟨j, hj, i, hi, β, hβ, g, hg, rfl⟩ + +/-- **The interpolation points of an active call have lifts with unit differences**: they are the +binary digits of `0, …, n - 1` with `n ≤ 2 ^ e` +(`GaloisRingArith.pairwise_isUnit_liftO_sub_digits`). -/ +theorem pairwise_isUnit_points (hirr : Irreducible ((monicOf x.fl).map (Int.castRingHom (ZMod 2)))) + (ha : active x = true) : + Pairwise fun i j : Fin (points x).length => + IsUnit (liftO hirr ((points x).getD i []) - liftO hirr ((points x).getD j [])) := by + have hn : numPoints x ≤ 2 ^ x.fl.length := by + simp only [active, decide_eq_true_eq] at ha + exact ha.2.2.2.2.2.1 + exact pairwise_isUnit_liftO_sub_digits hirr hn + +/-- **The step of an active call is correct**, for the native child builder and well-formed +calls. -/ +theorem stepCorrect_nativeMk : StepCorrect nativeMk WF := by + intro x hx ha vals hvals + obtain ⟨hirr, hD⟩ := hx + have ha' := ha + simp only [active, decide_eq_true_eq] at ha' + obtain ⟨-, hu, huM, hua, -, -, -⟩ := ha' + have hlen : (points x).length = numPoints x := by simp [points] + have hr := rows_le_of_isRowsFirstBipartite hD (one_le_edges_of_active ha) + refine NativeCall.val_recover_eq_contraction hirr hD hu huM hua (le_taperStep_mul hu) + (taperStep_mul_le _ _ _) (pow_slots_le_edges_of_active ha) ?_ (points x) + (pairwise_isUnit_points hirr ha) + (by rw [hlen]; exact Nat.lt_succ_self _) _ ?_ + · calc (slots x + 1) * ∑ t ∈ recoveryNodes (slots x), taperMult (levels x) (step x) t + ≤ (slots x + 1) * (2 ^ (slots x + 1) * (step x * levels x)) := + Nat.mul_le_mul_left _ (sum_taperMult_le _ _ _) + _ ≤ shift x := by + have e : (slots x + 1) * (2 ^ (slots x + 1) * (step x * levels x)) = + (slots x + 1) * 2 ^ (slots x + 1) * step x * levels x := by ring + unfold shift; omega + · intro k hk hkL hq i hi + rw [hlen] at hi + have hkmem : k ∈ nodes x := by + simp only [nodes, List.mem_filter, List.mem_range, decide_eq_true_eq] + exact ⟨hk, hkL, hq⟩ + obtain ⟨j, hj, hjk⟩ := List.getElem_of_mem hkmem + have hgetD : (nodes x).getD j 0 = k := by rw [List.getD_eq_getElem _ _ hj, hjk] + have hlev := level_eq (x := x) hk + have hprec : nodePrec x j = nodePrecision x.prec (step x) (slots x) x.edges + (padicValInt 2 (node (slots x) k)) := by + rw [nodePrec, hgetD, hlev] + have hw : weights x j = modeWeights (nativeParentPacking x.rows (slots x) x.net).2.1 + (2 ^ (padicValInt 2 (node (slots x) k) + 1)) + (nodePrecision x.prec (step x) (slots x) x.edges (padicValInt 2 (node (slots x) k))) := by + rw [weights, packing_eq hr, hgetD, hlev, hprec] + have hstore := val_table_runLoop ha vals hj hi + rw [hjk, hw, hprec] at hstore + refine hstore.trans ?_ + refine congrArg List.sum (List.map_congr_left fun β hβ => ?_) + refine congrArg List.prod (List.map_congr_left fun g hg => ?_) + have hβ' : β ∈ BudgetTuples.budgetTuples (weights x j) (nodePrec x j) := by + rw [hw, hprec]; exact hβ + have hmem := mem_children hj (by omega) hβ' (List.mem_range.mp hg) + have key : ∀ v P, level x k = v → nodePrec x j = P → + val P x.fl (vals (j, i, β, g)) = + (nativeChild x.fl (slots x) v P (node (slots x) k) ((points x).getD i []) x.rows x.net + (nativeParentPacking x.rows (slots x) x.net) β g).contraction + fun d bs => val P x.fl (d.entry bs) := by + rintro v P rfl rfl + have h := hvals _ hmem + rw [nativeMk_eq ⟨hirr, hD⟩ ha hmem] at h + simp only at h + rw [hgetD] at h + exact h + exact key _ _ hlev hprec + +/-- The nodes of positive multiplicity, as grid indices. -/ +theorem mem_nodes {k : ℕ} : k ∈ nodes x ↔ + k < 2 ^ (slots x + 1) ∧ k ≠ 2 ^ slots x ∧ 0 < mult (slots x) (levels x) (step x) k := by + simp [nodes] + +/-- A child of the cursor lies at a node, a point and a group in range. -/ +theorem lt_of_mem_children {c : State} (hc : c ∈ children x) : + c.1 < (nodes x).length ∧ c.2.1 < numPoints x ∧ c.2.2.2 < 64 := by + simp only [children, cursorList, nodeBlock, pointBlock, groupBlock, List.mem_flatMap, + List.mem_range, List.mem_map] at hc + obtain ⟨j, hj, i, hi, β, -, g, hg, rfl⟩ := hc + exact ⟨hj, hi, hg⟩ + +/-- **Well-formedness passes to the native children**: they keep the modulus, and they are +rows-first (`NativeChild.isRowsFirstBipartite_nativeChild`). -/ +theorem wfChildren_nativeMk : WFChildren nativeMk WF := by + intro x hx ha c hc + obtain ⟨hirr, hD⟩ := hx + refine ⟨hirr, ?_⟩ + obtain ⟨hj, -, hg⟩ := lt_of_mem_children hc + have ha' := ha + simp only [active, decide_eq_true_eq] at ha' + obtain ⟨-, -, -, hua, -, -, -⟩ := ha' + have hkmem : (nodes x).getD c.1 0 ∈ nodes x := by + rw [List.getD_eq_getElem _ _ hj]; exact List.getElem_mem hj + obtain ⟨hk, -, hq⟩ := mem_nodes.mp hkmem + have hia : level x ((nodes x).getD c.1 0) < slots x := by + rw [level_eq hk] + exact padicValInt_lt_of_taperMult_pos (by rwa [← mult_eq _ _ hk]) hua + have h := isRowsFirstBipartite_nativeChild (fl := x.fl) hD (pow_slots_le_edges_of_active ha) hia + (nodePrec x c.1) (node (slots x) ((nodes x).getD c.1 0)) ((points x).getD c.2.1 []) + c.2.2.1 ⟨c.2.2.2, hg⟩ + simp only [child, nativeMk_eq ⟨hirr, hD⟩ ha hc] + exact h + +end Cslib.Circuits.Frontier.FastContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeCall.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeCall.lean new file mode 100644 index 0000000000..82af1b001c --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeCall.lean @@ -0,0 +1,169 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChild +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeRecovery +import Mathlib.Data.List.GetD + +/-! +# Correctness of one native active call + +An active call of the native contraction recursion of [OpenAI2026ETH] (§D.4.4–§D.4.7, +pp. 60–64) on a rows-first network of descriptors stores, for each node index `k` of positive +multiplicity and each interpolation point `i`, a value in `A_{p_t}`, and recovers its own value +with native tapered recovery (`NativeRecovery.recover`). This file proves the call correct from +the correctness of its stored values (`NativeCall.val_recover_eq_contraction`): if each stored value +is, in `A_{p_t}`, the sum over the exponent tuples of the products over the groups of the +contractions of the native children (`NativeChild.nativeChild`), then the recovered value is the +contraction of the network in `A_p`. + +**The proof** is carried over `O = ℤ_(2)[U] / (f)` with the canonical lifts of the entries +(`DescribedNetwork.toNetwork`, `GaloisRingArith.liftO`), as in §D.4.1 (p. 58). The values at the +nodes are the lifts of the stored lists, so + +* modulo `2 ^ {p_t}` they are the right side of (D.16) for the children of `callSplitting`: the + native children have these contractions (`NativeChild.contraction_nativeChild`), and the sum over + the exponent lists is the sum over the mode budget (`BudgetTuples.sum_filter_weight_lt_eq`); +* so interpolation and Lemma D.5 recover the contraction modulo `2 ^ p` + (`TensorNetwork.exists_recoverFromValues_eq_add_of_children`); +* and the lifts reduce in `A_{p + Δ}` to exactly the stored lists, so native recovery computes the + reduction of the contraction (`NativeRecovery.val_recover_of_eq_add`), with no condition on + `p_t` beyond Lemma D.5's. + +The call's precision `p_t` of the node of valuation `i` is `nodePrecision p h a M i`, and the +shift `Δ` only needs `(a + 1) ∑_t q_t ≤ Δ`. + +## Main results + +* `Cslib.Circuits.Frontier.NativeCall.val_recover_eq_contraction`: one native active call is + correct when its stored values are. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.4–§D.4.7, pp. 60–64. +-/ + +public section + +namespace Cslib.Circuits.Frontier.NativeCall + +open Finset GaloisRingArith NativeChild NativeRecovery +open ContractionSchedule (nodePrecision) + +/-- The node index of a node: `t + 2 ^ a`. -/ +theorem toNat_node_add (a k : ℕ) : (node a k + 2 ^ a).toNat = k := by + rw [node, sub_add_cancel, Int.toNat_natCast] + +/-- A product over a range as a list is a product over `Fin`. -/ +theorem prod_map_range {M : Type*} [CommMonoid M] (F : ℕ → M) (n : ℕ) : + ((List.range n).map F).prod = ∏ g : Fin n, F g := by + rw [← List.map_coe_finRange_eq_range, List.map_map, ← List.ofFn_eq_map, List.prod_ofFn] + rfl + +variable {fl : List ℕ} (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + {D : DescribedNetwork VertexDesc} {r : ℕ} (hD : D.IsRowsFirstBipartite r) + +include hD in +/-- **One native active call is correct when its stored values are** ([OpenAI2026ETH], +§D.4.4–§D.4.7). Let `D` be a rows-first network of descriptors with `M` edges, and let +`(a, u, h)` meet the hypotheses of Lemma D.5 (`1 ≤ u ≤ M`, `u ≤ a`, `3 M ≤ h u ≤ 3 M + u`), with +`64 ^ a ≤ M` and `(a + 1) ∑_t q_t ≤ Δ`. Let the interpolation values `ζl` have lifts with unit +differences, and more than `M (2 L - 1)` of them. Suppose that at every node index `k` of positive +multiplicity, with the node `t = k - 2 ^ a` of valuation `i` and precision `p_t`, each stored value +`W[k][i']` is, in `A_{p_t}`, the sum over the exponent lists of the packet-major modes of the +products over the `64` groups of the contractions in `A_{p_t}` of the native children. Then native +recovery returns the contraction of `D` in `A_p`. -/ +theorem val_recover_eq_contraction {a u h p Δ : ℕ} (hu : 1 ≤ u) (huM : u ≤ D.numEdges) + (hua : u ≤ a) (hh : 3 * D.numEdges ≤ h * u) (hh' : h * u ≤ 3 * D.numEdges + u) + (hK : 64 ^ a ≤ D.numEdges) + (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) (ζl : List (List ℕ)) + (hζ : Pairwise fun i j : Fin ζl.length => + IsUnit (liftO hirr (ζl.getD i []) - liftO hirr (ζl.getD j []))) + (hn : D.numEdges * (2 * 2 ^ a - 1) < ζl.length) (W : List (List (List ℕ))) + (hW : ∀ k < 2 ^ (a + 1), k ≠ 2 ^ a → 0 < mult a u h k → ∀ i < ζl.length, + val (nodePrecision p h a D.numEdges (padicValInt 2 (node a k))) fl + ((W.getD k []).getD i []) = + ((BudgetTuples.budgetTuples + (modeWeights (nativeParentPacking r a D).2.1 (2 ^ (padicValInt 2 (node a k) + 1)) + (nodePrecision p h a D.numEdges (padicValInt 2 (node a k)))) + (nodePrecision p h a D.numEdges (padicValInt 2 (node a k)))).map fun β => + ((List.range 64).map fun g => + (nativeChild fl a (padicValInt 2 (node a k)) + (nodePrecision p h a D.numEdges (padicValInt 2 (node a k))) (node a k) + (ζl.getD i []) r D (nativeParentPacking r a D) β g).contraction + fun d bs => val (nodePrecision p h a D.numEdges (padicValInt 2 (node a k))) fl + (d.entry bs)).prod).sum) : + val p fl (recover p Δ fl a u h ζl W) = + D.contraction fun d bs => val p fl (d.entry bs) := by + have hB := AdjoinRoot.algebraMap_mem_integer_padicValuation (monic_monicOf fl) hirr + have hcard : Fintype.card (Fin D.numEdges) = D.numEdges := Fintype.card_fin _ + have hpk1 : (nativeParentPacking r a D).2.1 = (parentPacking hD a).numPackets := by + rw [nativeParentPacking_eq hD hK] + -- The points and the values over `O`: the lifts of the native lists. + let ζ : Fin ζl.length → RingO hirr := fun i => liftO hirr (ζl.getD i []) + let v : ℤ → Fin ζl.length → RingO hirr := fun t i => + liftO hirr ((W.getD (t + 2 ^ a).toNat []).getD i []) + -- Modulo `2 ^ {p_t}` the values are the right side of (D.16). + have hv : ∀ t (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (k : Fin ζl.length), + Ideal.Quotient.mk (nodeIdeal (RingO hirr) (Fintype.card (Fin D.numEdges)) a h p t) (v t k) = + ∑ β ∈ TensorNetwork.modeBudget (fun Z : Fin (parentPacking hD a).numPackets × + Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t)) => + (Z.2.2 : ℕ) + 1) + (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t)), + ∏ g, ((parentSplitting hirr hD a p u h hua ht hq (ζl.getD k [])).child β + g).contraction := by + intro t ht hq k + obtain ⟨kk, hkk, hkt⟩ : ∃ kk, (kk < 2 ^ (a + 1) ∧ kk ≠ 2 ^ a) ∧ node a kk = t := by + rw [recoveryNodes_eq_image, mem_image] at ht + obtain ⟨kk, hk, e⟩ := ht + exact ⟨kk, by simpa using hk, e⟩ + subst hkt + have hmult : 0 < mult a u h kk := by rw [mult_eq u h hkk.1]; exact hq + have hWk := hW kk hkk.1 hkk.2 hmult k k.2 + rw [← hcard] at hWk + simp only [v, toNat_node_add] + rw [mk_liftO hirr, hWk, map_list_sum, List.map_map, TensorNetwork.modeBudget, + BudgetTuples.sum_filter_weight_lt_eq (nodup_modeList _ _ _) mem_modeList + (fun Z => Nat.succ_pos _), map_modeList_weight, hpk1] + refine congrArg List.sum (List.map_congr_left fun β _ => ?_) + simp only [Function.comp_apply] + rw [map_list_prod, List.map_map, prod_map_range] + refine Fintype.prod_congr _ _ fun g => ?_ + simp only [Function.comp_apply] + rw [DescribedNetwork.map_contraction] + exact contraction_nativeChild hirr hD hK hua ht hq (ζl.getD k []) β g + obtain ⟨b, hb, hx⟩ := TensorNetwork.exists_recoverFromValues_eq_add_of_children (RingO hirr) hB + (parentNetwork hirr hD) (parentSide r) (parentSide_src hD) (parentSide_tgt hD) + (parentPacking hD a) (p := p) hu (by rwa [hcard]) hua (by rwa [hcard]) (by rwa [hcard]) ζ hζ + (by rwa [hcard]) v hv + -- Native recovery computes the reduction of the recovered value. + have hpt : ∀ t ∈ recoveryNodes a, 0 < taperMult u h t → + p + 6 * h * 2 ^ (a - padicValInt 2 t) + taperMult u h t * (a + 1) ≤ + nodePrecision p h a D.numEdges (padicValInt 2 t) := by + intro t _ _ + have h1 : h * (u - padicValInt 2 t) ≤ 8 * D.numEdges := + (Nat.mul_le_mul_left _ (Nat.sub_le _ _)).trans (by omega) + have e1 : 6 * h * 2 ^ (a - padicValInt 2 t) ≤ 8 * h * 2 ^ (a - padicValInt 2 t) := + Nat.mul_le_mul_right _ (by omega) + have e2 : h * (u - padicValInt 2 t) * (a + 1) ≤ 8 * D.numEdges * (a + 1) := + Nat.mul_le_mul_right _ h1 + simp only [nodePrecision, taperMult] + omega + have hrec := val_recover_of_eq_add hirr (RingO hirr) hB + (AdjoinRoot.reduction (monic_monicOf fl) hirr (p + Δ)) hΔ + (fun t => nodePrecision p h a D.numEdges (padicValInt 2 t)) hpt ζ hζ ζl rfl + (fun i => (reduction_liftO hirr _ _).symm) v W + (fun k _ _ _ i => ⟨0, by simp only [v, toNat_node_add, reduction_liftO, mul_zero, add_zero]⟩) + (parentNetwork hirr hD).contraction hb hx + rw [hrec, ← DescribedNetwork.reduction_contraction_toNetwork hD hirr (fun d bs => d.entry bs) p] + exact RingHom.congr_fun (AdjoinRoot.castHom_comp_reduction (monic_monicOf fl) hirr + (Nat.le_add_right p Δ)) _ + +end Cslib.Circuits.Frontier.NativeCall diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChild.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChild.lean new file mode 100644 index 0000000000..e90fd66f59 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChild.lean @@ -0,0 +1,1027 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedLift +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildEntries +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildNumbering +public import Complexity.Computability.PolyTime.PacketPacking +public import Complexity.Foundations.Combinatorics.BudgetTuples +import Complexity.Foundations.Data.List.Lookup +import Mathlib.Data.List.GetD + +/-! +# The native child networks of an active call + +An active call of the contraction recursion of [OpenAI2026ETH] (§D.4.5–§D.4.7, pp. 62–64) on a +network of vertex descriptors evaluates, for each node `t`, each interpolation value `ζ`, each +exponent tuple `β` and each group `g`, the child network of group `g`. This file builds that child +natively as a network of descriptors (`NativeChild.nativeChild`), from the parent, the native +packing of its edges (`Cslib.PacketPacking.nativePacking`) and `(t, ζ, β, g)`, and proves that it +has the contraction of the child of the mode splitting at the node (`TensorNetwork.callSplitting`). + +**The child.** Its vertices are numbered as in `ChildNumbering.childVertexEquiv` and its edges as +in `ChildNumbering.childEdgeEquiv`: + +* the vertices of group `g`, in order, keep their descriptors, and their ports are their parent + ports, *in the parent's order*, each renumbered to its child edge: a row port by its position + among the row ports of group `g`, a column port by the number of row ports plus its position + among the column ports (`NativeChild.childEdgeOf`). A retained vertex reads its ports in its own + order, so its ports are not sorted; +* the packet `q` is a new vertex whose ports are its child edges in increasing order + (`ChildNumbering.childPorts`), each tagged with whether it is a row port and with the slot of its + parent edge, and whose descriptor carries the node, the value `ζ`, the symbols of `q` against + `g` and the slice of `β` at `q` (`NativeChild.packetCtx`, `NativeChild.betaSlice`), at the + child's precision `p_t`. + +The modes of the node are listed packet-major (`NativeChild.modeList`): the mode `(q, y, b)` is at +`q · 2 ^ (i + 1) · p_t + y · p_t + b`, so the dense exponents of packet `q` are a slice of `β`, and +`β` stands for the mode tuple `BudgetTuples.ofList (modeList …) β`. + +**Correctness** (`NativeChild.contraction_nativeChild`). Over `O = ℤ_(2)[U] / (f)` the parent is +the network `DescribedNetwork.toNetwork` with the lifts of its entries, and its packing is the +native packing, which is `PacketPacking.packing` of its edges (`PacketPacking.nativePacking_eq`), +moved to the ends of the parent (`NativeChild.parentPacking`). Along `reductionLift`, the entries of +the native child in `A_{p_t}` give the contraction of the child of `callSplitting` at the lift of +`ζ` and the mode tuple of `β`: the retained vertices by `ModeSplitting.child_tensor_inl_eq_tensor` +and the packets by `TensorNetwork.map_val_entry_child_inr`, assembled by +`DescribedNetwork.contraction_eq_of_ports`. The native child is again rows-first, with its retained +vertices as rows (`NativeChild.isRowsFirstBipartite_nativeChild`). + +## Main definitions + +* `Cslib.PacketPacking.Packing.relabel`: a packing moved to other ends of the edges. +* `Cslib.Circuits.Frontier.NativeChild.nativeChild`: the native child. +* `Cslib.Circuits.Frontier.NativeChild.childRows`: its number of rows. +* `Cslib.Circuits.Frontier.NativeChild.modeList`, `Cslib.Circuits.Frontier.NativeChild.modeWeights`: + the modes of a node, packet-major, and their weights. +* `Cslib.Circuits.Frontier.NativeChild.parentPacking`: the packing of the parent over `O`. + +## Main results + +* `Cslib.Circuits.Frontier.NativeChild.contraction_nativeChild`: the native child has the + contraction of the child. +* `Cslib.Circuits.Frontier.NativeChild.isRowsFirstBipartite_nativeChild`: it is rows-first. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.5–§D.4.7, pp. 62–64. +-/ + +public section + +/-! ### Moving a packing to other ends -/ + +namespace Cslib.PacketPacking + +variable {E R C R' C' : Type*} {rowOf : E → R} {colOf : E → C} {a r : ℕ} + +/-- **A packing moved to other ends of the edges**: the same packets, slots and symbols, with the +labels of the new ends read through `ρ` and `κ`, which agree with the old labels at the ends of +every edge. -/ +@[expose] def Packing.relabel (P : Packing rowOf colOf a r) (rowOf' : E → R') (colOf' : E → C') + (ρ : R' → Fin a → Fin r) (κ : C' → Fin a → Fin r) + (hρ : ∀ e, ρ (rowOf' e) = P.rowLabel (rowOf e)) + (hκ : ∀ e, κ (colOf' e) = P.colLabel (colOf e)) : + Packing rowOf' colOf' a r where + rowLabel := ρ + colLabel := κ + numPackets := P.numPackets + packet := P.packet + slot := P.slot + symbols := P.symbols + injective := P.injective + labels_eq e i := by rw [hρ, hκ]; exact P.labels_eq e i + +end Cslib.PacketPacking + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +namespace NativeChild + +open ChildNumbering GaloisRingArith NativePacket +open ContractionSchedule (nodePrecision) + +/-! ### The native child -/ + +/-- **The groups of the vertices** in coordinate `i`: digit `i` of the row word of a row +(`v < r`), and of the column word of a column. -/ +@[expose] def groupsOf (r i k : ℕ) (words : List (List ℕ) × List (List ℕ)) : List ℕ := + (List.range k).map fun v => + if v < r then (words.1.getD v []).getD i 0 else (words.2.getD (v - r) []).getD i 0 + +/-- The parent edges whose end `sel` is in group `g`, in order: the row ports of group `g` for +`sel = Prod.fst`, its column ports for `sel = Prod.snd`. -/ +@[expose] def portEdges (groups : List ℕ) (g : ℕ) (ends : List (ℕ × ℕ)) (sel : ℕ × ℕ → ℕ) : + List ℕ := + (List.range ends.length).filter fun e => groups.getD (sel (ends.getD e (0, 0))) 0 = g + +/-- The position of the edge `e` among the parent edges whose end `sel` is in group `g`. -/ +@[expose] def portRank (groups : List ℕ) (g : ℕ) (ends : List (ℕ × ℕ)) (sel : ℕ × ℕ → ℕ) + (e : ℕ) : ℕ := + (List.range e).countP fun e' => groups.getD (sel (ends.getD e' (0, 0))) 0 = g + +/-- **The child edge of the parent port `e` of the vertex `v`**: for a row, its position among the +row ports of group `g`; for a column, the number of row ports plus its position among the column +ports. -/ +@[expose] def childEdgeOf (r : ℕ) (groups : List ℕ) (g : ℕ) (ends : List (ℕ × ℕ)) (v e : ℕ) : ℕ := + if v < r then portRank groups g ends Prod.fst e + else (portEdges groups g ends Prod.fst).length + portRank groups g ends Prod.snd e + +/-- **The parent edge of the child edge `j`**: the `j`-th row port of group `g`, or the +`(j - R)`-th column port for `R` row ports. -/ +@[expose] def parentEdge (groups : List ℕ) (g : ℕ) (ends : List (ℕ × ℕ)) (j : ℕ) : ℕ := + if j < (portEdges groups g ends Prod.fst).length then + (portEdges groups g ends Prod.fst).getD j 0 + else (portEdges groups g ends Prod.snd).getD (j - (portEdges groups g ends Prod.fst).length) 0 + +/-- **The context of the packet `q`** in the child of group `g` at the node `t` of valuation `i` +and the interpolation value `ζ`: the precision `p_t` and `p_t` exponents per tile, `2 ^ a` slots, +`2 ^ (i + 1)` tiles, and the symbols of `q` in coordinate `i` against `g`. -/ +@[expose] def packetCtx (fl : List ℕ) (a i pt : ℕ) (t : ℤ) (ζ : List ℕ) (g q : ℕ) : PacketCtx where + p := pt + fl := fl + a := a + L := 2 ^ a + i := i + tiles := 2 ^ (i + 1) + t := t + nb := pt + e₁ := decide ((PacketPacking.packetSymbols a 64 q i).1 = g) + e₂ := decide ((PacketPacking.packetSymbols a 64 q i).2 = g) + differ := decide ((PacketPacking.packetSymbols a 64 q i).1 ≠ + (PacketPacking.packetSymbols a 64 q i).2) + cols := [] + ζ := ζ + +/-- **The dense exponents of the packet `q`**: its slice of the packet-major list `β`. -/ +@[expose] def betaSlice (i pt : ℕ) (β : List ℕ) (q : ℕ) : List ℕ := + (β.drop (q * (2 ^ (i + 1) * pt))).take (2 ^ (i + 1) * pt) + +/-- The default vertex: no ports and the empty table. -/ +abbrev defaultVertex : List ℕ × VertexDesc := ([], .inl []) + +/-- **The native child** of group `g` at the node `t` of valuation `i`, the interpolation value +`ζ`, the precision `p_t` and the packet-major exponents `β`, of the rows-first parent `D` with `r` +rows and the native packing `pk` of its edges: the vertices of group `g` with their descriptors +and renumbered ports, then one packet vertex per packet. -/ +@[expose] def nativeChild (fl : List ℕ) (a i pt : ℕ) (t : ℤ) (ζ : List ℕ) (r : ℕ) + (D : DescribedNetwork VertexDesc) + (pk : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ)) (β : List ℕ) (g : ℕ) : + DescribedNetwork VertexDesc := + let groups := groupsOf r i D.vertices.length pk.1 + let cedges := childEdgeList groups g D.edgeEnds (pk.2.2.map Prod.fst) + ⟨cedges.length, + ((List.range D.vertices.length).filter fun v => groups.getD v 0 = g).map (fun v => + (((D.vertices.getD v defaultVertex).1.map (childEdgeOf r groups g D.edgeEnds v)), + (D.vertices.getD v defaultVertex).2)) ++ + (List.range pk.2.1).map fun q => + (childPorts cedges (groups.count g + q), + .inr (packetCtx fl a i pt t ζ g q, + (childPorts cedges (groups.count g + q)).map fun j => + (decide (j < (portEdges groups g D.edgeEnds Prod.fst).length), + (pk.2.2.getD (parentEdge groups g D.edgeEnds j) (0, 0)).2), + betaSlice i pt β q))⟩ + +/-- **The number of rows of the native child**: the number of vertices of group `g`. -/ +@[expose] def childRows (r i : ℕ) (D : DescribedNetwork VertexDesc) + (pk : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ)) (g : ℕ) : ℕ := + (groupsOf r i D.vertices.length pk.1).count g + +/-! ### The modes of a node -/ + +/-- The packet-major numbering of the modes `(q, y, b)`: `q · T p + y · p + b`. -/ +@[expose] def modeEquiv (P T p : ℕ) : Fin P × Fin T × Fin p ≃ Fin (P * (T * p)) := + (Equiv.prodCongr (Equiv.refl _) finProdFinEquiv).trans finProdFinEquiv + +/-- **The modes of a node, packet-major.** -/ +@[expose] def modeList (P T p : ℕ) : List (Fin P × Fin T × Fin p) := + List.ofFn (modeEquiv P T p).symm + +/-- **The weights of the modes, packet-major**: the mode `(q, y, b)` has weight `b + 1`. -/ +@[expose] def modeWeights (P T p : ℕ) : List ℕ := + (List.range (P * (T * p))).map fun j => j % p + 1 + +theorem modeEquiv_apply_val {P T p : ℕ} (Z : Fin P × Fin T × Fin p) : + (modeEquiv P T p Z : ℕ) = Z.1 * (T * p) + (Z.2.1 * p + Z.2.2) := by + simp only [modeEquiv, Equiv.trans_apply, Equiv.prodCongr_apply, Equiv.coe_refl, Prod.map, + id_eq, finProdFinEquiv_apply_val] + ring + +theorem nodup_modeList (P T p : ℕ) : (modeList P T p).Nodup := + List.nodup_ofFn.mpr (modeEquiv P T p).symm.injective + +theorem mem_modeList {P T p : ℕ} (Z : Fin P × Fin T × Fin p) : Z ∈ modeList P T p := + List.mem_ofFn.mpr ⟨modeEquiv P T p Z, by simp⟩ + +theorem idxOf_modeList {P T p : ℕ} (Z : Fin P × Fin T × Fin p) : + (modeList P T p).idxOf Z = modeEquiv P T p Z := by + have h : (modeList P T p)[(modeEquiv P T p Z : ℕ)]'(by simp [modeList]) = Z := by + simp [modeList] + have := List.Nodup.idxOf_getElem (nodup_modeList P T p) (modeEquiv P T p Z : ℕ) + (by simp [modeList]) + rwa [h] at this + +/-- The last coordinate of a mode is its number modulo `p`. -/ +theorem val_modeEquiv_symm_snd_snd {P T p : ℕ} (j : Fin (P * (T * p))) : + (((modeEquiv P T p).symm j).2.2 : ℕ) = j % p := by + obtain ⟨Z, hZ⟩ : ∃ Z, (modeEquiv P T p).symm j = Z := ⟨_, rfl⟩ + have hj : (j : ℕ) = Z.1 * (T * p) + (Z.2.1 * p + Z.2.2) := by + rw [← modeEquiv_apply_val, ← hZ, Equiv.apply_symm_apply] + rw [hZ, hj, show (Z.1 : ℕ) * (T * p) + (Z.2.1 * p + Z.2.2) = Z.2.2 + p * (Z.1 * T + Z.2.1) by + ring, Nat.add_mul_mod_self_left, Nat.mod_eq_of_lt Z.2.2.2] + +/-- The weights listed along the modes are `modeWeights`. -/ +theorem map_modeList_weight (P T p : ℕ) : + (modeList P T p).map (fun Z => (Z.2.2 : ℕ) + 1) = modeWeights P T p := by + apply List.ext_getElem (by simp [modeList, modeWeights]) + intro j h₁ h₂ + simp only [modeList, List.getElem_map, List.getElem_ofFn, modeWeights, List.getElem_range, + val_modeEquiv_symm_snd_snd] + +/-- **The slice of a packet** reads the mode tuple of `β`: entry `b + p y` of the slice of `q` is +the exponent of the mode `(q, y, b)`. -/ +theorem getD_betaSlice {P i pt : ℕ} (β : List ℕ) (q : Fin P) (Z : Fin (2 ^ (i + 1)) × Fin pt) : + (betaSlice i pt β q).getD (finProdFinEquiv Z) 0 = + BudgetTuples.ofList (modeList P (2 ^ (i + 1)) pt) β (q, Z) := by + have hZ : (finProdFinEquiv Z : ℕ) < 2 ^ (i + 1) * pt := (finProdFinEquiv Z).2 + rw [BudgetTuples.ofList, idxOf_modeList, modeEquiv_apply_val, betaSlice, + List.getD_eq_getElem?_getD, List.getElem?_take_of_lt hZ, List.getElem?_drop, + ← List.getD_eq_getElem?_getD, finProdFinEquiv_apply_val] + congr 2 + ring + +/-! ### The native lists in a numbered network -/ + +section Bridge + +variable {m k s : ℕ} (src tgt : Fin m → Fin k) (group : Fin k → Fin s) (g : Fin s) + +/-- Reading the group of an end of an edge from the lists. -/ +theorem getD_ofFn_group (sel : ℕ × ℕ → ℕ) (endF : Fin m → Fin k) + (hsel : ∀ e, sel ((src e : ℕ), (tgt e : ℕ)) = endF e) (e : Fin m) : + (List.ofFn fun v => (group v : ℕ)).getD + (sel ((List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))).getD e (0, 0))) 0 = + group (endF e) := by + rw [List.getD_ofFn, hsel, List.getD_ofFn] + +/-- **The native ports of group `g` at the end `sel`** are its members, in order. -/ +theorem portEdges_eq (sel : ℕ × ℕ → ℕ) (endF : Fin m → Fin k) + (hsel : ∀ e, sel ((src e : ℕ), (tgt e : ℕ)) = endF e) : + portEdges (List.ofFn fun v => (group v : ℕ)) g (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) + sel = (members fun e => group (endF e) = g).map Fin.val := by + rw [portEdges, List.length_ofFn, ← List.map_coe_finRange_eq_range, List.filter_map, members] + congr 1 + refine List.filter_congr fun e _ => ?_ + simp only [Function.comp_apply, getD_ofFn_group src tgt group sel endF hsel, Fin.val_inj] + +/-- **The native position of a port** is its position among the members. -/ +theorem portRank_eq (sel : ℕ × ℕ → ℕ) (endF : Fin m → Fin k) + (hsel : ∀ e, sel ((src e : ℕ), (tgt e : ℕ)) = endF e) (e : Fin m) : + portRank (List.ofFn fun v => (group v : ℕ)) g (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) + sel e = rank (fun e => group (endF e) = g) e := by + have hr : List.range e = ((List.finRange m).take e).map Fin.val := by + rw [List.map_take, List.map_coe_finRange_eq_range, List.take_range, min_eq_left e.2.le] + rw [portRank, rank, hr, List.countP_map] + refine List.countP_congr fun u _ => ?_ + simp only [Function.comp_apply, getD_ofFn_group src tgt group sel endF hsel, Fin.val_inj, + decide_eq_true_eq] + +/-- Counting a group among the groups of the vertices gives the number of its vertices. -/ +theorem count_ofFn_group : + (List.ofFn fun v => (group v : ℕ)).count (g : ℕ) = (members fun v => group v = g).length := by + rw [List.ofFn_eq_map, List.count, List.countP_map, members, ← List.countP_eq_length_filter] + refine List.countP_congr fun u _ => ?_ + simp [Fin.val_inj] + +/-- The native vertices of group `g` are its members, in order. -/ +theorem filter_range_group : + (List.range k).filter (fun v => (List.ofFn fun v => (group v : ℕ)).getD v 0 = (g : ℕ)) = + (members fun v => group v = g).map Fin.val := by + rw [← List.map_coe_finRange_eq_range, List.filter_map, members] + congr 1 + refine List.filter_congr fun v _ => ?_ + simp only [Function.comp_apply, List.getD_ofFn, Fin.val_inj] + +variable {r : ℕ} (hsrc : ∀ e, (src e : ℕ) < r) (htgt : ∀ e, r ≤ (tgt e : ℕ)) +include hsrc + +/-- **The child edge of a row port** is its number in `childEdgeEquiv`. -/ +theorem childEdgeOf_src (e : Fin m) (h : group (src e) = g) : + childEdgeOf r (List.ofFn fun v => (group v : ℕ)) g + (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) (src e) e = + (childEdgeEquiv src tgt group g (.inl ⟨e, h⟩) : ℕ) := by + rw [childEdgeOf, ite_eq_left (hsrc e), portRank_eq src tgt group g Prod.fst src (fun _ => rfl)] + simp [childEdgeEquiv] + +omit hsrc in +include htgt in +/-- **The child edge of a column port** is its number in `childEdgeEquiv`. -/ +theorem childEdgeOf_tgt (e : Fin m) (h : group (tgt e) = g) : + childEdgeOf r (List.ofFn fun v => (group v : ℕ)) g + (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) (tgt e) e = + (childEdgeEquiv src tgt group g (.inr ⟨e, h⟩) : ℕ) := by + rw [childEdgeOf, ite_eq_right (by have := htgt e; omega), + portRank_eq src tgt group g Prod.snd tgt (fun _ => rfl), + portEdges_eq src tgt group g Prod.fst src (fun _ => rfl), List.length_map] + simp [childEdgeEquiv] + +omit hsrc in +/-- **The parent edge of a numbered child edge.** -/ +theorem parentEdge_childEdgeEquiv (c : ChildEdge src tgt group g) : + parentEdge (List.ofFn fun v => (group v : ℕ)) g + (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) (childEdgeEquiv src tgt group g c) = + (Sum.elim (fun e => (e.1 : ℕ)) (fun e => (e.1 : ℕ)) c) := by + rw [parentEdge, portEdges_eq src tgt group g Prod.fst src (fun _ => rfl), + portEdges_eq src tgt group g Prod.snd tgt (fun _ => rfl), List.length_map] + rcases c with c | c + · have hlt := rank_lt (fun e => group (src e) = g) c.1 c.2 + simp only [childEdgeEquiv, Equiv.trans_apply, Equiv.sumCongr_apply, Sum.map_inl, + finSumFinEquiv_apply_left, Fin.val_castAdd, rankEquiv_apply_val, Sum.elim_inl] + rw [ite_eq_left hlt, List.getD_eq_getElem _ _ (by simpa using hlt), List.getElem_map, + getElem_rank (fun e => group (src e) = g) c.1 c.2] + · have hlt := rank_lt (fun e => group (tgt e) = g) c.1 c.2 + simp only [childEdgeEquiv, Equiv.trans_apply, Equiv.sumCongr_apply, Sum.map_inr, + finSumFinEquiv_apply_right, Fin.val_natAdd, rankEquiv_apply_val, Sum.elim_inr] + rw [ite_eq_right (by omega), Nat.add_sub_cancel_left, + List.getD_eq_getElem _ _ (by simpa using hlt), List.getElem_map, + getElem_rank (fun e => group (tgt e) = g) c.1 c.2] + +omit hsrc in +/-- A numbered child edge is a row port exactly when its number is below the number of row +ports. -/ +theorem lt_length_portEdges_iff (c : ChildEdge src tgt group g) : + (childEdgeEquiv src tgt group g c : ℕ) < + (portEdges (List.ofFn fun v => (group v : ℕ)) g + (List.ofFn fun e => ((src e : ℕ), (tgt e : ℕ))) Prod.fst).length ↔ c.isLeft := by + rw [portEdges_eq src tgt group g Prod.fst src (fun _ => rfl), List.length_map] + rcases c with c | c + · simp only [childEdgeEquiv, Equiv.trans_apply, Equiv.sumCongr_apply, Sum.map_inl, + finSumFinEquiv_apply_left, Fin.val_castAdd, rankEquiv_apply_val, Sum.isLeft_inl, iff_true] + exact rank_lt (fun e => group (src e) = g) c.1 c.2 + · simp only [childEdgeEquiv, Equiv.trans_apply, Equiv.sumCongr_apply, Sum.map_inr, + finSumFinEquiv_apply_right, Fin.val_natAdd, Sum.isLeft_inr, Bool.false_eq_true, iff_false] + omega + +end Bridge + +/-- A filter-map that keeps every entry is a map. -/ +theorem map_filterMap_of_isSome {α β γ : Type*} (f : α → Option β) (y : β → γ) (d : γ) + (l : List α) (h : ∀ x ∈ l, (f x).isSome) : + (l.filterMap f).map y = l.map fun x => ((f x).map y).getD d := by + induction l with + | nil => rfl + | cons x l ih => + obtain ⟨b, hb⟩ := Option.isSome_iff_exists.mp (h x List.mem_cons_self) + rw [List.filterMap_cons_some hb, List.map_cons, List.map_cons, hb, + ih fun x' hx' => h x' (List.mem_cons_of_mem _ hx')] + rfl + +/-! ### The parent over `O` -/ + +section Parent + +variable {fl : List ℕ} (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + {D : DescribedNetwork VertexDesc} {r : ℕ} (hD : D.IsRowsFirstBipartite r) + +/-- The valuation ring `O = ℤ_(2)[U] / (f)` of `f = monicOf fl`. -/ +noncomputable abbrev RingO : Subring (AdjoinRoot ((monicOf fl).map (Int.castRingHom ℚ))) := + (AdjoinRoot.padicValuation 2 (monic_monicOf fl) hirr).integer + +/-- **The parent over `O`**: the network of the descriptors with the lifts of their entries. -/ +@[expose] noncomputable def parentNetwork : + TensorNetwork (RingO hirr) Bool (Fin D.vertices.length) (Fin D.numEdges) := + D.toNetwork hD fun d bs => liftO hirr (d.entry bs) + +/-- The sides of the parent: the first `r` vertices are the rows. -/ +@[expose] def parentSide (r : ℕ) {k : ℕ} (v : Fin k) : Bool := decide ((v : ℕ) < r) + +theorem parentSide_src (j : Fin D.numEdges) : parentSide r (D.srcFin hD j) = true := + decide_eq_true (DescribedNetwork.srcFin_lt hD j) + +theorem parentSide_tgt (j : Fin D.numEdges) : parentSide r (D.tgtFin hD j) = false := + decide_eq_false (by have := DescribedNetwork.le_tgtFin hD j; omega) + +/-- The row of an edge, among the rows. -/ +@[expose] def rowOfP (j : Fin D.numEdges) : Fin r := + ⟨D.srcFin hD j, DescribedNetwork.srcFin_lt hD j⟩ + +/-- The column of an edge, among the columns. -/ +@[expose] def colOfP (j : Fin D.numEdges) : Fin (D.vertices.length - r) := + ⟨D.tgtFin hD j - r, by + have := DescribedNetwork.le_tgtFin hD j + have := (D.tgtFin hD j).2 + omega⟩ + +/-- **The native packing of the parent**: `nativePacking` of its `(row, column)` edges, with `r` +rows, `k - r` columns, packets of `2 ^ a` slots and `64` symbols. -/ +@[expose] def nativeParentPacking (r a : ℕ) (D : DescribedNetwork VertexDesc) : + (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ) := + PacketPacking.nativePacking r (D.vertices.length - r) a 64 + (D.edgeEnds.map fun e => (e.1, e.2 - r)) + +/-- The `(row, column)` edges of the parent. -/ +theorem edgeEnds_shift : + D.edgeEnds.map (fun e => (e.1, e.2 - r)) = PacketPacking.edgeList (rowOfP hD) (colOfP hD) := by + rw [DescribedNetwork.edgeEnds_eq hD, PacketPacking.edgeList, List.map_ofFn] + rfl + +/-- **The packing of the parent over `O`**: `PacketPacking.packing` of its `(row, column)` edges, +moved to the ends of the parent. -/ +@[expose] noncomputable def parentPacking (a : ℕ) : + PacketPacking.Packing (D.srcFin hD) (D.tgtFin hD) a 64 := + (PacketPacking.packing (rowOfP hD) (colOfP hD) a 64).relabel (D.srcFin hD) (D.tgtFin hD) + (fun v => if h : (v : ℕ) < r then + (PacketPacking.packing (rowOfP hD) (colOfP hD) a 64).rowLabel ⟨v, h⟩ else fun _ => 0) + (fun v => if h : r ≤ (v : ℕ) then + (PacketPacking.packing (rowOfP hD) (colOfP hD) a 64).colLabel ⟨v - r, by omega⟩ + else fun _ => 0) + (fun e => by rw [dite_eq_left (DescribedNetwork.srcFin_lt hD e)]; rfl) + (fun e => by rw [dite_eq_left (DescribedNetwork.le_tgtFin hD e)]; rfl) + +/-- **The native packing of the parent is its packing over `O`** when `64 ^ a` is at most the +number of edges: the words of its labels, its number of packets, and the packet and slot of each +edge. -/ +theorem nativeParentPacking_eq {a : ℕ} (hK : 64 ^ a ≤ D.numEdges) : + nativeParentPacking r a D = + ((List.ofFn fun v => BalancedSchedules.scheduleToWord + ((PacketPacking.packing (rowOfP hD) (colOfP hD) a 64).rowLabel v), + List.ofFn fun w => BalancedSchedules.scheduleToWord + ((PacketPacking.packing (rowOfP hD) (colOfP hD) a 64).colLabel w)), + (parentPacking hD a).numPackets, + List.ofFn fun e => (((parentPacking hD a).packet e : ℕ), + ((parentPacking hD a).slot e : ℕ))) := by + rw [nativeParentPacking, edgeEnds_shift hD, PacketPacking.nativePacking_eq _ _ hK] + rfl + +/-- **The native groups of the parent** in coordinate `i < a` are the groups of the packing. -/ +theorem groupsOf_eq {a i : ℕ} (hK : 64 ^ a ≤ D.numEdges) (hia : i < a) : + groupsOf r i D.vertices.length (nativeParentPacking r a D).1 = + List.ofFn fun v => (packingGroup (parentSide r) (parentPacking hD a) ⟨i, hia⟩ v : ℕ) := by + rw [nativeParentPacking_eq hD hK] + apply List.ext_getElem (by simp [groupsOf]) + intro v h₁ h₂ + simp only [groupsOf, List.getElem_map, List.getElem_range, List.getElem_ofFn, packingGroup, + parentSide, parentPacking, PacketPacking.Packing.relabel] + have hvk : v < D.vertices.length := by simpa using h₂ + by_cases hv : v < r + · rw [ite_eq_left hv, decide_eq_true hv, ite_eq_left rfl, dite_eq_left hv, + List.getD_ofFn (fun v : Fin r => BalancedSchedules.scheduleToWord _) ⟨v, hv⟩, + BalancedSchedules.scheduleToWord, List.getD_ofFn (fun t : Fin a => _) ⟨i, hia⟩] + · have hvr : r ≤ v := by omega + have hlt : v - r < D.vertices.length - r := by omega + rw [ite_eq_right hv, decide_eq_false hv, ite_eq_right (by simp), dite_eq_left hvr, + List.getD_ofFn (fun w : Fin (D.vertices.length - r) => BalancedSchedules.scheduleToWord _) + ⟨v - r, hlt⟩, BalancedSchedules.scheduleToWord, + List.getD_ofFn (fun t : Fin a => _) ⟨i, hia⟩] + +/-- **The mode splitting at a node of the parent** over `O`: `callSplitting` of the parent with +its packing, at the node `t`, the precision `p`, and the lift of the interpolation value `ζ`. -/ +@[expose] noncomputable def parentSplitting (a p u h : ℕ) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (ζ : List ℕ) := + TensorNetwork.callSplitting (RingO hirr) + (AdjoinRoot.algebraMap_mem_integer_padicValuation _ hirr) (parentNetwork hirr hD) + (parentSide r) (parentSide_src hD) (parentSide_tgt hD) (parentPacking hD a) p u h hua ht hq + (liftO hirr ζ) + +section Fields + +variable {a p u h : ℕ} (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) + (hq : 0 < taperMult u h t) (ζ : List ℕ) + +/-- The rows of the splitting are the first `r` vertices. -/ +theorem parentSplitting_side : (parentSplitting hirr hD a p u h hua ht hq ζ).side = parentSide r := + TensorNetwork.callSplitting_side (RingO hirr) _ (parentNetwork hirr hD) (parentSide r) _ _ + (parentPacking hD a) p u h hua ht hq (liftO hirr ζ) + +/-- The groups of the splitting are the groups of the packing in coordinate `v₂(t)`. -/ +theorem parentSplitting_group : (parentSplitting hirr hD a p u h hua ht hq ζ).group = + packingGroup (parentSide r) (parentPacking hD a) + ⟨_, padicValInt_lt_of_taperMult_pos hq hua⟩ := + TensorNetwork.callSplitting_group (RingO hirr) _ (parentNetwork hirr hD) (parentSide r) _ _ + (parentPacking hD a) p u h hua ht hq (liftO hirr ζ) + +/-- The packets of the splitting are the packets of the packing. -/ +theorem parentSplitting_packet : + (parentSplitting hirr hD a p u h hua ht hq ζ).packet = (parentPacking hD a).packet := + TensorNetwork.callSplitting_packet (RingO hirr) _ (parentNetwork hirr hD) (parentSide r) _ _ + (parentPacking hD a) p u h hua ht hq (liftO hirr ζ) + +end Fields + +section Numbered + +variable {P : ℕ} (group : Fin D.vertices.length → Fin 64) (packet : Fin D.numEdges → Fin P) + (slot : Fin D.numEdges → ℕ) (g : Fin 64) + +/-- The ports of a retained vertex: its parent ports, each as its child edge. -/ +@[expose] def retainedPort (v : {v // group v = g}) (j : ℕ) : + Option (ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) := + if hj : j < D.numEdges then + if (v.1 : ℕ) < r then + if hg : group (D.srcFin hD ⟨j, hj⟩) = g then some (.inl ⟨⟨j, hj⟩, hg⟩) else none + else if hg : group (D.tgtFin hD ⟨j, hj⟩) = g then some (.inr ⟨⟨j, hj⟩, hg⟩) else none + else none + +/-- **A parent port of a retained vertex is a child edge**, numbered by `childEdgeOf`. -/ +theorem retainedPort_spec (v : {v // group v = g}) {j : ℕ} + (hj : j ∈ (D.vertices[(v.1 : ℕ)]'v.1.2).1) : + ∃ c, retainedPort hD group g v j = some c ∧ + (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g c : ℕ) = + childEdgeOf r (List.ofFn fun v => (group v : ℕ)) g + (List.ofFn fun e => ((D.srcFin hD e : ℕ), (D.tgtFin hD e : ℕ))) v j := by + have hjm : j < D.numEdges := hD.1 _ (List.getElem_mem _) j hj + have hends := (DescribedNetwork.mem_ports_iff hD v.1 ⟨j, hjm⟩).mp hj + have hsrcr := DescribedNetwork.srcFin_lt hD + have htgtr := DescribedNetwork.le_tgtFin hD + by_cases hvr : (v.1 : ℕ) < r + · have hs : D.srcFin hD ⟨j, hjm⟩ = v.1 := hends.resolve_right fun h => by + have := htgtr ⟨j, hjm⟩ + rw [h] at this + omega + have hg : group (D.srcFin hD ⟨j, hjm⟩) = g := hs ▸ v.2 + refine ⟨.inl ⟨⟨j, hjm⟩, hg⟩, ?_, ?_⟩ + · simp only [retainedPort, dite_eq_left hjm, ite_eq_left hvr, dite_eq_left hg] + · have := childEdgeOf_src (D.srcFin hD) (D.tgtFin hD) group g hsrcr ⟨j, hjm⟩ hg + rw [show ((v.1 : Fin _) : ℕ) = (D.srcFin hD ⟨j, hjm⟩ : ℕ) by rw [hs]] + exact this.symm + · have ht : D.tgtFin hD ⟨j, hjm⟩ = v.1 := hends.resolve_left fun h => by + have := hsrcr ⟨j, hjm⟩ + rw [h] at this + exact hvr this + have hg : group (D.tgtFin hD ⟨j, hjm⟩) = g := ht ▸ v.2 + refine ⟨.inr ⟨⟨j, hjm⟩, hg⟩, ?_, ?_⟩ + · simp only [retainedPort, dite_eq_left hjm, ite_eq_right hvr, dite_eq_left hg] + · have := childEdgeOf_tgt (D.srcFin hD) (D.tgtFin hD) group g htgtr ⟨j, hjm⟩ hg + rw [show ((v.1 : Fin _) : ℕ) = (D.tgtFin hD ⟨j, hjm⟩ : ℕ) by rw [ht]] + exact this.symm + +/-- **The ports of the packet vertex `q`**: the child edges of packet `q`, in increasing order of +their numbers. -/ +@[expose] def packetPorts (q : Fin P) : List (ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) := + ((List.finRange _).filter fun j => + childTgt (D.srcFin hD) (D.tgtFin hD) group packet g + ((childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm j) = .inr q).map + (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm + +/-- **The ports of the numbered child vertices**: the renumbered parent ports of a retained +vertex, and the child edges of a packet. -/ +@[expose] def numberedPorts : + ChildVertex group g P → List (ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) := + Sum.elim (fun v => (D.vertices[(v.1 : ℕ)]'v.1.2).1.filterMap (retainedPort hD group g v)) + (packetPorts hD group packet g) + +/-- **The descriptors of the numbered child vertices**: the parent descriptor of a retained +vertex, and the packet descriptor of a packet. -/ +@[expose] def numberedDesc (fl : List ℕ) (a i pt : ℕ) (t : ℤ) (ζ β : List ℕ) : + ChildVertex group g P → VertexDesc := + Sum.elim (fun v => (D.vertices[(v.1 : ℕ)]'v.1.2).2) fun q => + .inr (packetCtx fl a i pt t ζ g q, + (packetPorts hD group packet g q).map fun c => + (c.isLeft, slot (Sum.elim Subtype.val Subtype.val c)), + betaSlice i pt β q) + +/-- The bit that a retained vertex reads at a parent port. -/ +theorem getD_map_retainedPort (v : {v // group v = g}) {j : ℕ} + (hj : j ∈ (D.vertices[(v.1 : ℕ)]'v.1.2).1) + (y : ChildEdge (D.srcFin hD) (D.tgtFin hD) group g → Bool) : + ((retainedPort hD group g v j).map y).getD false = + if parentSide r v.1 then + (if hg : group (D.srcFin hD ⟨j, hD.1 _ (List.getElem_mem _) j hj⟩) = g then + y (.inl ⟨_, hg⟩) else false) + else + (if hg : group (D.tgtFin hD ⟨j, hD.1 _ (List.getElem_mem _) j hj⟩) = g then + y (.inr ⟨_, hg⟩) else false) := by + have hjm : j < D.numEdges := hD.1 _ (List.getElem_mem _) j hj + by_cases hvr : (v.1 : ℕ) < r + · simp only [retainedPort, dite_eq_left hjm, ite_eq_left hvr, parentSide, decide_eq_true hvr, + ite_true] + split_ifs <;> rfl + · simp only [retainedPort, dite_eq_left hjm, ite_eq_right hvr, parentSide, + decide_eq_false hvr, Bool.false_eq_true, ite_false] + split_ifs <;> rfl + +/-- The ports of a packet are its child edges. -/ +theorem mem_packetPorts (q : Fin P) (c : ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) : + c ∈ packetPorts hD group packet g q ↔ packet (Sum.elim Subtype.val Subtype.val c) = q := by + simp only [packetPorts, List.mem_map, List.mem_filter, List.mem_finRange, true_and, + decide_eq_true_eq] + constructor + · rintro ⟨j, hj, rfl⟩ + rcases hc : (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm j with c | c <;> + rw [hc] at hj <;> simpa [childTgt] using hj + · intro hc + refine ⟨childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g c, ?_, by simp⟩ + rw [Equiv.symm_apply_apply] + rcases c with c | c <;> simpa [childTgt] using hc + +/-- The ports of a packet are distinct. -/ +theorem nodup_packetPorts (q : Fin P) : (packetPorts hD group packet g q).Nodup := + ((List.nodup_finRange _).filter _).map (Equiv.injective _) + +/-- **The native ports of a packet** are its numbered ports. -/ +theorem childPorts_packet (q : Fin P) : + childPorts (List.ofFn fun j => + (((childVertexEquiv (P := P) group g) (childSrc (D.srcFin hD) (D.tgtFin hD) group g + ((childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm j)) : ℕ), + ((childVertexEquiv (P := P) group g) (childTgt (D.srcFin hD) (D.tgtFin hD) group packet g + ((childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm j)) : ℕ))) + ((List.ofFn fun v => (group v : ℕ)).count (g : ℕ) + q) = + (packetPorts hD group packet g q).map fun c => + (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g c : ℕ) := by + have h1 : (List.ofFn fun v => (group v : ℕ)).count (g : ℕ) + q = + ((childVertexEquiv (P := P) group g) (.inr q) : ℕ) := by + rw [count_ofFn_group] + simp [childVertexEquiv] + rw [h1, childPorts, List.length_ofFn, ← List.map_coe_finRange_eq_range, List.filter_map, + packetPorts, List.map_map] + congr 1 + · funext j + simp + · refine List.filter_congr fun j _ => ?_ + simp only [Function.comp_apply, List.getD_ofFn, Fin.val_inj, Equiv.apply_eq_iff_eq, + decide_eq_decide] + constructor + · rintro (h | h) + · rcases hc : (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g).symm j with c | c <;> + rw [hc] at h <;> simp [childSrc] at h + · exact h + · exact Or.inr + +/-- **The native child is the numbered child**: with the groups, packets and slots of the native +packing given by functions, the native child lists the numbered child vertices with their +numbered ports and descriptors. -/ +theorem nativeChild_eq (fl : List ℕ) (a i pt : ℕ) (t : ℤ) (ζ β : List ℕ) + (pk : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ)) + (hgroups : groupsOf r i D.vertices.length pk.1 = List.ofFn fun v => (group v : ℕ)) + (hpk1 : pk.2.1 = P) (hpk2 : pk.2.2 = List.ofFn fun e => ((packet e : ℕ), slot e)) : + nativeChild fl a i pt t ζ r D pk β g = + ⟨(members fun e => group (D.srcFin hD e) = g).length + + (members fun e => group (D.tgtFin hD e) = g).length, + List.ofFn fun c => + ((numberedPorts hD group packet g ((childVertexEquiv (P := P) group g).symm c)).map + fun e => (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) group g e : ℕ), + numberedDesc hD group packet slot g fl a i pt t ζ β + ((childVertexEquiv (P := P) group g).symm c))⟩ := by + have hpackets : pk.2.2.map Prod.fst = List.ofFn fun e => (packet e : ℕ) := by + rw [hpk2, List.map_ofFn] + rfl + have hcedges := childEdgeList_eq (P := P) (D.srcFin hD) (D.tgtFin hD) group packet g + have hsrcr := DescribedNetwork.srcFin_lt hD + have htgtr := DescribedNetwork.le_tgtFin hD + simp only [nativeChild] + rw [hgroups, DescribedNetwork.edgeEnds_eq hD, hpackets, hpk1, hpk2, hcedges] + simp only [DescribedNetwork.mk.injEq, List.length_ofFn, true_and] + refine Eq.trans ?_ (List.ofFn_add (n := (members fun v => group v = g).length) (m := P)).symm + congr 1 + · rw [filter_range_group, List.map_map] + apply List.ext_getElem (by simp) + intro n h₁ h₂ + have hn : n < (members fun v => group v = g).length := by simpa using h₁ + have hmem : group (members fun v => group v = g)[n] = g := + of_decide_eq_true (List.mem_filter.mp (List.getElem_mem hn)).2 + have hevl : (childVertexEquiv (P := P) group g).symm + (Fin.castLE (Nat.le_add_right _ _) ⟨n, hn⟩) = + .inl ⟨(members fun v => group v = g)[n], hmem⟩ := by + rw [childVertexEquiv, Equiv.symm_trans_apply, + show Fin.castLE (Nat.le_add_right _ _) ⟨n, hn⟩ = Fin.castAdd _ ⟨n, hn⟩ from rfl, + finSumFinEquiv_symm_apply_castAdd] + rfl + simp only [List.getElem_map, List.getElem_ofFn, Function.comp_apply] + rw [hevl] + refine Prod.ext ?_ ?_ + · simp only [numberedPorts, Sum.elim_inl] + rw [List.getD_eq_getElem _ _ ((members fun v => group v = g)[n]).2] + rw [map_filterMap_of_isSome _ _ 0 _ fun j hj => by + obtain ⟨c, hc, -⟩ := retainedPort_spec hD group g ⟨_, hmem⟩ hj + rw [hc] + rfl] + refine List.map_congr_left fun j hj => ?_ + obtain ⟨c, hc, hce⟩ := retainedPort_spec hD group g ⟨_, hmem⟩ hj + rw [hc] + exact hce.symm + · simp only [numberedDesc, Sum.elim_inl] + rw [List.getD_eq_getElem _ _ ((members fun v => group v = g)[n]).2] + · apply List.ext_getElem (by simp) + intro n h₁ h₂ + have hn : n < P := by simpa using h₁ + have hevr : (childVertexEquiv (P := P) group g).symm (Fin.natAdd _ ⟨n, hn⟩) = + .inr ⟨n, hn⟩ := by + rw [childVertexEquiv, Equiv.symm_trans_apply, finSumFinEquiv_symm_apply_natAdd] + rfl + simp only [List.getElem_map, List.getElem_range, List.getElem_ofFn] + rw [hevr] + have hcpq := childPorts_packet hD group packet g ⟨n, hn⟩ + simp only at hcpq + refine Prod.ext hcpq ?_ + simp only [numberedDesc, Sum.elim_inr] + congr 2 + refine Prod.ext ?_ rfl + rw [hcpq, List.map_map] + refine List.map_congr_left fun c _ => ?_ + refine Prod.ext ?_ ?_ + · simp only [Function.comp_apply] + rw [Bool.eq_iff_iff, decide_eq_true_iff] + exact lt_length_portEdges_iff (D.srcFin hD) (D.tgtFin hD) group g c + · simp only [Function.comp_apply] + rw [parentEdge_childEdgeEquiv] + rcases c with c | c <;> simp + +/-- **The ports of a retained vertex are its child edges.** -/ +theorem mem_filterMap_retainedPort (x : {v // group v = g}) + (c : ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) : + c ∈ (D.vertices[(x.1 : ℕ)]'x.1.2).1.filterMap (retainedPort hD group g x) ↔ + childSrc (P := P) (D.srcFin hD) (D.tgtFin hD) group g c = .inl x := by + have hsrcr := DescribedNetwork.srcFin_lt hD + have htgtr := DescribedNetwork.le_tgtFin hD + rw [List.mem_filterMap] + rcases c with ⟨e, he⟩ | ⟨e, he⟩ + · simp only [childSrc, Sum.elim_inl, Sum.inl.injEq, Subtype.ext_iff] + constructor + · rintro ⟨j, hj, hc⟩ + simp only [retainedPort] at hc + split_ifs at hc with hjm hxr hg hg <;> + simp only [Option.some.injEq, Sum.inl.injEq, reduceCtorEq, + Subtype.mk.injEq] at hc + subst hc + rcases (DescribedNetwork.mem_ports_iff hD x.1 ⟨j, hjm⟩).mp hj with h | h + · exact h + · have := htgtr ⟨j, hjm⟩ + rw [h] at this + omega + · intro hs + refine ⟨e.val, (DescribedNetwork.mem_ports_iff hD x.1 e).mpr (Or.inl hs), ?_⟩ + have hxr : (x.1 : ℕ) < r := hs ▸ hsrcr e + simp only [retainedPort, dite_eq_left e.2, ite_eq_left hxr, Fin.eta] + rw [dite_eq_left he] + · simp only [childSrc, Sum.elim_inr, Sum.inl.injEq, Subtype.ext_iff] + constructor + · rintro ⟨j, hj, hc⟩ + simp only [retainedPort] at hc + split_ifs at hc with hjm hxr hg hg <;> + simp only [Option.some.injEq, Sum.inr.injEq, reduceCtorEq, + Subtype.mk.injEq] at hc + subst hc + rcases (DescribedNetwork.mem_ports_iff hD x.1 ⟨j, hjm⟩).mp hj with h | h + · have := hsrcr ⟨j, hjm⟩ + rw [h] at this + exact absurd this hxr + · exact h + · intro ht + refine ⟨e.val, (DescribedNetwork.mem_ports_iff hD x.1 e).mpr (Or.inr ht), ?_⟩ + have hxr : ¬ (x.1 : ℕ) < r := by have := htgtr e; rw [ht] at this; omega + simp only [retainedPort, dite_eq_left e.2, ite_eq_right hxr, Fin.eta] + rw [dite_eq_left he] + +/-- A retained port names its parent edge. -/ +theorem val_of_retainedPort_eq_some {x : {v // group v = g}} {j : ℕ} + {c : ChildEdge (D.srcFin hD) (D.tgtFin hD) group g} (h : retainedPort hD group g x j = some c) : + (Sum.elim (fun e => (e.1 : ℕ)) (fun e => (e.1 : ℕ)) c) = j := by + simp only [retainedPort] at h + split_ifs at h <;> simp only [Option.some.injEq] at h <;> subst h <;> rfl + +include hD in +/-- The ports of a vertex of a rows-first network are distinct. -/ +theorem nodup_ports (v : Fin D.vertices.length) : (D.vertices[v]).1.Nodup := by + refine List.nodup_iff_count_le_one.mpr fun j => ?_ + by_cases hj : j ∈ (D.vertices[v]).1 + · have hjm : j < D.numEdges := hD.1 _ (List.getElem_mem _) j hj + obtain ⟨s, t, hs, ht, -, -, hsc, htc, hinc⟩ := DescribedNetwork.incident_eq hD hjm + rcases (DescribedNetwork.mem_ports_iff hD v ⟨j, hjm⟩).mp hj with h | h + · have hv : (v : ℕ) = s := by + rw [← h] + simp [DescribedNetwork.srcFin, hinc] + have : (D.vertices[v]).1 = (D.vertices[s]).1 := by simp only [Fin.getElem_fin, hv] + rw [this, hsc] + · have hv : (v : ℕ) = t := by + rw [← h] + simp [DescribedNetwork.tgtFin, hinc] + have : (D.vertices[v]).1 = (D.vertices[t]).1 := by simp only [Fin.getElem_fin, hv] + rw [this, htc] + · rw [List.count_eq_zero.mpr hj] + exact Nat.zero_le _ + +/-- **The numbered ports of a child vertex are its child edges**, without repetition. -/ +theorem mem_numberedPorts (v : ChildVertex group g P) + (c : ChildEdge (D.srcFin hD) (D.tgtFin hD) group g) : + c ∈ numberedPorts hD group packet g v ↔ + childSrc (D.srcFin hD) (D.tgtFin hD) group g c = v ∨ + childTgt (D.srcFin hD) (D.tgtFin hD) group packet g c = v := by + rcases v with x | q + · rw [numberedPorts, Sum.elim_inl, mem_filterMap_retainedPort] + refine ⟨Or.inl, fun h => h.resolve_right ?_⟩ + rcases c with c | c <;> simp [childTgt] + · rw [numberedPorts, Sum.elim_inr, mem_packetPorts] + rcases c with c | c <;> simp [childSrc, childTgt] + +theorem nodup_numberedPorts (v : ChildVertex group g P) : + (numberedPorts hD group packet g v).Nodup := by + rcases v with x | q + · exact (nodup_ports hD x.1).filterMap fun j j' c hc hc' => by + rw [← val_of_retainedPort_eq_some hD group g hc, ← val_of_retainedPort_eq_some hD group g hc'] + · exact nodup_packetPorts hD group packet g q + +end Numbered + +/-- **Being rows-first depends only on the multisets of ports.** -/ +theorem isRowsFirstBipartite_ofFn_iff {T T' : Type} {k l r : ℕ} (F : Fin k → List ℕ × T) + (F' : Fin k → List ℕ × T') (h : ∀ x, (F x).1.Perm (F' x).1) : + (⟨l, List.ofFn F⟩ : DescribedNetwork T).IsRowsFirstBipartite r ↔ + (⟨l, List.ofFn F'⟩ : DescribedNetwork T').IsRowsFirstBipartite r := by + have hc (i : ℕ) : (List.ofFn F).map (fun v => v.1.count i) = + (List.ofFn F').map (fun v => v.1.count i) := by + simp only [List.map_ofFn] + congr 1 + funext x + exact (h x).count_eq i + have hm : (∀ v ∈ List.ofFn F, ∀ i ∈ v.1, i < l) ↔ (∀ v ∈ List.ofFn F', ∀ i ∈ v.1, i < l) := by + simp only [List.mem_ofFn, forall_exists_index, forall_apply_eq_imp_iff] + exact forall_congr' fun x => forall_congr' fun i => imp_congr_left (h x).mem_iff + simp only [DescribedNetwork.IsRowsFirstBipartite, List.map_take, List.map_drop, hc, hm] + +section Rows + +variable {a i : ℕ} (hK : 64 ^ a ≤ D.numEdges) (hia : i < a) + +include hD hK hia in +/-- **The native child is rows-first**, with its retained vertices as its rows. -/ +theorem isRowsFirstBipartite_nativeChild (pt : ℕ) (t : ℤ) (ζ β : List ℕ) (g : Fin 64) : + (nativeChild fl a i pt t ζ r D (nativeParentPacking r a D) β g).IsRowsFirstBipartite + (childRows r i D (nativeParentPacking r a D) g) := by + have hgroups := groupsOf_eq hD hK hia + have hpk1 : (nativeParentPacking r a D).2.1 = (parentPacking hD a).numPackets := by + rw [nativeParentPacking_eq hD hK] + have hpk2 : (nativeParentPacking r a D).2.2 = List.ofFn fun e => + (((parentPacking hD a).packet e : ℕ), ((parentPacking hD a).slot e : ℕ)) := by + rw [nativeParentPacking_eq hD hK] + rw [childRows, hgroups, count_ofFn_group, + nativeChild_eq hD _ (parentPacking hD a).packet (fun e => ((parentPacking hD a).slot e : ℕ)) g + fl a i pt t ζ β _ hgroups hpk1 hpk2] + -- The incidence of the numbered child, with zero tensors. + let N : TensorNetwork ℕ Bool + (ChildVertex (packingGroup (parentSide r) (parentPacking hD a) ⟨i, hia⟩) g + (parentPacking hD a).numPackets) + (ChildEdge (D.srcFin hD) (D.tgtFin hD) + (packingGroup (parentSide r) (parentPacking hD a) ⟨i, hia⟩) g) := + { src := childSrc (D.srcFin hD) (D.tgtFin hD) _ g + tgt := childTgt (D.srcFin hD) (D.tgtFin hD) _ (parentPacking hD a).packet g + tensor := fun _ _ => 0 + tensor_congr := fun _ _ _ _ => rfl } + have hside : ∀ e, ((childVertexEquiv (P := (parentPacking hD a).numPackets) _ g (N.src e) : + Fin _) : ℕ) < (members fun v => + packingGroup (parentSide r) (parentPacking hD a) ⟨i, hia⟩ v = g).length ↔ + ¬ ((childVertexEquiv (P := (parentPacking hD a).numPackets) _ g (N.tgt e) : Fin _) : ℕ) < + (members fun v => + packingGroup (parentSide r) (parentPacking hD a) ⟨i, hia⟩ v = g).length := by + rintro (e | e) <;> simp only [N, childSrc, childTgt, childVertexEquiv, Sum.elim_inl, + Sum.elim_inr, Equiv.trans_apply, Equiv.sumCongr_apply, Sum.map_inl, Equiv.refl_apply, + finSumFinEquiv_apply_left, finSumFinEquiv_apply_right, Fin.val_castAdd, Fin.val_natAdd, + rankEquiv_apply_val, Sum.map_inr] + · exact ⟨fun _ => by omega, fun _ => rank_lt _ _ e.2⟩ + · exact ⟨fun _ => by omega, fun _ => rank_lt _ _ e.2⟩ + have hexp := TensorNetwork.isRowsFirstBipartite_toExplicit N + (childVertexEquiv (P := (parentPacking hD a).numPackets) _ g) + (childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) _ g) _ hside + refine (isRowsFirstBipartite_ofFn_iff _ _ fun c => ?_).mpr hexp + set v := (childVertexEquiv (P := (parentPacking hD a).numPackets) _ g).symm c + refine (List.perm_ext_iff_of_nodup ((nodup_numberedPorts hD _ _ g v).map + (Fin.val_injective.comp (Equiv.injective _))) + ((N.nodup_explicitPorts _ v).map Fin.val_injective)).mpr fun n => ?_ + simp only [List.mem_map, TensorNetwork.mem_explicitPorts, mem_numberedPorts] + constructor + · rintro ⟨e, he, rfl⟩ + exact ⟨_, by simpa [N] using he, rfl⟩ + · rintro ⟨j, hj, rfl⟩ + exact ⟨_, by simpa [N] using hj, by simp⟩ + +end Rows + + +section Child + +variable {a p u h : ℕ} (hK : 64 ^ a ≤ D.numEdges) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (ζ β : List ℕ) (g : Fin 64) + +/-- The valuation of the node. -/ +local notation "iₜ" => padicValInt 2 t +/-- The precision of the node. -/ +local notation "ptₜ" => nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t) +/-- The mode splitting at the node. -/ +local notation "S₀" => parentSplitting hirr hD a p u h hua ht hq ζ +/-- The mode tuple of `β`. -/ +local notation "β₀" => BudgetTuples.ofList + (modeList (PacketPacking.Packing.numPackets (parentPacking hD a)) + (2 ^ (padicValInt 2 t + 1)) (nodePrecision p h a (Fintype.card (Fin D.numEdges)) + (padicValInt 2 t))) β +/-- The numbering of the child vertices. -/ +local notation "ev₀" => + childVertexEquiv (P := PacketPacking.Packing.numPackets (parentPacking hD a)) + (TensorNetwork.ModeSplitting.group (parentSplitting hirr hD a p u h hua ht hq ζ)) g +/-- The numbering of the child edges. -/ +local notation "ee₀" => childEdgeEquiv (D.srcFin hD) (D.tgtFin hD) + (TensorNetwork.ModeSplitting.group (parentSplitting hirr hD a p u h hua ht hq ζ)) g + +/-- **The entries of the retained vertices** of the numbered child are the tensors of the child. -/ +theorem entry_numbered_inl (v : {v // (S₀).group v = g}) + (y : ChildEdge (D.srcFin hD) (D.tgtFin hD) (S₀).group g → Bool) : + reductionLift (monic_monicOf fl) hirr ptₜ (val ptₜ fl + ((numberedDesc hD (S₀).group (S₀).packet (fun e => ((parentPacking hD a).slot e : ℕ)) g + fl a iₜ ptₜ t ζ β (.inl v)).entry + ((numberedPorts hD (S₀).group (S₀).packet g (.inl v)).map y))) = + ((S₀).child β₀ g).tensor (.inl v) y := by + rw [TensorNetwork.ModeSplitting.child_tensor_inl_eq_tensor (S₀) β₀ g v y, + TensorNetwork.map_tensor, + ← mk_liftO, parentSplitting_side hirr hD hua ht hq ζ] + simp only [numberedDesc, numberedPorts, Sum.elim_inl, parentNetwork, + DescribedNetwork.toNetwork, DescribedNetwork.wordBits] + congr 3 + rw [map_filterMap_of_isSome _ _ false _ fun j hj => by + obtain ⟨c, hc, -⟩ := retainedPort_spec hD (S₀).group g v hj + rw [hc] + rfl] + refine List.map_congr_left fun j hj => ?_ + rw [getD_map_retainedPort hD (S₀).group g v hj y, + List.getD_ofFn_of_lt _ (hD.1 _ (List.getElem_mem _) j hj)] + rfl + +/-- **The entries of the packet vertices** of the numbered child are the tensors of the child. -/ +theorem entry_numbered_inr (q : Fin (parentPacking hD a).numPackets) + (y : ChildEdge (D.srcFin hD) (D.tgtFin hD) (S₀).group g → Bool) : + reductionLift (monic_monicOf fl) hirr ptₜ (val ptₜ fl + ((numberedDesc hD (S₀).group (S₀).packet (fun e => ((parentPacking hD a).slot e : ℕ)) g + fl a iₜ ptₜ t ζ β (.inr q)).entry + ((numberedPorts hD (S₀).group (S₀).packet g (.inr q)).map y))) = + ((S₀).child β₀ g).tensor (.inr q) y := by + have hia : iₜ < a := padicValInt_lt_of_taperMult_pos hq hua + have hsym := PacketPacking.packetSymbols_eq (rowOfP hD) (colOfP hD) _ _ q ⟨iₜ, hia⟩ + simp only [numberedDesc, numberedPorts, Sum.elim_inr] + exact TensorNetwork.map_val_entry_child_inr (N := (parentNetwork hirr hD).map + (Ideal.Quotient.mk (nodeIdeal (RingO hirr) (Fintype.card (Fin D.numEdges)) a h p t))) + (parentSide r) (parentSide_src hD) (parentSide_tgt hD) + (packingGroup_src_tgt (parentSide r) (parentSide_src hD) (parentSide_tgt hD) + (parentPacking hD a) ⟨iₜ, hia⟩) + (packing_slot_lt (parentPacking hD a)) hia _ _ (two_pow_eq_zero_twoPowIdeal _ _) + (Finset.mem_of_mem_erase ht) (two_pow_padicValInt_dvd_and_not_dvd (Finset.ne_of_mem_erase ht)).1 + (two_pow_padicValInt_dvd_and_not_dvd (Finset.ne_of_mem_erase ht)).2 β₀ g q _ + (nodup_packetPorts hD (S₀).group (S₀).packet g q) + (fun c => (mem_packetPorts hD (S₀).group (S₀).packet g q c).trans (by + rw [parentSplitting_packet hirr hD hua ht hq ζ] + exact Iff.rfl)) + (packetCtx fl a iₜ ptₜ t ζ g q) (betaSlice iₜ ptₜ β q) + (reductionLift (monic_monicOf fl) hirr ptₜ) (mk_liftO hirr ptₜ ζ).symm rfl rfl rfl rfl rfl rfl + (by simp only [packetCtx, hsym, Fin.val_inj]; rfl) + (by simp only [packetCtx, hsym, Fin.val_inj]; rfl) + (by simp only [packetCtx, hsym, ne_eq, Fin.val_inj]; rfl) + (getD_betaSlice β q) y + +include hK in +/-- **The native child has the contraction of the child** ([OpenAI2026ETH], §D.4.6). For a +rows-first parent with `64 ^ a` at most its number of edges, a node `t` of positive multiplicity +with `u ≤ a`, an interpolation value `ζ`, packet-major exponents `β` and a group `g`, the native +child at the precision `p_t` of the node, with the entries of its descriptors in `A_{p_t}` mapped +along `reductionLift` to `O / (2 ^ {p_t})`, has the contraction of the child of group `g` of the +mode splitting at `t` (`parentSplitting`) at the mode tuple of `β`. -/ +theorem contraction_nativeChild : + (nativeChild fl a (padicValInt 2 t) + (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t)) t ζ r D + (nativeParentPacking r a D) β g).contraction + (fun d bs => reductionLift (monic_monicOf fl) hirr + (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t)) + (val (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t)) fl + (d.entry bs))) = + ((parentSplitting hirr hD a p u h hua ht hq ζ).child + (BudgetTuples.ofList (modeList (parentPacking hD a).numPackets (2 ^ (padicValInt 2 t + 1)) + (nodePrecision p h a (Fintype.card (Fin D.numEdges)) (padicValInt 2 t))) β) + g).contraction := by + have hia : iₜ < a := padicValInt_lt_of_taperMult_pos hq hua + have hgroups : groupsOf r iₜ D.vertices.length (nativeParentPacking r a D).1 = + List.ofFn fun v => ((S₀).group v : ℕ) := by + rw [parentSplitting_group hirr hD hua ht hq ζ] + exact groupsOf_eq hD hK hia + have hpk1 : (nativeParentPacking r a D).2.1 = (parentPacking hD a).numPackets := by + rw [nativeParentPacking_eq hD hK] + have hpk2 : (nativeParentPacking r a D).2.2 = + List.ofFn fun e => (((S₀).packet e : ℕ), ((parentPacking hD a).slot e : ℕ)) := by + rw [nativeParentPacking_eq hD hK, parentSplitting_packet hirr hD hua ht hq ζ] + rw [nativeChild_eq hD (S₀).group (S₀).packet (fun e => ((parentPacking hD a).slot e : ℕ)) g + fl a iₜ ptₜ t ζ β _ hgroups hpk1 hpk2] + refine DescribedNetwork.contraction_eq_of_ports ((S₀).child β₀ g) _ _ _ _ _ ?_ + rintro (v | q) y + · exact entry_numbered_inl hirr hD hua ht hq ζ β g v y + · exact entry_numbered_inr hirr hD hua ht hq ζ β g q y + +end Child + +end Parent + +end NativeChild + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChildPolyTime.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChildPolyTime.lean new file mode 100644 index 0000000000..a58650892b --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeChildPolyTime.lean @@ -0,0 +1,734 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChild +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Membership +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Sum +import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListBinaryLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Mathlib.Data.List.GetD + +/-! +# The native child networks in polynomial time + +The native child of group `g` of an active call of the contraction recursion of [OpenAI2026ETH] +(§D.4.6, p. 63), `NativeChild.nativeChild`, and its number of rows, `NativeChild.childRows`, are +computable in polynomial time over the encodings of the native recursion. The counts are unary, +the node `t` is in binary sign-magnitude form, and the lists are binary, except the exponents +`β`, which are unary as in a packet descriptor. The parent is encoded by +`DescribedNetwork.encDescribed VertexDesc.enc` and its packing by +`PacketPacking.encNativePacking`. + +The theorems are stated in composition form, for components computed from any input. Besides the +components themselves, they ask for `2 ^ a`, `2 ^ (i + 1)` and the number of packets in unary. +Every packet vertex stores `2 ^ a` and `2 ^ (i + 1)` in unary in its context, and the child has +one packet vertex per packet. For the native parent packing +(`NativeChild.nativeParentPacking`), the packing itself and its number of packets, at most +`M (2 ^ a) ^ 12` for `M` edges, are computed here, with the number of rows in unary. + +**The pieces.** +* The groups of the vertices are looked up in the label words, one vertex at a time. +* The child edges are `ChildNumbering.childEdgeList`, and the ports of a packet are + `ChildNumbering.childPorts`. +* A retained vertex renumbers its ports by counting the earlier edges with an end in its group. + `NativeChild.portRank_eq_countP` puts that count in a form without a unary loop over the port. +* A packet vertex tags each port with the slot of the parent edge, read off the ports of the + group. + +**The shape of the child**, for size bounds along the recursion: +* the child has at most twice as many edges as the parent; +* it has `childRows + P` vertices; +* each vertex is either a parent vertex, with its descriptor and its number of ports, or the + vertex of a packet `q`, with its context, its tags and its slice of `β`. + +## Main results + +* `Cslib.Circuits.Frontier.NativeChild.computableInPolyTime_nativeChild`, + `Cslib.Circuits.Frontier.NativeChild.computableInPolyTime_childRows`: the native child and its + number of rows are polynomial-time. +* `Cslib.Circuits.Frontier.NativeChild.computableInPolyTime_nativeChild_nativeParentPacking`, + `Cslib.Circuits.Frontier.NativeChild.computableInPolyTime_childRows_nativeParentPacking`: the + same for the native parent packing. +* `Cslib.Circuits.Frontier.NativeChild.numPackets_nativeParentPacking_le`: the number of packets. +* `Cslib.Circuits.Frontier.NativeChild.numEdges_nativeChild_le`, + `Cslib.Circuits.Frontier.NativeChild.length_vertices_nativeChild`, + `Cslib.Circuits.Frontier.NativeChild.mem_vertices_nativeChild`: the shape of the child. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.6, p. 63. +-/ + +public section + +namespace Cslib.Circuits.Frontier.NativeChild + +open Turing.MultiTapeTM ChildNumbering NativePacket + +/-- **The rank of a port without a loop over the port**: the edges before `e` with an end in group +`g`, the edges past the list counting as `(0, 0)`. -/ +theorem portRank_eq_countP (groups : List ℕ) (g : ℕ) (ends : List (ℕ × ℕ)) (sel : ℕ × ℕ → ℕ) + (e : ℕ) : + portRank groups g ends sel e = + (List.range (min ends.length e)).countP + (fun e' => decide (groups.getD (sel (ends.getD e' (0, 0))) 0 = g)) + + (e - ends.length) * (if groups.getD (sel (0, 0)) 0 = g then 1 else 0) := by + induction e with + | zero => simp [portRank] + | succ e ih => + have hstep : portRank groups g ends sel (e + 1) = portRank groups g ends sel e + + if groups.getD (sel (ends.getD e (0, 0))) 0 = g then 1 else 0 := by + simp only [portRank, List.range_succ, List.countP_append, List.countP_singleton, + decide_eq_true_eq] + rw [hstep, ih] + rcases lt_or_ge e ends.length with h | h + · rw [min_eq_right h.le, min_eq_right (show e + 1 ≤ ends.length by omega), List.range_succ, + List.countP_append, List.countP_singleton, Nat.sub_eq_zero_of_le h.le, + Nat.sub_eq_zero_of_le (show e + 1 ≤ ends.length by omega)] + simp only [zero_mul, add_zero, decide_eq_true_eq] + · rw [min_eq_left h, min_eq_left (show ends.length ≤ e + 1 by omega), + List.getD_eq_default _ _ h, show e + 1 - ends.length = e - ends.length + 1 by omega] + split_ifs <;> ring + +section PolyTime + +variable {δ : Type} {encIn : δ ↪ List Bool} {groups : δ → List ℕ} {g : δ → ℕ} + {ends : δ → List (ℕ × ℕ)} + +/-- Whether the end `sel` of the edge `e`, in unary, is in group `g`. -/ +private theorem test_polyTime (sel : ℕ × ℕ → ℕ) + (hsel : ComputableInPolyTime sel (encPair encNat encNat) encNat) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) : + ComputableInPolyTime (fun p : δ × ℕ => + decide ((groups p.1).getD (sel ((ends p.1).getD p.2 (0, 0))) 0 = g p.1)) + (encPair encIn encUnary) encBool := by + have hentry : ComputableInPolyTime (fun p : δ × ℕ => (ends p.1).getD p.2 (0, 0)) + (encPair encIn encUnary) (encPair encNat encNat) := + (ComputableInPolyTime.listGetD (0, 0)).comp + ((hends.comp (ComputableInPolyTime.fst _ _)).prod (ComputableInPolyTime.snd _ _)) + exact ComputableInPolyTime.decide_eq' + (ComputableInPolyTime.listGetD_binary 0 (hgroups.comp (ComputableInPolyTime.fst _ _)) + (hsel.comp hentry)) + (hg.comp (ComputableInPolyTime.fst _ _)) + +/-- **The ports of a group are polynomial-time.** -/ +theorem computableInPolyTime_portEdges (sel : ℕ × ℕ → ℕ) + (hsel : ComputableInPolyTime sel (encPair encNat encNat) encNat) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) : + ComputableInPolyTime (fun x => portEdges (groups x) (g x) (ends x) sel) encIn + (encList encUnary) := + ComputableInPolyTime.filterWithInput + (test := fun x e => decide ((groups x).getD (sel ((ends x).getD e (0, 0))) 0 = g x)) + (ComputableInPolyTime.range.comp ((ComputableInPolyTime.length_list _).comp hends)) + (test_polyTime sel hsel hgroups hg hends) + +/-- **The rank of a port is polynomial-time**, with the port in binary. -/ +theorem computableInPolyTime_portRank (sel : ℕ × ℕ → ℕ) + (hsel : ComputableInPolyTime sel (encPair encNat encNat) encNat) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) + {e : δ → ℕ} (he : ComputableInPolyTime e encIn encNat) : + ComputableInPolyTime (fun x => portRank (groups x) (g x) (ends x) sel (e x)) encIn encNat := by + have hlen : ComputableInPolyTime (fun x => (ends x).length) encIn encUnary := + (ComputableInPolyTime.length_list _).comp hends + have hcount := ComputableInPolyTime.countPWithInput + (test := fun x e' => decide ((groups x).getD (sel ((ends x).getD e' (0, 0))) 0 = g x)) + (ComputableInPolyTime.range.comp (ComputableInPolyTime.binary_min_unary' hlen he)) + (test_polyTime sel hsel hgroups hg hends) + have hdefault : ComputableInPolyTime (fun x => decide ((groups x).getD (sel (0, 0)) 0 = g x)) + encIn encBool := + ComputableInPolyTime.decide_eq' + (ComputableInPolyTime.listGetD_binary 0 hgroups (ComputableInPolyTime.const (sel (0, 0)))) + hg + have hextra : ComputableInPolyTime (fun x => + (e x - (ends x).length) * (if decide ((groups x).getD (sel (0, 0)) 0 = g x) then 1 else 0)) + encIn encNat := + ComputableInPolyTime.binary_mul' + (ComputableInPolyTime.binary_sub' he (ComputableInPolyTime.unary_to_binary.comp hlen)) + (ComputableInPolyTime.ite hdefault (ComputableInPolyTime.const 1) + (ComputableInPolyTime.const 0)) + refine (ComputableInPolyTime.binary_add.comp + ((ComputableInPolyTime.unary_to_binary.comp hcount).prod hextra)).of_enc_eq fun x => ?_ + rw [portRank_eq_countP] + simp only [decide_eq_true_eq, Function.comp_apply] + +/-- **The child edge of a port is polynomial-time**, with the vertex and the port in binary. -/ +theorem computableInPolyTime_childEdgeOf {r v e : δ → ℕ} + (hr : ComputableInPolyTime r encIn encNat) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) + (hv : ComputableInPolyTime v encIn encNat) (he : ComputableInPolyTime e encIn encNat) : + ComputableInPolyTime (fun x => childEdgeOf (r x) (groups x) (g x) (ends x) (v x) (e x)) + encIn encNat := by + have hR : ComputableInPolyTime (fun x => (portEdges (groups x) (g x) (ends x) Prod.fst).length) + encIn encNat := + ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.length_list _).comp + (computableInPolyTime_portEdges Prod.fst (ComputableInPolyTime.fst _ _) hgroups hg hends)) + refine (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hv hr) + (computableInPolyTime_portRank Prod.fst (ComputableInPolyTime.fst _ _) hgroups hg hends he) + (ComputableInPolyTime.binary_add.comp (hR.prod (computableInPolyTime_portRank Prod.snd + (ComputableInPolyTime.snd _ _) hgroups hg hends he)))).of_enc_eq fun x => ?_ + simp only [childEdgeOf, decide_eq_true_eq, Function.comp_apply] + +/-- **The parent edge of a child edge is polynomial-time**, with the child edge in binary and the +parent edge in unary. -/ +theorem computableInPolyTime_parentEdge {j : δ → ℕ} + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) + (hj : ComputableInPolyTime j encIn encNat) : + ComputableInPolyTime (fun x => parentEdge (groups x) (g x) (ends x) (j x)) encIn + encUnary := by + have hfst := computableInPolyTime_portEdges Prod.fst (ComputableInPolyTime.fst _ _) hgroups hg + hends + have hsnd := computableInPolyTime_portEdges Prod.snd (ComputableInPolyTime.snd _ _) hgroups hg + hends + have hR : ComputableInPolyTime (fun x => (portEdges (groups x) (g x) (ends x) Prod.fst).length) + encIn encNat := + ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.length_list _).comp hfst) + refine (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hj hR) + (ComputableInPolyTime.listGetD_binary (encA := encUnary) 0 hfst hj) + (ComputableInPolyTime.listGetD_binary (encA := encUnary) 0 hsnd + (ComputableInPolyTime.binary_sub' hj hR))).of_enc_eq fun x => ?_ + simp only [parentEdge, decide_eq_true_eq] + +/-- **The groups of the vertices are polynomial-time.** -/ +theorem computableInPolyTime_groupsOf {r i k : δ → ℕ} + {words : δ → List (List ℕ) × List (List ℕ)} + (hr : ComputableInPolyTime r encIn encNat) (hi : ComputableInPolyTime i encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) + (hwords : ComputableInPolyTime words encIn + (encPair (encList (encList encNat)) (encList (encList encNat)))) : + ComputableInPolyTime (fun x => groupsOf (r x) (i x) (k x) (words x)) encIn + (encList encNat) := by + let e := encPair encIn encUnary + have hx : ComputableInPolyTime (fun p : δ × ℕ => p.1) e encIn := ComputableInPolyTime.fst _ _ + have hv : ComputableInPolyTime (fun p : δ × ℕ => p.2) e encNat := + ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _) + have hw₁ := (ComputableInPolyTime.fst _ _).comp (hwords.comp hx) + have hw₂ := (ComputableInPolyTime.snd _ _).comp (hwords.comp hx) + have hrow : ComputableInPolyTime (fun p : δ × ℕ => + (((words p.1).1.getD p.2 []).getD (i p.1) 0)) e encNat := + (ComputableInPolyTime.listGetD (encA := encNat) (encL := encList encNat) 0).comp + (((ComputableInPolyTime.listGetD (encA := encList encNat) (encL := encList (encList encNat)) + []).comp (hw₁.prod (ComputableInPolyTime.snd _ _))).prod (hi.comp hx)) + have hcol : ComputableInPolyTime (fun p : δ × ℕ => + (((words p.1).2.getD (p.2 - r p.1) []).getD (i p.1) 0)) e encNat := + (ComputableInPolyTime.listGetD (encA := encNat) (encL := encList encNat) 0).comp + ((ComputableInPolyTime.listGetD_binary (encA := encList encNat) [] hw₂ + (ComputableInPolyTime.binary_sub' hv (hr.comp hx))).prod (hi.comp hx)) + have h := ComputableInPolyTime.mapWithInput + (f := fun x v => if decide (v < r x) then ((words x).1.getD v []).getD (i x) 0 + else ((words x).2.getD (v - r x) []).getD (i x) 0) + (ComputableInPolyTime.range.comp hk) + (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hv (hr.comp hx)) hrow hcol) + refine h.of_enc_eq fun x => ?_ + simp only [groupsOf, decide_eq_true_eq, Function.comp_apply] + +/-- Taking a unary number of elements of a list. -/ +private theorem listTake {α : Type} {encA : α ↪ List Bool} {l : δ → List α} {n : δ → ℕ} + (hl : ComputableInPolyTime l encIn (encList encA)) + (hn : ComputableInPolyTime n encIn encUnary) : + ComputableInPolyTime (fun x => (l x).take (n x)) encIn (encList encA) := by + have hrev := ComputableInPolyTime.listReverseWithInput hl + have hdrop := (ComputableInPolyTime.dropUnary (encA := encA) (encL := encList encA)).comp + (hrev.prod (ComputableInPolyTime.unary_sub' + ((ComputableInPolyTime.length_list _).comp hl) hn)) + refine (ComputableInPolyTime.listReverseWithInput hdrop).of_enc_eq fun x => ?_ + change encList encA ((l x).take (n x)) = + encList encA (((l x).reverse.drop ((l x).length - n x)).reverse) + rw [List.drop_reverse, List.reverse_reverse] + rcases le_total (l x).length (n x) with h | h + · rw [Nat.sub_eq_zero_of_le h, Nat.sub_zero, List.take_length, List.take_of_length_le h] + · rw [Nat.sub_sub_self h] + +/-- Counting a group among the groups, in binary. -/ +private theorem count_polyTime (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun x => (groups x).count (g x)) encIn encNat := by + have h := ComputableInPolyTime.countPWithInput (test := fun x v => decide (v = g x)) hgroups + (ComputableInPolyTime.decide_eq' (ComputableInPolyTime.snd _ _) + (hg.comp (ComputableInPolyTime.fst _ _))) + refine (ComputableInPolyTime.unary_to_binary.comp h).of_enc_eq fun x => ?_ + rw [List.count] + congr 2 + +/-- **The retained vertices**: the vertices of group `g` with their renumbered ports. -/ +private theorem retained_polyTime {r : δ → ℕ} {V : δ → List (List ℕ × VertexDesc)} + (hr : ComputableInPolyTime r encIn encNat) + (hV : ComputableInPolyTime V encIn (encList (encPair (encList encNat) VertexDesc.enc))) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) : + ComputableInPolyTime (fun x => + ((List.range (V x).length).filter fun v => decide ((groups x).getD v 0 = g x)).map fun v => + (((V x).getD v defaultVertex).1.map (childEdgeOf (r x) (groups x) (g x) (ends x) v), + ((V x).getD v defaultVertex).2)) + encIn (encList (encPair (encList encNat) VertexDesc.enc)) := by + let e₁ := encPair encIn encUnary + have hx : ComputableInPolyTime (fun p : δ × ℕ => p.1) e₁ encIn := ComputableInPolyTime.fst _ _ + have hy : ComputableInPolyTime (fun p : δ × ℕ => p.2) e₁ encUnary := + ComputableInPolyTime.snd _ _ + have hfilter : ComputableInPolyTime (fun x => (List.range (V x).length).filter + fun v => decide ((groups x).getD v 0 = g x)) encIn (encList encUnary) := + ComputableInPolyTime.filterWithInput (test := fun x v => decide ((groups x).getD v 0 = g x)) + (ComputableInPolyTime.range.comp ((ComputableInPolyTime.length_list _).comp hV)) + (ComputableInPolyTime.decide_eq' + ((ComputableInPolyTime.listGetD (encA := encNat) (encL := encList encNat) 0).comp + ((hgroups.comp hx).prod hy)) (hg.comp hx)) + have hvtx : ComputableInPolyTime (fun p : δ × ℕ => (V p.1).getD p.2 defaultVertex) e₁ + (encPair (encList encNat) VertexDesc.enc) := + (ComputableInPolyTime.listGetD (encA := encPair (encList encNat) VertexDesc.enc) + (encL := encList (encPair (encList encNat) VertexDesc.enc)) defaultVertex).comp + ((hV.comp hx).prod hy) + have hxx : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => q.1.1) (encPair e₁ encNat) encIn := + hx.comp (ComputableInPolyTime.fst _ _) + have hr' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => r q.1.1) (encPair e₁ encNat) encNat := + hr.comp hxx + have hgroups' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => groups q.1.1) (encPair e₁ encNat) + (encList encNat) := hgroups.comp hxx + have hg' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => g q.1.1) (encPair e₁ encNat) encNat := + hg.comp hxx + have hends' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => ends q.1.1) (encPair e₁ encNat) + (encList (encPair encNat encNat)) := hends.comp hxx + have hv' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => q.1.2) (encPair e₁ encNat) encNat := + ComputableInPolyTime.unary_to_binary.comp (hy.comp (ComputableInPolyTime.fst _ _)) + have he' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => q.2) (encPair e₁ encNat) encNat := + ComputableInPolyTime.snd _ _ + have hce : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => + childEdgeOf (r q.1.1) (groups q.1.1) (g q.1.1) (ends q.1.1) q.1.2 q.2) + (encPair e₁ encNat) encNat := + computableInPolyTime_childEdgeOf hr' hgroups' hg' hends' hv' he' + have hports : ComputableInPolyTime (fun p : δ × ℕ => + ((V p.1).getD p.2 defaultVertex).1.map (childEdgeOf (r p.1) (groups p.1) (g p.1) + (ends p.1) p.2)) e₁ (encList encNat) := + ComputableInPolyTime.mapWithInput + (f := fun p e => childEdgeOf (r p.1) (groups p.1) (g p.1) (ends p.1) p.2 e) + ((ComputableInPolyTime.fst _ _).comp hvtx) hce + exact ComputableInPolyTime.mapWithInput + (f := fun x v => (((V x).getD v defaultVertex).1.map + (childEdgeOf (r x) (groups x) (g x) (ends x) v), ((V x).getD v defaultVertex).2)) + hfilter (hports.prod ((ComputableInPolyTime.snd _ _).comp hvtx)) + +variable {fl ζ β : δ → List ℕ} {a i pt r : δ → ℕ} {t : δ → ℤ} + {D : δ → DescribedNetwork VertexDesc} + {pk : δ → (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ)} + +/-- **The context of a packet** is polynomial-time, with the packet in unary. -/ +private theorem packetCtx_polyTime (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) + (hT : ComputableInPolyTime (fun x => 2 ^ (i x + 1)) encIn encUnary) + (hpt : ComputableInPolyTime pt encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun p : δ × ℕ => + packetCtx (fl p.1) (a p.1) (i p.1) (pt p.1) (t p.1) (ζ p.1) (g p.1) p.2) + (encPair encIn encUnary) PacketCtx.enc := by + let e₁ := encPair encIn encUnary + have hx : ComputableInPolyTime (fun p : δ × ℕ => p.1) e₁ encIn := ComputableInPolyTime.fst _ _ + have hsym : ComputableInPolyTime (fun p : δ × ℕ => + PacketPacking.packetSymbols (a p.1) 64 p.2 (i p.1)) e₁ (encPair encNat encNat) := + PacketPacking.computableInPolyTime_packetSymbols (ha.comp hx) (ComputableInPolyTime.const 64) + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _)) (hi.comp hx) + have hs₁ := (ComputableInPolyTime.fst _ _).comp hsym + have hs₂ := (ComputableInPolyTime.snd _ _).comp hsym + have htuple : ComputableInPolyTime (fun p : δ × ℕ => + PacketCtx.equivProd (packetCtx (fl p.1) (a p.1) (i p.1) (pt p.1) (t p.1) (ζ p.1) (g p.1) p.2)) + e₁ PacketCtx.encProd := + ((hpt.comp hx).prod (hfl.comp hx)).prod (((ha.comp hx).prod (hL.comp hx)).prod + (((hi.comp hx).prod (hT.comp hx)).prod ((ht.comp hx).prod ((hpt.comp hx).prod + (((ComputableInPolyTime.decide_eq' hs₁ (hg.comp hx)).prod + ((ComputableInPolyTime.decide_eq' hs₂ (hg.comp hx)).prod + (ComputableInPolyTime.decide_not (ComputableInPolyTime.decide_eq' hs₁ hs₂)))).prod + ((ComputableInPolyTime.const []).prod (hζ.comp hx))))))) + have hsymm : ComputableInPolyTime PacketCtx.equivProd.symm PacketCtx.encProd PacketCtx.enc := + ComputableInPolyTime.of_eq fun y => by + rw [PacketCtx.enc, encOfEquiv_apply, Equiv.apply_symm_apply] + exact (hsymm.comp htuple).of_enc_eq fun p => by + rw [Function.comp_apply, Equiv.symm_apply_apply] + +/-- **The packet vertices**: one per packet, with its ports, their tags, its context and its slice +of the exponents. -/ +private theorem packetVertices_polyTime {P : δ → ℕ} {cedges : δ → List (ℕ × ℕ)} + {place : δ → List (ℕ × ℕ)} + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) + (hT : ComputableInPolyTime (fun x => 2 ^ (i x + 1)) encIn encUnary) + (hpt : ComputableInPolyTime pt encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) + (hβ : ComputableInPolyTime β encIn (encList encUnary)) + (hg : ComputableInPolyTime g encIn encNat) + (hgroups : ComputableInPolyTime groups encIn (encList encNat)) + (hends : ComputableInPolyTime ends encIn (encList (encPair encNat encNat))) + (hP : ComputableInPolyTime P encIn encUnary) + (hcedges : ComputableInPolyTime cedges encIn (encList (encPair encNat encNat))) + (hplace : ComputableInPolyTime place encIn (encList (encPair encNat encNat))) : + ComputableInPolyTime (fun x => (List.range (P x)).map fun q => + (childPorts (cedges x) ((groups x).count (g x) + q), + (Sum.inr (packetCtx (fl x) (a x) (i x) (pt x) (t x) (ζ x) (g x) q, + (childPorts (cedges x) ((groups x).count (g x) + q)).map fun j => + (decide (j < (portEdges (groups x) (g x) (ends x) Prod.fst).length), + ((place x).getD (parentEdge (groups x) (g x) (ends x) j) (0, 0)).2), + betaSlice (i x) (pt x) (β x) q) : VertexDesc))) + encIn (encList (encPair (encList encNat) VertexDesc.enc)) := by + let e₁ := encPair encIn encUnary + have hx : ComputableInPolyTime (fun p : δ × ℕ => p.1) e₁ encIn := ComputableInPolyTime.fst _ _ + have hy : ComputableInPolyTime (fun p : δ × ℕ => p.2) e₁ encUnary := + ComputableInPolyTime.snd _ _ + -- The ports. + have hpq : ComputableInPolyTime (fun p : δ × ℕ => + childPorts (cedges p.1) ((groups p.1).count (g p.1) + p.2)) e₁ (encList encUnary) := + computableInPolyTime_childPorts.comp ((hcedges.comp hx).prod + (ComputableInPolyTime.binary_add.comp (((count_polyTime hgroups hg).comp hx).prod + (ComputableInPolyTime.unary_to_binary.comp hy)))) + have hpqb : ComputableInPolyTime (fun p : δ × ℕ => + childPorts (cedges p.1) ((groups p.1).count (g p.1) + p.2)) e₁ (encList encNat) := + (ComputableInPolyTime.mapWithInput (f := fun _ j => j) hpq + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _))).of_enc_eq + fun p => by rw [List.map_id'] + -- The tags of the ports. + have hR : ComputableInPolyTime (fun x => (portEdges (groups x) (g x) (ends x) Prod.fst).length) + encIn encUnary := + (ComputableInPolyTime.length_list _).comp (computableInPolyTime_portEdges Prod.fst + (ComputableInPolyTime.fst _ _) hgroups hg hends) + have hxu : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => q.1.1) (encPair e₁ encUnary) encIn := + hx.comp (ComputableInPolyTime.fst _ _) + have hgroups' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => groups q.1.1) (encPair e₁ encUnary) + (encList encNat) := hgroups.comp hxu + have hg' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => g q.1.1) (encPair e₁ encUnary) encNat := + hg.comp hxu + have hends' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => ends q.1.1) (encPair e₁ encUnary) + (encList (encPair encNat encNat)) := hends.comp hxu + have hj' : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => q.2) (encPair e₁ encUnary) encNat := + ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _) + have hpe : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => + parentEdge (groups q.1.1) (g q.1.1) (ends q.1.1) q.2) (encPair e₁ encUnary) encUnary := + computableInPolyTime_parentEdge hgroups' hg' hends' hj' + have htag : ComputableInPolyTime (fun q : (δ × ℕ) × ℕ => + (decide (q.2 < (portEdges (groups q.1.1) (g q.1.1) (ends q.1.1) Prod.fst).length), + ((place q.1.1).getD (parentEdge (groups q.1.1) (g q.1.1) (ends q.1.1) q.2) (0, 0)).2)) + (encPair e₁ encUnary) (encPair encBool encNat) := + (ComputableInPolyTime.unary_lt' (ComputableInPolyTime.snd _ _) (hR.comp hxu)).prod + ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.listGetD (encA := encPair encNat encNat) + (encL := encList (encPair encNat encNat)) (0, 0)).comp ((hplace.comp hxu).prod hpe))) + have htags := ComputableInPolyTime.mapWithInput + (f := fun p j => (decide (j < (portEdges (groups p.1) (g p.1) (ends p.1) Prod.fst).length), + ((place p.1).getD (parentEdge (groups p.1) (g p.1) (ends p.1) j) (0, 0)).2)) hpq htag + -- The exponents. + have hTp := ComputableInPolyTime.unary_mul' hT hpt + have hdrop : ComputableInPolyTime (fun p : δ × ℕ => + (β p.1).drop (p.2 * (2 ^ (i p.1 + 1) * pt p.1))) e₁ (encList encUnary) := + (ComputableInPolyTime.dropUnary (encA := encUnary) (encL := encList encUnary)).comp + ((hβ.comp hx).prod (ComputableInPolyTime.unary_mul' hy (hTp.comp hx))) + have hslice : ComputableInPolyTime (fun p : δ × ℕ => betaSlice (i p.1) (pt p.1) (β p.1) p.2) + e₁ (encList encUnary) := listTake hdrop (hTp.comp hx) + have hpacket := hpqb.prod ((ComputableInPolyTime.sumInr (encList encNat) encPacketDesc).comp + ((packetCtx_polyTime hfl ha hL hi hT hpt ht hζ hg).prod (htags.prod hslice))) + exact ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp hP) hpacket + +/-- The parts of a native packing. -/ +private theorem packing_parts (hpk : ComputableInPolyTime pk encIn PacketPacking.encNativePacking) : + ComputableInPolyTime (fun x => (pk x).1) encIn + (encPair (encList (encList encNat)) (encList (encList encNat))) ∧ + ComputableInPolyTime (fun x => (pk x).2.2) encIn (encList (encPair encNat encNat)) := by + have hpk' : ComputableInPolyTime pk encIn + (encPair (encPair (encList (encList encNat)) (encList (encList encNat))) + (encPair encNat (encList (encPair encNat encNat)))) := hpk + exact ⟨(ComputableInPolyTime.fst _ _).comp hpk', + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp hpk')⟩ + +/-- **The number of rows of the native child is polynomial-time.** -/ +theorem computableInPolyTime_childRows (hr : ComputableInPolyTime r encIn encNat) + (hi : ComputableInPolyTime i encIn encUnary) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) + (hpk : ComputableInPolyTime pk encIn PacketPacking.encNativePacking) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun x => childRows (r x) (i x) (D x) (pk x) (g x)) encIn encNat := + count_polyTime (computableInPolyTime_groupsOf hr hi ((ComputableInPolyTime.length_list _).comp + (DescribedNetwork.vertices_polyTime.comp hD)) (packing_parts hpk).1) hg + +/-- **The native child is polynomial-time.** The counts `a`, `i` and `p_t` are unary, the node +`t` is in binary sign-magnitude form, the lists `fl` and `ζ`, the number of rows `r` and the group +`g` are in binary, and the exponents `β` are unary. The child stores `2 ^ a` and `2 ^ (i + 1)` in +unary in every packet vertex and has one packet vertex per packet, so these and the number of +packets are asked for in unary. -/ +theorem computableInPolyTime_nativeChild (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) + (hT : ComputableInPolyTime (fun x => 2 ^ (i x + 1)) encIn encUnary) + (hpt : ComputableInPolyTime pt encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) + (hr : ComputableInPolyTime r encIn encNat) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) + (hpk : ComputableInPolyTime pk encIn PacketPacking.encNativePacking) + (hP : ComputableInPolyTime (fun x => (pk x).2.1) encIn encUnary) + (hβ : ComputableInPolyTime β encIn (encList encUnary)) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun x => + nativeChild (fl x) (a x) (i x) (pt x) (t x) (ζ x) (r x) (D x) (pk x) (β x) (g x)) + encIn (DescribedNetwork.encDescribed VertexDesc.enc) := by + obtain ⟨hwords, hplace⟩ := packing_parts hpk + have hV := DescribedNetwork.vertices_polyTime.comp hD + have hends := (DescribedNetwork.computableInPolyTime_edgeEnds VertexDesc.enc).comp hD + have hgroups := computableInPolyTime_groupsOf hr hi + ((ComputableInPolyTime.length_list _).comp hV) hwords + have hpackets : ComputableInPolyTime (fun x => (pk x).2.2.map Prod.fst) encIn + (encList encNat) := + ComputableInPolyTime.mapWithInput (f := fun _ q => q.1) hplace + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)) + have hcedges : ComputableInPolyTime (fun x => + childEdgeList (groupsOf (r x) (i x) (D x).vertices.length (pk x).1) (g x) (D x).edgeEnds + ((pk x).2.2.map Prod.fst)) encIn (encList (encPair encNat encNat)) := + computableInPolyTime_childEdgeList.comp ((hgroups.prod hg).prod (hends.prod hpackets)) + have hpair := ((ComputableInPolyTime.length_list _).comp hcedges).prod + (ComputableInPolyTime.listAppend' (retained_polyTime hr hV hgroups hg hends) + (packetVertices_polyTime hfl ha hL hi hT hpt ht hζ hβ hg hgroups hends hP hcedges hplace)) + have hsymm : ComputableInPolyTime (DescribedNetwork.equivProd (T := VertexDesc)).symm + (encPair encUnary (encList (encPair (encList encNat) VertexDesc.enc))) + (DescribedNetwork.encDescribed VertexDesc.enc) := + ComputableInPolyTime.of_eq fun y => by + rw [DescribedNetwork.encDescribed, encOfEquiv_apply, Equiv.apply_symm_apply] + exact (hsymm.comp hpair).of_enc_eq fun x => by + rw [Function.comp_apply] + rfl + +end PolyTime + +/-! ### The child of the native parent packing -/ + +/-- A left fold of `max` over numbers at most `n`, from at most `n`, is at most `n`. -/ +private theorem foldl_max_le {l : List ℕ} {m n : ℕ} (hm : m ≤ n) (hl : ∀ x ∈ l, x ≤ n) : + l.foldl max m ≤ n := by + induction l generalizing m with + | nil => exact hm + | cons x l ih => + exact ih (max_le hm (hl x List.mem_cons_self)) fun y hy => hl y (List.mem_cons_of_mem _ hy) + +/-- **At most `M (2 ^ a) ^ 12` packets**: the native parent packing of a network with `M` edges +has at most one block of `2 ^ a` slots per edge, times the `4096 ^ a` words of pairs of +symbols. -/ +theorem numPackets_nativeParentPacking_le (r a : ℕ) (D : DescribedNetwork VertexDesc) : + (nativeParentPacking r a D).2.1 ≤ D.numEdges * (2 ^ a) ^ 12 := by + set pairs := PacketPacking.edgeWordPairs (PacketPacking.labelWords r (D.vertices.length - r) a 64 + (D.edgeEnds.map fun e => (e.1, e.2 - r))).1 (PacketPacking.labelWords r + (D.vertices.length - r) a 64 (D.edgeEnds.map fun e => (e.1, e.2 - r))).2 + (D.edgeEnds.map fun e => (e.1, e.2 - r)) with hpairs + have hlen : pairs.length = D.numEdges := by + simp [hpairs, PacketPacking.edgeWordPairs, DescribedNetwork.edgeEnds] + have hload : PacketPacking.maxLoadOf (PacketPacking.classIndices pairs) ≤ D.numEdges := by + refine foldl_max_le (Nat.zero_le _) fun x hx => ?_ + simp only [PacketPacking.classIndices, List.map_map, List.mem_map, List.mem_range, + Function.comp_apply] at hx + obtain ⟨j, hj, rfl⟩ := hx + have := List.countP_le_length (l := List.range j) + (p := fun j' => decide (pairs.getD j' ([], []) = pairs.getD j ([], []))) + rw [List.length_range] at this + omega + have hblocks : (PacketPacking.maxLoadOf (PacketPacking.classIndices pairs) + (2 ^ a - 1)) / + 2 ^ a ≤ D.numEdges := by + have hL : 1 ≤ 2 ^ a := Nat.one_le_two_pow + rcases Nat.eq_zero_or_pos D.numEdges with h0 | hpos + · rw [h0] at hload ⊢ + rw [Nat.le_zero.mp hload, zero_add] + exact (Nat.div_eq_of_lt (by omega)).le + · refine Nat.div_le_of_le_mul ?_ + have : 2 ^ a - 1 ≤ (2 ^ a - 1) * D.numEdges := Nat.le_mul_of_pos_right _ hpos + have e : 2 ^ a * D.numEdges = D.numEdges + (2 ^ a - 1) * D.numEdges := by + rw [Nat.sub_mul, one_mul] + have : D.numEdges ≤ 2 ^ a * D.numEdges := Nat.le_mul_of_pos_left _ (by omega) + omega + omega + have h64 : (64 * 64) ^ a = (2 ^ a) ^ 12 := by + rw [← pow_mul, mul_comm a 12, pow_mul] + norm_num + change (PacketPacking.maxLoadOf (PacketPacking.classIndices pairs) + (2 ^ a - 1)) / 2 ^ a * + (64 * 64) ^ a ≤ _ + rw [h64] + exact Nat.mul_le_mul_right _ hblocks + +section PolyTime + +variable {δ : Type} {encIn : δ ↪ List Bool} {fl ζ β : δ → List ℕ} {a i pt r g : δ → ℕ} + {t : δ → ℤ} {D : δ → DescribedNetwork VertexDesc} + +/-- **The native parent packing is polynomial-time**, with the number of rows and `a` in +unary. -/ +theorem computableInPolyTime_nativeParentPacking (hr : ComputableInPolyTime r encIn encUnary) + (ha : ComputableInPolyTime a encIn encUnary) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) : + ComputableInPolyTime (fun x => nativeParentPacking (r x) (a x) (D x)) encIn + PacketPacking.encNativePacking := by + have hends := (DescribedNetwork.computableInPolyTime_edgeEnds VertexDesc.enc).comp hD + have hrb := ComputableInPolyTime.unary_to_binary.comp hr + have hshift : ComputableInPolyTime (fun x => (D x).edgeEnds.map fun e => (e.1, e.2 - r x)) + encIn (encList (encPair encNat encNat)) := + ComputableInPolyTime.mapWithInput (f := fun x e => (e.1, e.2 - r x)) hends + (((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)).prod + (ComputableInPolyTime.binary_sub' ((ComputableInPolyTime.snd _ _).comp + (ComputableInPolyTime.snd _ _)) (hrb.comp (ComputableInPolyTime.fst _ _)))) + have hnC : ComputableInPolyTime (fun x => (D x).vertices.length - r x) encIn encUnary := + ComputableInPolyTime.unary_sub' ((ComputableInPolyTime.length_list _).comp + (DescribedNetwork.vertices_polyTime.comp hD)) hr + have hin : ComputableInPolyTime (fun x => (((r x, (D x).vertices.length - r x, a x), 64, + (D x).edgeEnds.map fun e => (e.1, e.2 - r x)) : PacketPacking.PackingInput)) encIn + (encPair (encPair encUnary (encPair encUnary encUnary)) + (encPair encNat (encList (encPair encNat encNat)))) := + (hr.prod (hnC.prod ha)).prod ((ComputableInPolyTime.const 64).prod hshift) + have h := PacketPacking.computableInPolyTime_nativePacking.comp hin + refine h.of_enc_eq fun x => ?_ + simp only [Function.comp_apply, nativeParentPacking] + +/-- **The number of packets of the native parent packing in unary** is polynomial-time, with `a` +and `2 ^ a` in unary. -/ +theorem computableInPolyTime_numPackets_nativeParentPacking + (hr : ComputableInPolyTime r encIn encUnary) (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) : + ComputableInPolyTime (fun x => (nativeParentPacking (r x) (a x) (D x)).2.1) encIn + encUnary := by + have hpk : ComputableInPolyTime (fun x => nativeParentPacking (r x) (a x) (D x)) encIn + (encPair (encPair (encList (encList encNat)) (encList (encList encNat))) + (encPair encNat (encList (encPair encNat encNat)))) := + computableInPolyTime_nativeParentPacking hr ha hD + exact ComputableInPolyTime.binary_to_unary_bounded + ((ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp hpk)) + (ComputableInPolyTime.unary_mul' (DescribedNetwork.numEdges_polyTime.comp hD) + ((ComputableInPolyTime.unary_pow 12).comp hL)) + fun x => numPackets_nativeParentPacking_le (r x) (a x) (D x) + +/-- **The native child of the native parent packing is polynomial-time**, with the number of rows +in unary (`computableInPolyTime_nativeChild` for `nativeParentPacking`). -/ +theorem computableInPolyTime_nativeChild_nativeParentPacking + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) + (hT : ComputableInPolyTime (fun x => 2 ^ (i x + 1)) encIn encUnary) + (hpt : ComputableInPolyTime pt encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) + (hr : ComputableInPolyTime r encIn encUnary) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) + (hβ : ComputableInPolyTime β encIn (encList encUnary)) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun x => nativeChild (fl x) (a x) (i x) (pt x) (t x) (ζ x) (r x) (D x) + (nativeParentPacking (r x) (a x) (D x)) (β x) (g x)) + encIn (DescribedNetwork.encDescribed VertexDesc.enc) := + computableInPolyTime_nativeChild hfl ha hL hi hT hpt ht hζ + (ComputableInPolyTime.unary_to_binary.comp hr) hD + (computableInPolyTime_nativeParentPacking hr ha hD) + (computableInPolyTime_numPackets_nativeParentPacking hr ha hL hD) hβ hg + +/-- **The number of rows of the native child of the native parent packing is polynomial-time.** -/ +theorem computableInPolyTime_childRows_nativeParentPacking + (hr : ComputableInPolyTime r encIn encUnary) (ha : ComputableInPolyTime a encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) + (hD : ComputableInPolyTime D encIn (DescribedNetwork.encDescribed VertexDesc.enc)) + (hg : ComputableInPolyTime g encIn encNat) : + ComputableInPolyTime (fun x => childRows (r x) (i x) (D x) + (nativeParentPacking (r x) (a x) (D x)) (g x)) encIn encNat := + computableInPolyTime_childRows (ComputableInPolyTime.unary_to_binary.comp hr) hi hD + (computableInPolyTime_nativeParentPacking hr ha hD) hg + +end PolyTime + +/-! ### The shape of the native child -/ + +section Shape + +variable (fl : List ℕ) (a i pt : ℕ) (t : ℤ) (ζ : List ℕ) (r : ℕ) (D : DescribedNetwork VertexDesc) + (pk : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ)) (β : List ℕ) (g : ℕ) + +/-- **The child has at most twice as many edges as the parent**: one per end of a parent edge in +group `g`. -/ +theorem numEdges_nativeChild_le : + (nativeChild fl a i pt t ζ r D pk β g).numEdges ≤ 2 * D.numEdges := by + have hends : D.edgeEnds.length = D.numEdges := by simp [DescribedNetwork.edgeEnds] + have hzip : (D.edgeEnds.zip (pk.2.2.map Prod.fst)).length ≤ D.numEdges := by + rw [List.length_zip] + omega + simp only [nativeChild, childEdgeList, List.length_append, List.length_map] + have h₁ := List.length_filter_le (fun q : (ℕ × ℕ) × ℕ => + decide ((groupsOf r i D.vertices.length pk.1).getD q.1.1 0 = g)) + (D.edgeEnds.zip (pk.2.2.map Prod.fst)) + have h₂ := List.length_filter_le (fun q : (ℕ × ℕ) × ℕ => + decide ((groupsOf r i D.vertices.length pk.1).getD q.1.2 0 = g)) + (D.edgeEnds.zip (pk.2.2.map Prod.fst)) + omega + +/-- **The vertices of the child**: its rows, the parent vertices of group `g`, then one packet +vertex per packet. -/ +theorem length_vertices_nativeChild : + (nativeChild fl a i pt t ζ r D pk β g).vertices.length = childRows r i D pk g + pk.2.1 := by + simp only [nativeChild, childRows, List.length_append, List.length_map, List.length_range] + congr 1 + rw [List.count_eq_countP, List.countP_eq_length_filter, groupsOf, List.filter_map, + List.length_map] + congr 1 + refine List.filter_congr fun v hv => ?_ + rw [List.mem_range] at hv + simp only [Function.comp_apply] + rw [List.getD_eq_getElem _ _ (by simpa using hv), List.getElem_map, List.getElem_range, + beq_eq_decide] + +/-- **Every vertex of the child is a parent vertex or a packet vertex.** A vertex of the child is +either a parent vertex with renumbered ports, with the same descriptor and the same number of +ports, or the vertex of a packet `q`, whose descriptor is its context, tags of its ports with +slots from the packing (or `0`), and its slice of `β`. -/ +theorem mem_vertices_nativeChild {v : List ℕ × VertexDesc} + (hv : v ∈ (nativeChild fl a i pt t ζ r D pk β g).vertices) : + (∃ w ∈ D.vertices, v.2 = w.2 ∧ v.1.length = w.1.length) ∨ + ∃ q < pk.2.1, ∃ tags : List (Bool × ℕ), + v.2 = .inr (packetCtx fl a i pt t ζ g q, tags, betaSlice i pt β q) ∧ + tags.length = v.1.length ∧ ∀ b ∈ tags, b.2 = 0 ∨ ∃ y ∈ pk.2.2, b.2 = y.2 := by + simp only [nativeChild, List.mem_append, List.mem_map, List.mem_filter, List.mem_range] at hv + rcases hv with ⟨u, ⟨hu, -⟩, rfl⟩ | ⟨q, hq, rfl⟩ + · refine Or.inl ⟨D.vertices.getD u defaultVertex, ?_, rfl, List.length_map _⟩ + rw [List.getD_eq_getElem _ _ hu] + exact List.getElem_mem hu + · refine Or.inr ⟨q, hq, _, rfl, List.length_map _, fun b hb => ?_⟩ + obtain ⟨j, -, rfl⟩ := List.mem_map.mp hb + rcases lt_or_ge (parentEdge (groupsOf r i D.vertices.length pk.1) g D.edgeEnds j) + pk.2.2.length with h | h + · rw [List.getD_eq_getElem _ _ h] + exact Or.inr ⟨_, List.getElem_mem h, rfl⟩ + · rw [List.getD_eq_default _ _ h] + exact Or.inl rfl + +/-- The slice of `β` at a packet has at most `2 ^ (i + 1) p_t` exponents. -/ +theorem length_betaSlice_le (q : ℕ) : (betaSlice i pt β q).length ≤ 2 ^ (i + 1) * pt := + List.length_take_le _ _ + +/-- The exponents in the slice of `β` at a packet are exponents of `β`. -/ +theorem mem_of_mem_betaSlice {q b : ℕ} (hb : b ∈ betaSlice i pt β q) : b ∈ β := + List.mem_of_mem_drop (List.mem_of_mem_take hb) + +end Shape + +end Cslib.Circuits.Frontier.NativeChild diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean index a6606b51b9..46ead171a8 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -182,7 +182,7 @@ variable {R V E : Type*} [CommRing R] [Fintype E] {a r : ℕ} a packing `Q` of its edges, and a nonzero grid point `t` of valuation `i < a`, the mode splitting of the Cauchy matrix at `T = t + α ζ` with the vertices grouped by their symbol in coordinate `i` of `Q` (`cauchyModeSplitting`, oriented by `packingGroup_src_tgt`). -/ -noncomputable def nodeSplitting (N : TensorNetwork R Bool V E) (side : V → Bool) +@[expose] noncomputable def nodeSplitting (N : TensorNetwork R Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) (Q : PacketPacking.Packing N.src N.tgt a r) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ recoveryNodes a) (hia : padicValInt 2 t < a) : @@ -225,7 +225,7 @@ variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) /-- The mode splitting at the active node `t` and the point `z` of a call with input precision `p`: `nodeSplitting` for the network reduced modulo `2 ^ p_t`, at the reduction of `z`. -/ -noncomputable def callSplitting (N : TensorNetwork B Bool V E) (side : V → Bool) +@[expose] noncomputable def callSplitting (N : TensorNetwork B Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/Admissible.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/Admissible.lean index d1afe23004..80fd8c882e 100644 --- a/Complexity/Computability/Complexity/Classes/CountingSimulation/Admissible.lean +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/Admissible.lean @@ -22,7 +22,7 @@ explicit Boolean tensor networks with a number of row vertices, networks are built (milestone H3) and the contraction theorem consumes them (milestone T2). A network `(r, G)` is admissible at size `s` with constant `C` when `G` is bipartite with its -first `r` vertices as rows (`Cslib.Circuits.Frontier.ExplicitTensorNetwork.IsRowsFirstBipartite`), +first `r` vertices as rows (`Cslib.Circuits.Frontier.DescribedNetwork.IsRowsFirstBipartite`), it has at most `C s` edges and at most `C s` vertices, its maximum degree is at most `C ⌊log₂ s⌋`, every table lists the values at all bit strings on its ports, and it has at least `s` edges. The last two conditions spare the contraction algorithm short tables and networks much smaller than diff --git a/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean new file mode 100644 index 0000000000..e652204be5 --- /dev/null +++ b/Complexity/Computability/Complexity/Classes/CountingSimulation/FastAlgorithm.lean @@ -0,0 +1,653 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.Complexity.Classes.CountingSimulation.Guarded +public import Complexity.Computability.Complexity.Classes.CountingSimulation.Admissible +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionMachine +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionStep +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.FastContractionPolyTime +import Mathlib.Analysis.SpecificLimits.Normed +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Log +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryDivision +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimePrimitives + +/-! +# The fast contraction algorithm, assembled + +The contraction interface (`CountingSimulation.ContractionAlgorithm`) for admissible explicit +networks, from the native machine of the recursion of Theorem D.1 of [OpenAI2026ETH] +(`FastContraction.fastCertificate`), reduced to its remaining obligations +(`CountingSimulation.contractionAlgorithmOfFast`). + +**The guard** (`CountingSimulation.fastGuard`) tests what `ExplicitAdmissible` already implies +(rows first, full tables) and the degree bound `degreeBound m` of the schedule (§D.4.2, (D.6), +p. 58). An admissible network of degree at most `C ⌊log₂ s⌋` fails it only for `s` below a +threshold depending on `C` (`CountingSimulation.exists_le_of_fastGuard_eq_false`), since +`degreeBound m ≥ m ^ (9 / 10)` and `m ≥ s`. + +**The clock** is subexponential on admissible inputs: with `m ≤ C s` edges and precision +`P ≤ D s` it is at most `2 ^ (K₀ (C + D) s / √⌊log₂ s⌋)` (`CountingSimulation.fuel_le`). + +**The resources.** `CountingSimulation.FastAnalysis` is the resource analysis of the recursion on +admissible guarded inputs: a derivation of the root with polynomial depth, node sizes and leaf +space, leaf time `poly(n) 2 ^ O(s / √⌊log₂ s⌋)`, and, from some size on, a node count within the +clock. From it and polynomial-time components of the scheme, the fast machine meets the +interface's bounds (`CountingSimulation.fastCertificate_le`), through +`Cslib.GenericRAM.StackRecursion.Scheme.certificate_le`. Below the size threshold the run may not +finish, and the machine falls back to direct summation, at a constant factor. + +The guard is polynomial-time (`CountingSimulation.fastGuard_polyTime`). + +For the native child builder (`FastContraction.nativeMk`), the components of the scheme are +polynomial-time (`FastContraction.scheme_nativeMk_polyTime`), and the step, the passage of +well-formedness to the children and the well-formed roots are proved; there the one remaining +obligation is the resource analysis (`CountingSimulation.contractionAlgorithmOfNative`). + +**Remaining obligations** (the hypotheses of `CountingSimulation.contractionAlgorithmOfFast`): the +components of the scheme in polynomial time, the correctness of one active call +(`FastContraction.StepCorrect`), the passage of well-formedness to the children +(`FastContraction.WFChildren`) and to the root, and the resource analysis. + +## Main definitions + +* `Cslib.Complexity.CountingSimulation.fastGuard`: the guard. +* `Cslib.Complexity.CountingSimulation.FastAnalysis`: the resource analysis. +* `Cslib.Complexity.CountingSimulation.contractionAlgorithmOfFast`: the contraction algorithm from + the remaining obligations. +* `Cslib.Complexity.CountingSimulation.contractionAlgorithmOfNative`: the same for the native child + builder, from the resource analysis alone. + +## Main results + +* `Cslib.Complexity.CountingSimulation.exists_le_of_fastGuard_eq_false`: admissible networks + failing the guard are small. +* `Cslib.Complexity.CountingSimulation.fastGuard_polyTime`: the guard is polynomial-time. +* `Cslib.Complexity.CountingSimulation.wf_root`: guarded inputs have well-formed roots. +* `Cslib.Complexity.CountingSimulation.fuel_le`: the clock on admissible inputs. +* `Cslib.Complexity.CountingSimulation.fastCertificate_le`: the fast machine within the + interface's bounds. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Theorem D.1 (p. 53) and + Appendix D.4. +-/ + +public section + +namespace Cslib.Complexity.CountingSimulation + +open Cslib.Circuits.Frontier Cslib.Circuits.Frontier.ContractionSchedule Filter + +/-- **The guard of the fast algorithm**: the network is rows-first bipartite at its number of +rows, every table lists the values at all bit strings on its ports, and its maximum degree is at +most `degreeBound m`, for `m` its number of edges. -/ +@[expose] def fastGuard (G : ℕ × ExplicitTensorNetwork ℕ) : Bool := + decide (G.2.IsRowsFirstBipartite G.1 ∧ (∀ v ∈ G.2.vertices, v.2.length = 2 ^ v.1.length) ∧ + G.2.maxDegree ≤ degreeBound G.2.numEdges) + +/-- A power of the logarithm is eventually below a power: `(C L) ^ 10 ≤ 2 ^ (9 L)` from some +`L₀` on. -/ +private theorem exists_pow_ten_le (C : ℕ) : ∃ L₀, ∀ L, L₀ ≤ L → (C * L) ^ 10 ≤ 2 ^ (9 * L) := by + have hlim := tendsto_pow_const_div_const_pow_of_one_lt 10 (r := (2 : ℝ) ^ 9) (by norm_num) + have hC : (0 : ℝ) < 1 / ((C : ℝ) ^ 10 + 1) := by positivity + obtain ⟨L₀, hL₀⟩ := eventually_atTop.mp ((tendsto_order.1 hlim).2 _ hC) + refine ⟨L₀, fun L hL => ?_⟩ + have h := hL₀ L hL + have h2 : (0 : ℝ) < ((2 : ℝ) ^ 9) ^ L := by positivity + have h1 : (0 : ℝ) < (C : ℝ) ^ 10 + 1 := by positivity + rw [div_lt_div_iff₀ h2 h1, one_mul] at h + have hL10 : (0 : ℝ) ≤ (L : ℝ) ^ 10 := by positivity + have hle : ((C * L) ^ 10 : ℕ) ≤ ((2 ^ (9 * L) : ℕ) : ℝ) := by + push_cast + rw [mul_pow, pow_mul] + nlinarith + exact_mod_cast hle + +/-- **Admissible networks failing the guard are small.** For every `C` there is `s₀` such that a +network admissible at size `s` with constant `C` that fails `fastGuard` has `s ≤ s₀`. -/ +theorem exists_le_of_fastGuard_eq_false (C : ℕ) : + ∃ s₀, ∀ s G, ExplicitAdmissible C s G → fastGuard G = false → s ≤ s₀ := by + obtain ⟨L₀, hL₀⟩ := exists_pow_ten_le C + refine ⟨2 ^ L₀, fun s G hA hg => ?_⟩ + obtain ⟨hrows, -, hdeg, -, htab, hsm⟩ := hA + have hbig : degreeBound G.2.numEdges < G.2.maxDegree := by + simp only [fastGuard, decide_eq_false_iff_not, not_and, not_le] at hg + exact hg hrows htab + by_contra hs + rw [not_le] at hs + have hL : L₀ ≤ Nat.log 2 s := Nat.le_log_of_pow_le one_lt_two hs.le + have h1 : s ^ 9 ≤ G.2.numEdges ^ 9 := Nat.pow_le_pow_left hsm 9 + have h2 := pow_nine_le_degreeBound_pow_ten G.2.numEdges + have h3 : degreeBound G.2.numEdges ^ 10 < (C * Nat.log 2 s) ^ 10 := + Nat.pow_lt_pow_left (hbig.trans_le hdeg) (by norm_num) + have h4 := hL₀ _ hL + have h5 : 2 ^ (9 * Nat.log 2 s) ≤ s ^ 9 := by + rw [pow_mul'] + exact Nat.pow_le_pow_left + (Nat.pow_log_le_self 2 (by have := Nat.one_le_two_pow (n := L₀); omega)) 9 + omega + +/-! ### The guard in polynomial time -/ + +section GuardPolyTime + +open Turing.MultiTapeTM + +/-- The encoding of an explicit network with its number of rows. -/ +local notation "encG" => encPair encNat ExplicitTensorNetwork.encExplicitNetwork + +/-- The guard as a conjunction of list tests. -/ +private def guardTest (G : ℕ × ExplicitTensorNetwork ℕ) : Bool := + G.2.vertices.all (fun v => v.1.all fun i => decide (i < G.2.numEdges)) && + (List.range G.2.numEdges).all (fun i => + decide (((G.2.vertices.take G.1).flatMap Prod.fst).count i = 1) && + decide (((G.2.vertices.drop G.1).flatMap Prod.fst).count i = 1)) && + G.2.vertices.all (fun v => decide (v.2.length = 2 ^ v.1.length)) && + G.2.vertices.all (fun v => decide (v.1.length ≤ degreeBound G.2.numEdges)) + +private theorem fastGuard_eq_guardTest (G : ℕ × ExplicitTensorNetwork ℕ) : + fastGuard G = guardTest G := by + refine Bool.eq_iff_iff.mpr ?_ + simp only [fastGuard, guardTest, DescribedNetwork.IsRowsFirstBipartite, + ExplicitTensorNetwork.maxDegree_le_iff, decide_eq_true_eq, Bool.and_eq_true, + List.all_eq_true, List.mem_range, List.count_flatMap, Function.comp_def] + constructor + · rintro ⟨⟨h1, h2⟩, h3, h4⟩ + exact ⟨⟨⟨fun v hv i hi => h1 v hv i hi, fun i hi => h2 i hi⟩, h3⟩, h4⟩ + · rintro ⟨⟨⟨h1, h2⟩, h3⟩, h4⟩ + exact ⟨⟨fun v hv i hi => h1 v hv i hi, fun i hi => h2 i hi⟩, h3, h4⟩ + +/-- **The guard is polynomial-time.** -/ +theorem fastGuard_polyTime : ComputableInPolyTime fastGuard encG encBool := by + have hpair : ComputableInPolyTime (DescribedNetwork.equivProd (T := List ℕ)) + ExplicitTensorNetwork.encExplicitNetwork + (encPair encUnary (encList (encPair (encList encNat) (encList encNat)))) := + ComputableInPolyTime.of_eq fun _ => rfl + have hd := ComputableInPolyTime.snd encNat ExplicitTensorNetwork.encExplicitNetwork + have hr := ComputableInPolyTime.fst encNat ExplicitTensorNetwork.encExplicitNetwork + have hm : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => G.2.numEdges) encG + encUnary := (ComputableInPolyTime.fst _ _).comp (hpair.comp hd) + have hvs : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => G.2.vertices) encG + (encList (encPair (encList encNat) (encList encNat))) := + (ComputableInPolyTime.snd _ _).comp (hpair.comp hd) + -- Ports in range. + have h1 : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + G.2.vertices.all (fun v => v.1.all fun i => decide (i < G.2.numEdges))) encG encBool := by + refine ComputableInPolyTime.allWithInput hvs ?_ + refine ComputableInPolyTime.allWithInput + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)) ?_ + exact ComputableInPolyTime.binary_lt.comp ((ComputableInPolyTime.snd _ _).prod + (ComputableInPolyTime.unary_to_binary.comp (hm.comp ((ComputableInPolyTime.fst _ _).comp + (ComputableInPolyTime.fst _ _))))) + -- Each edge has one row port and one column port. + have hlen := (ComputableInPolyTime.length_list _).comp hvs + have hcap := ComputableInPolyTime.binary_min_unary' hlen hr + have htake : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + G.2.vertices.take (min G.2.vertices.length G.1)) encG + (encList (encPair (encList encNat) (encList encNat))) := + .of_tidy (ComputableTidilyInPolyTime.listTakeWithInput hvs.tidy hcap.tidy) + have hdrop : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + G.2.vertices.drop (min G.2.vertices.length G.1)) encG + (encList (encPair (encList encNat) (encList encNat))) := + .of_tidy (ComputableTidilyInPolyTime.listDropWithInput hvs.tidy hcap.tidy) + have hflat {l : ℕ × ExplicitTensorNetwork ℕ → List (List ℕ × List ℕ)} + (hl : ComputableInPolyTime l encG (encList (encPair (encList encNat) (encList encNat)))) : + ComputableInPolyTime (fun G => (l G).flatMap Prod.fst) encG (encList encNat) := + ComputableInPolyTime.flatMapWithInput (f := fun _ v => v.1) hl + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)) + have hcount {l : ℕ × ExplicitTensorNetwork ℕ → List ℕ} + (hl : ComputableInPolyTime l encG (encList encNat)) : + ComputableInPolyTime (fun p : (ℕ × ExplicitTensorNetwork ℕ) × ℕ => + decide ((l p.1).count p.2 = 1)) (encPair encG encUnary) encBool := by + have hc := ComputableInPolyTime.countPWithInput (test := fun p a => decide (a = p.2)) + (hl.comp (ComputableInPolyTime.fst encG encUnary)) + (ComputableInPolyTime.decide_eq' (ComputableInPolyTime.snd _ _) + (ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.snd _ _).comp + (ComputableInPolyTime.fst _ _)))) + refine (ComputableInPolyTime.decide_eq' hc (ComputableInPolyTime.const 1)).of_enc_eq + fun p => ?_ + simp only [Function.comp_apply, List.count_eq_countP] + congr 3 + have h2 : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + (List.range G.2.numEdges).all (fun i => + decide (((G.2.vertices.take (min G.2.vertices.length G.1)).flatMap Prod.fst).count i = 1) && + decide (((G.2.vertices.drop (min G.2.vertices.length G.1)).flatMap Prod.fst).count i = + 1))) encG encBool := + ComputableInPolyTime.allWithInput (ComputableInPolyTime.range.comp hm) + (ComputableInPolyTime.bool_and.comp ((hcount (hflat htake)).prod (hcount (hflat hdrop)))) + -- Full tables. + have h3 : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + G.2.vertices.all (fun v => decide (v.2.length = 2 ^ v.1.length))) encG encBool := by + refine ComputableInPolyTime.allWithInput hvs ?_ + have hv := ComputableInPolyTime.snd encG (encPair (encList encNat) (encList encNat)) + exact ComputableInPolyTime.decide_eq' + (ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.length_list _).comp + ((ComputableInPolyTime.snd _ _).comp hv))) + (ComputableInPolyTime.binary_two_pow.comp ((ComputableInPolyTime.length_list _).comp + ((ComputableInPolyTime.fst _ _).comp hv))) + -- The degree bound. + have h4 : ComputableInPolyTime (fun G : ℕ × ExplicitTensorNetwork ℕ => + G.2.vertices.all (fun v => decide (v.1.length ≤ degreeBound G.2.numEdges))) encG + encBool := by + refine ComputableInPolyTime.allWithInput hvs ?_ + exact ComputableInPolyTime.binary_le' + (ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.length_list _).comp + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)))) + (degreeBound_polyTime.comp (hm.comp (ComputableInPolyTime.fst _ _))) + refine (ComputableInPolyTime.bool_and.comp ((ComputableInPolyTime.bool_and.comp + ((ComputableInPolyTime.bool_and.comp (h1.prod h2)).prod h3)).prod h4)).of_enc_eq fun G => ?_ + have ht : G.2.vertices.take (min G.2.vertices.length G.1) = G.2.vertices.take G.1 := by + rcases le_total G.2.vertices.length G.1 with hle | hle + · rw [min_eq_left hle, List.take_length, List.take_of_length_le hle] + · rw [min_eq_right hle] + have hdr : G.2.vertices.drop (min G.2.vertices.length G.1) = G.2.vertices.drop G.1 := by + rcases le_total G.2.vertices.length G.1 with hle | hle + · rw [min_eq_left hle, List.drop_length, List.drop_of_length_le hle] + · rw [min_eq_right hle] + rw [fastGuard_eq_guardTest, guardTest] + simp only [Function.comp_apply, ht, hdr] + +end GuardPolyTime + +/-! ### The clock -/ + +open FastContraction in +/-- **The clock is subexponential on admissible inputs**: with `m ≤ C s` edges and precision +`P ≤ D s`, it is at most `2 ^ (K₀ (C + D) s / √⌊log₂ s⌋)`. -/ +theorem fuel_le {C D s P : ℕ} {G : ℕ × ExplicitTensorNetwork ℕ} (hA : ExplicitAdmissible C s G) + (hs : 2 ≤ s) (hP : P ≤ D * s) : + fuel (ExplicitTensorNetwork.capPrecision (P, G)) ≤ + 2 ^ (fuelConst * (C + D) * s / Nat.sqrt (Nat.log 2 s)) := by + obtain ⟨-, hm, -, -, -, hsm⟩ := hA + simp only [fuel, ExplicitTensorNetwork.capPrecision] + refine Nat.pow_le_pow_right (by norm_num) ?_ + set x := G.2.numEdges + min (ExplicitTensorNetwork.encExplicitNetwork G.2).length P + have hx : x ≤ (C + D) * s := by + have := min_le_right (ExplicitTensorNetwork.encExplicitNetwork G.2).length P + rw [add_mul]; omega + have hsx : s ≤ x := by omega + have hq : 1 ≤ Nat.sqrt (Nat.log 2 s) := by + rw [Nat.le_sqrt, Nat.one_mul]; exact Nat.le_log_of_pow_le one_lt_two (by simpa using hs) + have hq' : Nat.sqrt (Nat.log 2 s) ≤ Nat.sqrt (Nat.log 2 x) := + Nat.sqrt_le_sqrt (Nat.log_mono_right hsx) + calc fuelConst * x / Nat.sqrt (Nat.log 2 x) ≤ fuelConst * x / Nat.sqrt (Nat.log 2 s) := + Nat.div_le_div_left hq' hq + _ ≤ fuelConst * (C + D) * s / Nat.sqrt (Nat.log 2 s) := by + refine Nat.div_le_div_right ?_ + rw [Nat.mul_assoc] + exact Nat.mul_le_mul_left _ hx + +/-! ### The resources of the fast machine -/ + +section Resources + +open FastContraction Cslib.GenericRAM Cslib.GenericRAM.StackRecursion Turing.MultiTapeTM + +variable (mk : Call → ChildCursor.State → ℕ × DescribedNetwork VertexDesc) + +/-- **The resource analysis of the fast machine**, the remaining obligation of the running time. +For all constants `C` and `D` there are `K` and `s₀` such that on every input `(P, r, G)` with +`2 ≤ s`, `1 ≤ P ≤ D s`, `G` admissible at size `s` with constant `C` and passing the guard, the +root of the capped input has a derivation whose nodes, frames and values are at most `F` long, +whose leaves take time at most `TL` and space at most `SL`, with `H` open calls, such that +`H + F + SL ≤ K (n + 1) ^ K` and `TL ≤ K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` for the input length +`n`; and from `s₀` on its `N` nodes fit in the clock, `2 N - 1 ≤ fuel`. -/ +@[expose] def FastAnalysis : Prop := + ∀ C D : ℕ, ∃ K s₀ : ℕ, ∀ (s P : ℕ) (G : ℕ × ExplicitTensorNetwork ℕ), 2 ≤ s → 1 ≤ P → + P ≤ D * s → ExplicitAdmissible C s G → fastGuard G = true → + ∃ (I : Invariant Call Frame (ℕ × List ℕ)) (N H F TL SL : ℕ), + (scheme mk).Eval I (root (ExplicitTensorNetwork.capPrecision (P, G))) + (0, value mk (root (ExplicitTensorNetwork.capPrecision (P, G)))) N H ∧ + (s₀ ≤ s → 2 * N - 1 ≤ fuel (ExplicitTensorNetwork.capPrecision (P, G))) ∧ + (∀ x, I.node x → (encCall x).length ≤ F ∧ ((scheme mk).isLeaf x = true → + (leafCertificate mk).time x ≤ TL ∧ (leafCertificate mk).space x ≤ SL)) ∧ + (∀ x a, I.frame x a → (encPair encCall encFrame (x, a)).length ≤ F) ∧ + (∀ v, I.value v → (encValue v).length ≤ F) ∧ + H + F + SL ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K ∧ + TL ≤ K * ((FastContraction.encInput (P, G)).length + 1) ^ K * + 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) + +/-- `c B ^ d ≤ c B ^ E X` for `1 ≤ B`, `d ≤ E` and `1 ≤ X`. -/ +private theorem lift_le {c B d E X : ℕ} (hB : 1 ≤ B) (hd : d ≤ E) (hX : 1 ≤ X) : + c * B ^ d ≤ c * B ^ E * X := + (Nat.mul_le_mul_left c (Nat.pow_le_pow_right hB hd)).trans (Nat.le_mul_of_pos_right _ hX) + +private theorem mul_succ_le_sq (H F : ℕ) : (H + 1) * (F + 1) ≤ (H + F + 1) ^ 2 := by nlinarith + +private theorem mul_add_one_le {c x : ℕ} (hx : 1 ≤ x) : c * x + 1 ≤ (c + 1) * x := by nlinarith + +/-- The arithmetic of the time bound. -/ +private theorem time_assemble + {capT runT cnT crT capL fuel Z TL Wb n R₁ R₂ Q W c₁ k₁ c₃ ka c₂ k₂ : ℕ} + (hrun : runT ≤ cnT + crT + 2 * capL + (fuel + 1) * (Z + TL + Wb)) + (hq : capT + cnT + crT + capL ≤ Q) (hQ : Q ≤ c₁ * W) + (hP1 : (fuel + 1) * Z ≤ 2 * k₁ * W) (hP2 : (fuel + 1) * TL ≤ 2 * ka * W) + (hP3 : (fuel + 1) * Wb ≤ 2 * c₃ * W) (hn : n + 1 ≤ 1 * W) (hR₁ : R₁ ≤ c₂ * W) + (hR₂ : R₂ ≤ k₂ * W) : + 2 * (capT + runT) + n + (R₁ + R₂) ≤ (6 * c₁ + 1 + 4 * k₁ + 4 * c₃ + 4 * ka + c₂ + k₂) * W := by + have e : (fuel + 1) * (Z + TL + Wb) = (fuel + 1) * Z + (fuel + 1) * TL + (fuel + 1) * Wb := by + ring + rw [e] at hrun + nlinarith + +/-- The arithmetic of the space bound. -/ +private theorem space_assemble + {capS runS cnS crS capL fb Z SL n R₁ R₂ Q W c₁ k₁ c₃ ka c₂ k₂ : ℕ} + (hrun : runS ≤ cnS + crS + 2 * capL + Z + SL + fb) + (hq : capS + cnS + crS + capL + fb ≤ Q) (hQ : Q ≤ c₁ * W) + (hZ : Z ≤ k₁ * W) (hSL : SL ≤ ka * W) (hn : n + 1 ≤ 1 * W) (hR₁ : R₁ ≤ c₂ * W) + (hR₂ : R₂ ≤ k₂ * W) : + 2 * (capS + runS) + n + (R₁ + R₂) ≤ (6 * c₁ + 1 + 4 * k₁ + 4 * c₃ + 4 * ka + c₂ + k₂) * W := by + nlinarith + +variable {mk} + +/-- **The fast machine within the interface's bounds.** Given polynomial-time components of the +scheme and the resource analysis, for all constants `C` and `D` there is `K` such that on every +guarded input admissible at size `s ≥ 2` with constant `C` and with precision `1 ≤ P ≤ D s`, the +fast machine takes time at most `K (n + 1) ^ K 2 ^ (K s / √⌊log₂ s⌋)` and space at most +`K (n + 1) ^ K`. -/ +theorem fastCertificate_le (hpoly : (scheme mk).PolyTime encCall encFrame encValue) + (hana : FastAnalysis mk) (C D : ℕ) : + ∃ K : ℕ, ∀ (s P : ℕ) (G : ℕ × ExplicitTensorNetwork ℕ), 2 ≤ s → 1 ≤ P → P ≤ D * s → + ExplicitAdmissible C s G → fastGuard G = true → + (fastCertificate hpoly).time (P, G) ≤ + K * ((FastContraction.encInput (P, G)).length + 1) ^ K * + 2 ^ (K * s / Nat.sqrt (Nat.log 2 s)) ∧ + (fastCertificate hpoly).space (P, G) ≤ + K * ((FastContraction.encInput (P, G)).length + 1) ^ K := by + obtain ⟨Ka, s₀, hKa⟩ := hana C D + obtain ⟨K₁, hK₁⟩ := Scheme.certificate_le hpoly (leafCertificate mk) + obtain ⟨K₂, hK₂⟩ := ExplicitTensorNetwork.exists_directContractionBinaryCertificate_le + have hcapP := NativeCertificate.ofPolyTime_isPolynomial + ExplicitTensorNetwork.capPrecision_polyTime + have hcnP := NativeCertificate.ofPolyTime_isPolynomial fuel_polyTime + have hcrP := NativeCertificate.ofPolyTime_isPolynomial root_polyTime + have hcapL := ExplicitTensorNetwork.capPrecision_polyTime.polyBounded_length + have hfuelL := fuel_polyTime.polyBounded_length + obtain ⟨c₁, d₁, h₁⟩ := (((((((hcapP.1.add hcapP.2).add (hcnP.1.comp hcapL)).add + (hcnP.2.comp hcapL)).add (hcrP.1.comp hcapL)).add (hcrP.2.comp hcapL)).add hcapL).add + (hfuelL.comp hcapL)).exists_le + have htP := NativeCertificate.ofPolyTime_isPolynomial tag_polyTime + have hdP := NativeCertificate.ofPolyTime_isPolynomial decode_polyTime + have hfP := NativeCertificate.ofPolyTime_isPolynomial + (ComputableInPolyTime.fst FastContraction.encInput encValue) + obtain ⟨c₂, d₂, h₂⟩ := (((((htP.1.add htP.2).add hdP.1).add hdP.2).add hfP.1).add + hfP.2).exists_le + -- The constants. + set E := 2 * d₁ + 2 * Ka * K₁ + (Ka + 1) * d₂ + K₂ + Ka + 1 with hE + set Kc := 6 * c₁ + 1 + 4 * K₁ * (Ka + 1) ^ (2 * K₁) + 4 * (c₁ + 1) ^ 2 + 4 * Ka + + c₂ * (Ka + 2) ^ d₂ + K₂ * 2 ^ (C * s₀) with hKc + refine ⟨Kc + E + FastContraction.fuelConst * (C + D) + Ka, fun s P G hs hP hPD hA hg => ?_⟩ + obtain ⟨I, N, H, F, TL, SL, he, hN, hnode, hframe, hvalue, hHFS, hTL⟩ := + hKa s P G hs hP hPD hA hg + set q : ℕ × ℕ × ExplicitTensorNetwork ℕ := (P, G) with hq + set n := (FastContraction.encInput q).length with hn + set B := n + 1 with hB + have hB1 : 1 ≤ B := by omega + set S := Nat.sqrt (Nat.log 2 s) + set Xf := 2 ^ (FastContraction.fuelConst * (C + D) * s / S) + set Xa := 2 ^ (Ka * s / S) + set X := 2 ^ ((FastContraction.fuelConst * (C + D) + Ka) * s / S) + have hXf : 1 ≤ Xf := Nat.one_le_two_pow + have hXa : 1 ≤ Xa := Nat.one_le_two_pow + have hXfX : Xf ≤ X := Nat.pow_le_pow_right (by norm_num) + (Nat.div_le_div_right (Nat.mul_le_mul_right _ (by omega))) + have hXfXa : Xf * Xa ≤ X := by + rw [← pow_add] + refine Nat.pow_le_pow_right (by norm_num) (Nat.div_add_div_le_add_div.trans ?_) + rw [add_mul] + have hX1 : 1 ≤ X := Nat.one_le_two_pow + -- The run. + obtain ⟨hrT, hrS⟩ := hK₁ (NativeCertificate.ofPolyTime fuel_polyTime) + (NativeCertificate.ofPolyTime root_polyTime) I F TL SL hnode hframe hvalue + (ExplicitTensorNetwork.capPrecision q) _ N H he + have hfuel : FastContraction.fuel (ExplicitTensorNetwork.capPrecision q) ≤ Xf := + fuel_le hA hs hPD + have hq1 := h₁ q + have hrT' : (runCertificate hpoly).time (ExplicitTensorNetwork.capPrecision q) ≤ _ := hrT + have hrS' : (runCertificate hpoly).space (ExplicitTensorNetwork.capPrecision q) ≤ _ := hrS + -- Polynomial pieces. + set Q₁ := c₁ * B ^ d₁ with hQ₁ + have hBK (k : ℕ) : 1 ≤ B ^ k := Nat.one_le_pow _ _ (by omega) + have hHF : (H + 1) * (F + 1) ≤ ((Ka + 1) * B ^ Ka) ^ 2 := by + have h1 : H + F ≤ Ka * B ^ Ka := by have := hHFS; omega + have h2 := hBK Ka + calc (H + 1) * (F + 1) ≤ (H + F + 1) ^ 2 := mul_succ_le_sq H F + _ ≤ (Ka * B ^ Ka + B ^ Ka) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ = ((Ka + 1) * B ^ Ka) ^ 2 := by ring + have hZ : K₁ * ((H + 1) * (F + 1)) ^ K₁ ≤ K₁ * (Ka + 1) ^ (2 * K₁) * B ^ (2 * Ka * K₁) := by + calc K₁ * ((H + 1) * (F + 1)) ^ K₁ ≤ K₁ * (((Ka + 1) * B ^ Ka) ^ 2) ^ K₁ := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hHF _) + _ = K₁ * (Ka + 1) ^ (2 * K₁) * B ^ (2 * Ka * K₁) := by + rw [mul_pow, ← pow_mul, mul_pow, ← pow_mul, ← pow_mul, + show Ka * 2 * K₁ = 2 * Ka * K₁ by ring] + ring + have hW : ((encNat (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q))).length + 1) ^ 2 + ≤ (c₁ + 1) ^ 2 * B ^ (2 * d₁) := by + have h1 : (encNat (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q))).length ≤ Q₁ := + by omega + have h2 := hBK d₁ + calc _ ≤ (Q₁ + 1) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ ≤ ((c₁ + 1) * B ^ d₁) ^ 2 := Nat.pow_le_pow_left (mul_add_one_le h2) 2 + _ = (c₁ + 1) ^ 2 * B ^ (2 * d₁) := by ring + have hTL' : TL ≤ Ka * B ^ Ka * Xa := hTL + have hF : F ≤ Ka * B ^ Ka := by have := hHFS; omega + have hSL : SL ≤ Ka * B ^ Ka := by have := hHFS; omega + -- Decoding and direct summation. + set R₁ := c₂ * (Ka + 2) ^ d₂ * B ^ ((Ka + 1) * d₂) with hR₁ + set R₂ := K₂ * 2 ^ (C * s₀) * B ^ K₂ with hR₂ + have hdec : ∀ v : ℕ × List ℕ, I.value v → + (NativeCertificate.ofPolyTime tag_polyTime).time (q, v) + + (NativeCertificate.ofPolyTime tag_polyTime).space (q, v) + + (NativeCertificate.ofPolyTime decode_polyTime).time (q, v) + + (NativeCertificate.ofPolyTime decode_polyTime).space (q, v) + + (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst FastContraction.encInput encValue)).time (q, v) + + (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst FastContraction.encInput encValue)).space (q, v) ≤ R₁ := by + intro v hv + have h := h₂ (q, v) + have hv' := hvalue v hv + have hk1 : B ≤ B ^ (Ka + 1) := by + calc B = B ^ 1 := (pow_one B).symm + _ ≤ B ^ (Ka + 1) := Nat.pow_le_pow_right hB1 (by omega) + have hk2 : B ^ Ka ≤ B ^ (Ka + 1) := Nat.pow_le_pow_right hB1 (by omega) + have hlen : (encPair FastContraction.encInput encValue (q, v)).length + 1 ≤ + (Ka + 2) * B ^ (Ka + 1) := by + have hl := length_encPair (enc₁ := FastContraction.encInput) (enc₂ := encValue) (q, v) + simp only at hl + rw [hl] + have : Ka * B ^ Ka ≤ Ka * B ^ (Ka + 1) := Nat.mul_le_mul_left _ hk2 + have e : (Ka + 2) * B ^ (Ka + 1) = Ka * B ^ (Ka + 1) + 2 * B ^ (Ka + 1) := by ring + omega + calc _ ≤ c₂ * ((encPair FastContraction.encInput encValue (q, v)).length + 1) ^ d₂ := h + _ ≤ c₂ * ((Ka + 2) * B ^ (Ka + 1)) ^ d₂ := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlen _) + _ = R₁ := by rw [hR₁, mul_pow, ← pow_mul, Nat.mul_assoc] + obtain ⟨hdT, hdS⟩ := hK₂ q + have hm : q.2.2.numEdges ≤ C * s := hA.2.1 + have hdir (hs₀ : s < s₀) : + ExplicitTensorNetwork.directContractionBinaryCertificate.time q ≤ R₂ := by + refine hdT.trans ?_ + have h2 : 2 ^ q.2.2.numEdges ≤ 2 ^ (C * s₀) := + Nat.pow_le_pow_right (by norm_num) (hm.trans (Nat.mul_le_mul_left _ hs₀.le)) + calc K₂ * ((encInput q).length + 1) ^ K₂ * 2 ^ q.2.2.numEdges + ≤ K₂ * B ^ K₂ * 2 ^ (C * s₀) := Nat.mul_le_mul_left _ h2 + _ = R₂ := by rw [hR₂]; ring + have hdirS : ExplicitTensorNetwork.directContractionBinaryCertificate.space q ≤ R₂ := + hdS.trans (by + rw [hR₂] + calc K₂ * ((encInput q).length + 1) ^ K₂ ≤ + K₂ * ((encInput q).length + 1) ^ K₂ * 2 ^ (C * s₀) := + Nat.le_mul_of_pos_right _ Nat.one_le_two_pow + _ = K₂ * 2 ^ (C * s₀) * B ^ K₂ := by ring) + have hbig : s₀ ≤ s → (scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q) = + some (0, value mk (root (ExplicitTensorNetwork.capPrecision q))) := + fun h => Scheme.run_eq_some he (hN h) + clear hKa hK₁ hK₂ h₁ h₂ + -- Everything against `W = B ^ E X`. + set W := B ^ E * X with hWdef + have lift {c d : ℕ} (hd : d ≤ E) : c * B ^ d ≤ c * W := by + rw [hWdef, ← Nat.mul_assoc]; exact lift_le hB1 hd hX1 + have lift' {c d : ℕ} (hd : d ≤ E) : c * B ^ d ≤ c * B ^ E := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hB1 hd) + have hQW : Q₁ ≤ c₁ * W := lift (by omega) + have hR₁W : R₁ ≤ c₂ * (Ka + 2) ^ d₂ * W := lift (by omega) + have hR₂W : R₂ ≤ K₂ * 2 ^ (C * s₀) * W := lift (by omega) + have hnW : n + 1 ≤ 1 * W := by + have := lift (c := 1) (d := 1) (by omega); rw [pow_one] at this; omega + have hfuel1 : FastContraction.fuel (ExplicitTensorNetwork.capPrecision q) + 1 ≤ 2 * Xf := by + omega + have hP1 : (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q) + 1) * + (K₁ * ((H + 1) * (F + 1)) ^ K₁) ≤ 2 * (K₁ * (Ka + 1) ^ (2 * K₁)) * W := by + calc _ ≤ (2 * Xf) * (K₁ * (Ka + 1) ^ (2 * K₁) * B ^ (2 * Ka * K₁)) := + Nat.mul_le_mul hfuel1 hZ + _ = 2 * (K₁ * (Ka + 1) ^ (2 * K₁)) * (B ^ (2 * Ka * K₁) * Xf) := by ring + _ ≤ 2 * (K₁ * (Ka + 1) ^ (2 * K₁)) * W := Nat.mul_le_mul_left _ + (Nat.mul_le_mul (Nat.pow_le_pow_right hB1 (by omega)) hXfX) + have hP2 : (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q) + 1) * TL ≤ + 2 * Ka * W := by + calc _ ≤ (2 * Xf) * (Ka * B ^ Ka * Xa) := Nat.mul_le_mul hfuel1 hTL' + _ = 2 * Ka * (B ^ Ka * (Xf * Xa)) := by ring + _ ≤ 2 * Ka * W := Nat.mul_le_mul_left _ + (Nat.mul_le_mul (Nat.pow_le_pow_right hB1 (by omega)) hXfXa) + have hP3 : (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q) + 1) * + ((encNat (FastContraction.fuel (ExplicitTensorNetwork.capPrecision q))).length + 1) ^ 2 ≤ + 2 * (c₁ + 1) ^ 2 * W := by + calc _ ≤ (2 * Xf) * ((c₁ + 1) ^ 2 * B ^ (2 * d₁)) := Nat.mul_le_mul hfuel1 hW + _ = 2 * (c₁ + 1) ^ 2 * (B ^ (2 * d₁) * Xf) := by ring + _ ≤ 2 * (c₁ + 1) ^ 2 * W := Nat.mul_le_mul_left _ + (Nat.mul_le_mul (Nat.pow_le_pow_right hB1 (by omega)) hXfX) + have hZE : K₁ * ((H + 1) * (F + 1)) ^ K₁ ≤ K₁ * (Ka + 1) ^ (2 * K₁) * B ^ E := + hZ.trans (lift' (by omega)) + have hQE : Q₁ ≤ c₁ * B ^ E := lift' (by omega) + have hR₁E : R₁ ≤ c₂ * (Ka + 2) ^ d₂ * B ^ E := lift' (by omega) + have hR₂E : R₂ ≤ K₂ * 2 ^ (C * s₀) * B ^ E := lift' (by omega) + have hSLE : SL ≤ Ka * B ^ E := hSL.trans (lift' (by omega)) + have hnE : n + 1 ≤ 1 * B ^ E := by + have := lift' (c := 1) (d := 1) (by omega); rw [pow_one] at this; omega + have hKK : Kc ≤ Kc + E + FastContraction.fuelConst * (C + D) + Ka := by omega + have hBE : B ^ E ≤ B ^ (Kc + E + FastContraction.fuelConst * (C + D) + Ka) := + Nat.pow_le_pow_right hB1 (by omega) + have hXK : X ≤ 2 ^ ((Kc + E + FastContraction.fuelConst * (C + D) + Ka) * s / S) := + Nat.pow_le_pow_right (by norm_num) (Nat.div_le_div_right (Nat.mul_le_mul_right _ (by omega))) + refine ⟨?_, ?_⟩ + · rw [fastCertificate_time] + have hrest : ((scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q)).elim + (ExplicitTensorNetwork.directContractionBinaryCertificate.time q) (fun v => + (NativeCertificate.ofPolyTime tag_polyTime).time (q, v) + + if decide (v.1 = 0) = true then + (NativeCertificate.ofPolyTime decode_polyTime).time (q, v) + else (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst FastContraction.encInput encValue)).time (q, v) + + ExplicitTensorNetwork.directContractionBinaryCertificate.time q) ≤ R₁ + R₂ := by + rcases hr : (scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q) with _ | v + · by_cases hs₀ : s₀ ≤ s + · rw [hbig hs₀] at hr; cases hr + · simp only [Option.elim_none]; have := hdir (by omega); omega + · have hd := hdec v (Scheme.value_of_run_eq_some he hr) + simp only [Option.elim_some] + split_ifs with h0 + · omega + · by_cases hs₀ : s₀ ≤ s + · rw [hbig hs₀] at hr; cases hr; simp at h0 + · have := hdir (by omega); omega + have htot := time_assemble (capT := (NativeCertificate.ofPolyTime + ExplicitTensorNetwork.capPrecision_polyTime).time q) hrT' (by omega) hQW hP1 hP2 + hP3 hnW hR₁W hR₂W + have hKc' : Kc = 6 * c₁ + 1 + 4 * (K₁ * (Ka + 1) ^ (2 * K₁)) + 4 * (c₁ + 1) ^ 2 + 4 * Ka + + c₂ * (Ka + 2) ^ d₂ + K₂ * 2 ^ (C * s₀) := by rw [hKc]; ring + rw [← hKc'] at htot + calc _ ≤ _ := Nat.add_le_add_left hrest _ + _ ≤ Kc * W := htot + _ ≤ _ := by + rw [hWdef, ← Nat.mul_assoc] + exact Nat.mul_le_mul (Nat.mul_le_mul hKK hBE) hXK + · rw [fastCertificate_space] + have hrest : ((scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q)).elim + (ExplicitTensorNetwork.directContractionBinaryCertificate.space q) (fun v => + (NativeCertificate.ofPolyTime tag_polyTime).space (q, v) + + if decide (v.1 = 0) = true then + (NativeCertificate.ofPolyTime decode_polyTime).space (q, v) + else (NativeCertificate.ofPolyTime + (ComputableInPolyTime.fst FastContraction.encInput encValue)).space (q, v) + + ExplicitTensorNetwork.directContractionBinaryCertificate.space q) ≤ R₁ + R₂ := by + rcases hr : (scheme mk).run fuel root (ExplicitTensorNetwork.capPrecision q) with _ | v + · simp only [Option.elim_none]; omega + · have hd := hdec v (Scheme.value_of_run_eq_some he hr) + simp only [Option.elim_some] + split_ifs <;> omega + have htot := space_assemble (capS := (NativeCertificate.ofPolyTime + ExplicitTensorNetwork.capPrecision_polyTime).space q) (c₃ := (c₁ + 1) ^ 2) hrS' + (by omega) hQE hZE hSLE hnE hR₁E hR₂E + have hKc' : Kc = 6 * c₁ + 1 + 4 * (K₁ * (Ka + 1) ^ (2 * K₁)) + 4 * (c₁ + 1) ^ 2 + 4 * Ka + + c₂ * (Ka + 2) ^ d₂ + K₂ * 2 ^ (C * s₀) := by rw [hKc]; ring + rw [← hKc'] at htot + calc _ ≤ _ := Nat.add_le_add_left hrest _ + _ ≤ Kc * B ^ E := htot + _ ≤ _ := Nat.mul_le_mul hKK hBE + +/-- **The contraction algorithm from the fast machine**, given its remaining obligations: +polynomial-time components of the scheme, the correctness of one active +call and the passage of well-formedness to the children (`FastContraction.StepCorrect`, +`FastContraction.WFChildren`), well-formed roots on guarded inputs, and the resource analysis +(`FastAnalysis`). It branches on the guard (`ContractionAlgorithm.ofGuarded`) to the fast machine +or to direct summation. -/ +noncomputable def contractionAlgorithmOfFast + (hpoly : (scheme mk).PolyTime encCall encFrame encValue) {WF : Call → Prop} + (hstep : StepCorrect mk WF) (hwf : WFChildren mk WF) + (hroot : ∀ P G, fastGuard G = true → WF (root (ExplicitTensorNetwork.capPrecision (P, G)))) + (hana : FastAnalysis mk) : + ContractionAlgorithm (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) + (fun G => G.2.contraction) ExplicitAdmissible := + ContractionAlgorithm.ofGuarded fastGuard_polyTime (fastCertificate hpoly) + (fun P G hg => fastValue_eq mk hstep hwf (P, G) (hroot P G hg)) + (fun C D => fastCertificate_le hpoly hana C D) (fun _ _ _ hA => hA.2.1) + exists_le_of_fastGuard_eq_false + +/-- **Guarded inputs have well-formed roots**: the modulus is irreducible modulo `2`, and the +tables as descriptors keep the rows-first ports. -/ +theorem wf_root (P : ℕ) {G : ℕ × ExplicitTensorNetwork ℕ} (hg : fastGuard G = true) : + FastContraction.WF (root (ExplicitTensorNetwork.capPrecision (P, G))) := by + refine ⟨irreducible_modulus _, ?_⟩ + have hrows : G.2.IsRowsFirstBipartite G.1 := by + simp only [fastGuard, decide_eq_true_eq] at hg + exact hg.1 + obtain ⟨h1, h2⟩ := hrows + refine ⟨fun v hv i hi => ?_, fun i hi => ?_⟩ + · simp only [root, ExplicitTensorNetwork.capPrecision, rootNet, List.mem_map] at hv + obtain ⟨w, hw, rfl⟩ := hv + exact h1 w hw i hi + · simp only [root, ExplicitTensorNetwork.capPrecision, rootNet, ← List.map_take, + ← List.map_drop, List.map_map, Function.comp_def] at hi ⊢ + exact h2 i hi + +/-- **The contraction algorithm from the native recursion**, given its one remaining obligation, +the resource analysis. The components of the scheme are polynomial-time +(`FastContraction.scheme_nativeMk_polyTime`), and the step, the passage of well-formedness to the +children and the well-formed roots are proved (`FastContraction.stepCorrect_nativeMk`, +`FastContraction.wfChildren_nativeMk`, `CountingSimulation.wf_root`). -/ +noncomputable def contractionAlgorithmOfNative (hana : FastAnalysis nativeMk) : + ContractionAlgorithm (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) + (fun G => G.2.contraction) ExplicitAdmissible := + contractionAlgorithmOfFast scheme_nativeMk_polyTime stepCorrect_nativeMk wfChildren_nativeMk + (fun P _ hg => wf_root P hg) hana + +end Resources + +end Cslib.Complexity.CountingSimulation diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/ExplicitBlockNetwork.lean b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/ExplicitBlockNetwork.lean index 56ed22be45..da05642876 100644 --- a/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/ExplicitBlockNetwork.lean +++ b/Complexity/Computability/Machines/Turing/MultiTape/Probabilistic/ExplicitBlockNetwork.lean @@ -43,7 +43,7 @@ accepting count (`Turing.MultiTapePTM.sum_contraction_explicitBlockNetwork`). `2 * (q * (3 * k + 1) * m)` edges and maximum degree at most `2 * (3 * k + 1) * m`, both `O(b)` per interval. Its first `q * (3 * k + 2)` vertices are the rows of a bipartition: every edge has exactly one port at a row and one at a column -(`Cslib.Circuits.Frontier.ExplicitTensorNetwork.IsRowsFirstBipartite`). Every table has length +(`Cslib.Circuits.Frontier.DescribedNetwork.IsRowsFirstBipartite`). Every table has length `2 ^ (number of ports)`, and its entries are at most `2 ^ b`. These are the inputs of the Boolean tensor contraction theorem [OpenAI2026ETH, Theorem D.1] diff --git a/Complexity/Foundations/Combinatorics/BudgetTuples.lean b/Complexity/Foundations/Combinatorics/BudgetTuples.lean index 3ed809c732..20d48a9bcd 100644 --- a/Complexity/Foundations/Combinatorics/BudgetTuples.lean +++ b/Complexity/Foundations/Combinatorics/BudgetTuples.lean @@ -55,6 +55,7 @@ along `budgetTuples` (`sum_filter_weight_lt_eq`). * `Cslib.BudgetTuples.mem_budgetTuples`, `Cslib.BudgetTuples.nodup_budgetTuples`: the list holds each tuple of weight below the budget once. +* `Cslib.BudgetTuples.lt_of_mem_budgetTuples`: the entries of these tuples are below the budget. * `Cslib.BudgetTuples.head?_budgetTuples`, `Cslib.BudgetTuples.isChain_budgetTuples`, `Cslib.BudgetTuples.budgetNext_getLast?`: the successor walks the list from the zero tuple. * `Cslib.BudgetTuples.budgetNextScan_eq`: the scan computes the successor. @@ -133,6 +134,26 @@ theorem mem_budgetTuples {ws : List ℕ} (hw : ∀ w ∈ ws, 0 < w) {p : ℕ} { · simp only [List.length_cons, Nat.add_right_cancel_iff, weight_cons_cons] at hlen hwt exact ⟨b, by omega, γ, ih'.mpr ⟨hlen, by omega⟩, rfl⟩ +/-- Each entry of a tuple is at most its weight, for positive weights. -/ +theorem le_weight_of_mem : ∀ {ws β : List ℕ}, (∀ w ∈ ws, 0 < w) → β.length = ws.length → + ∀ b ∈ β, b ≤ weight ws β + | [], [], _, _, b, hb => by simp at hb + | [], _ :: _, _, hl, _, _ => by simp at hl + | _ :: _, [], _, hl, _, _ => by simp at hl + | w :: ws, b' :: β, hw, hl, b, hb => by + rw [weight_cons_cons] + rcases List.mem_cons.mp hb with rfl | hb + · exact (Nat.le_mul_of_pos_left b (hw w List.mem_cons_self)).trans (Nat.le_add_right _ _) + · have := le_weight_of_mem (fun w' hw' => hw w' (List.mem_cons_of_mem _ hw')) + (by simpa using hl) b hb + omega + +/-- **Every entry of a tuple of weight below `p` is below `p`**, for positive weights. -/ +theorem lt_of_mem_budgetTuples {ws : List ℕ} (hw : ∀ w ∈ ws, 0 < w) {p : ℕ} {β : List ℕ} + (hβ : β ∈ budgetTuples ws p) {b : ℕ} (hb : b ∈ β) : b < p := by + obtain ⟨hl, hwt⟩ := (mem_budgetTuples hw).mp hβ + exact (le_weight_of_mem hw hl b hb).trans_lt hwt + /-- **Each tuple appears once.** -/ theorem nodup_budgetTuples (ws : List ℕ) (p : ℕ) : (budgetTuples ws p).Nodup := by induction ws generalizing p with diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean index 9206e0547a..dcde7ed35a 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean @@ -103,7 +103,7 @@ example : ¬ parallel.IsRowsFirstBipartite 2 := by decide example : ¬ (cycle eqTable).IsRowsFirstBipartite 2 := by decide /-- A port naming no edge is excluded. -/ -example : ¬ ExplicitTensorNetwork.IsRowsFirstBipartite +example : ¬ DescribedNetwork.IsRowsFirstBipartite (⟨1, [([0], [1, 1]), ([0, 5], [1, 1, 1, 1])]⟩ : ExplicitTensorNetwork ℕ) 1 := by decide /-- Written out with the old vertices first, the subdivision of `parallelNetwork` is rows-first diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChild.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChild.lean new file mode 100644 index 0000000000..1431e819e9 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChild.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ +module +-- upstream: local-only (test driver) + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeCall + +public section + +open Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativeChild + +namespace Cslib.Tests.TensorNetworkNativeChild + +/-! ### A path with one row and two columns + +The row `0` joins the columns `1` and `2` by the edges `0` and `1`, and the vertices carry tables. +The network is rows-first at `1`, and the ends of its edges are computed from the ports. -/ + +/-- The tables of the vertices. -/ +def table (n : ℕ) : VertexDesc := .inl (List.replicate n 1) + +/-- The row `0` with ports `1, 0` (not in edge order), the columns `1` and `2`. -/ +def path : DescribedNetwork VertexDesc := + ⟨2, [([1, 0], table 4), ([0], table 2), ([1], table 2)]⟩ + +example : path.IsRowsFirstBipartite 1 := by decide + +example : ¬ path.IsRowsFirstBipartite 2 := by decide + +example : path.edgeEnds = [(0, 1), (0, 2)] := by decide + +/-- The native child of group `0` for a packing that puts the row and the column `1` in group `0`, +the column `2` in group `1`, and both edges in packet `0` with slots `0` and `1`. -/ +def child : DescribedNetwork VertexDesc := + nativeChild [1] 1 0 3 (-1) [0] 1 path (([[0]], [[0], [1]]), 1, [(0, 0), (0, 1)]) [] 0 + +/-- The child keeps the row and the column `1`, and adds the packet: three child edges, the two +row ports of group `0` and the column port of edge `0`. -/ +example : child.numEdges = 3 := by decide + +/-- The row keeps its parent's port order, renumbered (edge `1` is row port `1`, edge `0` row port +`0`); the column `1` reads the column port `2`; the packet reads all three child edges in order. -/ +example : child.vertices.map Prod.fst = [[1, 0], [2], [0, 1, 2]] := by decide + +/-- The packet tags its ports as row ports, row ports and a column port, with the slots of their +parent edges. -/ +example : (child.vertices.getD 2 ([], .inl [])).2.getRight?.map (fun d => d.2.1) = + some [(true, 0), (true, 1), (false, 0)] := by decide + +example : childRows 1 0 path (([[0]], [[0], [1]]), 1, [(0, 0), (0, 1)]) 0 = 2 := by decide + +example : child.IsRowsFirstBipartite 2 := by decide + +/-! ### The modes of a node -/ + +example : modeWeights 2 2 3 = [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] := by decide + +example : betaSlice 0 3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 1 = [7, 8, 9, 10, 11, 12] := by + decide + +end Cslib.Tests.TensorNetworkNativeChild diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChildPolyTime.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChildPolyTime.lean new file mode 100644 index 0000000000..5a4e66be69 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeChildPolyTime.lean @@ -0,0 +1,117 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ +module +-- upstream: local-only (test driver) + +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeChildPolyTime +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends + +public section + +open Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativeChild Cslib.GaloisRingArith + Turing.MultiTapeTM + +namespace Cslib.Tests.TensorNetworkNativeChildPolyTime + +/-! ### Ranks of ports past the edges + +A port past the edges reads the edge `(0, 0)`, so the rank of a large port counts it. -/ + +example : portRank [0, 1] 0 [(0, 1)] Prod.fst 3 = 3 := by decide + +example : portRank [0, 1] 0 [(0, 1)] Prod.fst 3 = + (List.range (min 1 3)).countP (fun e' => decide ([0, 1].getD (([(0, 1)].getD e' (0, 0)).1) 0 = + 0)) + (3 - 1) * (if [0, 1].getD ((0, 0) : ℕ × ℕ).1 0 = 0 then 1 else 0) := + portRank_eq_countP _ _ _ _ _ + +/-! ### The shape of a small child + +The path of `TensorNetworkNativeChild` with one row joined to two columns. The child of group `0` +keeps the row and the column `1` and adds one packet. -/ + +/-- The tables of the vertices. -/ +def table (n : ℕ) : VertexDesc := .inl (List.replicate n 1) + +/-- The row `0` with ports `1, 0`, the columns `1` and `2`. -/ +def path : DescribedNetwork VertexDesc := + ⟨2, [([1, 0], table 4), ([0], table 2), ([1], table 2)]⟩ + +/-- The packing: the row and the column `1` in group `0`, the column `2` in group `1`, both edges +in packet `0`. -/ +def packing : (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ) := + (([[0]], [[0], [1]]), 1, [(0, 0), (0, 1)]) + +example : (nativeChild [1] 1 0 3 (-1) [0] 1 path packing [] 0).numEdges ≤ 2 * path.numEdges := + numEdges_nativeChild_le _ _ _ _ _ _ _ _ _ _ _ + +example : (nativeChild [1] 1 0 3 (-1) [0] 1 path packing [] 0).vertices.length = 3 := by + rw [length_vertices_nativeChild] + decide + +/-! ### Polynomial time on a concrete input + +The composition form applies to an input carrying a parent, its packing words and placements, +and its number of packets in unary, with the other parameters fixed. -/ + +/-- A parent, the packing words, the number of packets and the placements. -/ +abbrev Input : Type := + DescribedNetwork VertexDesc × (List (List ℕ) × List (List ℕ)) × ℕ × List (ℕ × ℕ) + +/-- The encoding of `Input`, with the number of packets in unary. -/ +abbrev encInput : Input ↪ List Bool := + encPair (DescribedNetwork.encDescribed VertexDesc.enc) + (encPair (encPair (encList (encList encNat)) (encList (encList encNat))) + (encPair encUnary (encList (encPair encNat encNat)))) + +example : ComputableInPolyTime (fun x : Input => + nativeChild [1] 1 0 3 (-1) [0] 1 x.1 x.2 [] 0) encInput + (DescribedNetwork.encDescribed VertexDesc.enc) := by + have hpk : ComputableInPolyTime (fun x : Input => x.2) encInput + PacketPacking.encNativePacking := + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)).prod + ((ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _)))).prod + ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _)))) + exact computableInPolyTime_nativeChild (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) (ComputableInPolyTime.fst _ _) + hpk ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _))) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + +/-- With the native parent packing, the number of rows and `a` are the only further inputs. -/ +example : ComputableInPolyTime (fun D : DescribedNetwork VertexDesc => + nativeChild [1] 1 0 3 (-1) [0] 1 D (nativeParentPacking 1 1 D) [] 0) + (DescribedNetwork.encDescribed VertexDesc.enc) + (DescribedNetwork.encDescribed VertexDesc.enc) := + computableInPolyTime_nativeChild_nativeParentPacking (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) ComputableInPolyTime.id' + (ComputableInPolyTime.const _) (ComputableInPolyTime.const _) + +example : (nativeParentPacking 1 0 path).2.1 ≤ 2 := + (numPackets_nativeParentPacking_le 1 0 path).trans (by decide) + +/-! ### Binary-digit points + +The digits below `e = 2` of `0, 1, 2` are the lists of the interpolation points. -/ + +example : ((List.range 3).map fun k => (List.range 2).map fun j => if k.testBit j then 1 else 0) = + [[0, 0], [1, 0], [0, 1]] := by decide + +example (hirr : Irreducible ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2)))) : + Pairwise fun i j : Fin ((List.range 3).map fun k => + (List.range [1, 1].length).map fun j => if k.testBit j then 1 else 0).length => + IsUnit (liftO hirr (((List.range 3).map fun k => + (List.range [1, 1].length).map fun j => if k.testBit j then 1 else 0).getD i []) - + liftO hirr (((List.range 3).map fun k => + (List.range [1, 1].length).map fun j => if k.testBit j then 1 else 0).getD j [])) := + pairwise_isUnit_liftO_sub_digits hirr (by decide) + +end Cslib.Tests.TensorNetworkNativeChildPolyTime diff --git a/ComplexityTests/FastContraction.lean b/ComplexityTests/FastContraction.lean new file mode 100644 index 0000000000..7a0f8cff74 --- /dev/null +++ b/ComplexityTests/FastContraction.lean @@ -0,0 +1,47 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Complexity.Classes.CountingSimulation.FastAlgorithm + +/-! # The fast contraction machine: checked instances + +The modulus at `m = 4` edges, the root call of a small network, its guard, and the arithmetic of +the call data. -/ + +namespace ComplexityTests.FastContraction + +open Cslib.Circuits.Frontier Cslib.Circuits.Frontier.FastContraction + Cslib.Complexity.CountingSimulation + +-- At `m = 4`: `t = ⌊log₂ 10⌋ = 3`, so the modulus has `2 ⬝ 27 = 54` coefficients, `1` at `0` and +-- at `27`, which gives `2 ^ 54` interpolation residues. +example : modulusExp 4 = 3 := by decide +example : (modulus 4).length = 54 := by decide +example : (modulus 4).getD 0 0 = 1 ∧ (modulus 4).getD 27 0 = 1 ∧ (modulus 4).getD 1 0 = 0 := by + decide + +-- The modulus is irreducible modulo `2`, at every size. +example (m : ℕ) : Irreducible ((Cslib.GaloisRingArith.monicOf (modulus m)).map + (Int.castRingHom (ZMod 2))) := irreducible_modulus m + +/-- One row `0` and one column `1` joined by edge `0`, with full tables. -/ +def tiny : ℕ × ExplicitTensorNetwork ℕ := (1, ⟨1, [([0], [1, 2]), ([0], [3, 4])]⟩) + +-- The guard holds: rows first, full tables, degree `1 ≤ degreeBound 1 = 4`. +example : fastGuard tiny = true := by decide + +-- The root of `(P, r, G)` at `P = 3`: scale `1`, depth `0`, bound `2`, capped precision `3`. +example : (root (3, tiny)).1.1 = 1 ∧ (root (3, tiny)).1.2.1 = 0 ∧ + (root (3, tiny)).1.2.2.1 = 2 ∧ (root (3, tiny)).1.2.2.2.1 = 3 := by decide + +-- A call this small is a leaf: `leafSize 1 = 1`, so it has no more edges than the leaf size. +example : active (root (3, tiny)) = false := by decide + +-- The contraction of `tiny` is `1 ⬝ 3 + 2 ⬝ 4 = 11`, and the machine's result is `11 mod 2 ^ P` +-- whenever its root is well-formed (`fastValue_eq`). +example : tiny.2.contraction = 11 := by decide + +end ComplexityTests.FastContraction diff --git a/ComplexityTests/FastContractionPolyTime.lean b/ComplexityTests/FastContractionPolyTime.lean new file mode 100644 index 0000000000..2b8c3ca973 --- /dev/null +++ b/ComplexityTests/FastContractionPolyTime.lean @@ -0,0 +1,51 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.Complexity.Classes.CountingSimulation.FastAlgorithm + +/-! # The components of the native contraction recursion in polynomial time: checked instances + +The caps on the rows and the exponents, the schedule's quantities on a call that is not active, +and the instance of the contraction interface from the resource analysis alone. -/ + +namespace ComplexityTests.FastContractionPolyTime + +open Turing.MultiTapeTM Cslib.Circuits.Frontier Cslib.Circuits.Frontier.FastContraction + Cslib.Circuits.Frontier.ContractionSchedule Cslib.Complexity.CountingSimulation + +/-- One row `0` and one column `1` joined by edge `0`, with full tables. -/ +def tiny : ExplicitTensorNetwork ℕ := ⟨1, [([0], [1, 2]), ([0], [3, 4])]⟩ + +-- The number of rows is stored in binary, so a call may claim `2 ^ 100` rows; the cap keeps the +-- two vertices, and on the rows-first root it is inactive. +example : rowCap (root (3, 2 ^ 100, tiny)) = 2 := by decide +example : rowCap (root (3, 1, tiny)) = 1 := by decide + +-- The packing of the root: one packet of `2 ^ 0` slots, within the bound `M (2 ^ a) ^ 12`. +example : slots (root (3, 1, tiny)) = 0 := by decide +example : (packing (root (3, 1, tiny))).2.1 = 1 := by decide +example : (packing (root (3, 1, tiny))).2.1 ≤ + (root (3, 1, tiny)).edges * (2 ^ slots (root (3, 1, tiny))) ^ 12 := + NativeChild.numPackets_nativeParentPacking_le _ _ _ + +-- On every call, `2 ^ a ≤ s + 1`, and the levels are at most `a + 1`. +example (x : Call) : 2 ^ slots x ≤ x.scale + 1 := two_pow_slots_le x +example (x : Call) (k : ℕ) : level x k ≤ slots x + 1 := level_le x k + +-- The cap on the exponents leaves the tuples of weight below the budget unchanged. +example : ([1, 0, 1] : List ℕ).map (min · 3) = [1, 0, 1] := + map_min_of_mem_budgetTuples (ws := [1, 1, 1]) (by decide) (by decide) + +-- The components of the scheme of the native child builder are polynomial-time. +example : (scheme nativeMk).PolyTime encCall encFrame encValue := scheme_nativeMk_polyTime + +-- The contraction interface for admissible explicit networks, from the resource analysis alone. +noncomputable example (hana : FastAnalysis nativeMk) : + ContractionAlgorithm (encPair encNat ExplicitTensorNetwork.encExplicitNetwork) + (fun G => G.2.contraction) ExplicitAdmissible := + contractionAlgorithmOfNative hana + +end ComplexityTests.FastContractionPolyTime