From c33195005eae3e6da0e33ad648b13d3cefaf9b42 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 17:41:28 -0400 Subject: [PATCH 01/33] feat(circuit/frontier): Boolean tensor networks and their contraction Tensor networks over a commutative semiring as the weighted form of the frontier constraint networks (shared Multigraph and locality), with the contraction Z, subdivision by equality tensors (preserves Z, bipartite, keeps degree bounds), explicit tables with ordered ports and size measures, the write-out theorem, and the 0-1 bridge from constraint networks. Tracking: #1833 (T1), #1538. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 + .../Circuit/Frontier/TensorNetwork.lean | 251 ++++++++++++++++++ .../Frontier/TensorNetwork/Constraint.lean | 81 ++++++ .../Frontier/TensorNetwork/Explicit.lean | 210 +++++++++++++++ .../Circuit/Frontier/TensorNetwork.lean | 116 ++++++++ 5 files changed, 661 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Constraint.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean diff --git a/Complexity.lean b/Complexity.lean index 3942e2e28b..3e53d5172a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -857,6 +857,9 @@ public import Complexity.Computability.Circuit.Frontier.Projection public import Complexity.Computability.Circuit.Frontier.Rectangle public import Complexity.Computability.Circuit.Frontier.Reduction public import Complexity.Computability.Circuit.Frontier.Sweep +public import Complexity.Computability.Circuit.Frontier.TensorNetwork +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork.lean new file mode 100644 index 0000000000..cf45df7f82 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork.lean @@ -0,0 +1,251 @@ +/- +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.Multigraph +public import Mathlib.Algebra.BigOperators.Group.Finset.Basic +public import Mathlib.Combinatorics.SimpleGraph.Bipartite +public import Mathlib.Basic.Finite.Sum +public import Mathlib.Data.Fintype.Pi +import Mathlib.Algebra.BigOperators.GroupWithZero.Finset +import Mathlib.Algebra.BigOperators.Ring.Finset + +/-! +# Tensor networks and their contraction + +A *tensor network* over a commutative semiring `R` and an alphabet `U` is a multigraph whose +edges carry values in `U`, with a *tensor* at each vertex `v`: a value `N.tensor v x ∈ R` for each +assignment `x : E → U` of values to the edges, which depends only on the values on the edges at +`v`. Its *contraction* sums, over all assignments, the product of the tensors: + + `Z(N) = ∑ x : E → U, ∏ v, N.tensor v x`. + +A *Boolean* tensor network has `U = Bool`, one bit per edge. The usual presentation gives the +tensor at `v` as a function `F_v` of the bits on the ordered list `δ(v)` of edges at `v`, with +`Z = ∑ x ∏ v F_v (x|δ(v))`. Here the tensor is a function of the whole assignment, constrained to +read only the edges at `v` (`TensorNetwork.tensor_congr`); the ordered presentation, with +explicit tables, is `Cslib.Circuits.Frontier.ExplicitTensorNetwork`, and +`Cslib.Circuits.Frontier.TensorNetwork.contraction_toExplicit` shows that the two contractions +agree. Parallel edges are distinct elements of `E` and so give distinct ports. + +A tensor network is the weighted form of a constraint network +(`Cslib.Circuits.Frontier.Network`) on a fixed input: the two share the multigraph and the +locality condition, and a constraint becomes its `0`–`1` indicator tensor, whose contraction +counts the satisfying assignments +(`Cslib.Circuits.Frontier.Network.contraction_toTensorNetwork`). + +**Subdivision** (`TensorNetwork.subdivide`). Splitting every edge `e` into two edges joined at a +new vertex that carries the *equality tensor* `[y₁ = y₂]` preserves the contraction +(`TensorNetwork.contraction_subdivide`): each assignment of the old network extends in exactly one +way to an assignment of the new one with nonzero weight. The subdivided network is bipartite +(`TensorNetwork.isBipartite_subdivide`), with the old vertices on one side and the new ones on the +other, and it keeps the degree bound of a loopless network (`TensorNetwork.maxDegreeLE_subdivide`). +It has twice as many edges and one new vertex per old edge. + +## Main definitions + +* `Cslib.Circuits.Frontier.Multigraph.IsBipartite`: the vertices split into two sides and every + edge joins the two sides. +* `Cslib.Circuits.Frontier.TensorNetwork`: a tensor network. +* `Cslib.Circuits.Frontier.TensorNetwork.contraction`: its contraction `Z`. +* `Cslib.Circuits.Frontier.TensorNetwork.subdivide`: subdivision of every edge by an equality + tensor. + +## Main results + +* `Cslib.Circuits.Frontier.Multigraph.isBipartite_iff`: a multigraph is bipartite iff it has no + loops and its underlying simple graph is bipartite (`SimpleGraph.IsBipartite`). +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_subdivide`: subdivision preserves the + contraction. +* `Cslib.Circuits.Frontier.TensorNetwork.isBipartite_subdivide`: a subdivided network is + bipartite. +* `Cslib.Circuits.Frontier.TensorNetwork.maxDegreeLE_subdivide`: subdividing a loopless network of + maximum degree at most `d ≥ 2` gives maximum degree at most `d`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.1 (p. 53) and + the subdivision step of Appendix D.2 (p. 54). +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +open Set + +/-! ### Bipartite multigraphs -/ + +namespace Multigraph + +variable {V E : Type*} (G : Multigraph V E) + +/-- A multigraph is *bipartite* when its vertices split into two sides, `side v = true` and +`side v = false`, and every edge joins the two sides. A bipartite multigraph has no loops +(`Multigraph.IsBipartite.loopless`). -/ +def IsBipartite : Prop := + ∃ side : V → Bool, ∀ e, side (G.src e) ≠ side (G.tgt e) + +variable {G} + +/-- A bipartite multigraph has no loops. -/ +theorem IsBipartite.loopless (h : G.IsBipartite) : G.Loopless := by + obtain ⟨side, hside⟩ := h + exact fun e he => hside e (congrArg side he) + +/-- A multigraph is bipartite exactly when it has no loops and its underlying simple graph is +bipartite in the sense of Mathlib (`SimpleGraph.IsBipartite`, a `2`-colouring). -/ +theorem isBipartite_iff : G.IsBipartite ↔ G.Loopless ∧ G.toSimpleGraph.IsBipartite := by + constructor + · intro h + refine ⟨h.loopless, ?_⟩ + obtain ⟨side, hside⟩ := h + have hcol : G.toSimpleGraph.Coloring Bool := SimpleGraph.Coloring.mk side fun {u v} huv => by + obtain ⟨-, e, he⟩ := toSimpleGraph_adj.mp huv + rcases Sym2.eq_iff.mp he with ⟨hu, hv⟩ | ⟨hu, hv⟩ + · exact hu ▸ hv ▸ hside e + · exact hu ▸ hv ▸ (hside e).symm + simpa using hcol.colorable + · rintro ⟨hloop, ⟨c⟩⟩ + refine ⟨fun v => decide (c v = 0), fun e he => ?_⟩ + have hadj : G.toSimpleGraph.Adj (G.src e) (G.tgt e) := toSimpleGraph_adj.mpr ⟨hloop e, e, rfl⟩ + have key : ∀ a b : Fin 2, a ≠ b → decide (a = 0) ≠ decide (b = 0) := by decide + exact key _ _ (c.valid hadj) he + +end Multigraph + +/-! ### Tensor networks -/ + +/-- A *tensor network* over the commutative semiring `R` with alphabet `U`: a multigraph on the +vertices `V` and edges `E`, whose edges carry values in `U`, and at each vertex a *tensor*, a value +in `R` for each assignment of values to the edges, that sees only the values on the edges at that +vertex. A *Boolean* tensor network has `U = Bool`. -/ +structure TensorNetwork (R U V E : Type*) extends Multigraph V E where + /-- The tensor at a vertex: its value on an assignment of values to the edges. -/ + tensor : V → (E → U) → R + /-- Tensors are local: the tensor at `v` sees only the values on the edges incident to `v`. -/ + tensor_congr : ∀ v x y, (∀ e, src e = v ∨ tgt e = v → x e = y e) → tensor v x = tensor v y + +namespace TensorNetwork + +variable {R U V E : Type*} + +section Contraction + +variable [CommSemiring R] [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] + +/-- The *contraction* of a tensor network: the sum over all assignments of values to the edges of +the product of the tensors at all vertices. -/ +def contraction (N : TensorNetwork R U V E) : R := + ∑ x : E → U, ∏ v, N.tensor v x + +end Contraction + +/-! ### Subdivision by equality tensors -/ + +section Subdivide + +variable [DecidableEq U] [DecidableEq V] [Zero R] [One R] (N : TensorNetwork R U V E) + +/-- The *subdivision* of a tensor network by equality tensors. Each edge `e` becomes a new vertex +`Sum.inr e` and two edges: `Sum.inl e`, from the source of `e`, and `Sum.inr e`, from its target, +both ending at `Sum.inr e`. The new vertex carries the *equality tensor*, one when its two edges +carry the same value and zero otherwise. An old vertex reads the value of `e` on the half of `e` +at its end. -/ +def subdivide : TensorNetwork R U (V ⊕ E) (E ⊕ E) where + src := Sum.elim (fun e => .inl (N.src e)) (fun e => .inl (N.tgt e)) + tgt := Sum.elim .inr .inr + tensor + | .inl v, y => N.tensor v fun e => if N.src e = v then y (.inl e) else y (.inr e) + | .inr e, y => if y (.inl e) = y (.inr e) then 1 else 0 + tensor_congr := by + rintro (v | e) y y' h + · refine N.tensor_congr v _ _ fun e he => ?_ + by_cases hs : N.src e = v + · simp only [hs, ite_true] + exact h (.inl e) (Or.inl (by simp [hs])) + · simp only [hs, ite_false] + exact h (.inr e) (Or.inl (by simp [he.resolve_left hs])) + · simp only + rw [h (.inl e) (Or.inr rfl), h (.inr e) (Or.inr rfl)] + +@[simp] theorem subdivide_src_inl (e : E) : N.subdivide.src (.inl e) = .inl (N.src e) := rfl + +@[simp] theorem subdivide_src_inr (e : E) : N.subdivide.src (.inr e) = .inl (N.tgt e) := rfl + +@[simp] theorem subdivide_tgt_inl (e : E) : N.subdivide.tgt (.inl e) = .inr e := rfl + +@[simp] theorem subdivide_tgt_inr (e : E) : N.subdivide.tgt (.inr e) = .inr e := rfl + +/-- The tensor at an old vertex of the subdivision. -/ +theorem subdivide_tensor_inl (v : V) (y : E ⊕ E → U) : + N.subdivide.tensor (.inl v) y = + N.tensor v fun e => if N.src e = v then y (.inl e) else y (.inr e) := rfl + +/-- The equality tensor at a new vertex of the subdivision. -/ +theorem subdivide_tensor_inr (e : E) (y : E ⊕ E → U) : + N.subdivide.tensor (.inr e) y = if y (.inl e) = y (.inr e) then 1 else 0 := rfl + +/-- **Subdivision is bipartite.** Every edge of the subdivision joins an old vertex to a new one. -/ +theorem isBipartite_subdivide : N.subdivide.IsBipartite := + ⟨Sum.isLeft, fun e => by cases e <;> simp⟩ + +/-- **Subdivision preserves the contraction.** An assignment of the subdivision has nonzero +weight only when the two halves of every edge carry the same value, and such assignments are +exactly the assignments of the original network. -/ +theorem contraction_subdivide {R : Type*} [CommSemiring R] [Fintype U] [Fintype V] [Fintype E] + [DecidableEq E] (N : TensorNetwork R U V E) : N.subdivide.contraction = N.contraction := by + classical + -- Split an assignment of the subdivision into its values `a` on the source halves and `b` on + -- the target halves. + rw [contraction, contraction, ← (Equiv.sumArrowEquivProdArrow E E U).symm.sum_comp, + Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun a _ => ?_ + simp only [Fintype.prod_sum_type, subdivide_tensor_inl, subdivide_tensor_inr, + Equiv.sumArrowEquivProdArrow_symm_apply_inl, Equiv.sumArrowEquivProdArrow_symm_apply_inr] + rw [Finset.sum_eq_single a] + · -- Equal halves: every equality tensor is one, and the old vertices read `a`. + simp only [ite_self, ite_true, Finset.prod_const_one, mul_one] + · -- Unequal halves on some edge `e`: the equality tensor at `e` vanishes. + intro b _ hb + obtain ⟨e, he⟩ := Function.ne_iff.mp hb + exact mul_eq_zero_of_right _ + (Finset.prod_eq_zero (Finset.mem_univ e) (ite_eq_right fun h => he h.symm)) + · exact fun h => absurd (Finset.mem_univ a) h + +/-- The edges at an old vertex of the subdivision are the halves of its old edges at it. -/ +theorem incidenceSet_subdivide_inl (v : V) : + N.subdivide.incidenceSet (.inl v) = + Sum.inl '' {e | N.src e = v} ∪ Sum.inr '' {e | N.tgt e = v} := by + ext (e | e) <;> simp [Multigraph.mem_incidenceSet] + +/-- The edges at a new vertex of the subdivision are the two halves of its edge. -/ +theorem incidenceSet_subdivide_inr (e : E) : + N.subdivide.incidenceSet (.inr e) = {.inl e, .inr e} := by + ext (e' | e') <;> simp [Multigraph.mem_incidenceSet, eq_comm] + +/-- **Subdivision keeps the degree bound.** In a loopless network, the edges at a vertex are those +with it as source and those with it as target, and they are the halves at its old vertex in the +subdivision; a new vertex has degree two. -/ +theorem maxDegreeLE_subdivide [Finite E] {d : ℕ} (hloop : N.Loopless) + (hd : N.MaxDegreeLE d) (h2 : 2 ≤ d) : N.subdivide.MaxDegreeLE d := by + rintro (v | e) + · rw [incidenceSet_subdivide_inl, ncard_union_eq _ (toFinite _) (toFinite _), + ncard_image_of_injective _ Sum.inl_injective, ncard_image_of_injective _ Sum.inr_injective, + ← ncard_union_eq _ (toFinite _) (toFinite _)] + · exact hd v + · exact disjoint_left.mpr fun e hs ht => hloop e (hs.trans ht.symm) + · exact disjoint_left.mpr fun _ ⟨_, _, h⟩ ⟨_, _, h'⟩ => Sum.inl_ne_inr (h.trans h'.symm) + · rw [incidenceSet_subdivide_inr, ncard_pair Sum.inl_ne_inr] + exact h2 + +end Subdivide + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Constraint.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Constraint.lean new file mode 100644 index 0000000000..08c80a6649 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Constraint.lean @@ -0,0 +1,81 @@ +/- +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.Network +public import Complexity.Computability.Circuit.Frontier.TensorNetwork +import Mathlib.Algebra.BigOperators.GroupWithZero.Finset +import Mathlib.Algebra.BigOperators.Ring.Finset + +/-! +# Constraint networks as `0`–`1` tensor networks + +A constraint network (`Cslib.Circuits.Frontier.Network`) on a fixed input is a tensor network +whose tensors take only the values zero and one: the tensor at `v` is the indicator of the +constraint at `v` (`Network.toTensorNetwork`). The two share the multigraph, and the locality of +the constraints is the locality of the tensors. The contraction of this tensor network is the +number of satisfying assignments (`Network.contraction_toTensorNetwork`), so over `ℕ` it is +nonzero exactly at the accepted inputs (`Network.mem_accepted_iff_contraction_ne_zero`). + +A general tensor network is thus the weighted form of a constraint network, and the frontier +constraint networks of circuits (`Cslib.Circuits.Frontier.Compiler.network`) are special cases. + +## Main definitions + +* `Cslib.Circuits.Frontier.Network.toTensorNetwork`: the indicator tensor network of a constraint + network on an input. + +## Main results + +* `Cslib.Circuits.Frontier.Network.contraction_toTensorNetwork`: its contraction counts the + satisfying assignments. +* `Cslib.Circuits.Frontier.Network.mem_accepted_iff_contraction_ne_zero`: an input is accepted iff + the contraction over `ℕ` is nonzero. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier.Network + +variable {U ι V E : Type*} (R : Type*) (N : Network U ι V E) + +open Classical in +/-- The `0`–`1` tensor network of a constraint network on the input `x`: the same multigraph, with +the tensor at `v` equal to one where the constraint at `v` holds and to zero elsewhere. -/ +noncomputable def toTensorNetwork [Zero R] [One R] (x : ι → U) : TensorNetwork R U V E where + toMultigraph := N.toMultigraph + tensor v α := if N.Constraint v x α then 1 else 0 + tensor_congr v α β h := by + have hiff : N.Constraint v x α ↔ N.Constraint v x β := + ⟨N.constraint_congr v x x α β (fun _ _ => rfl) h, + N.constraint_congr v x x β α (fun _ _ => rfl) fun e he => (h e he).symm⟩ + simp only [hiff] + +/-- The tensor network of a constraint network has the same multigraph. -/ +@[simp] theorem toMultigraph_toTensorNetwork [Zero R] [One R] (x : ι → U) : + (N.toTensorNetwork R x).toMultigraph = N.toMultigraph := + rfl + +/-- **The contraction counts satisfying assignments.** The contraction of the `0`–`1` tensor +network of a constraint network on the input `x` is the number of assignments of values to the +edges that satisfy every constraint on `x`. -/ +theorem contraction_toTensorNetwork [CommSemiring R] [Fintype U] [Fintype V] [Fintype E] + [DecidableEq E] (x : ι → U) : + (N.toTensorNetwork R x).contraction = ({α | N.Satisfies x α} : Set (E → U)).ncard := by + classical + simp only [TensorNetwork.contraction, toTensorNetwork, Fintype.prod_boole, Finset.sum_boole, + Set.ncard_eq_toFinset_card', Set.toFinset_ofPred, Satisfies] + +/-- Over `ℕ`, the contraction is nonzero exactly at the accepted inputs. -/ +theorem mem_accepted_iff_contraction_ne_zero [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] + (x : ι → U) : x ∈ N.accepted ↔ (N.toTensorNetwork ℕ x).contraction ≠ 0 := by + rw [contraction_toTensorNetwork, Nat.cast_id, Ne, Set.ncard_eq_zero (Set.toFinite _), + ← Ne, ← Set.nonempty_iff_ne_empty] + rfl + +end Cslib.Circuits.Frontier.Network diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean new file mode 100644 index 0000000000..28f5dd3f54 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean @@ -0,0 +1,210 @@ +/- +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 +import Mathlib.Algebra.BigOperators.Fin +import Mathlib.Data.List.GetD + +/-! +# Explicit Boolean tensor networks + +An *explicit* Boolean tensor network (`ExplicitTensorNetwork R`) is the data an algorithm reads: +the number of edges, numbered `0, 1, …`, and a list of vertices, each given by + +* its ordered list of *ports*, the numbers of the edges at the vertex, and +* its *table*: the value of its tensor when port `i` carries the bit `bs[i]` is the entry at + position `tableIndex bs`, the number whose binary digit `i` is `bs[i]`. + +So a vertex with `k` ports has a table of `2 ^ k` entries. The contraction of an explicit network +(`ExplicitTensorNetwork.contraction`) sums, over all assignments of bits to the edges, the product +of the table entries at the bits on the ports. It needs no well-formedness: a port naming no edge +reads `false`, and a position past the end of a table has 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`). + +**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 +order and tabulate its tensor on them. The contraction is unchanged +(`TensorNetwork.contraction_toExplicit`). + +## Main definitions + +* `Cslib.Circuits.Frontier.tableIndex`: the position of a list of bits in a table. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork`: an explicit Boolean tensor network. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.entry`, + `Cslib.Circuits.Frontier.ExplicitTensorNetwork.portBits`: table lookup and the bits on a list + of ports. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.contraction`: the contraction. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.maxDegree`, + `Cslib.Circuits.Frontier.ExplicitTensorNetwork.tableLength`: size measures. +* `Cslib.Circuits.Frontier.TensorNetwork.explicitPorts`, + `Cslib.Circuits.Frontier.TensorNetwork.explicitTable`, + `Cslib.Circuits.Frontier.TensorNetwork.toExplicit`: writing a Boolean tensor network out. + +## Main results + +* `Cslib.Circuits.Frontier.testBit_tableIndex`, `Cslib.Circuits.Frontier.tableIndex_lt`: the + digits and range of a table position. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork.maxDegree_le_iff`: the degree bound. +* `Cslib.Circuits.Frontier.TensorNetwork.entry_explicitTable`: a written-out table evaluates the + tensor. +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_toExplicit`: writing a network out preserves + its contraction. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.1 (p. 53). +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +/-- The position of a list of bits in a table: the number whose binary digit `i` is `bs[i]`, so +that the bits are read little-endian. -/ +def tableIndex (bs : List Bool) : ℕ := + (BitVec.ofBoolListLE bs).toNat + +/-- Binary digit `i` of the position of `bs` is `bs[i]`, or `false` past the end of `bs`. -/ +@[simp] theorem testBit_tableIndex (bs : List Bool) (i : ℕ) : + (tableIndex bs).testBit i = bs.getD i false := by + rw [tableIndex, BitVec.testBit_toNat, BitVec.getLsbD_ofBoolListLE] + +/-- A list of `k` bits has a position below `2 ^ k`. -/ +theorem tableIndex_lt (bs : List Bool) : tableIndex bs < 2 ^ bs.length := + BitVec.isLt _ + +/-- An *explicit Boolean tensor network* over `R`: the number of edges, numbered from zero, and a +list of vertices. A vertex is given by its ordered list of *ports*, the numbers of its edges, and +its *table*, whose entry at position `tableIndex bs` is the value of its tensor when port `i` +carries the bit `bs[i]`. -/ +structure ExplicitTensorNetwork (R : Type*) where + /-- The number of edges; the edges are numbered `0, …, numEdges - 1`. -/ + numEdges : ℕ + /-- The vertices, each given by its ports and its table. -/ + vertices : List (List ℕ × List R) + +namespace ExplicitTensorNetwork + +variable {R : Type*} (d : ExplicitTensorNetwork R) + +/-- The entry of a table at a list of bits. A position past the end of the table has entry +zero. -/ +def entry [Zero R] (table : List R) (bs : List Bool) : R := + table.getD (tableIndex bs) 0 + +/-- The bits on a list of ports under an assignment of bits to the edges. A port naming no edge +reads `false`. -/ +def portBits (x : Fin d.numEdges → Bool) (ports : List ℕ) : List Bool := + ports.map fun i => if h : i < d.numEdges then x ⟨i, h⟩ else false + +/-- The *contraction* of an explicit network: the sum over all assignments of bits to the edges of +the product, over the vertices, of the table entries at the bits on their ports. -/ +def contraction [CommSemiring R] : R := + ∑ x : Fin d.numEdges → Bool, (d.vertices.map fun v => entry v.2 (d.portBits x v.1)).prod + +/-- The *maximum degree*: the largest number of ports of a vertex, or zero without vertices. -/ +def maxDegree : ℕ := + (d.vertices.map fun v => v.1.length).foldr max 0 + +/-- The total length of the tables. -/ +def tableLength : ℕ := + (d.vertices.map fun v => v.2.length).sum + +private theorem foldr_max_le_iff (l : List ℕ) (k : ℕ) : l.foldr max 0 ≤ k ↔ ∀ n ∈ l, n ≤ k := by + induction l with + | nil => simp + | cons a l ih => simp [ih] + +/-- The maximum degree is at most `k` exactly when every vertex has at most `k` ports. -/ +theorem maxDegree_le_iff {k : ℕ} : d.maxDegree ≤ k ↔ ∀ v ∈ d.vertices, v.1.length ≤ k := by + rw [maxDegree, foldr_max_le_iff] + constructor + · exact fun h v hv => h _ (List.mem_map_of_mem hv) + · rintro h _ hn + obtain ⟨v, hv, rfl⟩ := List.mem_map.mp hn + exact h v hv + +/-- Every vertex has at most `maxDegree` ports. -/ +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 + +end ExplicitTensorNetwork + +/-! ### Writing a tensor network out -/ + +namespace TensorNetwork + +variable {R V E : Type*} [DecidableEq V] {n m : ℕ} (N : TensorNetwork R Bool V E) (ev : V ≃ Fin n) + (ee : E ≃ Fin m) + +/-- The ports of a vertex when the edges are numbered by `ee`: the numbers of the edges at the +vertex, in increasing order. -/ +def explicitPorts (v : V) : List (Fin m) := + (List.finRange m).filter fun j => N.src (ee.symm j) = v ∨ N.tgt (ee.symm j) = v + +/-- An edge is a port of `v` exactly when it is incident to `v`. -/ +@[simp] theorem mem_explicitPorts {v : V} {j : Fin m} : + j ∈ N.explicitPorts ee v ↔ N.src (ee.symm j) = v ∨ N.tgt (ee.symm j) = v := by + simp [explicitPorts] + +/-- The table of a vertex: its tensor at each list of bits on its ports, by position. -/ +def explicitTable (v : V) : List R := + (List.range (2 ^ (N.explicitPorts ee v).length)).map fun j => + N.tensor v fun e => j.testBit ((N.explicitPorts ee v).idxOf (ee e)) + +/-- **A written-out table evaluates the tensor.** At the bits that an assignment puts on the ports +of `v`, the table of `v` has the value of the tensor at that assignment. -/ +theorem entry_explicitTable [Zero R] (v : V) (x : Fin m → Bool) : + ExplicitTensorNetwork.entry (N.explicitTable ee v) ((N.explicitPorts ee v).map x) = + N.tensor v (x ∘ ee) := by + set ps := N.explicitPorts ee v + have hlt : tableIndex (ps.map x) < (N.explicitTable ee v).length := by + simpa [explicitTable, ps] using tableIndex_lt (ps.map x) + rw [ExplicitTensorNetwork.entry, List.getD_eq_getElem _ _ hlt] + simp only [explicitTable, List.getElem_map, List.getElem_range] + refine N.tensor_congr v _ _ fun e he => ?_ + have hmem : ee e ∈ ps := (N.mem_explicitPorts ee).mpr (by simpa using he) + have hidx := List.idxOf_lt_length_of_mem hmem + rw [testBit_tableIndex, List.getD_eq_getElem _ _ (by simpa using hidx), List.getElem_map, + List.getElem_idxOf hidx, Function.comp_apply] + +/-- A Boolean tensor network on finite types written out as an explicit network, numbering the +vertices by `ev` and the edges by `ee`: vertex `i` is `ev.symm i`, with its ports in increasing +order and its table. -/ +def toExplicit : ExplicitTensorNetwork R where + numEdges := m + vertices := List.ofFn fun i => + ((N.explicitPorts ee (ev.symm i)).map Fin.val, N.explicitTable ee (ev.symm i)) + +/-- **Writing a network out preserves its contraction.** -/ +theorem contraction_toExplicit [CommSemiring R] [Fintype V] [Fintype E] [DecidableEq E] : + (N.toExplicit ev ee).contraction = N.contraction := by + rw [ExplicitTensorNetwork.contraction, contraction] + refine Fintype.sum_equiv (ee.symm.arrowCongr (Equiv.refl Bool)) _ _ fun x => ?_ + have hv : (N.toExplicit ev ee).vertices = List.ofFn fun i => + ((N.explicitPorts ee (ev.symm i)).map Fin.val, N.explicitTable ee (ev.symm i)) := rfl + rw [hv, List.map_ofFn, List.prod_ofFn, ← ev.symm.prod_comp] + refine Finset.prod_congr rfl fun i _ => ?_ + -- Every port names an edge, so the bits on the ports are those of the assignment. + have hbits : (N.toExplicit ev ee).portBits x ((N.explicitPorts ee (ev.symm i)).map Fin.val) = + (N.explicitPorts ee (ev.symm i)).map x := by + unfold ExplicitTensorNetwork.portBits + rw [List.map_map] + exact List.map_congr_left fun j _ => dite_eq_left j.isLt + simp only [Function.comp_apply] + rw [hbits, entry_explicitTable] + rfl + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean new file mode 100644 index 0000000000..5be31a11e9 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean @@ -0,0 +1,116 @@ +/- +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.Constraint +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Mathlib.Data.ZMod.Defs + +/-! +# Tests for tensor networks + +Hand-checked contractions of small explicit Boolean tensor networks (two parallel edges, and +four-cycles of equality and negation tensors over `ZMod 8`), a tensor network on finite types +written out and subdivided, and a constraint network counted by its indicator tensor network. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork + +open Cslib.Circuits.Frontier + +/-! ### Explicit networks -/ + +/-- Two vertices joined by two parallel edges `0` and `1`, with the tables `A = (1, 2, 3, 4)` and +`B = (5, 6, 7, 8)`, read at positions `x₀ + 2 x₁`. -/ +def parallel : ExplicitTensorNetwork ℤ := + ⟨2, [([0, 1], [1, 2, 3, 4]), ([0, 1], [5, 6, 7, 8])]⟩ + +/-- By hand: `Z = ∑ᵢ Aᵢ Bᵢ = 1·5 + 2·6 + 3·7 + 4·8 = 70`. -/ +example : parallel.contraction = 70 := by decide + +example : parallel.maxDegree = 2 ∧ parallel.tableLength = 8 := by decide + +/-- The equality tensor on two ports. -/ +def eqTable : List (ZMod 8) := [1, 0, 0, 1] + +/-- The negation tensor on two ports: its two bits differ. -/ +def neTable : List (ZMod 8) := [0, 1, 1, 0] + +/-- A bipartite four-cycle `r₀ - c₀ - r₁ - c₁ - r₀` on the edges `0, 1, 2, 3`, with the table `t` +at `c₁` and equality tensors elsewhere. -/ +def cycle (t : List (ZMod 8)) : ExplicitTensorNetwork (ZMod 8) := + ⟨4, [([3, 0], eqTable), ([0, 1], eqTable), ([1, 2], eqTable), ([2, 3], t)]⟩ + +/-- With equality tensors all around, an assignment contributes exactly when all four bits are +equal: `Z = 2`. -/ +example : (cycle eqTable).contraction = 2 := by decide + +/-- One negation on a cycle forces an odd number of bit changes around it, which is impossible: +`Z = 0`. -/ +example : (cycle neTable).contraction = 0 := by decide + +/-! ### A network on finite types, written out and subdivided -/ + +/-- The table `A` or `B` of `parallel` as a tensor on assignments to `Fin 2`. -/ +def table (v : Fin 2) (x : Fin 2 → Bool) : ℤ := + [[1, 2, 3, 4], [5, 6, 7, 8]][v]!.getD (tableIndex [x 0, x 1]) 0 + +/-- The network `parallel` on `Fin 2` vertices and `Fin 2` edges, both edges from `0` to `1`. +Every edge is at both vertices, so the tensors are trivially local. -/ +def parallelNetwork : TensorNetwork ℤ Bool (Fin 2) (Fin 2) where + src _ := 0 + tgt _ := 1 + tensor := table + tensor_congr v x y h := by + have hxy : x = y := funext fun e => h e (by fin_cases v <;> simp) + rw [hxy] + +/-- Writing the network out gives `parallel` up to the order of its data, and the same +contraction. -/ +example : parallelNetwork.contraction = 70 := by + rw [← parallelNetwork.contraction_toExplicit (Equiv.refl _) (Equiv.refl _)] + decide + +/-- Subdividing both edges by equality tensors gives a bipartite network with the same +contraction and degree bound. -/ +example : parallelNetwork.subdivide.contraction = 70 ∧ parallelNetwork.subdivide.IsBipartite ∧ + parallelNetwork.subdivide.MaxDegreeLE 2 := by + refine ⟨?_, parallelNetwork.isBipartite_subdivide, + parallelNetwork.maxDegreeLE_subdivide (fun e => by simp [parallelNetwork]) (fun v => ?_) + le_rfl⟩ + · rw [TensorNetwork.contraction_subdivide, + ← parallelNetwork.contraction_toExplicit (Equiv.refl _) (Equiv.refl _)] + decide + · exact (Set.ncard_le_card _).trans (by simp) + +/-! ### Constraint networks -/ + +/-- Two vertices joined by two parallel edges, each requiring its two edges to agree. -/ +def agree : Network Bool Empty (Fin 2) (Fin 2) where + src _ := 0 + tgt _ := 1 + site := Empty.elim + Constraint _ _ α := α 0 = α 1 + constraint_congr v _ _ α β _ h hα := by + have h' : ∀ e, α e = β e := fun e => h e (by fin_cases v <;> simp) + rw [← h' 0, ← h' 1] + exact hα + +/-- The indicator tensor network of `agree` counts its two satisfying assignments. -/ +example : (agree.toTensorNetwork ℕ Empty.elim).contraction = 2 := by + rw [Network.contraction_toTensorNetwork, Nat.cast_id] + have hsat : {α | agree.Satisfies Empty.elim α} = {fun _ => false, fun _ => true} := by + ext α + simp only [Network.Satisfies, agree, Set.mem_ofPred_eq, forall_const, Set.mem_insert_iff, + Set.mem_singleton_iff, funext_iff, Fin.forall_fin_two] + cases α 0 <;> cases α 1 <;> simp + rw [hsat, Set.ncard_pair (fun h => Bool.false_ne_true (congrFun h 0))] + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork From ae638974f0c2f9120d89832435883fcb9cdd17ae Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 17:52:13 -0400 Subject: [PATCH 02/33] feat(circuit/frontier): deformed contraction and the Cauchy kernel Start of Theorem D.1 of OpenAI's ETH manuscript (section D.4.3, (D.7)-(D.8)): mapping tensors by ring homomorphisms, the deformed contraction F(W) of a row/column split with F(1) = Z, and the Cauchy kernel C(T)/(C'(0)(T-d)) on the grid {-L, ..., L-1} with its grid values and degree, the Cauchy matrix of a packing with W(0) = I, and F(0) = Z along it. Tracking: #1538 (T2.1), #1833. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/Deformation.lean | 299 ++++++++++++++++++ .../Frontier/TensorNetworkDeformation.lean | 57 ++++ 3 files changed, 357 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Deformation.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkDeformation.lean diff --git a/Complexity.lean b/Complexity.lean index 3e53d5172a..8ea2c2b140 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -859,6 +859,7 @@ public import Complexity.Computability.Circuit.Frontier.Reduction public import Complexity.Computability.Circuit.Frontier.Sweep public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Deformation.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Deformation.lean new file mode 100644 index 0000000000..c5aa9c025f --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Deformation.lean @@ -0,0 +1,299 @@ +/- +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 +public import Mathlib.Data.Matrix.Mul +public import Mathlib.LinearAlgebra.Lagrange +public import Mathlib.Order.Interval.Finset.Defs +public import Mathlib.Data.Int.Interval +import Mathlib.Algebra.BigOperators.Ring.Finset +import Mathlib.Algebra.Polynomial.BigOperators + +/-! +# Deformed contractions and the Cauchy kernel + +This file starts the proof of the Boolean tensor contraction theorem of [OpenAI2026ETH] +(Theorem D.1, §D.1, p. 53) with the deformation of §D.4.3 (p. 59). + +**Deformed contraction** (`TensorNetwork.deformedContraction`). Split the vertices of a Boolean +tensor network into *rows* (`side v = true`) and *columns*. Give each edge `e` a *row bit* `Y e`, +read by the row tensors, and a *column bit* `X e`, read by the column tensors. For a matrix `W` +indexed by the edges, the deformed contraction is + + `F(W) = ∑ X Y, A(Y) B(X) ∏ e, l_{Y e}((W X) e)`, + +where `A` and `B` are the products of the row and column tensors, `l_false z = 1 - z` and +`l_true z = z` (the manuscript's (D.8), with `l₀`, `l₁`). At the identity matrix the product of +the affine factors is the indicator of `X = Y`, so `F(1)` is the contraction +(`TensorNetwork.deformedContraction_one`). Deforming `W` away from the identity lets the +interactions between groups of ports be expanded separately; that expansion is later work. + +**The Cauchy kernel** (`cauchyKernel`). On the grid `S = {-L, …, L - 1}`, with +`C(T) = ∏_{t ∈ S} (T - t)`, the kernel at `d ∈ S` is the polynomial `C(T) / (C'(0) (T - d))` of +(D.7). It is one at `T = 0` when `d = 0` and vanishes at every other grid point +(`eval_zero_cauchyKernel`, `eval_cauchyKernel_of_ne`). The *Cauchy matrix* of a packing of the +edges (`cauchyMatrix`), with entry `cauchyKernel L (slot e' - slot e)` between edges of the same +packet and zero between packets, is therefore the identity at `T = 0` when every edge has its own +slot in its packet (`cauchyMatrix_eval_zero`), and the deformed contraction along it evaluates to +the contraction at zero (`eval_zero_deformedContraction_cauchyMatrix`). + +## Main definitions + +* `Cslib.Circuits.Frontier.TensorNetwork.map`: a tensor network with its tensors mapped by a + ring homomorphism. +* `Cslib.Circuits.Frontier.TensorNetwork.affineFactor`: the affine factors `l_b`. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction`: the deformed contraction `F(W)`. +* `Cslib.Circuits.Frontier.cauchyGrid`, `Cslib.Circuits.Frontier.cauchyKernel`, + `Cslib.Circuits.Frontier.cauchyMatrix`: the grid, the Cauchy kernel and the Cauchy matrix of a + packing. + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_map`: contraction commutes with ring + homomorphisms, e.g. reduction of integer entries modulo `2 ^ P`. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_one`: `F(1) = Z`. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_map`: deformed contraction commutes + with ring homomorphisms. +* `Cslib.Circuits.Frontier.eval_zero_cauchyKernel`, + `Cslib.Circuits.Frontier.eval_cauchyKernel_of_ne`: values of the kernel on the grid. +* `Cslib.Circuits.Frontier.natDegree_cauchyKernel`: its degree `2 L - 1`. +* `Cslib.Circuits.Frontier.cauchyMatrix_eval_zero`: the Cauchy matrix is the identity at zero. +* `Cslib.Circuits.Frontier.TensorNetwork.eval_zero_deformedContraction_cauchyMatrix`: the + polynomial `F(T)` along the Cauchy matrix has value `Z` at `T = 0`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3, (D.7)–(D.8), + p. 59. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +open Polynomial + +namespace TensorNetwork + +variable {R S U V E : Type*} + +/-! ### Mapping the tensors -/ + +section Map + +variable [CommSemiring R] [CommSemiring S] (f : R →+* S) + +/-- The tensor network with every tensor entry mapped by the ring homomorphism `f`. -/ +def map (N : TensorNetwork R U V E) : TensorNetwork S U V E where + toMultigraph := N.toMultigraph + tensor v x := f (N.tensor v x) + tensor_congr v x y h := by rw [N.tensor_congr v x y h] + +@[simp] theorem map_tensor (N : TensorNetwork R U V E) (v : V) (x : E → U) : + (N.map f).tensor v x = f (N.tensor v x) := + rfl + +@[simp] theorem toMultigraph_map (N : TensorNetwork R U V E) : + (N.map f).toMultigraph = N.toMultigraph := + rfl + +/-- **Contraction commutes with ring homomorphisms.** For instance, contracting integer tensors +and reducing modulo `2 ^ P` is contracting the reduced tensors. -/ +theorem contraction_map [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] + (N : TensorNetwork R U V E) : (N.map f).contraction = f N.contraction := by + simp only [contraction, map_tensor, map_sum, map_prod] + +end Map + +/-! ### Deformed contraction -/ + +section Deformed + +variable [CommRing R] [Fintype V] [Fintype E] [DecidableEq E] + +/-- The affine factor `l_b`: `l_b z = z` for `b = true` and `l_b z = 1 - z` for `b = false`. At a +bit `z`, it is the indicator of `z = b`. -/ +def affineFactor (b : Bool) (z : R) : R := + if b then z else 1 - z + +/-- At a bit, the affine factor is the indicator of equality. -/ +theorem affineFactor_bit (b c : Bool) : + affineFactor b (if c then (1 : R) else 0) = if b = c then 1 else 0 := by + cases b <;> cases c <;> simp [affineFactor] + +/-- The *deformed contraction* of a Boolean tensor network whose vertices are split into rows +(`side v = true`) and columns, along a matrix `W` indexed by the edges: every edge carries a row +bit, read by the row tensors, and a column bit, read by the column tensors, joined through the +affine factors of `W`. -/ +def deformedContraction (N : TensorNetwork R Bool V E) (side : V → Bool) (W : Matrix E E R) : R := + ∑ X : E → Bool, ∑ Y : E → Bool, + (∏ v ∈ Finset.univ.filter (side · = true), N.tensor v Y) * + (∏ v ∈ Finset.univ.filter (side · = false), N.tensor v X) * + ∏ e, affineFactor (Y e) (W.mulVec (fun e' => if X e' then 1 else 0) e) + +/-- **The deformed contraction at the identity is the contraction.** The affine factors of the +identity matrix force the row bits to equal the column bits. -/ +theorem deformedContraction_one (N : TensorNetwork R Bool V E) (side : V → Bool) : + N.deformedContraction side 1 = N.contraction := by + classical + simp only [deformedContraction, Matrix.one_mulVec, affineFactor_bit, Fintype.prod_boole, + ← funext_iff, mul_ite, mul_one, mul_zero, Finset.sum_ite_eq', Finset.mem_univ, ite_true] + refine Finset.sum_congr rfl fun X _ => ?_ + have h := Finset.prod_filter_mul_prod_filter_not Finset.univ (fun v => side v = true) + (fun v => N.tensor v X) + simp only [Bool.not_eq_true] at h + exact h + +/-- Deformed contraction commutes with ring homomorphisms. -/ +theorem deformedContraction_map {S : Type*} [CommRing S] (f : R →+* S) + (N : TensorNetwork R Bool V E) (side : V → Bool) (W : Matrix E E R) : + (N.map f).deformedContraction side (W.map f) = f (N.deformedContraction side W) := by + simp only [deformedContraction, map_sum, map_mul, map_prod, map_tensor] + refine Finset.sum_congr rfl fun X _ => Finset.sum_congr rfl fun Y _ => ?_ + congr 1 + refine Finset.prod_congr rfl fun e _ => ?_ + have hv : (W.map f).mulVec (fun e' => if X e' then (1 : S) else 0) = + fun e => f (W.mulVec (fun e' => if X e' then (1 : R) else 0) e) := by + ext e + simp only [Matrix.mulVec, dotProduct, Matrix.map_apply, map_sum, map_mul, apply_ite f, + map_one, map_zero] + rw [hv] + cases Y e <;> simp [affineFactor] + +end Deformed + +end TensorNetwork + +/-! ### The Cauchy kernel -/ + +/-- The interpolation grid `S = {-L, …, L - 1}` of a packet with `L` slots. -/ +def cauchyGrid (L : ℕ) : Finset ℤ := + Finset.Ico (-L : ℤ) L + +theorem mem_cauchyGrid {L : ℕ} {t : ℤ} : t ∈ cauchyGrid L ↔ -(L : ℤ) ≤ t ∧ t < L := + Finset.mem_Ico + +/-- The difference of two slots of a packet lies on the grid. -/ +theorem sub_mem_cauchyGrid {L : ℕ} {x y : ℕ} (hx : x < L) (hy : y < L) : + (y : ℤ) - x ∈ cauchyGrid L := by + rw [mem_cauchyGrid] + omega + +/-- The *Cauchy kernel* at `d` on the grid of `L` slots: `C(T) / (C'(0) (T - d))`, where +`C(T) = ∏_{t ∈ S} (T - t)`. For `d ∈ S` it is the polynomial `C'(0)⁻¹ ∏_{t ∈ S, t ≠ d} (T - t)` +of degree `2 L - 1`; here `C'(0) = ∏_{t ∈ S, t ≠ 0} (0 - t)`. -/ +noncomputable def cauchyKernel (L : ℕ) (d : ℤ) : ℚ[X] := + C ((Lagrange.nodal ((cauchyGrid L).erase 0) (Int.cast : ℤ → ℚ)).eval 0)⁻¹ * + Lagrange.nodal ((cauchyGrid L).erase d) (Int.cast : ℤ → ℚ) + +/-- The normalization is `C'(0)`, the derivative of `C(T) = ∏_{t ∈ S} (T - t)` at zero. -/ +theorem eval_zero_nodal_erase_zero {L : ℕ} (hL : 0 < L) : + (Lagrange.nodal ((cauchyGrid L).erase 0) (Int.cast : ℤ → ℚ)).eval 0 = + (derivative (Lagrange.nodal (cauchyGrid L) (Int.cast : ℤ → ℚ))).eval 0 := by + have h0 : (0 : ℤ) ∈ cauchyGrid L := by rw [mem_cauchyGrid]; omega + have := Lagrange.eval_nodal_derivative_eval_node_eq (v := (Int.cast : ℤ → ℚ)) h0 + simpa using this.symm + +/-- `C'(0)` is nonzero: the grid has distinct points. -/ +theorem eval_zero_nodal_erase_zero_ne_zero (L : ℕ) : + (Lagrange.nodal ((cauchyGrid L).erase 0) (Int.cast : ℤ → ℚ)).eval 0 ≠ 0 := by + rw [Lagrange.eval_nodal, Finset.prod_ne_zero_iff] + intro t ht + have : t ≠ 0 := Finset.ne_of_mem_erase ht + simpa [sub_eq_zero, eq_comm] using this + +/-- On the grid, the kernel has degree `2 L - 1`, so a deformed contraction along a Cauchy matrix +has degree at most `M (2 L - 1)` in `T` for `M` edges. -/ +theorem natDegree_cauchyKernel {L : ℕ} {d : ℤ} (hd : d ∈ cauchyGrid L) : + (cauchyKernel L d).natDegree = 2 * L - 1 := by + rw [cauchyKernel, natDegree_C_mul (inv_ne_zero (eval_zero_nodal_erase_zero_ne_zero L)), + Lagrange.natDegree_nodal, Finset.card_erase_of_mem hd, cauchyGrid, Int.card_Ico] + omega + +/-- **The kernel at zero.** The kernel at `d = 0` is one at `T = 0`. -/ +theorem eval_zero_cauchyKernel_zero (L : ℕ) : (cauchyKernel L 0).eval 0 = 1 := by + rw [cauchyKernel, eval_mul, eval_C] + exact inv_mul_cancel₀ (eval_zero_nodal_erase_zero_ne_zero L) + +/-- **The kernel vanishes at the other grid points.** The kernel at `d` vanishes at every grid +point `t ≠ d`. -/ +theorem eval_cauchyKernel_of_ne {L : ℕ} {d t : ℤ} (ht : t ∈ cauchyGrid L) (htd : t ≠ d) : + (cauchyKernel L d).eval (t : ℚ) = 0 := by + rw [cauchyKernel, eval_mul, Lagrange.eval_nodal_at_node (Finset.mem_erase.mpr ⟨htd, ht⟩), + mul_zero] + +/-- The values of the kernel at `T = 0`: one at `d = 0`, and zero at every `d ≠ 0`, because then +`T = 0` is a root of one of its factors. -/ +theorem eval_zero_cauchyKernel {L : ℕ} (hL : 0 < L) (d : ℤ) : + (cauchyKernel L d).eval 0 = if d = 0 then 1 else 0 := by + split_ifs with h + · rw [h, eval_zero_cauchyKernel_zero] + · have h0 : (0 : ℤ) ∈ cauchyGrid L := by rw [mem_cauchyGrid]; omega + simpa using eval_cauchyKernel_of_ne h0 (Ne.symm h) + +/-- The *Cauchy matrix* of a packing of the edges into packets of `L` slots: between edges `e` +and `e'` of the same packet, the Cauchy kernel at the difference `slot e' - slot e` of their +slots; zero between different packets (the manuscript's (D.7)). -/ +noncomputable def cauchyMatrix {E P : Type*} [DecidableEq P] (L : ℕ) (packet : E → P) + (slot : E → ℕ) : Matrix E E ℚ[X] := + fun e e' => if packet e = packet e' then cauchyKernel L ((slot e' : ℤ) - slot e) else 0 + +/-- **The Cauchy matrix is the identity at zero** when every edge has its own slot in its packet, +below `L`. -/ +theorem cauchyMatrix_eval_zero {E P : Type*} [DecidableEq E] [DecidableEq P] {L : ℕ} + {packet : E → P} {slot : E → ℕ} (hslot : ∀ e, slot e < L) + (hinj : ∀ e e', packet e = packet e' → slot e = slot e' → e = e') : + (cauchyMatrix L packet slot).map (eval 0) = 1 := by + ext e e' + have hL : 0 < L := lt_of_le_of_lt (Nat.zero_le _) (hslot e) + simp only [Matrix.map_apply, cauchyMatrix, Matrix.one_apply] + split_ifs with hp he he + · subst he; simp [eval_zero_cauchyKernel_zero] + · rw [eval_zero_cauchyKernel hL, ite_eq_right] + intro h + exact he (hinj e e' hp (by omega)) + · subst he; exact absurd rfl hp + · exact eval_zero + +namespace TensorNetwork + +variable {R V E P : Type*} [CommRing R] [Algebra ℚ R] [Fintype V] [Fintype E] [DecidableEq E] + [DecidableEq P] + +/-- **The deformation along the Cauchy matrix starts at the contraction.** Over a `ℚ`-algebra, +the deformed contraction along the Cauchy matrix of a packing is a polynomial `F(T)` whose value at +`T = 0` is the contraction, when every edge has its own slot in its packet. -/ +theorem eval_zero_deformedContraction_cauchyMatrix (N : TensorNetwork R Bool V E) + (side : V → Bool) {L : ℕ} {packet : E → P} {slot : E → ℕ} (hslot : ∀ e, slot e < L) + (hinj : ∀ e e', packet e = packet e' → slot e = slot e' → e = e') : + ((N.map Polynomial.C).deformedContraction side + ((cauchyMatrix L packet slot).map (Polynomial.mapRingHom (algebraMap ℚ R)))).eval 0 = + N.contraction := by + have h := deformedContraction_map (Polynomial.evalRingHom (0 : R)) (N.map Polynomial.C) side + ((cauchyMatrix L packet slot).map (Polynomial.mapRingHom (algebraMap ℚ R))) + rw [coe_evalRingHom] at h + rw [← h] + have hN : (N.map Polynomial.C).map (Polynomial.evalRingHom (0 : R)) = N := by + cases N + simp [map] + have hW : ((cauchyMatrix L packet slot).map (Polynomial.mapRingHom (algebraMap ℚ R))).map + (eval 0) = 1 := by + rw [Matrix.map_map] + have : (eval (0 : R) ∘ ⇑(Polynomial.mapRingHom (algebraMap ℚ R))) = + (algebraMap ℚ R) ∘ (eval 0) := by + ext p + simp only [Function.comp_apply, coe_mapRingHom, eval_map, eval₂_at_zero, + coeff_zero_eq_eval_zero] + rw [this, ← Matrix.map_map, cauchyMatrix_eval_zero hslot hinj, + Matrix.map_one _ (map_zero _) (map_one _)] + rw [hN, hW, deformedContraction_one] + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkDeformation.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkDeformation.lean new file mode 100644 index 0000000000..1f1322b399 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkDeformation.lean @@ -0,0 +1,57 @@ +/- +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.Deformation + +/-! +# Tests for deformed contractions and the Cauchy kernel + +A one-edge network deformed along a `1 × 1` matrix, checked by hand against the expansion of +`F(W)`, and the Cauchy kernel of a packet with one slot, `C(T) = T (T + 1)`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Deformation + +open Cslib.Circuits.Frontier Polynomial + +/-- A row vertex `0` and a column vertex `1` joined by one edge, with tensors `A = (1, 2)` and +`B = (3, 5)` on its bit. -/ +def oneEdge : TensorNetwork ℤ Bool (Fin 2) (Fin 1) where + src _ := 0 + tgt _ := 1 + tensor v x := if v = 0 then (if x 0 then 2 else 1) else (if x 0 then 5 else 3) + tensor_congr v x y h := by rw [h 0 (by fin_cases v <;> simp)] + +/-- The rows: vertex `0`. -/ +def side (v : Fin 2) : Bool := v = 0 + +/-- By hand, `F(w) = A₀B₀ + A₀B₁ (1 - w) + A₁B₁ w`; at `w = 2` this is `3 - 5 + 20 = 18`. -/ +example : oneEdge.deformedContraction side (fun _ _ => 2) = 18 := by decide + +/-- At `w = 1` it is the contraction `A₀B₀ + A₁B₁ = 13`. -/ +example : oneEdge.deformedContraction side 1 = 13 ∧ oneEdge.contraction = 13 := by decide + +/-- Contraction commutes with reduction modulo `8`: `13 ≡ 5`. -/ +example : (oneEdge.map (Int.castRingHom (ZMod 8))).contraction = 5 := by + rw [TensorNetwork.contraction_map] + decide + +/-- With one slot the grid is `{-1, 0}`, `C'(0) = 1`, and the kernel at `-1` is `T`, whose value +at `T = 1` is `1`; the kernel at `0` is `T + 1`. -/ +example : (cauchyKernel 1 (-1)).eval 1 = 1 ∧ (cauchyKernel 1 0).eval 1 = 2 := by + have hS : cauchyGrid 1 = {-1, 0} := by decide + have h0 : ({-1, 0} : Finset ℤ).erase 0 = {-1} := by decide + have h1 : ({-1, 0} : Finset ℤ).erase (-1) = {0} := by decide + simp only [cauchyKernel, hS, h0, h1, eval_mul, eval_C, Lagrange.eval_nodal, + Finset.prod_singleton] + norm_num + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Deformation From b06c2112f2badd9381e157200e2880137e5a13db Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 18:19:33 -0400 Subject: [PATCH 03/33] refactor(circuit/frontier): index explicit tables by BitString.lsbValue Address the #1844 review: replace the duplicate public `tableIndex` encoding and its lemmas by the existing `Cslib.BitString.lsbValue` and `lsbValue_lt`. Add the missing digit lemma `BitString.testBit_lsbValue` next to `lsbValue` in `BitString/Counter.lean`, and update the table lookup, its proof and the tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Explicit.lean | 37 ++++++------------- .../Foundations/Data/BitString/Counter.lean | 11 ++++++ .../Circuit/Frontier/TensorNetwork.lean | 2 +- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean index 28f5dd3f54..051a95b90a 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Computability.Circuit.Frontier.TensorNetwork +public import Complexity.Foundations.Data.BitString.Counter import Mathlib.Algebra.BigOperators.Fin import Mathlib.Data.List.GetD @@ -19,7 +20,8 @@ the number of edges, numbered `0, 1, …`, and a list of vertices, each given by * its ordered list of *ports*, the numbers of the edges at the vertex, and * its *table*: the value of its tensor when port `i` carries the bit `bs[i]` is the entry at - position `tableIndex bs`, the number whose binary digit `i` is `bs[i]`. + position `BitString.lsbValue bs`, the number whose binary digit `i` is `bs[i]` + (`BitString.testBit_lsbValue`). So a vertex with `k` ports has a table of `2 ^ k` entries. The contraction of an explicit network (`ExplicitTensorNetwork.contraction`) sums, over all assignments of bits to the edges, the product @@ -37,7 +39,6 @@ order and tabulate its tensor on them. The contraction is unchanged ## Main definitions -* `Cslib.Circuits.Frontier.tableIndex`: the position of a list of bits in a table. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork`: an explicit Boolean tensor network. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.entry`, `Cslib.Circuits.Frontier.ExplicitTensorNetwork.portBits`: table lookup and the bits on a list @@ -51,8 +52,6 @@ order and tabulate its tensor on them. The contraction is unchanged ## Main results -* `Cslib.Circuits.Frontier.testBit_tableIndex`, `Cslib.Circuits.Frontier.tableIndex_lt`: the - digits and range of a table position. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.maxDegree_le_iff`: the degree bound. * `Cslib.Circuits.Frontier.TensorNetwork.entry_explicitTable`: a written-out table evaluates the tensor. @@ -68,24 +67,10 @@ order and tabulate its tensor on them. The contraction is unchanged namespace Cslib.Circuits.Frontier -/-- The position of a list of bits in a table: the number whose binary digit `i` is `bs[i]`, so -that the bits are read little-endian. -/ -def tableIndex (bs : List Bool) : ℕ := - (BitVec.ofBoolListLE bs).toNat - -/-- Binary digit `i` of the position of `bs` is `bs[i]`, or `false` past the end of `bs`. -/ -@[simp] theorem testBit_tableIndex (bs : List Bool) (i : ℕ) : - (tableIndex bs).testBit i = bs.getD i false := by - rw [tableIndex, BitVec.testBit_toNat, BitVec.getLsbD_ofBoolListLE] - -/-- A list of `k` bits has a position below `2 ^ k`. -/ -theorem tableIndex_lt (bs : List Bool) : tableIndex bs < 2 ^ bs.length := - BitVec.isLt _ - /-- An *explicit Boolean tensor network* over `R`: the number of edges, numbered from zero, and a list of vertices. A vertex is given by its ordered list of *ports*, the numbers of its edges, and -its *table*, whose entry at position `tableIndex bs` is the value of its tensor when port `i` -carries the bit `bs[i]`. -/ +its *table*, whose entry at position `BitString.lsbValue bs` is the value of its tensor when +port `i` carries the bit `bs[i]`. -/ structure ExplicitTensorNetwork (R : Type*) where /-- The number of edges; the edges are numbered `0, …, numEdges - 1`. -/ numEdges : ℕ @@ -96,10 +81,10 @@ namespace ExplicitTensorNetwork variable {R : Type*} (d : ExplicitTensorNetwork R) -/-- The entry of a table at a list of bits. A position past the end of the table has entry -zero. -/ +/-- The entry of a table at a list of bits, read little-endian (`BitString.lsbValue`). A position +past the end of the table has entry zero. -/ def entry [Zero R] (table : List R) (bs : List Bool) : R := - table.getD (tableIndex bs) 0 + table.getD (BitString.lsbValue bs) 0 /-- The bits on a list of ports under an assignment of bits to the edges. A port naming no edge reads `false`. -/ @@ -168,14 +153,14 @@ theorem entry_explicitTable [Zero R] (v : V) (x : Fin m → Bool) : ExplicitTensorNetwork.entry (N.explicitTable ee v) ((N.explicitPorts ee v).map x) = N.tensor v (x ∘ ee) := by set ps := N.explicitPorts ee v - have hlt : tableIndex (ps.map x) < (N.explicitTable ee v).length := by - simpa [explicitTable, ps] using tableIndex_lt (ps.map x) + have hlt : BitString.lsbValue (ps.map x) < (N.explicitTable ee v).length := by + simpa [explicitTable, ps] using BitString.lsbValue_lt (ps.map x) rw [ExplicitTensorNetwork.entry, List.getD_eq_getElem _ _ hlt] simp only [explicitTable, List.getElem_map, List.getElem_range] refine N.tensor_congr v _ _ fun e he => ?_ have hmem : ee e ∈ ps := (N.mem_explicitPorts ee).mpr (by simpa using he) have hidx := List.idxOf_lt_length_of_mem hmem - rw [testBit_tableIndex, List.getD_eq_getElem _ _ (by simpa using hidx), List.getElem_map, + rw [BitString.testBit_lsbValue, List.getD_eq_getElem _ _ (by simpa using hidx), List.getElem_map, List.getElem_idxOf hidx, Function.comp_apply] /-- A Boolean tensor network on finite types written out as an explicit network, numbering the diff --git a/Complexity/Foundations/Data/BitString/Counter.lean b/Complexity/Foundations/Data/BitString/Counter.lean index d13e8137f0..40319f05b8 100644 --- a/Complexity/Foundations/Data/BitString/Counter.lean +++ b/Complexity/Foundations/Data/BitString/Counter.lean @@ -35,6 +35,7 @@ their candidates in this way, storing only the current one. * `Cslib.BitString.iterate_increment`, `Cslib.BitString.iterate_increment_lsbValue`: counting from zero enumerates the words of a length. * `Cslib.BitString.lsbValue_eq_foldr_bit`: `lsbValue` is a right fold of `Nat.bit`. +* `Cslib.BitString.testBit_lsbValue`: binary digit `i` of `lsbValue w` is the bit `w[i]`. -/ @[expose] public section @@ -154,4 +155,14 @@ theorem lsbValue_eq_foldr_bit (w : List Bool) : lsbValue w = w.foldr Nat.bit 0 : | nil => rfl | cons b w ih => rw [lsbValue, List.foldr_cons, ← ih, Nat.bit_val, Nat.add_comm] +/-- Binary digit `i` of the value of a word is its bit `i`, or `false` past its end. -/ +@[simp] theorem testBit_lsbValue : ∀ (w : List Bool) (i : ℕ), + (lsbValue w).testBit i = w.getD i false + | [], _ => by simp [lsbValue] + | b :: w, i => by + rw [lsbValue_eq_foldr_bit, List.foldr_cons, ← lsbValue_eq_foldr_bit] + cases i with + | zero => simp + | succ i => simp [Nat.testBit_bit_succ, testBit_lsbValue w i] + end Cslib.BitString diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean index 5be31a11e9..27d867afd5 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean @@ -60,7 +60,7 @@ example : (cycle neTable).contraction = 0 := by decide /-- The table `A` or `B` of `parallel` as a tensor on assignments to `Fin 2`. -/ def table (v : Fin 2) (x : Fin 2 → Bool) : ℤ := - [[1, 2, 3, 4], [5, 6, 7, 8]][v]!.getD (tableIndex [x 0, x 1]) 0 + [[1, 2, 3, 4], [5, 6, 7, 8]][v]!.getD (Cslib.BitString.lsbValue [x 0, x 1]) 0 /-- The network `parallel` on `Fin 2` vertices and `Fin 2` edges, both edges from `0` to `1`. Every edge is at both vertices, so the tensors are trivially local. -/ From 31ff8a6ae3d937e6263f8f29045eb1005a0cf90d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 18:40:37 -0400 Subject: [PATCH 04/33] feat(circuit/frontier): Lemma D.4, integral local coordinates of the Cauchy kernel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T2.3 of #1538 (OpenAI2026ETH, §D.4.3, Lemma D.4, pp. 59-60), stacked on #1845. - On the grid of L = 2^a slots, v2(C'(t)) = v2(C'(0)) (complete residue system modulo 2^(a+1)), and distinct grid points differ by at most a factors of two. - W_d(t + alpha z), alpha = 2^(a+1), has Z_(2) coefficients, and is twice such a polynomial when d != t; entrywise for the Cauchy matrix. - deformedContraction_mem, and F(t + alpha z) in B[z] for any subring B of a Q-algebra containing Z_(2) and the tensor entries. - Rat.padicIntegerToZModPow: reduction of Z_(p) modulo p^n, through Z_[p]. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../TensorNetwork/IntegralCoordinates.lean | 365 ++++++++++++++++++ .../Padics/RatIntegerReduction.lean | 74 ++++ .../TensorNetworkIntegralCoordinates.lean | 110 ++++++ 4 files changed, 551 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean create mode 100644 Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean diff --git a/Complexity.lean b/Complexity.lean index 8ea2c2b140..ffc52e5b23 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -861,6 +861,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness @@ -2748,6 +2749,7 @@ public import Complexity.Foundations.NumberTheory.AKS.Criterion public import Complexity.Foundations.NumberTheory.AKS.Introspective public import Complexity.Foundations.NumberTheory.AKS.SmallR public import Complexity.Foundations.NumberTheory.Harmonic +public import Complexity.Foundations.NumberTheory.Padics.RatIntegerReduction public import Complexity.Foundations.NumberTheory.PrimeCounting public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.NumberTheory.SqrtPrimeWindow diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean new file mode 100644 index 0000000000..d44097932c --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean @@ -0,0 +1,365 @@ +/- +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.Deformation +public import Mathlib.Algebra.Polynomial.Lifts +public import Mathlib.NumberTheory.Padics.PadicNumbers +public import Mathlib.RingTheory.Valuation.Integers +import Mathlib.NumberTheory.Padics.PadicVal.Basic +import Mathlib.Tactic.FieldSimp +import Mathlib.Tactic.LinearCombination + +/-! +# Integral local coordinates for the Cauchy kernel + +This file proves Lemma D.4 of [OpenAI2026ETH] (§D.4.3, p. 59; proof on p. 60): near each grid +point the Cauchy kernel has `2`-integral coefficients. Let `L = 2 ^ a`, so that the grid +`S = {-L, …, L - 1}` is a complete residue system modulo `α = 2 ^ (a + 1)`. For `t, d ∈ S`, the +polynomial `z ↦ W_d(t + α z)` obtained from the Cauchy kernel `W_d = cauchyKernel L d` has +coefficients in `ℤ_(2)`, the rationals with odd denominator +(`Cslib.Circuits.Frontier.cauchyKernel_comp_mem_lifts`); when `d ≠ t` it is moreover divisible by +`2` (`Cslib.Circuits.Frontier.exists_cauchyKernel_comp_eq_two_mul`). Here `ℤ_(2)` is the valuation +ring `(Rat.padicValuation 2).integer`, and "coefficients in `ℤ_(2)`" is membership in +`Polynomial.lifts` of its inclusion. + +The proof is the manuscript's. Since `S` is a complete residue system modulo `2 ^ (a + 1)`, the +nonzero differences `t - q`, `q ∈ S`, have the same `2`-adic valuations for every `t`; so +`C'(t) = ∏_{q ≠ t} (t - q)` has the valuation of `C'(0)` +(`Cslib.Circuits.Frontier.padicValuation_prod_sub_eq`). Each factor `t - q + α z` with `q ≠ t` is +`(t - q) (1 + α / (t - q) z)` with `α / (t - q)` divisible by `2`, and the factor `q = t` is +`α z`. + +Entrywise, the Cauchy matrix has the same property +(`Cslib.Circuits.Frontier.cauchyMatrix_comp_mem_lifts`). Consequently, for tensors with entries in +a subring `B` of a `ℚ`-algebra `R` that contains `ℤ_(2)`, the deformed contraction `F(T)` along the +Cauchy matrix satisfies `F(t + α z) ∈ B[z]` +(`Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_cauchyMatrix_comp_mem_liftsRing`): the +manuscript's `F(t + α z) ∈ A[z]` with `B = O`. Coefficients in `ℤ_(2)` reduce modulo `2 ^ p` +through `Rat.padicIntegerToZModPow`. The algorithmic half of the lemma, evaluating +`W_xy(t + α ζ) mod 2 ^ p` in polynomial time, belongs to the runtime development. + +## Main results + +* `Cslib.Circuits.Frontier.padicValuation_prod_sub_eq`: `v₂(C'(t)) = v₂(C'(0))` on the grid. +* `Cslib.Circuits.Frontier.cauchyKernel_comp_mem_lifts`: `W_d(t + α z)` has `2`-integral + coefficients. +* `Cslib.Circuits.Frontier.exists_cauchyKernel_comp_eq_two_mul`: it is divisible by `2` unless + `d = t`. +* `Cslib.Circuits.Frontier.cauchyMatrix_comp_mem_lifts`: the same for the Cauchy matrix. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_mem`: a deformed contraction lies in + any subring containing the tensor and matrix entries. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_cauchyMatrix_comp_mem_liftsRing`: + `F(t + α z)` has coefficients in any subring containing `ℤ_(2)` and the tensor entries. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3, Lemma D.4, + pp. 59–60. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Polynomial +open scoped WithZero + +/-! ### Two-adic valuations on the grid -/ + +/-- The `2`-adic valuation of a nonzero integer. -/ +private theorem padicValuation_intCast {x : ℤ} (hx : x ≠ 0) : + Rat.padicValuation 2 (x : ℚ) = WithZero.exp (-(padicValInt 2 x : ℤ)) := by + simp [Rat.padicValuation, hx, padicValRat.of_int] + +/-- The `2`-adic valuation of a power of two. -/ +theorem padicValuation_two_pow (k : ℕ) : + Rat.padicValuation 2 ((2 : ℚ) ^ k) = WithZero.exp (-(k : ℤ)) := by + have h2 : Rat.padicValuation 2 (2 : ℚ) = WithZero.exp (-1) := by + exact_mod_cast Rat.padicValuation_self 2 + rw [map_pow, h2, ← WithZero.exp_nsmul, nsmul_eq_mul, mul_neg, mul_one] + +/-- **A nonzero integer below `2 ^ (a + 1)` in absolute value has at most `a` factors of two.** -/ +theorem padicValuation_two_pow_le {a : ℕ} {x : ℤ} (hx : x ≠ 0) (hlt : |x| < 2 ^ (a + 1)) : + Rat.padicValuation 2 ((2 : ℚ) ^ a) ≤ Rat.padicValuation 2 (x : ℚ) := by + rw [padicValuation_two_pow, padicValuation_intCast hx, WithZero.exp_le_exp, neg_le_neg_iff] + by_contra h + have hdvd : (2 : ℤ) ^ (a + 1) ∣ x := by + have := (padicValInt_dvd_iff (p := 2) (a + 1) x).mpr (Or.inr (by omega)) + exact_mod_cast this + exact absurd (Int.le_of_dvd (abs_pos.mpr hx) ((dvd_abs _ _).mpr hdvd)) (not_le.mpr hlt) + +/-- Adding `2 ^ (a + 1)` to a nonzero integer below it in absolute value keeps its `2`-adic +valuation. -/ +theorem padicValuation_add_two_pow {a : ℕ} {x : ℤ} (hx : x ≠ 0) (hlt : |x| < 2 ^ (a + 1)) : + Rat.padicValuation 2 ((x : ℚ) + 2 ^ (a + 1)) = Rat.padicValuation 2 (x : ℚ) := by + refine (Rat.padicValuation 2).map_add_eq_of_lt_left (lt_of_lt_of_le ?_ + (padicValuation_two_pow_le hx hlt)) + rw [padicValuation_two_pow, padicValuation_two_pow, WithZero.exp_lt_exp] + omega + +/-- **The grid is a complete residue system.** For `t` on the grid of `L = 2 ^ a` slots, the +nonzero differences `t - q`, `q ∈ S`, have the valuations of `1, …, 2 L - 1`: send `q` to the +residue of `t - q` modulo `2 L`. -/ +theorem padicValuation_prod_sub_eq_prod_Ioo {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) : + Rat.padicValuation 2 (∏ q ∈ (cauchyGrid (2 ^ a)).erase t, ((t : ℚ) - q)) = + ∏ r ∈ Finset.Ioo (0 : ℤ) (2 ^ (a + 1)), Rat.padicValuation 2 (r : ℚ) := by + rw [map_prod] + set L : ℤ := 2 ^ a with hL + have hL2 : (2 : ℤ) ^ (a + 1) = 2 * L := by rw [hL, pow_succ, mul_comm] + have hLpos : 0 < L := by positivity + rw [mem_cauchyGrid] at ht + simp only [cauchyGrid, Nat.cast_pow, Nat.cast_ofNat, ← hL] at ht ⊢ + rw [hL2] + refine Finset.prod_nbij' (fun q => if q < t then t - q else t - q + 2 * L) + (fun r => if -L ≤ t - r then t - r else t - r + 2 * L) ?_ ?_ ?_ ?_ ?_ + · intro q hq + simp only [Finset.mem_erase, Finset.mem_Ico, Finset.mem_Ioo] at hq ⊢ + split_ifs <;> omega + · intro r hr + simp only [Finset.mem_erase, Finset.mem_Ico, Finset.mem_Ioo] at hr ⊢ + split_ifs <;> omega + · intro q hq + simp only [Finset.mem_erase, Finset.mem_Ico] at hq + split_ifs <;> omega + · intro r hr + simp only [Finset.mem_Ioo] at hr + split_ifs <;> omega + · intro q hq + simp only [Finset.mem_erase, Finset.mem_Ico] at hq + have hne : t - q ≠ 0 := by omega + have habs : |t - q| < 2 ^ (a + 1) := by rw [hL2, abs_lt]; omega + have h2L : (2 : ℚ) * (L : ℚ) = 2 ^ (a + 1) := by exact_mod_cast hL2.symm + have hadd := padicValuation_add_two_pow hne habs + push_cast at hadd + split_ifs + · push_cast; rfl + · push_cast + rw [h2L] + exact hadd.symm + +/-- **`v₂(C'(t)) = v₂(C'(0))` on the grid** of `L = 2 ^ a` slots, where +`C'(t) = ∏_{q ∈ S, q ≠ t} (t - q)`. -/ +theorem padicValuation_prod_sub_eq {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) : + Rat.padicValuation 2 (∏ q ∈ (cauchyGrid (2 ^ a)).erase t, ((t : ℚ) - q)) = + Rat.padicValuation 2 (∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, ((0 : ℚ) - q)) := by + have h0 : (0 : ℤ) ∈ cauchyGrid (2 ^ a) := by + have : (0 : ℤ) < ((2 ^ a : ℕ) : ℤ) := by positivity + rw [mem_cauchyGrid] + exact ⟨by linarith, this⟩ + rw [padicValuation_prod_sub_eq_prod_Ioo ht] + exact_mod_cast (padicValuation_prod_sub_eq_prod_Ioo h0).symm + +/-- Distinct grid points of `L = 2 ^ a` slots differ by at most `a` factors of two. -/ +theorem padicValuation_two_pow_le_sub {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : + Rat.padicValuation 2 ((2 : ℚ) ^ a) ≤ Rat.padicValuation 2 ((t : ℚ) - q) := by + rw [mem_cauchyGrid] at ht hq + push_cast at ht hq + have hlt : |t - q| < 2 ^ (a + 1) := by + rw [pow_succ, abs_lt] + constructor <;> linarith + exact_mod_cast padicValuation_two_pow_le (sub_ne_zero.mpr hne.symm) hlt + +/-! ### The kernel in local coordinates -/ + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +private theorem C_mem_lifts_of_le_one {c : ℚ} (hc : Rat.padicValuation 2 c ≤ 1) : + C c ∈ lifts (ℤ₍₂₎).subtype := + C_mem_lifts (ℤ₍₂₎).subtype (⟨c, hc⟩ : ℤ₍₂₎) + +/-- A shifted factor `t + α z - q` with `q ≠ t` is `(t - q) (1 + α / (t - q) z)`. -/ +private theorem shift_factor {t q α : ℚ} (h : t - q ≠ 0) : + C t + C α * X - C q = C (t - q) * (1 + C (α / (t - q)) * X) := by + rw [mul_add, mul_one, ← mul_assoc, ← C_mul, mul_div_cancel₀ _ h, C_sub] + ring + +/-- The kernel in local coordinates, as a product of the shifted factors. -/ +private theorem cauchyKernel_comp (L : ℕ) (d : ℤ) (t α : ℚ) : + (cauchyKernel L d).comp (C t + C α * X) = + C ((∏ q ∈ (cauchyGrid L).erase 0, ((0 : ℚ) - q))⁻¹) * + ∏ q ∈ (cauchyGrid L).erase d, (C t + C α * X - C (q : ℚ)) := by + rw [cauchyKernel, Lagrange.eval_nodal, mul_comp, C_comp, Lagrange.nodal, prod_comp] + simp only [sub_comp, X_comp, C_comp] + +/-- The unit factors `1 + α / (t - q) z` with `q ≠ t` are integral. -/ +private theorem prod_one_add_mem_lifts {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (s : Finset ℤ) (hs : s ⊆ (cauchyGrid (2 ^ a)).erase t) : + ∏ q ∈ s, (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X) ∈ lifts (ℤ₍₂₎).subtype := by + refine Subsemiring.prod_mem _ fun q hq => ?_ + obtain ⟨hqt, hqS⟩ := Finset.mem_erase.mp (hs hq) + refine add_mem (one_mem _) (mul_mem (C_mem_lifts_of_le_one ?_) (X_mem_lifts _)) + have hle := padicValuation_two_pow_le_sub ht hqS hqt + have hne : ((t : ℚ) - q) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast hqt.symm) + rw [map_div₀, div_le_one₀ ((Rat.padicValuation 2).pos_iff.mpr hne)] + refine le_trans ?_ hle + rw [padicValuation_two_pow, padicValuation_two_pow, WithZero.exp_le_exp] + omega + +/-- A product of differences `t - q` over points `q ≠ t` is nonzero. -/ +private theorem prod_sub_ne_zero {t : ℤ} {s : Finset ℤ} (hs : t ∉ s) : + ∏ q ∈ s, ((t : ℚ) - q) ≠ 0 := + Finset.prod_ne_zero_iff.mpr fun q hq => + sub_ne_zero.mpr fun h => hs (by rw [show t = q by exact_mod_cast h]; exact hq) + +/-- Shifted factors at points `q ≠ t` split into a constant and the unit factors. -/ +private theorem prod_shift {t : ℤ} (α : ℚ) {s : Finset ℤ} (hs : t ∉ s) : + ∏ q ∈ s, (C (t : ℚ) + C α * X - C (q : ℚ)) = + C (∏ q ∈ s, ((t : ℚ) - q)) * ∏ q ∈ s, (1 + C (α / ((t : ℚ) - q)) * X) := by + calc + ∏ q ∈ s, (C (t : ℚ) + C α * X - C (q : ℚ)) = + ∏ q ∈ s, (C ((t : ℚ) - q) * (1 + C (α / ((t : ℚ) - q)) * X)) := + Finset.prod_congr rfl fun (q : ℤ) hq => shift_factor + (sub_ne_zero.mpr fun h => hs (by rw [show t = q by exact_mod_cast h]; exact hq)) + _ = _ := by rw [Finset.prod_mul_distrib, map_prod] + +/-- **Lemma D.4, divisibility.** Away from its own node, `d ≠ t`, the Cauchy kernel in local +coordinates `z ↦ W_d(t + α z)`, `α = 2 ^ (a + 1)`, is twice a polynomial with `2`-integral +coefficients: the factor `α z` survives, and `α / (t - d)` is divisible by `2`. -/ +theorem exists_cauchyKernel_comp_eq_two_mul {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) (hdt : d ≠ t) : + ∃ P ∈ lifts (ℤ₍₂₎).subtype, + (cauchyKernel (2 ^ a) d).comp (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X) = C 2 * P := by + have htd : t ∈ (cauchyGrid (2 ^ a)).erase d := Finset.mem_erase.mpr ⟨hdt.symm, ht⟩ + set s := ((cauchyGrid (2 ^ a)).erase d).erase t with hs_def + have hts : t ∉ s := Finset.notMem_erase t _ + have hs : s ⊆ (cauchyGrid (2 ^ a)).erase t := fun q hq => by + simp only [s, Finset.mem_erase] at hq ⊢ + exact ⟨hq.1, hq.2.2⟩ + have htd' : ((t : ℚ) - d) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast hdt.symm) + have hdt' : d ∈ (cauchyGrid (2 ^ a)).erase t := Finset.mem_erase.mpr ⟨hdt, hd⟩ + have hCt : ∏ q ∈ (cauchyGrid (2 ^ a)).erase t, ((t : ℚ) - q) = + ((t : ℚ) - d) * ∏ q ∈ s, ((t : ℚ) - q) := by + rw [hs_def, Finset.erase_right_comm] + exact (Finset.mul_prod_erase _ (fun q : ℤ => (t : ℚ) - q) hdt').symm + obtain ⟨C₀, hC₀def⟩ : ∃ C₀ : ℚ, C₀ = ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, ((0 : ℚ) - q) := + ⟨_, rfl⟩ + have hC₀ : C₀ ≠ 0 := by + have := prod_sub_ne_zero (Finset.notMem_erase (0 : ℤ) (cauchyGrid (2 ^ a))) + push_cast at this + rwa [hC₀def] + obtain ⟨c, hc⟩ : ∃ c : ℚ, c = C₀⁻¹ * (2 ^ (a + 1) * ∏ q ∈ s, ((t : ℚ) - q)) := ⟨_, rfl⟩ + refine ⟨C (c / 2) * X * ∏ q ∈ s, (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X), ?_, ?_⟩ + · refine mul_mem (mul_mem (C_mem_lifts_of_le_one ?_) (X_mem_lifts _)) + (prod_one_add_mem_lifts ht s hs) + have hc2 : c / 2 = + 2 ^ a * (C₀⁻¹ * ∏ q ∈ (cauchyGrid (2 ^ a)).erase t, ((t : ℚ) - q)) / ((t : ℚ) - d) := by + rw [hc, hCt, pow_succ] + field_simp + have hval : + Rat.padicValuation 2 (C₀⁻¹ * ∏ q ∈ (cauchyGrid (2 ^ a)).erase t, ((t : ℚ) - q)) = 1 := by + rw [map_mul, map_inv₀, padicValuation_prod_sub_eq ht, ← hC₀def, + inv_mul_cancel₀ ((Rat.padicValuation 2).ne_zero_iff.mpr hC₀)] + rw [hc2, map_div₀, map_mul, hval, mul_one, + div_le_one₀ ((Rat.padicValuation 2).pos_iff.mpr htd')] + exact padicValuation_two_pow_le_sub ht hd hdt + · rw [cauchyKernel_comp, ← Finset.mul_prod_erase _ _ htd, prod_shift _ hts, ← hC₀def, + show C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X - C (t : ℚ) = C ((2 : ℚ) ^ (a + 1)) * X by ring] + have h2 : C (2 : ℚ) * C (c / 2) = C c := by rw [← C_mul, mul_div_cancel₀ _ two_ne_zero] + have hcC : C c = C C₀⁻¹ * (C ((2 : ℚ) ^ (a + 1)) * C (∏ q ∈ s, ((t : ℚ) - q))) := by + rw [hc, C_mul, C_mul] + linear_combination + (-(X * ∏ q ∈ s, (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X))) * h2 - + (X * ∏ q ∈ s, (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X)) * hcC + +/-- **Lemma D.4, integrality.** For grid points `t, d` of `L = 2 ^ a` slots, the Cauchy kernel in +local coordinates `z ↦ W_d(t + α z)`, `α = 2 ^ (a + 1)`, has coefficients in `ℤ_(2)`. At its own +node, `d = t`, its constant term is `C'(t) / C'(0)`, of valuation one, and the other factors are +`1 + α / (t - q) z`. -/ +theorem cauchyKernel_comp_mem_lifts {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) : + (cauchyKernel (2 ^ a) d).comp (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X) ∈ + lifts (ℤ₍₂₎).subtype := by + rcases eq_or_ne d t with rfl | hdt + · have hts : d ∉ (cauchyGrid (2 ^ a)).erase d := Finset.notMem_erase d _ + rw [cauchyKernel_comp, prod_shift _ hts, ← mul_assoc, ← C_mul] + refine mul_mem (C_mem_lifts_of_le_one (le_of_eq ?_)) (prod_one_add_mem_lifts ht _ subset_rfl) + have hC₀ := prod_sub_ne_zero (Finset.notMem_erase (0 : ℤ) (cauchyGrid (2 ^ a))) + push_cast at hC₀ + rw [map_mul, map_inv₀, padicValuation_prod_sub_eq ht, + inv_mul_cancel₀ ((Rat.padicValuation 2).ne_zero_iff.mpr hC₀)] + · obtain ⟨P, hP, h⟩ := exists_cauchyKernel_comp_eq_two_mul ht hd hdt + rw [h] + refine mul_mem (C_mem_lifts_of_le_one ?_) hP + have h2 := padicValuation_two_pow 1 + rw [pow_one] at h2 + rw [h2, ← WithZero.exp_zero, WithZero.exp_le_exp] + norm_num + +/-! ### The Cauchy matrix and the deformed contraction -/ + +/-- **Lemma D.4 for the Cauchy matrix.** For a packing into packets of `2 ^ a` slots, every entry +of the Cauchy matrix in local coordinates `z ↦ W(t + α z)` has `2`-integral coefficients. -/ +theorem cauchyMatrix_comp_mem_lifts {E P : Type*} [DecidableEq P] {a : ℕ} {packet : E → P} + {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (e e' : E) : + (cauchyMatrix (2 ^ a) packet slot e e').comp (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X) ∈ + lifts (ℤ₍₂₎).subtype := by + unfold cauchyMatrix + split_ifs + · exact cauchyKernel_comp_mem_lifts ht (sub_mem_cauchyGrid (hslot e) (hslot e')) + · rw [zero_comp] + exact zero_mem _ + +namespace TensorNetwork + +variable {R V E : Type*} [CommRing R] [Fintype V] [Fintype E] [DecidableEq E] + +/-- A deformed contraction lies in every subring that contains the tensor entries and the matrix +entries: it is a polynomial expression in them. -/ +theorem deformedContraction_mem (N : TensorNetwork R Bool V E) (side : V → Bool) + (W : Matrix E E R) (T : Subring R) (hN : ∀ v x, N.tensor v x ∈ T) + (hW : ∀ e e', W e e' ∈ T) : N.deformedContraction side W ∈ T := by + unfold deformedContraction + refine T.sum_mem fun X _ => T.sum_mem fun Y _ => T.mul_mem (T.mul_mem + (T.prod_mem fun v _ => hN v Y) (T.prod_mem fun v _ => hN v X)) (T.prod_mem fun e _ => ?_) + have hv : W.mulVec (fun e' => if X e' then (1 : R) else 0) e ∈ T := by + unfold Matrix.mulVec dotProduct + refine T.sum_mem fun e' _ => T.mul_mem (hW e e') ?_ + dsimp only + split_ifs + exacts [T.one_mem, T.zero_mem] + unfold affineFactor + split_ifs + exacts [hv, T.sub_mem T.one_mem hv] + +/-- **Lemma D.4 for the deformed contraction.** Let `R` be a `ℚ`-algebra and `B` a subring of `R` +containing `ℤ_(2)`, and let the tensors have entries in `B`. Along the Cauchy matrix of a packing +into packets of `2 ^ a` slots, the deformed contraction `F(T)` satisfies `F(t + α z) ∈ B[z]` at +every grid point `t`, with `α = 2 ^ (a + 1)`. For the manuscript, `R = K`, `B = O` and `F(0)` is +the contraction (`eval_zero_deformedContraction_cauchyMatrix`). -/ +theorem deformedContraction_cauchyMatrix_comp_mem_liftsRing [Algebra ℚ R] {P : Type*} + [DecidableEq P] (N : TensorNetwork R Bool V E) (side : V → Bool) {a : ℕ} {packet : E → P} + {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (B : Subring R) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ R c ∈ B) (hN : ∀ v x, N.tensor v x ∈ B) : + ((N.map Polynomial.C).deformedContraction side + ((cauchyMatrix (2 ^ a) packet slot).map (Polynomial.mapRingHom (algebraMap ℚ R)))).comp + (C (t : R) + C (2 ^ (a + 1)) * X) ∈ liftsRing B.subtype := by + have hq : (C (t : R) + C (2 ^ (a + 1)) * X : R[X]) = + (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).map (algebraMap ℚ R) := by + rw [Polynomial.map_add, Polynomial.map_mul, Polynomial.map_C, Polynomial.map_C, + Polynomial.map_X] + exact congrArg₂ (· + ·) (congrArg C (map_intCast _ t).symm) + (congrArg (C · * X) (by rw [map_pow, map_ofNat])) + rw [← coe_compRingHom_apply, ← deformedContraction_map] + refine deformedContraction_mem _ side _ _ (fun v x => ?_) (fun e e' => ?_) + · rw [map_tensor, map_tensor, coe_compRingHom_apply, C_comp, ← lifts_iff_liftsRing] + exact C_mem_lifts B.subtype ⟨_, hN v x⟩ + · rw [Matrix.map_apply, Matrix.map_apply, coe_compRingHom_apply, coe_mapRingHom, hq, + ← map_comp, ← lifts_iff_liftsRing, lifts_iff_coeff_lifts] + intro n + obtain ⟨c, hc⟩ := (lifts_iff_coeff_lifts _).mp + (cauchyMatrix_comp_mem_lifts (packet := packet) hslot ht e e') n + rw [coeff_map, ← hc] + exact ⟨⟨_, hB c c.2⟩, rfl⟩ + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean b/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean new file mode 100644 index 0000000000..044fad57b9 --- /dev/null +++ b/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean @@ -0,0 +1,74 @@ +/- +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 (Mathlib, `Mathlib/NumberTheory/Padics/RingHoms.lean`) + +public import Complexity.Init +public import Mathlib.NumberTheory.Padics.RingHoms +public import Mathlib.RingTheory.Valuation.Integers + +/-! +# Reducing `p`-integral rationals modulo `p ^ n` + +The rationals whose denominator is prime to `p` form the valuation ring `ℤ_(p)` of +`Rat.padicValuation p`. It embeds in the `p`-adic integers (`Rat.padicIntegerToPadicInt`), so +it has reductions modulo every power of `p` (`Rat.padicIntegerToZModPow`). On `a / b` with `b` +prime to `p`, the reduction is `a * b⁻¹`: it is characterized by +`Rat.padicIntegerToZModPow_mul_den`. + +This reduces the integral local coordinates of the Cauchy kernel of [OpenAI2026ETH] (Lemma D.4, +pp. 59–60), whose coefficients lie in `ℤ_(2)`, modulo the working precision `2 ^ p`. + +## Main definitions + +* `Rat.padicIntegerToPadicInt`: the inclusion `ℤ_(p) → ℤ_[p]`. +* `Rat.padicIntegerToZModPow`: reduction `ℤ_(p) → ZMod (p ^ n)`. + +## Main results + +* `Rat.padicIntegerToZModPow_mul_den`: the reduction of `x` times the denominator of `x` is the + numerator of `x`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3, pp. 59–60. +-/ + +public section + +namespace Rat + +variable (p : ℕ) [Fact p.Prime] + +/-- The inclusion of the `p`-integral rationals `ℤ_(p)` in the `p`-adic integers. -/ +noncomputable def padicIntegerToPadicInt : (Rat.padicValuation p).integer →+* ℤ_[p] where + toFun x := ⟨(x : ℚ), Padic.norm_rat_le_one (Rat.padicValuation_le_one_iff.mp x.2)⟩ + map_one' := Subtype.ext (by simp) + map_mul' x y := Subtype.ext (by push_cast; rfl) + map_zero' := Subtype.ext (by simp) + map_add' x y := Subtype.ext (by push_cast; rfl) + +@[simp] +theorem coe_padicIntegerToPadicInt (x : (Rat.padicValuation p).integer) : + ((padicIntegerToPadicInt p x : ℤ_[p]) : ℚ_[p]) = ((x : ℚ) : ℚ_[p]) := by + simp only [padicIntegerToPadicInt] + rfl + +/-- Reduction of the `p`-integral rationals modulo `p ^ n`. -/ +noncomputable def padicIntegerToZModPow (n : ℕ) : (Rat.padicValuation p).integer →+* ZMod (p ^ n) := + (PadicInt.toZModPow n).comp (padicIntegerToPadicInt p) + +/-- **The reduction of `a / b` is `a * b⁻¹`**: multiplied by the denominator it gives the +numerator. -/ +theorem padicIntegerToZModPow_mul_den (n : ℕ) (x : (Rat.padicValuation p).integer) : + padicIntegerToZModPow p n x * ((x : ℚ).den : ZMod (p ^ n)) = ((x : ℚ).num : ZMod (p ^ n)) := by + have h : x * ((x : ℚ).den : (Rat.padicValuation p).integer) = + ((x : ℚ).num : (Rat.padicValuation p).integer) := + Subtype.ext (by push_cast; exact Rat.mul_den_eq_num _) + simpa only [map_mul, map_natCast, map_intCast] using congrArg (padicIntegerToZModPow p n) h + +end Rat diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean new file mode 100644 index 0000000000..998c69fca1 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean @@ -0,0 +1,110 @@ +/- +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.Circuit.Frontier.TensorNetwork.IntegralCoordinates +import Complexity.Foundations.NumberTheory.Padics.RatIntegerReduction + +/-! +# Tests for the integral local coordinates of the Cauchy kernel + +With `L = 2` slots the grid is `{-2, -1, 0, 1}`, `C'(0) = -2` and `α = 4`. The kernel at `1` is +`-(T + 2) (T + 1) T / 2`, whose leading coefficient `-1/2` is not `2`-integral; in the local +coordinate `T = 1 + 4 z` it becomes `2`-integral (Lemma D.4). +-/ + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates + +open Cslib.Circuits.Frontier Polynomial +open scoped WithZero + +/-- The grid of two slots. -/ +theorem cauchyGrid_two : cauchyGrid 2 = {-2, -1, 0, 1} := by decide + +/-- The leading coefficient of the kernel is `C'(0)⁻¹ = -1/2`. -/ +theorem coeff_cauchyKernel_two_one : (cauchyKernel 2 1).coeff 3 = -1 / 2 := by + have hmem : (1 : ℤ) ∈ cauchyGrid 2 := by decide + have hdeg := natDegree_cauchyKernel hmem + have hlead : (cauchyKernel 2 1).leadingCoeff = + ((Lagrange.nodal ((cauchyGrid 2).erase 0) (Int.cast : ℤ → ℚ)).eval 0)⁻¹ := by + rw [cauchyKernel, leadingCoeff_mul, leadingCoeff_C, Lagrange.nodal_monic.leadingCoeff, + mul_one] + rw [leadingCoeff, hdeg] at hlead + rw [show 3 = 2 * 2 - 1 from rfl, hlead, Lagrange.eval_nodal, cauchyGrid_two] + rw [show ({-2, -1, 0, 1} : Finset ℤ).erase 0 = {-2, -1, 1} by decide] + simp only [Finset.prod_insert (by decide : (-2 : ℤ) ∉ ({-1, 1} : Finset ℤ)), + Finset.prod_insert (by decide : (-1 : ℤ) ∉ ({1} : Finset ℤ)), Finset.prod_singleton] + norm_num + +theorem padicValRat_two : padicValRat 2 (2 : ℚ) = 1 := by + exact_mod_cast padicValRat.self (p := 2) one_lt_two + +/-- **The kernel itself is not `2`-integral**: its leading coefficient `-1/2` has a `2` in the +denominator. -/ +example : cauchyKernel 2 1 ∉ lifts (Rat.padicValuation 2).integer.subtype := by + rw [lifts_iff_coeff_lifts] + intro h + obtain ⟨c, hc⟩ := h 3 + have hle := c.2 + rw [Valuation.mem_integer_iff] at hle + change Rat.padicValuation 2 (c : ℚ) ≤ 1 at hle + have hv : Rat.padicValuation 2 (-1 / 2 : ℚ) = WithZero.exp 1 := by + rw [neg_div, Valuation.map_neg] + simp [Rat.padicValuation, padicValRat_two] + rw [show (c : ℚ) = -1 / 2 by rw [← coeff_cauchyKernel_two_one]; exact hc, hv, + ← WithZero.exp_zero, WithZero.exp_le_exp] at hle + exact absurd hle (by norm_num) + +/-- **In the local coordinate `T = 1 + 4 z` it is `2`-integral** (Lemma D.4, with `a = 1`). -/ +example : + (cauchyKernel 2 1).comp (C 1 + C 4 * X) ∈ lifts (Rat.padicValuation 2).integer.subtype := by + have h := cauchyKernel_comp_mem_lifts (a := 1) (t := 1) (d := 1) (by decide) (by decide) + norm_num at h + exact h + +/-- Away from its own node the kernel in local coordinates is divisible by `2`. -/ +example : ∃ P ∈ lifts (Rat.padicValuation 2).integer.subtype, + (cauchyKernel 2 0).comp (C 1 + C 4 * X) = C 2 * P := by + have h := exists_cauchyKernel_comp_eq_two_mul (a := 1) (t := 1) (d := 0) (by decide) + (by decide) (by decide) + norm_num at h + exact h + +/-! ### Reduction modulo `2 ^ p` -/ + +/-- `1 / 3 ≡ 3 (mod 8)`, since `3 * 3 = 9 ≡ 1`. -/ +example (h : Rat.padicValuation 2 ((3 : ℚ)⁻¹) ≤ 1) : + Rat.padicIntegerToZModPow 2 3 ⟨(3 : ℚ)⁻¹, h⟩ = 3 := by + have h3 := Rat.padicIntegerToZModPow_mul_den 2 3 ⟨(3 : ℚ)⁻¹, h⟩ + have hden : ((3 : ℚ)⁻¹).den = 3 := by simp + have hnum : ((3 : ℚ)⁻¹).num = 1 := by simp + simp only [hden, hnum, Nat.cast_ofNat, Int.cast_one] at h3 + have h9 : (3 * 3 : ZMod (2 ^ 3)) = 1 := by decide + calc Rat.padicIntegerToZModPow 2 3 ⟨(3 : ℚ)⁻¹, h⟩ + = Rat.padicIntegerToZModPow 2 3 ⟨(3 : ℚ)⁻¹, h⟩ * (3 * 3) := by rw [h9, mul_one] + _ = 3 := by rw [← mul_assoc, h3, one_mul] + +/-! ### The deformed contraction -/ + +/-- A row vertex `0` and a column vertex `1` joined by one edge, with integer tensors. -/ +def oneEdge : TensorNetwork ℚ Bool (Fin 2) (Fin 1) where + src _ := 0 + tgt _ := 1 + tensor v x := if v = 0 then (if x 0 then 2 else 1) else (if x 0 then 5 else 3) + tensor_congr v x y h := by rw [h 0 (by fin_cases v <;> simp)] + +/-- With one slot (`a = 0`, `α = 2`), `F(-1 + 2 z)` has `2`-integral coefficients. -/ +example : + ((oneEdge.map Polynomial.C).deformedContraction (fun v => v = 0) + ((cauchyMatrix (2 ^ 0) (fun _ : Fin 1 => ()) (fun _ => 0)).map + (Polynomial.mapRingHom (algebraMap ℚ ℚ)))).comp + (C ((-1 : ℤ) : ℚ) + C (2 ^ (0 + 1)) * X) ∈ + liftsRing (Rat.padicValuation 2).integer.subtype := + TensorNetwork.deformedContraction_cauchyMatrix_comp_mem_liftsRing _ _ (fun _ => by norm_num) + (by decide) _ (fun c hc => hc) fun v x => by + simp only [oneEdge] + split_ifs <;> exact (Rat.padicValuation_le_one_iff.mpr (by decide)) + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates From 3d383101a8009cd08e47489327f9146d83b48a90 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 18:44:25 -0400 Subject: [PATCH 05/33] refactor(foundations): move integer valuation bounds to RatValuation General p-adic facts used by Lemma D.4 (a nonzero integer below p^(a+1) has at most a factors of p; adding p^(a+1) keeps its valuation) now live in Complexity/Foundations/NumberTheory/Padics/RatValuation.lean for every prime p, next to the reduction of Z_(p) modulo p^n. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 +- .../TensorNetwork/IntegralCoordinates.lean | 42 ++---- .../Padics/RatIntegerReduction.lean | 74 ----------- .../NumberTheory/Padics/RatValuation.lean | 123 ++++++++++++++++++ .../TensorNetworkIntegralCoordinates.lean | 2 +- 5 files changed, 139 insertions(+), 104 deletions(-) delete mode 100644 Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean create mode 100644 Complexity/Foundations/NumberTheory/Padics/RatValuation.lean diff --git a/Complexity.lean b/Complexity.lean index ffc52e5b23..09b1a7b72d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2749,7 +2749,7 @@ public import Complexity.Foundations.NumberTheory.AKS.Criterion public import Complexity.Foundations.NumberTheory.AKS.Introspective public import Complexity.Foundations.NumberTheory.AKS.SmallR public import Complexity.Foundations.NumberTheory.Harmonic -public import Complexity.Foundations.NumberTheory.Padics.RatIntegerReduction +public import Complexity.Foundations.NumberTheory.Padics.RatValuation public import Complexity.Foundations.NumberTheory.PrimeCounting public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.NumberTheory.SqrtPrimeWindow diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean index d44097932c..be7ad962d0 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/IntegralCoordinates.lean @@ -11,7 +11,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformatio public import Mathlib.Algebra.Polynomial.Lifts public import Mathlib.NumberTheory.Padics.PadicNumbers public import Mathlib.RingTheory.Valuation.Integers -import Mathlib.NumberTheory.Padics.PadicVal.Basic +import Complexity.Foundations.NumberTheory.Padics.RatValuation import Mathlib.Tactic.FieldSimp import Mathlib.Tactic.LinearCombination @@ -41,8 +41,10 @@ a subring `B` of a `ℚ`-algebra `R` that contains `ℤ_(2)`, the deformed contr Cauchy matrix satisfies `F(t + α z) ∈ B[z]` (`Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_cauchyMatrix_comp_mem_liftsRing`): the manuscript's `F(t + α z) ∈ A[z]` with `B = O`. Coefficients in `ℤ_(2)` reduce modulo `2 ^ p` -through `Rat.padicIntegerToZModPow`. The algorithmic half of the lemma, evaluating -`W_xy(t + α ζ) mod 2 ^ p` in polynomial time, belongs to the runtime development. +through `Rat.padicIntegerToZModPow`; the valuations of integers that the proof uses are +`Rat.padicValuation_pow_le_of_abs_lt` and `Rat.padicValuation_add_pow_of_abs_lt`. The algorithmic +half of the lemma, evaluating `W_xy(t + α ζ) mod 2 ^ p` in polynomial time, belongs to the +runtime development. ## Main results @@ -72,36 +74,20 @@ open scoped WithZero /-! ### Two-adic valuations on the grid -/ -/-- The `2`-adic valuation of a nonzero integer. -/ -private theorem padicValuation_intCast {x : ℤ} (hx : x ≠ 0) : - Rat.padicValuation 2 (x : ℚ) = WithZero.exp (-(padicValInt 2 x : ℤ)) := by - simp [Rat.padicValuation, hx, padicValRat.of_int] - /-- The `2`-adic valuation of a power of two. -/ -theorem padicValuation_two_pow (k : ℕ) : +private theorem padicValuation_two_pow (k : ℕ) : Rat.padicValuation 2 ((2 : ℚ) ^ k) = WithZero.exp (-(k : ℤ)) := by - have h2 : Rat.padicValuation 2 (2 : ℚ) = WithZero.exp (-1) := by - exact_mod_cast Rat.padicValuation_self 2 - rw [map_pow, h2, ← WithZero.exp_nsmul, nsmul_eq_mul, mul_neg, mul_one] + exact_mod_cast Rat.padicValuation_pow_self 2 k -/-- **A nonzero integer below `2 ^ (a + 1)` in absolute value has at most `a` factors of two.** -/ -theorem padicValuation_two_pow_le {a : ℕ} {x : ℤ} (hx : x ≠ 0) (hlt : |x| < 2 ^ (a + 1)) : +private theorem padicValuation_two_pow_le {a : ℕ} {x : ℤ} (hx : x ≠ 0) + (hlt : |x| < 2 ^ (a + 1)) : Rat.padicValuation 2 ((2 : ℚ) ^ a) ≤ Rat.padicValuation 2 (x : ℚ) := by - rw [padicValuation_two_pow, padicValuation_intCast hx, WithZero.exp_le_exp, neg_le_neg_iff] - by_contra h - have hdvd : (2 : ℤ) ^ (a + 1) ∣ x := by - have := (padicValInt_dvd_iff (p := 2) (a + 1) x).mpr (Or.inr (by omega)) - exact_mod_cast this - exact absurd (Int.le_of_dvd (abs_pos.mpr hx) ((dvd_abs _ _).mpr hdvd)) (not_le.mpr hlt) - -/-- Adding `2 ^ (a + 1)` to a nonzero integer below it in absolute value keeps its `2`-adic -valuation. -/ -theorem padicValuation_add_two_pow {a : ℕ} {x : ℤ} (hx : x ≠ 0) (hlt : |x| < 2 ^ (a + 1)) : + exact_mod_cast Rat.padicValuation_pow_le_of_abs_lt (p := 2) hx (by exact_mod_cast hlt) + +private theorem padicValuation_add_two_pow {a : ℕ} {x : ℤ} (hx : x ≠ 0) + (hlt : |x| < 2 ^ (a + 1)) : Rat.padicValuation 2 ((x : ℚ) + 2 ^ (a + 1)) = Rat.padicValuation 2 (x : ℚ) := by - refine (Rat.padicValuation 2).map_add_eq_of_lt_left (lt_of_lt_of_le ?_ - (padicValuation_two_pow_le hx hlt)) - rw [padicValuation_two_pow, padicValuation_two_pow, WithZero.exp_lt_exp] - omega + exact_mod_cast Rat.padicValuation_add_pow_of_abs_lt (p := 2) hx (by exact_mod_cast hlt) /-- **The grid is a complete residue system.** For `t` on the grid of `L = 2 ^ a` slots, the nonzero differences `t - q`, `q ∈ S`, have the valuations of `1, …, 2 L - 1`: send `q` to the diff --git a/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean b/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean deleted file mode 100644 index 044fad57b9..0000000000 --- a/Complexity/Foundations/NumberTheory/Padics/RatIntegerReduction.lean +++ /dev/null @@ -1,74 +0,0 @@ -/- -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 (Mathlib, `Mathlib/NumberTheory/Padics/RingHoms.lean`) - -public import Complexity.Init -public import Mathlib.NumberTheory.Padics.RingHoms -public import Mathlib.RingTheory.Valuation.Integers - -/-! -# Reducing `p`-integral rationals modulo `p ^ n` - -The rationals whose denominator is prime to `p` form the valuation ring `ℤ_(p)` of -`Rat.padicValuation p`. It embeds in the `p`-adic integers (`Rat.padicIntegerToPadicInt`), so -it has reductions modulo every power of `p` (`Rat.padicIntegerToZModPow`). On `a / b` with `b` -prime to `p`, the reduction is `a * b⁻¹`: it is characterized by -`Rat.padicIntegerToZModPow_mul_den`. - -This reduces the integral local coordinates of the Cauchy kernel of [OpenAI2026ETH] (Lemma D.4, -pp. 59–60), whose coefficients lie in `ℤ_(2)`, modulo the working precision `2 ^ p`. - -## Main definitions - -* `Rat.padicIntegerToPadicInt`: the inclusion `ℤ_(p) → ℤ_[p]`. -* `Rat.padicIntegerToZModPow`: reduction `ℤ_(p) → ZMod (p ^ n)`. - -## Main results - -* `Rat.padicIntegerToZModPow_mul_den`: the reduction of `x` times the denominator of `x` is the - numerator of `x`. - -## References - -* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3, pp. 59–60. --/ - -public section - -namespace Rat - -variable (p : ℕ) [Fact p.Prime] - -/-- The inclusion of the `p`-integral rationals `ℤ_(p)` in the `p`-adic integers. -/ -noncomputable def padicIntegerToPadicInt : (Rat.padicValuation p).integer →+* ℤ_[p] where - toFun x := ⟨(x : ℚ), Padic.norm_rat_le_one (Rat.padicValuation_le_one_iff.mp x.2)⟩ - map_one' := Subtype.ext (by simp) - map_mul' x y := Subtype.ext (by push_cast; rfl) - map_zero' := Subtype.ext (by simp) - map_add' x y := Subtype.ext (by push_cast; rfl) - -@[simp] -theorem coe_padicIntegerToPadicInt (x : (Rat.padicValuation p).integer) : - ((padicIntegerToPadicInt p x : ℤ_[p]) : ℚ_[p]) = ((x : ℚ) : ℚ_[p]) := by - simp only [padicIntegerToPadicInt] - rfl - -/-- Reduction of the `p`-integral rationals modulo `p ^ n`. -/ -noncomputable def padicIntegerToZModPow (n : ℕ) : (Rat.padicValuation p).integer →+* ZMod (p ^ n) := - (PadicInt.toZModPow n).comp (padicIntegerToPadicInt p) - -/-- **The reduction of `a / b` is `a * b⁻¹`**: multiplied by the denominator it gives the -numerator. -/ -theorem padicIntegerToZModPow_mul_den (n : ℕ) (x : (Rat.padicValuation p).integer) : - padicIntegerToZModPow p n x * ((x : ℚ).den : ZMod (p ^ n)) = ((x : ℚ).num : ZMod (p ^ n)) := by - have h : x * ((x : ℚ).den : (Rat.padicValuation p).integer) = - ((x : ℚ).num : (Rat.padicValuation p).integer) := - Subtype.ext (by push_cast; exact Rat.mul_den_eq_num _) - simpa only [map_mul, map_natCast, map_intCast] using congrArg (padicIntegerToZModPow p n) h - -end Rat diff --git a/Complexity/Foundations/NumberTheory/Padics/RatValuation.lean b/Complexity/Foundations/NumberTheory/Padics/RatValuation.lean new file mode 100644 index 0000000000..3c249ac985 --- /dev/null +++ b/Complexity/Foundations/NumberTheory/Padics/RatValuation.lean @@ -0,0 +1,123 @@ +/- +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 (Mathlib, `Mathlib/NumberTheory/Padics/RingHoms.lean`) + +public import Complexity.Init +public import Mathlib.NumberTheory.Padics.RingHoms +public import Mathlib.RingTheory.Valuation.Integers +import Mathlib.NumberTheory.Padics.PadicVal.Basic + +/-! +# The `p`-adic valuation of rationals: integers and reduction modulo `p ^ n` + +Two facts about `Rat.padicValuation p`. First, valuations of integers below `p ^ (a + 1)`: such +a nonzero integer has at most `a` factors of `p` (`Rat.padicValuation_pow_le_of_abs_lt`), so adding +`p ^ (a + 1)` does not change its valuation (`Rat.padicValuation_add_pow_of_abs_lt`). + +Second, reduction modulo `p ^ n`. The rationals whose denominator is prime to `p` form the +valuation ring `ℤ_(p)` of `Rat.padicValuation p`. It embeds in the `p`-adic integers +(`Rat.padicIntegerToPadicInt`), so it has reductions modulo every power of `p` +(`Rat.padicIntegerToZModPow`). On `a / b` with `b` prime to `p`, the reduction is `a * b⁻¹`: it +is characterized by `Rat.padicIntegerToZModPow_mul_den`. + +Both serve the integral local coordinates of the Cauchy kernel of [OpenAI2026ETH] (Lemma D.4, +pp. 59–60): the first shows that its grid is a complete residue system for valuations, and the +second reduces its `ℤ_(2)` coefficients modulo the working precision `2 ^ p`. + +## Main definitions + +* `Rat.padicIntegerToPadicInt`: the inclusion `ℤ_(p) → ℤ_[p]`. +* `Rat.padicIntegerToZModPow`: reduction `ℤ_(p) → ZMod (p ^ n)`. + +## Main results + +* `Rat.padicValuation_pow_self`: `v (p ^ k) = exp (-k)`. +* `Rat.padicValuation_pow_le_of_abs_lt`: a nonzero integer below `p ^ (a + 1)` in absolute value + has valuation at least `v (p ^ a)`. +* `Rat.padicValuation_add_pow_of_abs_lt`: adding `p ^ (a + 1)` to it keeps its valuation. +* `Rat.padicIntegerToZModPow_mul_den`: the reduction of `x` times the denominator of `x` is the + numerator of `x`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3, pp. 59–60. +-/ + +public section + +open scoped WithZero + +namespace Rat + +variable (p : ℕ) [Fact p.Prime] + +/-! ### Valuations of integers -/ + +/-- The `p`-adic valuation of a nonzero integer. -/ +theorem padicValuation_intCast_of_ne_zero {x : ℤ} (hx : x ≠ 0) : + Rat.padicValuation p (x : ℚ) = WithZero.exp (-(padicValInt p x : ℤ)) := by + simp [Rat.padicValuation, hx, padicValRat.of_int] + +/-- The `p`-adic valuation of a power of `p`. -/ +theorem padicValuation_pow_self (k : ℕ) : + Rat.padicValuation p ((p : ℚ) ^ k) = WithZero.exp (-(k : ℤ)) := by + rw [map_pow, Rat.padicValuation_self, ← WithZero.exp_nsmul, nsmul_eq_mul, mul_neg, mul_one] + +variable {p} + +/-- **A nonzero integer below `p ^ (a + 1)` in absolute value has at most `a` factors of `p`.** -/ +theorem padicValuation_pow_le_of_abs_lt {a : ℕ} {x : ℤ} (hx : x ≠ 0) + (hlt : |x| < (p : ℤ) ^ (a + 1)) : + Rat.padicValuation p ((p : ℚ) ^ a) ≤ Rat.padicValuation p (x : ℚ) := by + rw [padicValuation_pow_self, padicValuation_intCast_of_ne_zero p hx, WithZero.exp_le_exp, + neg_le_neg_iff] + by_contra h + have hdvd : (p : ℤ) ^ (a + 1) ∣ x := (padicValInt_dvd_iff (a + 1) x).mpr (Or.inr (by omega)) + exact absurd (Int.le_of_dvd (abs_pos.mpr hx) ((dvd_abs _ _).mpr hdvd)) (not_le.mpr hlt) + +/-- **Adding `p ^ (a + 1)` to a nonzero integer below it in absolute value keeps its valuation.** -/ +theorem padicValuation_add_pow_of_abs_lt {a : ℕ} {x : ℤ} (hx : x ≠ 0) + (hlt : |x| < (p : ℤ) ^ (a + 1)) : + Rat.padicValuation p ((x : ℚ) + (p : ℚ) ^ (a + 1)) = Rat.padicValuation p (x : ℚ) := by + refine (Rat.padicValuation p).map_add_eq_of_lt_left (lt_of_lt_of_le ?_ + (padicValuation_pow_le_of_abs_lt hx hlt)) + rw [padicValuation_pow_self, padicValuation_pow_self, WithZero.exp_lt_exp] + omega + +variable (p) + +/-! ### Reduction modulo `p ^ n` -/ + +/-- The inclusion of the `p`-integral rationals `ℤ_(p)` in the `p`-adic integers. -/ +noncomputable def padicIntegerToPadicInt : (Rat.padicValuation p).integer →+* ℤ_[p] where + toFun x := ⟨(x : ℚ), Padic.norm_rat_le_one (Rat.padicValuation_le_one_iff.mp x.2)⟩ + map_one' := Subtype.ext (by simp) + map_mul' x y := Subtype.ext (by push_cast; rfl) + map_zero' := Subtype.ext (by simp) + map_add' x y := Subtype.ext (by push_cast; rfl) + +@[simp] +theorem coe_padicIntegerToPadicInt (x : (Rat.padicValuation p).integer) : + ((padicIntegerToPadicInt p x : ℤ_[p]) : ℚ_[p]) = ((x : ℚ) : ℚ_[p]) := by + simp only [padicIntegerToPadicInt] + rfl + +/-- Reduction of the `p`-integral rationals modulo `p ^ n`. -/ +noncomputable def padicIntegerToZModPow (n : ℕ) : (Rat.padicValuation p).integer →+* ZMod (p ^ n) := + (PadicInt.toZModPow n).comp (padicIntegerToPadicInt p) + +/-- **The reduction of `a / b` is `a * b⁻¹`**: multiplied by the denominator it gives the +numerator. -/ +theorem padicIntegerToZModPow_mul_den (n : ℕ) (x : (Rat.padicValuation p).integer) : + padicIntegerToZModPow p n x * ((x : ℚ).den : ZMod (p ^ n)) = ((x : ℚ).num : ZMod (p ^ n)) := by + have h : x * ((x : ℚ).den : (Rat.padicValuation p).integer) = + ((x : ℚ).num : (Rat.padicValuation p).integer) := + Subtype.ext (by push_cast; exact Rat.mul_den_eq_num _) + simpa only [map_mul, map_natCast, map_intCast] using congrArg (padicIntegerToZModPow p n) h + +end Rat diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean index 998c69fca1..65ad05b7a1 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkIntegralCoordinates.lean @@ -5,7 +5,7 @@ Authors: Samuel Schlesinger -/ import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates -import Complexity.Foundations.NumberTheory.Padics.RatIntegerReduction +import Complexity.Foundations.NumberTheory.Padics.RatValuation /-! # Tests for the integral local coordinates of the Cauchy kernel From adeeddc47d7dce6dc8f3da3a1866df592de1c22d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 19:10:47 -0400 Subject: [PATCH 06/33] feat(circuit/frontier): mode expansion and the child identity (D.16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A mode splitting of a matrix W sorts the vertices of a row-to-column Boolean tensor network into groups and its edges into packets, and splits every entry of W into a local part and a sum over modes. Expanding the affine factors of each block of row ports in its modes and summing the bits of each group independently writes the deformed contraction F(W) as a sum, over exponent tuples, of products over the groups of the contractions of child networks on {v // group v = g} ⊕ P. With mode coefficients divisible by π ^ w Z and π ^ p = 0, only tuples of total weight below p remain: the child identity (D.16) of OpenAI2026ETH, §D.4.5–§D.4.6, pp. 62–63. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/ModeExpansion.lean | 684 ++++++++++++++++++ .../Frontier/TensorNetworkModeExpansion.lean | 103 +++ 3 files changed, 788 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ModeExpansion.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkModeExpansion.lean diff --git a/Complexity.lean b/Complexity.lean index 8ea2c2b140..72282f4bf5 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -861,6 +861,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModeExpansion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModeExpansion.lean new file mode 100644 index 0000000000..422460a8ec --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ModeExpansion.lean @@ -0,0 +1,684 @@ +/- +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.Deformation +public import Mathlib.Data.Fintype.Option +import Mathlib.Algebra.BigOperators.Ring.Finset +import Mathlib.Data.Fintype.BigOperators + +/-! +# Modes and the child networks of a deformed contraction + +This file proves the child identity (D.16) of [OpenAI2026ETH] (§D.4.5–§D.4.6, pp. 62–63), the +step of the proof of the Boolean tensor contraction theorem (Theorem D.1) that turns one +deformed contraction into a sum of products of smaller contractions. + +**Setting.** Let `N` be a Boolean tensor network whose edges run from *rows* (their sources) to +*columns* (their targets), and let `W` be a matrix indexed by the edges. The deformed contraction +(`TensorNetwork.deformedContraction`, the manuscript's (D.8)) is + + `F(W) = ∑ X Y, A(Y) B(X) ∏ e, l_{Y e}((W X) e)`. + +A *mode splitting* (`TensorNetwork.ModeSplitting`) sorts the vertices on both sides into +*groups* and the edges into *packets*, and splits every entry of `W` as + + `W e e' = localPart e e' + ∑ Z, coeff e Z * form Z e'`, + +where the local part only joins a row port to a column port of the same packet and group, and +each *mode* `Z` has a packet, a *target* group, whose row ports carry its coefficients, and a +*source* group, whose column ports its linear form `L_Z(X) = ∑ e', form Z e' X e'` reads. In the +manuscript the modes are the terms of the geometric expansion (D.12) of the crossing entries of +the Cauchy matrix; here they are arbitrary. + +**Mode expansion.** Each affine factor `l_{Y e}((W X) e)` is affine in the mode values, so the +product of the affine factors of the row ports of one packet and group (a *block*) expands as a +polynomial in the modes targeted at that block. Its coefficient at the exponents `β` +(`ModeSplitting.blockCoeff`) sums, over the ways to pick the local term or one mode in each +factor with the multiplicities `β`, the product of the picked coefficients. + +**Children** (`ModeSplitting.child`). For a tuple of exponents `β` and a group `g`, the child +network has the vertices of group `g` from both sides of `N`, with their tensors, and one new +vertex per packet. Its edges are the ports of group `g`: an edge from the row of `e` to the packet +of `e` for each row port `e` of group `g`, carrying the row bit `Y e`, and one from the column of +`e` to the packet of `e` for each column port of group `g`, carrying the column bit `X e`. The +tensor at a packet (`ModeSplitting.packetTensor`) is the coefficient at `β` for that packet and +group times `∏ L_Z(X) ^ β Z` over the modes of that packet with source `g`. So children are +tensor networks on `{v // group v = g} ⊕ P`, bipartite with the old vertices as rows +(`ModeSplitting.isBipartite_child`). + +**The child identity** (`ModeSplitting.deformedContraction_eq_sum_prod_contraction_child`, the +manuscript's (D.16)). If the coefficient of each mode `Z` is divisible by `π ^ w Z` with +`w Z ≥ 1`, and `π ^ p = 0`, then + + `F(W) = ∑ β ∈ modeBudget w p, ∏ g, Z_{g, β}`, + +where `Z_{g, β}` is the contraction of the child of group `g` and `modeBudget w p` is the set of +tuples with `∑ Z, w Z * β Z < p` (the manuscript's (D.14)). The manuscript works modulo `2 ^ p_t` +with `π = 2` and `w Z = b + 1`; here the ring itself has `π ^ p = 0`, as in `A / 2 ^ p_t`, and a +congruence in a larger ring follows by mapping to its quotient +(`TensorNetwork.deformedContraction_map`). Without truncation, the same expansion gives +`F(W) = ∑ β, ∏ g, Z_{g, β}` over all tuples with entries at most the number of edges, in every +commutative ring (`ModeSplitting.deformedContraction_eq_sum_prod_contraction_child_of_le`). + +The proof is the manuscript's: distribute the product of the affine factors over the blocks, +expand each block in its modes, substitute the mode values, and sum the bits of each group +independently. The construction of the modes from the Cauchy kernel ((D.12)–(D.13)), the count +(D.15) of the tuples, and the size bounds on the children are separate steps. + +## Main definitions + +* `TensorNetwork.affineSign`: the sign of the variable part of an affine factor. +* `TensorNetwork.ModeSplitting`: groups, packets, and the splitting of a matrix into a local part + and modes. +* `TensorNetwork.modeBudget`: the tuples of exponents of total weight below `p`. +* `TensorNetwork.ModeSplitting.blockCoeff`: the coefficient of a mode monomial in the product of + the affine factors of a block. +* `TensorNetwork.ModeSplitting.packetTensor`: the tensor at a packet vertex of a child. +* `TensorNetwork.ModeSplitting.child`: the child network of a group. + +## Main results + +* `TensorNetwork.affineFactor_add`: `l_b (u + w) = l_b u ± w`. +* `TensorNetwork.mem_modeBudget`: membership in the budget. +* `TensorNetwork.ModeSplitting.packetTensor_congr`: a packet tensor reads only the ports of its + packet and group. +* `TensorNetwork.ModeSplitting.pow_dvd_blockCoeff`: the coefficient of a mode monomial is + divisible by `π` to its total weight. +* `TensorNetwork.ModeSplitting.isBipartite_child`, `TensorNetwork.ModeSplitting.card_edges_child`: + children are bipartite, with one edge per port of their group. +* `TensorNetwork.ModeSplitting.deformedContraction_eq_sum_prod_contraction_child_of_le`: the + exact expansion of `F(W)` into children. +* `TensorNetwork.ModeSplitting.deformedContraction_eq_sum_prod_contraction_child`: the child + identity (D.16). + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.5–§D.4.6, + (D.14) and (D.16), pp. 62–63. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Finset + +namespace TensorNetwork + +variable {R V E : Type*} [CommRing R] + +/-! ### Affine factors of a sum -/ + +/-- The sign of the variable part of an affine factor: `affineSign true = 1` and +`affineSign false = -1`, so that `l_b (u + w) = l_b u + affineSign b * w`. -/ +@[expose] def affineSign (b : Bool) : R := + if b then 1 else -1 + +/-- An affine factor of a sum: `l_b (u + w) = l_b u + affineSign b * w`. -/ +theorem affineFactor_add (b : Bool) (u w : R) : + affineFactor b (u + w) = affineFactor b u + affineSign b * w := by + cases b <;> simp only [affineFactor, affineSign, Bool.false_eq_true, ite_false, ite_true] <;> + ring + +/-! ### Mode splittings -/ + +/-- A *mode splitting* (§D.4.5 of [OpenAI2026ETH]) of the deformed contraction of a Boolean tensor +network `N` along a matrix `W`. The rows of `N` (`side v = true`) are the sources of its edges and +the columns their targets. The vertices on both sides are sorted into *groups* and the edges into +*packets*, and every entry of `W` splits into a *local part*, which joins a row port to a column +port of the same packet and group, and a sum over the *modes* `Z`, each contributing +`coeff e Z * form Z e'`. A mode has a packet, a *target* group, the only row ports of its packet +whose forms contain it, and a *source* group, the only column ports of its packet that its linear +form reads. -/ +structure ModeSplitting (N : TensorNetwork R Bool V E) (W : Matrix E E R) (G P M : Type*) + [Fintype M] where + /-- The side of a vertex: `true` for rows, `false` for columns. -/ + side : V → Bool + /-- The group of a vertex. -/ + group : V → G + /-- The packet of an edge. -/ + packet : E → P + /-- The local part of the matrix. -/ + localPart : Matrix E E R + /-- The packet of a mode. -/ + modePacket : M → P + /-- The source group of a mode: its form reads the column ports of this group. -/ + source : M → G + /-- The target group of a mode: it occurs in the forms of the row ports of this group. -/ + target : M → G + /-- The coefficient of a mode in the form of a row port. -/ + coeff : E → M → R + /-- The linear form in the column bits that a mode stands for. -/ + form : M → E → R + /-- Edges start at rows. -/ + side_src : ∀ e, side (N.src e) = true + /-- Edges end at columns. -/ + side_tgt : ∀ e, side (N.tgt e) = false + /-- The splitting of the matrix. -/ + apply_eq : ∀ e e', W e e' = localPart e e' + ∑ Z, coeff e Z * form Z e' + /-- The local part joins ports of the same packet and group. -/ + localPart_ne_zero : ∀ e e', localPart e e' ≠ 0 → + packet e' = packet e ∧ group (N.tgt e') = group (N.src e) + /-- A mode occurs only in the forms of the row ports of its packet and target group. -/ + coeff_ne_zero : ∀ e Z, coeff e Z ≠ 0 → modePacket Z = packet e ∧ target Z = group (N.src e) + /-- A mode reads only the column ports of its packet and source group. -/ + form_ne_zero : ∀ Z e, form Z e ≠ 0 → modePacket Z = packet e ∧ source Z = group (N.tgt e) + +/-- The tuples of exponents `β` of total weight `∑ Z, w Z * β Z` below `p`, the index set of the +manuscript's (D.14). -/ +@[expose] def modeBudget {M : Type*} [Fintype M] [DecidableEq M] (w : M → ℕ) (p : ℕ) : + Finset (M → ℕ) := + {β ∈ Fintype.piFinset fun _ => range p | ∑ Z, w Z * β Z < p} + +/-- For positive weights, the budget holds exactly the tuples of total weight below `p`. -/ +theorem mem_modeBudget {M : Type*} [Fintype M] [DecidableEq M] {w : M → ℕ} (hw : ∀ Z, 0 < w Z) + {p : ℕ} {β : M → ℕ} : β ∈ modeBudget w p ↔ ∑ Z, w Z * β Z < p := by + simp only [modeBudget, mem_filter, Fintype.mem_piFinset, mem_range, and_iff_right_iff_imp] + intro h Z + calc β Z ≤ w Z * β Z := Nat.le_mul_of_pos_left _ (hw Z) + _ ≤ ∑ Z, w Z * β Z := single_le_sum (fun Z _ => Nat.zero_le (w Z * β Z)) (mem_univ Z) + _ < p := h + +namespace ModeSplitting + +variable {G P M : Type*} [Fintype M] {N : TensorNetwork R Bool V E} {W : Matrix E E R} + (S : ModeSplitting N W G P M) + +/-- The block of a row port: its packet and the group of its row. -/ +@[expose] def rowBlock (e : E) : P × G := + (S.packet e, S.group (N.src e)) + +/-- The block of a mode: its packet and its target group. -/ +@[expose] def targetBlock (Z : M) : P × G := + (S.modePacket Z, S.target Z) + +/-- The source block of a mode: its packet and its source group. -/ +@[expose] def sourceBlock (Z : M) : P × G := + (S.modePacket Z, S.source Z) + +section Forms + +variable [Fintype E] + +/-- The local form of a row port on the column bits `X`. -/ +@[expose] def localForm (X : E → Bool) (e : E) : R := + ∑ e', S.localPart e e' * if X e' then 1 else 0 + +/-- The value `L_Z(X) = ∑ e', form Z e' X e'` of a mode on the column bits `X`. -/ +@[expose] def modeValue (X : E → Bool) (Z : M) : R := + ∑ e', S.form Z e' * if X e' then 1 else 0 + +/-- The terms of the expansion of the affine factor of a row port: the local term, and the +coefficient of each mode. -/ +@[expose] def choiceTerm (Y X : E → Bool) (e : E) : Option M → R + | none => affineFactor (Y e) (S.localForm X e) + | some Z => affineSign (Y e) * S.coeff e Z + +theorem localForm_congr {X X' : E → Bool} {e : E} + (h : ∀ e', S.packet e' = S.packet e → S.group (N.tgt e') = S.group (N.src e) → X e' = X' e') : + S.localForm X e = S.localForm X' e := by + refine sum_congr rfl fun e' _ => ?_ + by_cases h0 : S.localPart e e' = 0 + · rw [h0, zero_mul, zero_mul] + · obtain ⟨hp, hg⟩ := S.localPart_ne_zero e e' h0 + rw [h e' hp hg] + +theorem modeValue_congr {X X' : E → Bool} {Z : M} + (h : ∀ e, S.packet e = S.modePacket Z → S.group (N.tgt e) = S.source Z → X e = X' e) : + S.modeValue X Z = S.modeValue X' Z := by + refine sum_congr rfl fun e _ => ?_ + by_cases h0 : S.form Z e = 0 + · rw [h0, zero_mul, zero_mul] + · obtain ⟨hp, hg⟩ := S.form_ne_zero Z e h0 + rw [h e hp.symm hg.symm] + +/-- The form of a row port splits into its local form and its modes. -/ +theorem mulVec_apply (X : E → Bool) (e : E) : + W.mulVec (fun e' => if X e' then 1 else 0) e = + S.localForm X e + ∑ Z, S.coeff e Z * S.modeValue X Z := by + simp only [Matrix.mulVec, dotProduct, S.apply_eq, add_mul, sum_add_distrib, localForm, + modeValue, mul_sum, sum_mul] + rw [sum_comm (s := univ) (t := univ) (f := fun e' Z => S.coeff e Z * S.form Z e' * _)] + simp only [mul_assoc] + +/-- The affine factor of a row port as the sum of its terms, each times the value of its mode. -/ +theorem affineFactor_mulVec (Y X : E → Bool) (e : E) : + affineFactor (Y e) (W.mulVec (fun e' => if X e' then 1 else 0) e) = + ∑ o : Option M, S.choiceTerm Y X e o * o.elim 1 (S.modeValue X) := by + rw [S.mulVec_apply, affineFactor_add, Fintype.sum_option, mul_sum] + simp only [choiceTerm, Option.elim, mul_one, mul_assoc] + +end Forms + +/-! ### Coefficients of mode monomials -/ + +section Coefficients + +variable [Fintype E] [DecidableEq E] [DecidableEq P] [DecidableEq G] [DecidableEq M] + +/-- The coefficient of the mode monomial with exponents `β` in the product of the affine factors +of the row ports of the block `k`. It sums, over the ways `φ` to pick in each factor either its +local term or one of the modes targeted at `k`, each mode `Z` of `k` picked `β Z` times, the +product of the picked terms. It depends only on the exponents of the modes of `k`. -/ +@[expose] def blockCoeff (k : P × G) (β : M → ℕ) (Y X : E → Bool) : R := + ∑ φ : {e // S.rowBlock e = k} → Option M, + if (∀ e Z, φ e = some Z → S.targetBlock Z = k) ∧ + ∀ Z, S.targetBlock Z = k → #{e | φ e = some Z} = β Z then + ∏ e, S.choiceTerm Y X e.1 (φ e) + else 0 + +/-- The tensor at the vertex of packet `q` in the child of group `g`: the coefficient of the mode +monomial `β` for the row ports of `q` in group `g`, times `L_Z(X) ^ β Z` for the modes of `q` with +source `g`. -/ +@[expose] def packetTensor (β : M → ℕ) (q : P) (g : G) (Y X : E → Bool) : R := + S.blockCoeff (q, g) β Y X * ∏ Z with S.sourceBlock Z = (q, g), S.modeValue X Z ^ β Z + +/-- A block coefficient depends only on the exponents of the modes of its block. -/ +theorem blockCoeff_congr_exponents {k : P × G} {β β' : M → ℕ} + (h : ∀ Z, S.targetBlock Z = k → β Z = β' Z) (Y X : E → Bool) : + S.blockCoeff k β Y X = S.blockCoeff k β' Y X := by + refine sum_congr rfl fun φ _ => ?_ + refine if_congr (and_congr Iff.rfl (forall_congr' fun Z => imp_congr_right fun hZ => ?_)) rfl + rfl + rw [h Z hZ] + +/-- **A packet tensor is local:** it reads only the row and column ports of its packet and +group. -/ +theorem packetTensor_congr (β : M → ℕ) (q : P) (g : G) {Y Y' X X' : E → Bool} + (hY : ∀ e, S.packet e = q → S.group (N.src e) = g → Y e = Y' e) + (hX : ∀ e, S.packet e = q → S.group (N.tgt e) = g → X e = X' e) : + S.packetTensor β q g Y X = S.packetTensor β q g Y' X' := by + unfold packetTensor blockCoeff + congr 1 + · refine sum_congr rfl fun φ _ => if_congr Iff.rfl (prod_congr rfl fun e _ => ?_) rfl + obtain ⟨e, he⟩ := e + simp only [rowBlock, Prod.mk.injEq] at he + have hl : S.localForm X e = S.localForm X' e := S.localForm_congr fun e' hp hg => + hX e' (hp.trans he.1) (hg.trans he.2) + cases φ ⟨e, _⟩ <;> simp only [choiceTerm, hY e he.1 he.2, hl] + · refine prod_congr rfl fun Z hZ => ?_ + simp only [sourceBlock, mem_filter, mem_univ, true_and, Prod.mk.injEq] at hZ + rw [S.modeValue_congr fun e hp hg => hX e (hp.trans hZ.1) (hg.trans hZ.2)] + +/-- A choice of terms with exponent `β Z` above the number of edges never exists, so the block +coefficient of such a mode's block vanishes. -/ +theorem blockCoeff_eq_zero_of_card_lt {Z : M} {β : M → ℕ} (hZ : Fintype.card E < β Z) + (Y X : E → Bool) : S.blockCoeff (S.targetBlock Z) β Y X = 0 := by + refine sum_eq_zero fun φ _ => ite_eq_right fun h => ?_ + have hle : #{e | φ e = some Z} ≤ Fintype.card E := + (card_filter_le _ _).trans (by rw [card_univ]; exact Fintype.card_subtype_le _) + rw [h.2 Z rfl] at hle + omega + +omit [DecidableEq E] in +/-- The weight of a picked term: zero for the local term and `w Z` for the mode `Z`. -/ +private theorem sum_elim_weight {k : P × G} (w : M → ℕ) (φ : {e // S.rowBlock e = k} → Option M) : + ∑ e, (φ e).elim 0 w = ∑ Z, #{e | φ e = some Z} * w Z := by + rw [← sum_fiberwise' univ φ (fun o => o.elim 0 w)] + simp only [sum_const, smul_eq_mul, Fintype.sum_option, Option.elim, mul_zero, zero_add] + +/-- **The coefficient of a mode monomial is divisible by `π` to its total weight**, when the +coefficient of each mode `Z` is divisible by `π ^ w Z`. -/ +theorem pow_dvd_blockCoeff (w : M → ℕ) {π : R} (hdiv : ∀ e Z, π ^ w Z ∣ S.coeff e Z) + (k : P × G) (β : M → ℕ) (Y X : E → Bool) : + π ^ (∑ Z with S.targetBlock Z = k, w Z * β Z) ∣ S.blockCoeff k β Y X := by + refine dvd_sum fun φ _ => ?_ + split_ifs with h + · have hexp : ∑ Z with S.targetBlock Z = k, w Z * β Z = ∑ e, (φ e).elim 0 w := by + rw [sum_elim_weight, sum_filter] + refine sum_congr rfl fun Z _ => ?_ + split_ifs with hZ + · rw [h.2 Z hZ, mul_comm] + · have : #{e | φ e = some Z} = 0 := card_eq_zero.mpr (filter_eq_empty_iff.mpr + fun e _ he => hZ (h.1 e Z he)) + rw [this, zero_mul] + rw [hexp, ← prod_pow_eq_pow_sum] + refine prod_dvd_prod_of_dvd _ _ fun e _ => ?_ + cases φ e with + | none => simp only [Option.elim, pow_zero, one_dvd] + | some Z => exact Dvd.dvd.mul_left (hdiv e.1 Z) _ + · exact dvd_zero _ + +/-- The product of the block coefficients vanishes when the total weight reaches `p`, if +`π ^ p = 0`. -/ +theorem prod_blockCoeff_eq_zero (w : M → ℕ) {π : R} (hdiv : ∀ e Z, π ^ w Z ∣ S.coeff e Z) + {p : ℕ} (hπ : π ^ p = 0) [Fintype P] [Fintype G] {β : M → ℕ} + (hβ : p ≤ ∑ Z, w Z * β Z) (Y X : E → Bool) : ∏ k, S.blockCoeff k β Y X = 0 := by + have hdvd : π ^ (∑ Z, w Z * β Z) ∣ ∏ k, S.blockCoeff k β Y X := by + rw [← sum_fiberwise univ S.targetBlock (fun Z => w Z * β Z), ← prod_pow_eq_pow_sum] + exact prod_dvd_prod_of_dvd _ _ fun k _ => S.pow_dvd_blockCoeff w hdiv k β Y X + rw [← Nat.add_sub_cancel' hβ, pow_add, hπ, zero_mul] at hdvd + exact zero_dvd_iff.mp hdvd + +end Coefficients + +/-! ### The mode expansion -/ + +section Expansion + +variable [Fintype E] [DecidableEq E] [DecidableEq P] [DecidableEq G] [DecidableEq M] + +omit [DecidableEq E] in +/-- The product of the values of the picked modes, the local term counting as one. -/ +private theorem prod_elim_modeValue {k : P × G} (φ : {e // S.rowBlock e = k} → Option M) + (X : E → Bool) : + ∏ e, (φ e).elim 1 (S.modeValue X) = ∏ Z, S.modeValue X Z ^ #{e | φ e = some Z} := by + rw [← prod_fiberwise' univ φ (fun o => o.elim 1 (S.modeValue X))] + simp only [prod_const, Fintype.prod_option, Option.elim, one_pow, one_mul] + +/-- The exponent tuples supported on the modes of one block, with entries at most `n`. -/ +private abbrev blockBox (n : ℕ) (k : P × G) : Finset (M → ℕ) := + {β ∈ Fintype.piFinset fun _ => range (n + 1) | ∀ Z, S.targetBlock Z ≠ k → β Z = 0} + +/-- **Mode expansion of one block.** The product of the affine factors of the row ports of a +block is a polynomial in the values of the modes targeted at it, with the block coefficients. -/ +private theorem prod_block (Y X : E → Bool) (k : P × G) : + ∏ e : {e // S.rowBlock e = k}, + affineFactor (Y e) (W.mulVec (fun e' => if X e' then 1 else 0) e) = + ∑ β ∈ S.blockBox (Fintype.card E) k, S.blockCoeff k β Y X * ∏ Z, S.modeValue X Z ^ β Z := by + simp only [S.affineFactor_mulVec, Fintype.prod_sum, prod_mul_distrib, S.prod_elim_modeValue, + blockCoeff, sum_mul, ite_mul, zero_mul] + rw [sum_comm] + refine sum_congr rfl fun φ _ => ?_ + set c : M → ℕ := fun Z => #{e | φ e = some Z} with hc + by_cases hv : ∀ e Z, φ e = some Z → S.targetBlock Z = k + · rw [sum_eq_single c] + · rw [ite_eq_left ⟨hv, fun Z _ => rfl⟩] + · intro β hβ hne + refine ite_eq_right fun h => hne (funext fun Z => ?_) + by_cases hZ : S.targetBlock Z = k + · exact (h.2 Z hZ).symm + · rw [(mem_filter.mp hβ).2 Z hZ] + exact (card_eq_zero.mpr (filter_eq_empty_iff.mpr fun e _ he => hZ (hv e Z he))).symm + · intro hn + refine absurd ?_ hn + simp only [mem_filter, Fintype.mem_piFinset, mem_range] + refine ⟨fun Z => Nat.lt_succ_of_le ((card_filter_le _ _).trans ?_), fun Z hZ => ?_⟩ + · rw [card_univ] + exact Fintype.card_subtype_le _ + · exact card_eq_zero.mpr (filter_eq_empty_iff.mpr fun e _ he => hZ (hv e Z he)) + · push Not at hv + obtain ⟨e, Z, he, hZ⟩ := hv + rw [sum_eq_zero fun β _ => ite_eq_right fun h => hZ (h.1 e Z he)] + refine mul_eq_zero_of_left (prod_eq_zero (mem_univ e) ?_) _ + rw [he, choiceTerm] + refine mul_eq_zero_of_right _ (by_contra fun h0 => hZ ?_) + obtain ⟨hp, hg⟩ := S.coeff_ne_zero e.1 Z h0 + rw [targetBlock, hp, hg] + exact e.2 + +omit [Fintype E] [DecidableEq E] in +/-- Sums over the tuples supported on each block combine into one sum over all tuples. -/ +private theorem prod_sum_blockBox (n : ℕ) [Fintype P] [Fintype G] (f : P × G → (M → ℕ) → R) : + ∏ k, ∑ β ∈ S.blockBox n k, f k β = + ∑ β ∈ Fintype.piFinset (fun _ : M => range (n + 1)), + ∏ k, f k (fun Z => if S.targetBlock Z = k then β Z else 0) := by + rw [prod_univ_sum] + refine sum_nbij' (fun x Z => x (S.targetBlock Z) Z) + (fun β k Z => if S.targetBlock Z = k then β Z else 0) ?_ ?_ ?_ ?_ ?_ + · intro x hx + simp only [Fintype.mem_piFinset, mem_filter] at hx ⊢ + exact fun Z => (hx (S.targetBlock Z)).1 Z + · intro β hβ + simp only [Fintype.mem_piFinset, mem_filter, mem_range] at hβ ⊢ + refine fun k => ⟨fun Z => ?_, fun Z hZ => ite_eq_right hZ⟩ + split_ifs + exacts [hβ Z, Nat.succ_pos n] + · intro x hx + simp only [Fintype.mem_piFinset, mem_filter] at hx + funext k Z + split_ifs with h + · subst h + rfl + · exact ((hx k).2 Z h).symm + · intro β _ + funext Z + simp only [ite_true] + · intro x hx + simp only [Fintype.mem_piFinset, mem_filter] at hx + refine prod_congr rfl fun k _ => congrArg (f k) (funext fun Z => ?_) + split_ifs with h + · subst h + rfl + · exact (hx k).2 Z h + +/-- **Mode expansion.** The product of all affine factors is a sum over exponent tuples of the +product of the block coefficients times the mode monomial. -/ +private theorem prod_affineFactor_eq_sum [Fintype P] [Fintype G] (Y X : E → Bool) : + ∏ e, affineFactor (Y e) (W.mulVec (fun e' => if X e' then 1 else 0) e) = + ∑ β ∈ Fintype.piFinset (fun _ : M => range (Fintype.card E + 1)), + (∏ k, S.blockCoeff k β Y X) * ∏ Z, S.modeValue X Z ^ β Z := by + rw [← Fintype.prod_fiberwise S.rowBlock] + simp only [S.prod_block, S.prod_sum_blockBox] + refine sum_congr rfl fun β _ => ?_ + rw [prod_mul_distrib] + congr 1 + · exact prod_congr rfl fun k _ => S.blockCoeff_congr_exponents (fun Z hZ => ite_eq_left hZ) Y X + · rw [prod_comm] + refine prod_congr rfl fun Z _ => ?_ + simp only [pow_ite, pow_zero] + exact Fintype.prod_ite_eq (S.targetBlock Z) _ + +/-- **Truncated mode expansion.** When `π ^ p = 0`, only the tuples in the budget remain. -/ +private theorem prod_affineFactor_eq_sum_modeBudget [Fintype P] [Fintype G] (w : M → ℕ) + (hw : ∀ Z, 0 < w Z) {π : R} {p : ℕ} (hπ : π ^ p = 0) (hdiv : ∀ e Z, π ^ w Z ∣ S.coeff e Z) + (Y X : E → Bool) : + ∏ e, affineFactor (Y e) (W.mulVec (fun e' => if X e' then 1 else 0) e) = + ∑ β ∈ modeBudget w p, (∏ k, S.blockCoeff k β Y X) * ∏ Z, S.modeValue X Z ^ β Z := by + rw [S.prod_affineFactor_eq_sum] + set box := Fintype.piFinset (fun _ : M => range (Fintype.card E + 1)) + rw [← sum_subset (inter_subset_left (s₁ := box) (s₂ := modeBudget w p)), + ← sum_subset (inter_subset_right (s₁ := box) (s₂ := modeBudget w p))] + · intro β hβ hn + have hbox : β ∉ box := fun h => hn (mem_inter.mpr ⟨h, hβ⟩) + simp only [box, Fintype.mem_piFinset, mem_range, not_forall, not_lt] at hbox + obtain ⟨Z, hZ⟩ := hbox + have hZ' : Fintype.card E < β Z := by omega + rw [prod_eq_zero (i := S.targetBlock Z) (mem_univ _) + (S.blockCoeff_eq_zero_of_card_lt hZ' Y X), zero_mul] + · intro β hβ hn + have hb : β ∉ modeBudget w p := fun h => hn (mem_inter.mpr ⟨hβ, h⟩) + rw [mem_modeBudget hw, not_lt] at hb + rw [S.prod_blockCoeff_eq_zero w hdiv hπ hb, zero_mul] + +end Expansion + +/-! ### Child networks -/ + +section Child + +variable [Fintype E] [DecidableEq E] [DecidableEq P] [DecidableEq G] [DecidableEq M] + [DecidableEq V] + +/-- The *child network* of the group `g` for the exponents `β` (§D.4.6). Its vertices are the +vertices of group `g`, from both sides, and one vertex per packet. For every row port `e` of group +`g` it has an edge from the row of `e` to the packet of `e`, and for every column port of group +`g` an edge from the column of `e` to the packet of `e`. A row keeps its tensor, reading the row +bits on its edges, and a column keeps its tensor, reading the column bits; the tensor at a packet +is `packetTensor`. -/ +@[expose] def child (β : M → ℕ) (g : G) : + TensorNetwork R Bool ({v // S.group v = g} ⊕ P) + ({e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g}) where + src := Sum.elim (fun e => .inl ⟨N.src e.1, e.2⟩) (fun e => .inl ⟨N.tgt e.1, e.2⟩) + tgt := Sum.elim (fun e => .inr (S.packet e.1)) (fun e => .inr (S.packet e.1)) + tensor + | .inl v, y => + if S.side v.1 then + N.tensor v.1 fun e => if h : N.src e = v.1 then y (.inl ⟨e, h ▸ v.2⟩) else false + else + N.tensor v.1 fun e => if h : N.tgt e = v.1 then y (.inr ⟨e, h ▸ v.2⟩) else false + | .inr q, y => + S.packetTensor β q g (fun e => if h : S.group (N.src e) = g then y (.inl ⟨e, h⟩) else false) + (fun e => if h : S.group (N.tgt e) = g then y (.inr ⟨e, h⟩) else false) + tensor_congr := by + rintro (v | q) y y' h + · have hs : ∀ e (he : N.src e = v.1), y (.inl ⟨e, he ▸ v.2⟩) = y' (.inl ⟨e, he ▸ v.2⟩) := by + intro e he + exact h _ (Or.inl (congrArg Sum.inl (Subtype.ext he))) + have ht : ∀ e (he : N.tgt e = v.1), y (.inr ⟨e, he ▸ v.2⟩) = y' (.inr ⟨e, he ▸ v.2⟩) := by + intro e he + exact h _ (Or.inl (congrArg Sum.inl (Subtype.ext he))) + simp only + split_ifs + · congr 1 + funext e + split_ifs with he + exacts [hs e he, rfl] + · congr 1 + funext e + split_ifs with he + exacts [ht e he, rfl] + · refine S.packetTensor_congr β q g (fun e hq hg => ?_) (fun e hq hg => ?_) + · simp only [hg, dite_true] + exact h _ (Or.inr (by simp only [Sum.elim_inl, hq])) + · simp only [hg, dite_true] + exact h _ (Or.inr (by simp only [Sum.elim_inr, hq])) + +@[simp] theorem child_src_inl (β : M → ℕ) (g : G) (e : {e // S.group (N.src e) = g}) : + (S.child β g).src (.inl e) = .inl ⟨N.src e.1, e.2⟩ := + rfl + +@[simp] theorem child_src_inr (β : M → ℕ) (g : G) (e : {e // S.group (N.tgt e) = g}) : + (S.child β g).src (.inr e) = .inl ⟨N.tgt e.1, e.2⟩ := + rfl + +@[simp] theorem child_tgt_inl (β : M → ℕ) (g : G) (e : {e // S.group (N.src e) = g}) : + (S.child β g).tgt (.inl e) = .inr (S.packet e.1) := + rfl + +@[simp] theorem child_tgt_inr (β : M → ℕ) (g : G) (e : {e // S.group (N.tgt e) = g}) : + (S.child β g).tgt (.inr e) = .inr (S.packet e.1) := + rfl + +/-- **Children are bipartite**, with the old vertices on one side and the packets on the other; +every edge runs from an old vertex to a packet. -/ +theorem isBipartite_child (β : M → ℕ) (g : G) : (S.child β g).IsBipartite := + ⟨Sum.isLeft, by rintro (e | e) <;> simp⟩ + +omit [DecidableEq E] [DecidableEq P] [DecidableEq M] [DecidableEq V] in +/-- A child has one edge for each port of its group. -/ +theorem card_edges_child (g : G) : + Fintype.card ({e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g}) = + #{e | S.group (N.src e) = g} + #{e | S.group (N.tgt e) = g} := by + rw [Fintype.card_sum, Fintype.card_subtype, Fintype.card_subtype] + +/-- The bits of the ports of group `g`, read off the row bits `Y` and the column bits `X`. -/ +private abbrev groupBits (Y X : E → Bool) (g : G) : + {e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g} → Bool := + Sum.elim (fun e => Y e.1) (fun e => X e.1) + +/-- On the bits of its group, an old vertex of a child reads the row bits if it is a row and the +column bits if it is a column. -/ +private theorem child_tensor_inl_groupBits (β : M → ℕ) (g : G) (v : {v // S.group v = g}) + (Y X : E → Bool) : + (S.child β g).tensor (.inl v) (S.groupBits Y X g) = + if S.side v.1 then N.tensor v.1 Y else N.tensor v.1 X := by + simp only [child] + split_ifs with hv + · refine N.tensor_congr v.1 _ _ fun e he => ?_ + by_cases hs : N.src e = v.1 + · simp only [hs, dite_true, Sum.elim_inl] + · have ht : N.tgt e = v.1 := he.resolve_left hs + have := S.side_tgt e + rw [ht, hv] at this + exact absurd this (by decide) + · refine N.tensor_congr v.1 _ _ fun e he => ?_ + by_cases ht : N.tgt e = v.1 + · simp only [ht, dite_true, Sum.elim_inr] + · have hs : N.src e = v.1 := he.resolve_right ht + have := S.side_src e + rw [hs] at this + exact absurd this hv + +/-- On the bits of its group, a packet of a child reads the row and column bits. -/ +private theorem child_tensor_inr_groupBits (β : M → ℕ) (g : G) (q : P) (Y X : E → Bool) : + (S.child β g).tensor (.inr q) (S.groupBits Y X g) = S.packetTensor β q g Y X := by + simp only [child] + refine S.packetTensor_congr β q g (fun e _ hg => ?_) (fun e _ hg => ?_) + · simp only [hg, dite_true, Sum.elim_inl] + · simp only [hg, dite_true, Sum.elim_inr] + +/-- **The weight of an assignment factors over the groups.** For every tuple `β`, the term of +the expanded deformed contraction at the row bits `Y` and column bits `X` is the product, over the +groups, of the weights of the bits of each group in its child. -/ +private theorem weight_eq_prod_child [Fintype V] [Fintype P] [Fintype G] (β : M → ℕ) + (Y X : E → Bool) : + (∏ v ∈ univ.filter (S.side · = true), N.tensor v Y) * + (∏ v ∈ univ.filter (S.side · = false), N.tensor v X) * + ((∏ k, S.blockCoeff k β Y X) * ∏ Z, S.modeValue X Z ^ β Z) = + ∏ g, ∏ x, (S.child β g).tensor x (S.groupBits Y X g) := by + simp only [Fintype.prod_sum_type, S.child_tensor_inl_groupBits, S.child_tensor_inr_groupBits, + prod_mul_distrib] + rw [Fintype.prod_fiberwise S.group (fun v => if S.side v then N.tensor v Y else N.tensor v X), + prod_ite] + simp only [Bool.not_eq_true] + have hk : ∏ k, S.blockCoeff k β Y X = ∏ g, ∏ q, S.blockCoeff (q, g) β Y X := by + rw [Fintype.prod_prod_type, prod_comm] + have hZ : ∏ Z, S.modeValue X Z ^ β Z = + ∏ g, ∏ q, ∏ Z with S.sourceBlock Z = (q, g), S.modeValue X Z ^ β Z := by + rw [prod_comm, ← Fintype.prod_prod_type (f := fun k => ∏ Z with S.sourceBlock Z = k, + S.modeValue X Z ^ β Z), prod_fiberwise] + simp only [packetTensor, prod_mul_distrib, hk, hZ] + +/-- Splitting the row and column bits by group. -/ +private def splitBits : + (E → Bool) × (E → Bool) ≃ + ∀ g, ({e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g} → Bool) where + toFun YX g := S.groupBits YX.1 YX.2 g + invFun y := (fun e => y _ (.inl ⟨e, rfl⟩), fun e => y _ (.inr ⟨e, rfl⟩)) + left_inv _ := rfl + right_inv y := by + funext g c + rcases c with ⟨e, rfl⟩ | ⟨e, rfl⟩ <;> rfl + +/-- **The sum over the bits factors over the groups**: for each tuple `β`, the sum over all row +and column bits of the expanded term is the product of the contractions of the children. -/ +private theorem sum_weight_eq_prod_contraction_child [Fintype V] [Fintype P] [Fintype G] + (β : M → ℕ) : + ∑ X : E → Bool, ∑ Y : E → Bool, + (∏ v ∈ univ.filter (S.side · = true), N.tensor v Y) * + (∏ v ∈ univ.filter (S.side · = false), N.tensor v X) * + ((∏ k, S.blockCoeff k β Y X) * ∏ Z, S.modeValue X Z ^ β Z) = + ∏ g, (S.child β g).contraction := by + simp only [S.weight_eq_prod_child, contraction] + rw [Fintype.prod_sum, sum_comm, ← Fintype.sum_prod_type'] + exact Fintype.sum_equiv S.splitBits _ _ fun _ => rfl + +/-- **The exact child expansion.** In every commutative ring, the deformed contraction is the sum, +over the exponent tuples with entries at most the number of edges, of the products over the +groups of the contractions of the children. -/ +theorem deformedContraction_eq_sum_prod_contraction_child_of_le [Fintype V] [Fintype P] + [Fintype G] : + N.deformedContraction S.side W = + ∑ β ∈ Fintype.piFinset (fun _ : M => range (Fintype.card E + 1)), + ∏ g, (S.child β g).contraction := by + simp only [deformedContraction, S.prod_affineFactor_eq_sum, mul_sum] + exact (sum_congr rfl fun X _ => sum_comm).trans + (sum_comm.trans (sum_congr rfl fun β _ => S.sum_weight_eq_prod_contraction_child β)) + +/-- **The child identity** ((D.16) of [OpenAI2026ETH]). If the coefficient of every mode `Z` is +divisible by `π ^ w Z`, with positive weights, and `π ^ p = 0`, then the deformed contraction is +the sum, over the exponent tuples of total weight below `p`, of the products over the groups of +the contractions of the children. In the manuscript, `π = 2`, `p = p_t` and the ring is +`A / 2 ^ p_t`. -/ +theorem deformedContraction_eq_sum_prod_contraction_child [Fintype V] [Fintype P] [Fintype G] + (w : M → ℕ) (hw : ∀ Z, 0 < w Z) {π : R} {p : ℕ} (hπ : π ^ p = 0) + (hdiv : ∀ e Z, π ^ w Z ∣ S.coeff e Z) : + N.deformedContraction S.side W = ∑ β ∈ modeBudget w p, ∏ g, (S.child β g).contraction := by + simp only [deformedContraction, S.prod_affineFactor_eq_sum_modeBudget w hw hπ hdiv, mul_sum] + exact (sum_congr rfl fun X _ => sum_comm).trans + (sum_comm.trans (sum_congr rfl fun β _ => S.sum_weight_eq_prod_contraction_child β)) + +end Child + +end ModeSplitting + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkModeExpansion.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkModeExpansion.lean new file mode 100644 index 0000000000..227e628e6f --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkModeExpansion.lean @@ -0,0 +1,103 @@ +/- +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.ModeExpansion + +/-! +# Tests for the mode expansion and the child identity + +Two edges `0 : 0 → 2` and `1 : 1 → 3`, from the rows `0, 1` to the columns `2, 3`, in one packet. +Vertex `v` is in group `v % 2`, so edge `e` joins two vertices of group `e`, and the entries of the +matrix `W = !![1, 2; 2, 3]` off the diagonal cross between the groups. Over `ZMod 4` they become two +modes of weight one, since `2 ∣ 2` and `2 ^ 2 = 0`: mode `Z` reads the column bit of edge `1 - Z` +and enters the form of row port `Z`. The child identity (D.16) then writes the deformed +contraction as a sum over the three tuples `β` of total weight below two. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion + +open Cslib.Circuits.Frontier TensorNetwork + +/-- The edge at a vertex: vertices `0` and `2` are the ends of edge `0`, and `1` and `3` of +edge `1`. -/ +def edgeAt (v : Fin 4) : Fin 2 := ⟨v.val % 2, Nat.mod_lt _ (by decide)⟩ + +/-- The two-edge network, with tensor `(1, v + 1)` on the bit of the edge at `v`. -/ +def net : TensorNetwork (ZMod 4) Bool (Fin 4) (Fin 2) where + src e := ⟨e.val, by omega⟩ + tgt e := ⟨e.val + 2, by omega⟩ + tensor v x := if x (edgeAt v) then (v.val + 1 : ZMod 4) else 1 + tensor_congr v x y h := by rw [h (edgeAt v) (by fin_cases v <;> decide)] + +/-- The matrix: the identity on the diagonal, and `2` between the groups. -/ +def W : Matrix (Fin 2) (Fin 2) (ZMod 4) := !![1, 2; 2, 3] + +/-- The splitting of `W` into its diagonal and two modes. -/ +def S : net.ModeSplitting W (Fin 2) Unit (Fin 2) where + side v := decide (v.val < 2) + group := edgeAt + packet _ := () + localPart e e' := if e = e' then W e e else 0 + modePacket _ := () + source Z := Z + 1 + target Z := Z + coeff e Z := if e = Z then W Z (Z + 1) else 0 + form Z e' := if e' = Z + 1 then 1 else 0 + side_src := by decide + side_tgt := by decide + apply_eq e e' := by fin_cases e <;> fin_cases e' <;> decide + localPart_ne_zero e e' := by fin_cases e <;> fin_cases e' <;> decide + coeff_ne_zero e Z := by fin_cases e <;> fin_cases Z <;> decide + form_ne_zero Z e := by fin_cases Z <;> fin_cases e <;> decide + +/-- Both modes have coefficients divisible by `2`. -/ +theorem two_dvd_coeff (e Z : Fin 2) : 2 ^ 1 ∣ S.coeff e Z := by + have h : S.coeff e Z = 2 * (if e = Z then 1 else 0) := by + fin_cases e <;> fin_cases Z <;> decide + rw [h, pow_one] + exact dvd_mul_right _ _ + +/-- **The child identity** for this splitting, with weights one and `2 ^ 2 = 0` in `ZMod 4`. -/ +theorem deformedContraction_eq : + net.deformedContraction S.side W = + ∑ β ∈ modeBudget (fun _ : Fin 2 => 1) 2, ∏ g, (S.child β g).contraction := + S.deformedContraction_eq_sum_prod_contraction_child _ (fun _ => Nat.one_pos) + (by decide : (2 : ZMod 4) ^ 2 = 0) two_dvd_coeff + +/-- The budget holds the three tuples of total weight below two. -/ +example : modeBudget (fun _ : Fin 2 => 1) 2 = {![0, 0], ![1, 0], ![0, 1]} := by decide + +/-- The deformed contraction is `2`. -/ +example : net.deformedContraction S.side W = 2 := by decide + +/-- Without modes, the child of group `0` contracts to `1 + 3 = 0` and that of group `1` to `1`. +With mode `0`, whose coefficient `2` enters the row form of group `0`, the child of group `0` +vanishes. -/ +example : (S.child ![0, 0] 0).contraction = 0 ∧ (S.child ![0, 0] 1).contraction = 1 ∧ + (S.child ![1, 0] 0).contraction = 0 := by + decide + +/-- With mode `1`, the child of group `0` carries the mode value `L(X) = X₀` on its packet and +contracts to `3`, and the child of group `1` carries the coefficient `±2` and contracts to `2`. So +the only surviving term of (D.16) is `3 * 2 = 2`, the deformed contraction. -/ +example : (S.child ![0, 1] 0).contraction = 3 ∧ (S.child ![0, 1] 1).contraction = 2 := by + decide + +/-- The children are bipartite. -/ +example (β : Fin 2 → ℕ) (g : Fin 2) : (S.child β g).IsBipartite := S.isBipartite_child β g + +/-- Each child has the row port and the column port of its group. -/ +example (g : Fin 2) : + Fintype.card ({e // S.group (net.src e) = g} ⊕ {e // S.group (net.tgt e) = g}) = 2 := by + rw [S.card_edges_child] + fin_cases g <;> decide + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion From 05c80d0e48f19f7c39322f9a4d2e8886dd8bcb55 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 19:29:38 -0400 Subject: [PATCH 07/33] feat(circuit/frontier): modes of the Cauchy matrix, the geometric expansion (D.12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Cauchy kernel in local coordinates over Z_(2) (Lemma D.4) relates the kernels at d0 and d0 + 2^(i+1) j exactly once 2^v is cancelled, so in a ring with 2^p = 0 the factor 1 - rho j is inverted by its geometric series: W_d(T) = sum_{b Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 +- .../Frontier/TensorNetwork/CauchyModes.lean | 633 ++++++++++++++++++ .../Frontier/TensorNetworkCauchyModes.lean | 91 +++ 3 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCauchyModes.lean diff --git a/Complexity.lean b/Complexity.lean index b11988bdb5..db420cdf23 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -858,11 +858,12 @@ public import Complexity.Computability.Circuit.Frontier.Rectangle public import Complexity.Computability.Circuit.Frontier.Reduction public import Complexity.Computability.Circuit.Frontier.Sweep public import Complexity.Computability.Circuit.Frontier.TensorNetwork +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean new file mode 100644 index 0000000000..77494386cd --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean @@ -0,0 +1,633 @@ +/- +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.IntegralCoordinates +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Mathlib.NumberTheory.Padics.PadicVal.Defs +import Mathlib.Algebra.Ring.GeomSum +import Mathlib.NumberTheory.Padics.PadicVal.Basic +import Mathlib.RingTheory.Nilpotent.Basic +import Mathlib.Tactic.LinearCombination + +/-! +# The geometric expansion of the Cauchy kernel + +This file proves the geometric expansion (D.12) of [OpenAI2026ETH] (§D.4.5, p. 62), which turns +the crossing entries of the Cauchy matrix near a grid point into the modes of +`Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting`. + +**Local kernels** (`localKernel`). By Lemma D.4 (`cauchyKernel_comp_mem_lifts`), the Cauchy kernel +`W_d` on the grid of `L = 2 ^ a` slots has `2`-integral coefficients in local coordinates +`z ↦ W_d(t + α z)`, `α = 2 ^ (a + 1)`. `localKernel a t d` is this polynomial over `ℤ_(2)`, and for +a ring homomorphism `κ : ℤ_(2) → R` and `ζ ∈ R`, `kernelValue κ ζ a t d` is its value +`W_d(t + α ζ)` in `R`. + +**The expansion.** Fix `t` and two grid points `d₀` and `d = d₀ + 2 ^ (i + 1) j` with +`2 ^ (i + 1) ∤ t - d₀`, `i ≤ a`. All kernels are `C(T) / (C'(0) (T - d))`, so +`W_d(T) (T - d) = W_{d₀}(T) (T - d₀)`. Write `t - d₀ = 2 ^ v u₀` with `u₀` odd and `v ≤ i`; dividing +by `2 ^ v` in `ℤ_(2)[z]` makes + + `W_d(t + α z) (u₀ + 2 ^ (a + 1 - v) z - 2 ^ (i + 1 - v) j)` equal to + `W_{d₀}(t + α z) (u₀ + 2 ^ (a + 1 - v) z)` + +(`localKernel_mul_eq`). In a ring `R` with `2 ^ p = 0`, `U = u₀ + 2 ^ (a + 1 - v) ζ` is a unit, and +with the *cross ratio* `ρ = 2 ^ (i + 1 - v) U⁻¹ = 2 ^ (i + 1) / (T - d₀)` (`crossRatio`) the factor +`1 - ρ j` is inverted by its geometric series, since `2 ∣ ρ`: + + `W_d(T) = ∑_{b < p} W_{d₀}(T) ρ ^ b j ^ b` (`kernelValue_eq_sum`), + +the manuscript's (D.12), there written with the remainder beyond `b + 1 < p_t` dropped modulo +`2 ^ p_t`. When `d₀ ≠ t`, `W_{d₀}(T)` is divisible by `2` (Lemma D.4), so the coefficient of `j ^ b` +is divisible by `2 ^ (b + 1)` (`two_pow_dvd_kernelValue_mul_crossRatio_pow`): the weight +`w_Z = b + 1` of the mode. + +**The modes of a packing** (`TensorNetwork.cauchyModeSplitting`). Pack the edges into packets of +`2 ^ a` slots, oriented in coordinate `i` as in Lemma D.3 (2): each packet `q` has a pair of groups, +and digit `i` of an edge's slot says which of them is its row group. For a grid point `t` of +valuation `i`, a row slot `x` and a column slot `y` of a packet with two different groups lie in +different groups exactly when their digits `i` agree, and then `v₂(t - y₀ + x) ≤ i` for the tile +`y₀ = y mod 2 ^ (i + 1)` (`not_dvd_sub_sub_of_testBit_eq`). So the Cauchy matrix at `T = t + α ζ` +(`TensorNetwork.cauchyValueMatrix`) splits into its local entries and one mode per packet, tile and +`b < p`, of weight `b + 1`, and the child identity of +`Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting` applies +(`TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum`). The manuscript's modes are those +with `b + 1 < p_t`; the one with `b + 1 = p_t` has weight `p_t` and drops out of (D.14). + +## Main definitions + +* `Cslib.Circuits.Frontier.localKernel`: the Cauchy kernel in local coordinates over `ℤ_(2)`. +* `Cslib.Circuits.Frontier.kernelValue`: its value at `z = ζ` in a `ℤ_(2)`-algebra. +* `Cslib.Circuits.Frontier.crossRatio`: the ratio `2 ^ (i + 1) / (T - d₀)`. +* `Cslib.Circuits.Frontier.TensorNetwork.cauchyValueMatrix`: the Cauchy matrix of a packing at + `T = t + α ζ`. +* `Cslib.Circuits.Frontier.TensorNetwork.cauchyModeCoeff`, + `Cslib.Circuits.Frontier.TensorNetwork.cauchyModeForm`: the coefficients and forms of its modes. +* `Cslib.Circuits.Frontier.TensorNetwork.cauchyModeSplitting`: its mode splitting. + +## Main results + +* `Cslib.Circuits.Frontier.map_localKernel`: the local kernel is the composed Cauchy kernel. +* `Cslib.Circuits.Frontier.exists_localKernel_eq_two_mul`: away from its node it is divisible by + `2`. +* `Cslib.Circuits.Frontier.localKernel_mul_eq`: the exact relation between the kernels at `d₀` + and `d₀ + 2 ^ (i + 1) j`. +* `Cslib.Circuits.Frontier.kernelValue_eq_sum`: the geometric expansion (D.12). +* `Cslib.Circuits.Frontier.two_pow_dvd_kernelValue_mul_crossRatio_pow`: its coefficients are + divisible by `2 ^ (b + 1)`. +* `Cslib.Circuits.Frontier.not_dvd_sub_sub_of_testBit_eq`: a row and a column tile with the same + digit `i` give `v₂(t - y₀ + x) ≤ i`. +* `Cslib.Circuits.Frontier.TensorNetwork.two_pow_dvd_cauchyModeCoeff`: the modes have weight + `b + 1`. +* `Cslib.Circuits.Frontier.TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum`: the child + identity (D.16) for the Cauchy matrix of a packing. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.3 (2) (p. 58), + Lemma D.4 (pp. 59–60), and §D.4.5–§D.4.6, (D.12)–(D.16) (pp. 62–63). +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Polynomial Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### Local kernels over `ℤ_(2)` -/ + +/-- The Cauchy kernel `W_d` on the grid of `2 ^ a` slots in local coordinates +`z ↦ W_d(t + α z)`, `α = 2 ^ (a + 1)`, as a polynomial over `ℤ_(2)` (Lemma D.4 of +[OpenAI2026ETH]); zero when `t` or `d` is off the grid. -/ +noncomputable def localKernel (a : ℕ) (t d : ℤ) : ℤ₍₂₎[X] := + if h : t ∈ cauchyGrid (2 ^ a) ∧ d ∈ cauchyGrid (2 ^ a) then + ((mem_lifts _).mp (cauchyKernel_comp_mem_lifts h.1 h.2)).choose + else 0 + +/-- The local kernel is the Cauchy kernel composed with `t + α z`. -/ +theorem map_localKernel {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) : + (localKernel a t d).map (ℤ₍₂₎).subtype = + (cauchyKernel (2 ^ a) d).comp (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X) := by + rw [localKernel, dite_eq_left ⟨ht, hd⟩] + exact ((mem_lifts _).mp (cauchyKernel_comp_mem_lifts ht hd)).choose_spec + +private theorem map_subtype_injective : + Function.Injective (Polynomial.map (ℤ₍₂₎).subtype) := + Polynomial.map_injective _ Subtype.val_injective + +/-- **Lemma D.4, divisibility, over `ℤ_(2)`.** Away from its node, the local kernel is twice a +polynomial over `ℤ_(2)`. -/ +theorem exists_localKernel_eq_two_mul {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) (hdt : d ≠ t) : ∃ Q : ℤ₍₂₎[X], localKernel a t d = C 2 * Q := by + obtain ⟨P, hP, h⟩ := exists_cauchyKernel_comp_eq_two_mul ht hd hdt + obtain ⟨Q, rfl⟩ := (mem_lifts _).mp hP + refine ⟨Q, map_subtype_injective ?_⟩ + rw [map_localKernel ht hd, h, Polynomial.map_mul, Polynomial.map_C] + congr 2 + +/-- Every kernel on the grid is `C(T) / C'(0)` divided by `T - d`. -/ +private theorem cauchyKernel_mul_X_sub {L : ℕ} {d : ℤ} (hd : d ∈ cauchyGrid L) : + cauchyKernel L d * (X - C (d : ℚ)) = + C ((Lagrange.nodal ((cauchyGrid L).erase 0) (Int.cast : ℤ → ℚ)).eval 0)⁻¹ * + Lagrange.nodal (cauchyGrid L) (Int.cast : ℤ → ℚ) := by + rw [cauchyKernel, Lagrange.nodal_eq_mul_nodal_erase hd] + ring + +/-- **The kernels of a tile.** For grid points `d₀` and `d = d₀ + 2 ^ (i + 1) j`, with +`t - d₀ = 2 ^ v u₀` and `v ≤ i ≤ a`, the local kernels satisfy +`W_d (u₀ + 2 ^ (a + 1 - v) z - 2 ^ (i + 1 - v) j) = W_{d₀} (u₀ + 2 ^ (a + 1 - v) z)`: both sides +are `C(t + α z) / (C'(0) 2 ^ v)`. -/ +theorem localKernel_mul_eq {a i v : ℕ} {t d₀ d j u₀ : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd₀ : d₀ ∈ cauchyGrid (2 ^ a)) (hd : d ∈ cauchyGrid (2 ^ a)) + (hdd : d = d₀ + 2 ^ (i + 1) * j) (htd : t - d₀ = 2 ^ v * u₀) (hvi : v ≤ i) (hia : i ≤ a) : + localKernel a t d * (C (u₀ : ℤ₍₂₎) + C ((2 : ℤ₍₂₎) ^ (a + 1 - v)) * X - + C ((2 ^ (i + 1 - v) * j : ℤ) : ℤ₍₂₎)) = + localKernel a t d₀ * (C (u₀ : ℤ₍₂₎) + C ((2 : ℤ₍₂₎) ^ (a + 1 - v)) * X) := by + refine map_subtype_injective ?_ + have hmap₁ : (C (u₀ : ℤ₍₂₎) + C ((2 : ℤ₍₂₎) ^ (a + 1 - v)) * X).map ℤ₍₂₎.subtype = + C (u₀ : ℚ) + C ((2 : ℚ) ^ (a + 1 - v)) * X := by + rw [Polynomial.map_add, Polynomial.map_mul, Polynomial.map_C, Polynomial.map_C, + Polynomial.map_X, map_intCast, map_pow, map_ofNat] + have hmap₂ : (C ((2 ^ (i + 1 - v) * j : ℤ) : ℤ₍₂₎)).map ℤ₍₂₎.subtype = + C ((2 : ℚ) ^ (i + 1 - v) * j) := by + rw [Polynomial.map_C, map_intCast] + push_cast + rfl + rw [Polynomial.map_mul, Polynomial.map_mul, Polynomial.map_sub, hmap₁, hmap₂, + map_localKernel ht hd, map_localKernel ht hd₀] + set Y : ℚ[X] := C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X + -- Multiply both sides by `2 ^ v`, which turns the linear factors into `Y - d` and `Y - d₀`. + have h1 : (2 : ℚ) ^ v * u₀ = t - d₀ := by exact_mod_cast htd.symm + have h2 : (2 : ℚ) ^ v * 2 ^ (a + 1 - v) = 2 ^ (a + 1) := by + rw [← pow_add, Nat.add_sub_cancel' (by omega)] + have h3 : (2 : ℚ) ^ v * (2 ^ (i + 1 - v) * j) = d - d₀ := by + rw [← mul_assoc, ← pow_add, Nat.add_sub_cancel' (by omega)] + exact_mod_cast (sub_eq_of_eq_add' hdd).symm + have hYd : C ((2 : ℚ) ^ v) * (C (u₀ : ℚ) + C (2 ^ (a + 1 - v)) * X - + C (2 ^ (i + 1 - v) * (j : ℚ))) = Y - C (d : ℚ) := by + rw [mul_sub, mul_add, ← C_mul, ← mul_assoc, ← C_mul, ← C_mul, h1, h2, h3] + simp only [Y, C_sub] + ring + have hYd₀ : C ((2 : ℚ) ^ v) * (C (u₀ : ℚ) + C (2 ^ (a + 1 - v)) * X) = Y - C (d₀ : ℚ) := by + rw [mul_add, ← C_mul, ← mul_assoc, ← C_mul, h1, h2] + simp only [Y, C_sub] + ring + have hcomp : ∀ e ∈ cauchyGrid (2 ^ a), + (cauchyKernel (2 ^ a) e).comp Y * (Y - C (e : ℚ)) = + (C ((Lagrange.nodal ((cauchyGrid (2 ^ a)).erase 0) (Int.cast : ℤ → ℚ)).eval 0)⁻¹ * + Lagrange.nodal (cauchyGrid (2 ^ a)) (Int.cast : ℤ → ℚ)).comp Y := by + intro e he + rw [← cauchyKernel_mul_X_sub he, mul_comp, sub_comp, X_comp, C_comp] + have h2v : C ((2 : ℚ) ^ v) ≠ 0 := C_ne_zero.mpr (pow_ne_zero _ two_ne_zero) + refine mul_left_cancel₀ h2v ?_ + rw [mul_left_comm, hYd, mul_left_comm, hYd₀, hcomp d hd, hcomp d₀ hd₀] + +/-! ### Values in a ring where `2` is nilpotent -/ + +variable {R : Type*} [CommRing R] + +/-- The value `W_d(t + α ζ)` of the Cauchy kernel at `T = t + α ζ`, `α = 2 ^ (a + 1)`, in a ring `R` +receiving `ℤ_(2)` through `κ`. -/ +@[expose] noncomputable def kernelValue (κ : ℤ₍₂₎ →+* R) (ζ : R) (a : ℕ) (t d : ℤ) : R := + (localKernel a t d).eval₂ κ ζ + +/-- The *cross ratio* `ρ = 2 ^ (i + 1) / (T - d₀)` at `T = t + α ζ`: with `t - d₀ = 2 ^ v u₀`, `u₀` +odd, it is `2 ^ (i + 1 - v)` times the inverse of `u₀ + 2 ^ (a + 1 - v) ζ`. -/ +@[expose] noncomputable def crossRatio (a i : ℕ) (t d₀ : ℤ) (ζ : R) : R := + 2 ^ (i + 1 - padicValInt 2 (t - d₀)) * + Ring.inverse ((((t - d₀) / 2 ^ padicValInt 2 (t - d₀) : ℤ) : R) + + 2 ^ (a + 1 - padicValInt 2 (t - d₀)) * ζ) + +/-- An integer not divisible by `2 ^ (i + 1)` is `2 ^ v` times an odd integer, with `v ≤ i`: +`v` is its `2`-adic valuation. -/ +private theorem two_adic_split {n : ℤ} {i : ℕ} (hn : ¬ (2 : ℤ) ^ (i + 1) ∣ n) : + padicValInt 2 n ≤ i ∧ Odd (n / 2 ^ padicValInt 2 n) ∧ + n = 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) := by + have hn0 : n ≠ 0 := by rintro rfl; exact hn (dvd_zero _) + have hdvd : (2 : ℤ) ^ padicValInt 2 n ∣ n := by exact_mod_cast padicValInt_dvd (p := 2) n + have hsplit := (Int.mul_ediv_cancel' hdvd).symm + refine ⟨?_, ?_, hsplit⟩ + · by_contra h + exact hn (by exact_mod_cast (padicValInt_dvd_iff (p := 2) (i + 1) n).mpr (Or.inr (by omega))) + · rw [← Int.not_even_iff_odd, even_iff_two_dvd] + rintro ⟨c, hc⟩ + have : (2 : ℤ) ^ (padicValInt 2 n + 1) ∣ n := ⟨c, by + calc n = 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) := hsplit + _ = 2 ^ padicValInt 2 n * (2 * c) := by rw [hc] + _ = 2 ^ (padicValInt 2 n + 1) * c := by ring⟩ + have h := (padicValInt_dvd_iff (p := 2) (padicValInt 2 n + 1) n).mp (by exact_mod_cast this) + omega + +/-- In a ring where `2` is nilpotent, `1 + 2 x` is a unit. -/ +private theorem isUnit_one_add_two_mul {p : ℕ} (hp : (2 : R) ^ p = 0) (x : R) : + IsUnit (1 + 2 * x) := + IsNilpotent.isUnit_one_add ⟨p, by rw [mul_pow, hp, zero_mul]⟩ + +/-- **The geometric expansion (D.12).** Let `2 ^ p = 0` in `R`, and let `d₀` and +`d = d₀ + 2 ^ (i + 1) j` be grid points of `2 ^ a` slots with `2 ^ (i + 1) ∤ t - d₀` and `i ≤ a`. +Then +`W_d(T) = ∑_{b < p} W_{d₀}(T) ρ ^ b j ^ b` at `T = t + α ζ`, with `ρ = crossRatio a i t d₀ ζ`. -/ +theorem kernelValue_eq_sum (κ : ℤ₍₂₎ →+* R) (ζ : R) {p a i : ℕ} (hp : (2 : R) ^ p = 0) + {t d₀ d j : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hd₀ : d₀ ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) (hdd : d = d₀ + 2 ^ (i + 1) * j) + (hcross : ¬ (2 : ℤ) ^ (i + 1) ∣ t - d₀) (hia : i ≤ a) : + kernelValue κ ζ a t d = + ∑ b ∈ range p, kernelValue κ ζ a t d₀ * crossRatio a i t d₀ ζ ^ b * (j : R) ^ b := by + obtain ⟨hvi, hodd, hsplit⟩ := two_adic_split hcross + set v := padicValInt 2 (t - d₀) + set u₀ := (t - d₀) / 2 ^ v + have h : kernelValue κ ζ a t d * ((u₀ : R) + 2 ^ (a + 1 - v) * ζ - 2 ^ (i + 1 - v) * j) = + kernelValue κ ζ a t d₀ * ((u₀ : R) + 2 ^ (a + 1 - v) * ζ) := by + have h := congrArg (eval₂ κ ζ) (localKernel_mul_eq ht hd₀ hd hdd hsplit hvi hia) + simp only [eval₂_mul, eval₂_sub, eval₂_add, eval₂_C, eval₂_X] at h + simp only [map_intCast, map_pow, map_ofNat] at h + push_cast at h + exact h + -- The unit `U = u₀ + 2 ^ (a + 1 - v) ζ`. + set U : R := (u₀ : R) + 2 ^ (a + 1 - v) * ζ + have hU : IsUnit U := by + obtain ⟨c, hc⟩ := hodd + have : U = 1 + 2 * ((c : R) + 2 ^ (a - v) * ζ) := by + simp only [U, hc] + rw [show a + 1 - v = a - v + 1 by omega, pow_succ] + push_cast + ring + rw [this] + exact isUnit_one_add_two_mul hp _ + set ρ := crossRatio a i t d₀ ζ + have hρ : ρ = 2 ^ (i + 1 - v) * Ring.inverse U := rfl + have hUρ : U * ρ = 2 ^ (i + 1 - v) := by + rw [hρ, mul_left_comm, Ring.mul_inverse_cancel U hU, mul_one] + -- Cancel `U`: `W_d (1 - ρ j) = W_{d₀}`. + have hcancel : kernelValue κ ζ a t d * (1 - ρ * j) = kernelValue κ ζ a t d₀ := by + have h' : kernelValue κ ζ a t d * (1 - ρ * j) * U = kernelValue κ ζ a t d₀ * U := by + linear_combination h - (kernelValue κ ζ a t d * j) * hUρ + exact hU.mul_right_cancel h' + -- The geometric series inverts `1 - ρ j`, as `(ρ j) ^ p = 0`. + have hnil : (ρ * j) ^ p = 0 := by + have : ρ = 2 * (2 ^ (i - v) * Ring.inverse U) := by + rw [hρ, show i + 1 - v = i - v + 1 by omega, pow_succ] + ring + rw [this, mul_assoc, mul_pow, hp, zero_mul] + have hgeom := mul_neg_geom_sum (ρ * (j : R)) p + rw [hnil, sub_zero] at hgeom + calc kernelValue κ ζ a t d + = kernelValue κ ζ a t d * ((1 - ρ * j) * ∑ b ∈ range p, (ρ * (j : R)) ^ b) := by + rw [hgeom, mul_one] + _ = kernelValue κ ζ a t d₀ * ∑ b ∈ range p, (ρ * (j : R)) ^ b := by rw [← mul_assoc, hcancel] + _ = _ := by + rw [mul_sum] + refine sum_congr rfl fun b _ => ?_ + rw [mul_pow, mul_assoc] + +/-- **The coefficients of (D.12) have weight `b + 1`.** When `d₀ ≠ t`, the coefficient +`W_{d₀}(T) ρ ^ b` of `j ^ b` is divisible by `2 ^ (b + 1)`. -/ +theorem two_pow_dvd_kernelValue_mul_crossRatio_pow (κ : ℤ₍₂₎ →+* R) (ζ : R) {a i : ℕ} + {t d₀ : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hd₀ : d₀ ∈ cauchyGrid (2 ^ a)) + (hcross : ¬ (2 : ℤ) ^ (i + 1) ∣ t - d₀) (b : ℕ) : + (2 : R) ^ (b + 1) ∣ kernelValue κ ζ a t d₀ * crossRatio a i t d₀ ζ ^ b := by + have hd₀t : d₀ ≠ t := by rintro rfl; exact hcross (by rw [sub_self]; exact dvd_zero _) + obtain ⟨Q, hQ⟩ := exists_localKernel_eq_two_mul ht hd₀ hd₀t + have hvi := (two_adic_split hcross).1 + have hk : kernelValue κ ζ a t d₀ = 2 * Q.eval₂ κ ζ := by + rw [kernelValue, hQ, eval₂_mul, eval₂_C, map_ofNat] + have hρ : crossRatio a i t d₀ ζ = 2 * (2 ^ (i - padicValInt 2 (t - d₀)) * + Ring.inverse ((((t - d₀) / 2 ^ padicValInt 2 (t - d₀) : ℤ) : R) + + 2 ^ (a + 1 - padicValInt 2 (t - d₀)) * ζ)) := by + rw [crossRatio, show i + 1 - padicValInt 2 (t - d₀) = i - padicValInt 2 (t - d₀) + 1 by + omega, pow_succ] + ring + rw [hk, hρ, mul_pow, pow_succ'] + exact mul_dvd_mul (dvd_mul_right _ _) (dvd_mul_right _ _) + +/-! ### The modes of a packing -/ + +/-- A row and a column slot with the same binary digit `i`, at a grid point `t` of valuation `i`: +`t - (y₀ - x)` is not divisible by `2 ^ (i + 1)`, for the residue `y₀ < 2 ^ (i + 1)` of the column +slot. (Otherwise `y₀ ≡ x + 2 ^ i` modulo `2 ^ (i + 1)`, which flips digit `i`.) -/ +theorem not_dvd_sub_sub_of_testBit_eq {t : ℤ} {i x y₀ : ℕ} (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (hy₀ : y₀ < 2 ^ (i + 1)) (hbit : x.testBit i = y₀.testBit i) : + ¬ (2 : ℤ) ^ (i + 1) ∣ t - ((y₀ : ℤ) - x) := by + rintro ⟨n, hn⟩ + obtain ⟨m, rfl⟩ := hti + have hm : ¬ (2 : ℤ) ∣ m := fun ⟨c, hc⟩ => hti' ⟨c, by rw [hc, pow_succ]; ring⟩ + obtain ⟨c, rfl⟩ : ∃ c, m = 2 * c + 1 := ⟨m / 2, by omega⟩ + rw [Nat.testBit_eq_decide_div_mod_eq, Nat.testBit_eq_decide_div_mod_eq, decide_eq_decide] at hbit + obtain ⟨K, hK⟩ : ∃ K, 2 ^ i = K := ⟨_, rfl⟩ + have hKpos : 0 < K := hK ▸ by positivity + have hpow : (2 : ℤ) ^ i = K := by exact_mod_cast hK + have hpow' : (2 : ℤ) ^ (i + 1) = 2 * K := by rw [pow_succ, hpow, mul_comm] + have hy₀' : y₀ < K * 2 := by rw [← hK, ← pow_succ]; exact hy₀ + rw [hK] at hbit + rw [hpow', hpow] at hn + -- Digit `i` of both is `h`; write `x = K (2 q + h) + lx` and `y₀ = K h + ly`. + have hy₀K : y₀ / K < 2 := Nat.div_lt_of_lt_mul hy₀' + have hh : x / K % 2 = y₀ / K := by + have h1 := Nat.mod_lt (x / K) two_pos + have h2 : y₀ / K % 2 = y₀ / K := Nat.mod_eq_of_lt hy₀K + by_cases hx1 : x / K % 2 = 1 + · have := hbit.mp hx1 + omega + · have := mt hbit.mpr hx1 + omega + have hx := Nat.div_add_mod x K + have hx2 := Nat.div_add_mod (x / K) 2 + have hy := Nat.div_add_mod y₀ K + have hlx := Nat.mod_lt x hKpos + have hly := Nat.mod_lt y₀ hKpos + obtain ⟨lx, hlxd⟩ : ∃ lx, x % K = lx := ⟨_, rfl⟩ + obtain ⟨ly, hlyd⟩ : ∃ ly, y₀ % K = ly := ⟨_, rfl⟩ + obtain ⟨q, hqd⟩ : ∃ q, x / K / 2 = q := ⟨_, rfl⟩ + obtain ⟨h, hhd⟩ : ∃ h, y₀ / K = h := ⟨_, rfl⟩ + rw [hlxd] at hx hlx + rw [hlyd] at hy hly + rw [hqd, hh, hhd] at hx2 + rw [hhd] at hy + rw [← hx2] at hx + have hxZ : (x : ℤ) = K * (2 * q + h) + lx := by exact_mod_cast hx.symm + have hyZ : (y₀ : ℤ) = K * h + ly := by exact_mod_cast hy.symm + have hlx' : (lx : ℤ) < K := by exact_mod_cast hlx + have hly' : (ly : ℤ) < K := by exact_mod_cast hly + have hK' : (0 : ℤ) < K := by exact_mod_cast hKpos + have hlx0 : (0 : ℤ) ≤ lx := Int.natCast_nonneg lx + have hly0 : (0 : ℤ) ≤ ly := Int.natCast_nonneg ly + -- Then `0 < K + lx - ly < 2 K` would be a multiple of `2 K`. + rw [hxZ, hyZ] at hn + have hw : (K : ℤ) + lx - ly = 2 * K * (n - c - q) := by linear_combination hn + rcases le_or_gt (n - c - q) 0 with h0 | h0 + · have : (2 * K : ℤ) * (n - c - q) ≤ 0 := mul_nonpos_of_nonneg_of_nonpos (by positivity) h0 + linarith + · have : (2 * K : ℤ) * 1 ≤ 2 * K * (n - c - q) := + mul_le_mul_of_nonneg_left (by omega) (by positivity) + linarith + +namespace TensorNetwork + +variable {V E G P : Type*} + +/-- The Cauchy matrix of a packing at `T = t + α ζ`, `α = 2 ^ (a + 1)`: between two edges of the +same packet, the Cauchy kernel at the difference of their slots, and zero between packets (the +manuscript's (D.7) at `T`). -/ +@[expose] noncomputable def cauchyValueMatrix [DecidableEq P] (κ : ℤ₍₂₎ →+* R) (ζ : R) (a : ℕ) + (t : ℤ) (packet : E → P) (slot : E → ℕ) : Matrix E E R := + fun e e' => if packet e = packet e' then kernelValue κ ζ a t ((slot e' : ℤ) - slot e) else 0 + +section Modes + +variable [Fintype E] [Fintype P] [DecidableEq G] [DecidableEq P] {N : TensorNetwork R Bool V E} + {group : V → G} {packet : E → P} {slot : E → ℕ} {sym : P → G × G} {i : ℕ} + +omit [CommRing R] [Fintype E] [Fintype P] [DecidableEq G] [DecidableEq P] in +private theorem group_src_eq + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) (e : E) : + group (N.src e) = if (slot e).testBit i then (sym (packet e)).2 else (sym (packet e)).1 := by + have h := congrArg Prod.fst (horient e) + split_ifs at h ⊢ <;> exact h + +omit [CommRing R] [Fintype E] [Fintype P] [DecidableEq G] [DecidableEq P] in +private theorem group_tgt_eq + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) (e : E) : + group (N.tgt e) = if (slot e).testBit i then (sym (packet e)).1 else (sym (packet e)).2 := by + have h := congrArg Prod.snd (horient e) + split_ifs at h ⊢ <;> exact h + +/-- The coefficient of the mode `Z = (q, y₀, b)` in the row form of `e`: for a row of the packet +`q` whose two groups differ, with digit `i` of its slot `x` equal to that of `y₀`, it is +`W_{y₀ - x}(T) ρ ^ b`, the coefficient of `j ^ b` in (D.12); otherwise zero. -/ +@[expose] noncomputable def cauchyModeCoeff (κ : ℤ₍₂₎ →+* R) (ζ : R) (a i : ℕ) (t : ℤ) + (packet : E → P) (slot : E → ℕ) (sym : P → G × G) {p : ℕ} (e : E) + (Z : P × Fin (2 ^ (i + 1)) × Fin p) : R := + if packet e = Z.1 ∧ (sym Z.1).1 ≠ (sym Z.1).2 ∧ (slot e).testBit i = (Z.2.1 : ℕ).testBit i then + kernelValue κ ζ a t ((Z.2.1 : ℤ) - slot e) * + crossRatio a i t ((Z.2.1 : ℤ) - slot e) ζ ^ (Z.2.2 : ℕ) + else 0 + +/-- The form of the mode `Z = (q, y₀, b)` at the column `e'`: for a column of the packet `q` whose +two groups differ, in slot `y = y₀ + 2 ^ (i + 1) j` of the tile `y₀`, it is `j ^ b`; otherwise +zero. -/ +@[expose] noncomputable def cauchyModeForm (R : Type*) [CommRing R] (i : ℕ) (packet : E → P) + (slot : E → ℕ) (sym : P → G × G) {p : ℕ} (Z : P × Fin (2 ^ (i + 1)) × Fin p) (e' : E) : R := + if packet e' = Z.1 ∧ (sym Z.1).1 ≠ (sym Z.1).2 ∧ slot e' % 2 ^ (i + 1) = Z.2.1 then + ((slot e' / 2 ^ (i + 1) : ℕ) : R) ^ (Z.2.2 : ℕ) + else 0 + +omit [Fintype E] in +/-- The Cauchy matrix at `T` is its local part plus its modes: the geometric expansion (D.12) on +the crossing entries. -/ +private theorem cauchyValueMatrix_eq_add_sum + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (e e' : E) : + cauchyValueMatrix κ ζ a t packet slot e e' = + (if group (N.tgt e') = group (N.src e) then cauchyValueMatrix κ ζ a t packet slot e e' + else 0) + + ∑ Z : P × Fin (2 ^ (i + 1)) × Fin p, + cauchyModeCoeff κ ζ a i t packet slot sym e Z * + cauchyModeForm R i packet slot sym Z e' := by + have hsrc := group_src_eq horient e + have htgt := group_tgt_eq horient e' + -- A mode term is nonzero only at the packet of both edges, when its groups differ, at the digit + -- of `e` and the tile of `e'`. + have hterm : ∀ Z : P × Fin (2 ^ (i + 1)) × Fin p, + cauchyModeCoeff κ ζ a i t packet slot sym e Z * cauchyModeForm R i packet slot sym Z e' ≠ 0 → + packet e = Z.1 ∧ packet e' = Z.1 ∧ (sym Z.1).1 ≠ (sym Z.1).2 ∧ + (slot e).testBit i = (Z.2.1 : ℕ).testBit i ∧ slot e' % 2 ^ (i + 1) = Z.2.1 := by + intro Z hZ + have hc := left_ne_zero_of_mul hZ + have hf := right_ne_zero_of_mul hZ + unfold cauchyModeCoeff at hc + unfold cauchyModeForm at hf + obtain ⟨h1, h2, h3⟩ := not_not.mp fun h => hc (ite_eq_right h) + obtain ⟨h4, -, h5⟩ := not_not.mp fun h => hf (ite_eq_right h) + exact ⟨h1, h4, h2, h3, h5⟩ + have hbit' : ∀ y₀ : ℕ, slot e' % 2 ^ (i + 1) = y₀ → (slot e').testBit i = y₀.testBit i := by + rintro y₀ rfl + rw [Nat.testBit_mod_two_pow] + simp + by_cases hloc : group (N.tgt e') = group (N.src e) + · -- Local entries: no mode term survives. + rw [ite_eq_left hloc, left_eq_add] + refine sum_eq_zero fun Z _ => by_contra fun hZ => ?_ + obtain ⟨h1, h2, h3, h4, h5⟩ := hterm Z hZ + have hb : (slot e').testBit i = (slot e).testBit i := (hbit' _ h5).trans h4.symm + rw [htgt, hsrc, hb, h2, h1] at hloc + split_ifs at hloc + exacts [h3 hloc, h3 hloc.symm] + rw [ite_eq_right hloc, zero_add] + by_cases hq : packet e = packet e' + · -- Crossing entries of one packet: the geometric expansion. + have hσ : (sym (packet e)).1 ≠ (sym (packet e)).2 := by + intro hσ + apply hloc + rw [htgt, hsrc, ← hq] + split_ifs <;> simp only [hσ] + have hb : (slot e).testBit i = (slot e').testBit i := by + by_contra hb + apply hloc + rw [htgt, hsrc, ← hq] + rcases Bool.eq_false_or_eq_true ((slot e).testBit i) with h1 | h1 <;> + rcases Bool.eq_false_or_eq_true ((slot e').testBit i) with h2 | h2 <;> + simp only [h1, h2, ite_true, Bool.false_eq_true, ite_false, not_true_eq_false] at hb ⊢ + have h2a : 2 ^ (i + 1) ≤ 2 ^ a := Nat.pow_le_pow_right two_pos hia + have hy₀lt : slot e' % 2 ^ (i + 1) < 2 ^ (i + 1) := Nat.mod_lt _ (by positivity) + obtain ⟨y₀, hy₀⟩ : ∃ y₀ : Fin (2 ^ (i + 1)), (y₀ : ℕ) = slot e' % 2 ^ (i + 1) := + ⟨⟨_, hy₀lt⟩, rfl⟩ + have hx' : ((slot e : ℕ) : ℤ) < 2 ^ a := by exact_mod_cast hslot e + have hy₀' : ((y₀ : ℕ) : ℤ) < 2 ^ a := by + rw [hy₀] + exact_mod_cast lt_of_lt_of_le hy₀lt h2a + have hy' : ((slot e' : ℕ) : ℤ) < 2 ^ a := by exact_mod_cast hslot e' + rw [Fintype.sum_prod_type, sum_eq_single (packet e), Fintype.sum_prod_type, + sum_eq_single y₀] + · have hcoef : ∀ b : Fin p, + cauchyModeCoeff κ ζ a i t packet slot sym e (packet e, y₀, b) * + cauchyModeForm R i packet slot sym (packet e, y₀, b) e' = + kernelValue κ ζ a t ((y₀ : ℕ) - slot e) * + crossRatio a i t ((y₀ : ℕ) - slot e) ζ ^ (b : ℕ) * + (((slot e' / 2 ^ (i + 1) : ℕ) : ℤ) : R) ^ (b : ℕ) := by + intro b + unfold cauchyModeCoeff cauchyModeForm + rw [ite_eq_left ⟨rfl, hσ, hb.trans (hbit' _ hy₀.symm)⟩, + ite_eq_left ⟨hq.symm, hσ, hy₀.symm⟩, Int.cast_natCast] + simp only [hcoef] + rw [Fin.sum_univ_eq_sum_range (fun b => kernelValue κ ζ a t ((y₀ : ℕ) - slot e) * + crossRatio a i t ((y₀ : ℕ) - slot e) ζ ^ b * + (((slot e' / 2 ^ (i + 1) : ℕ) : ℤ) : R) ^ b) p, + cauchyValueMatrix, ite_eq_left hq] + have hdiv := Nat.mod_add_div (slot e') (2 ^ (i + 1)) + refine kernelValue_eq_sum κ ζ hp ht ?_ ?_ ?_ + (not_dvd_sub_sub_of_testBit_eq hti hti' y₀.isLt (hb.trans (hbit' _ hy₀.symm))) hia.le + · rw [mem_cauchyGrid] + push_cast + constructor <;> omega + · rw [mem_cauchyGrid] + push_cast + constructor <;> omega + · have : ((slot e' : ℕ) : ℤ) = ((y₀ : ℕ) : ℤ) + + 2 ^ (i + 1) * ((slot e' / 2 ^ (i + 1) : ℕ) : ℤ) := by + rw [hy₀] + exact_mod_cast hdiv.symm + rw [this] + ring + · intro y₀' _ hne + refine sum_eq_zero fun b _ => by_contra fun hZ => hne ?_ + exact Fin.ext ((hterm _ hZ).2.2.2.2.symm.trans hy₀.symm) + · exact fun h => absurd (mem_univ _) h + · intro q' _ hne + refine sum_eq_zero fun Z _ => by_contra fun hZ => hne ?_ + exact (hterm _ hZ).1.symm + · exact fun h => absurd (mem_univ _) h + · -- Different packets: the entry and every mode term vanish. + rw [cauchyValueMatrix, ite_eq_right hq] + refine (sum_eq_zero fun Z _ => by_contra fun hZ => hq ?_).symm + obtain ⟨h1, h2, -⟩ := hterm Z hZ + exact h1.trans h2.symm + +/-- **The modes of the Cauchy matrix (§D.4.5).** Let the edges of `N` be packed into packets of +`2 ^ a` slots, with an orientation in coordinate `i < a`: each packet `q` has a pair of groups +`sym q`, and an edge in slot `x` of `q` runs from group `(sym q).1` to group `(sym q).2` when digit +`i` of `x` is `0`, and the other way when it is `1`. Let `t` be a grid point of valuation `i` and +`2 ^ p = 0` in `R`. Then the Cauchy matrix at `T = t + α ζ` has a mode splitting: entries between +the same group are local, and for each packet `q` whose two groups differ, each *tile* `y₀` of the +column slots `y ≡ y₀ (mod 2 ^ (i + 1))` and each `b < p` there is a mode with coefficient +`W_{y₀ - x}(T) ρ ^ b` in the row form of slot `x` (`cauchyModeCoeff`) and form `∑ j ^ b X_y` over +the columns `y = y₀ + 2 ^ (i + 1) j` of the tile (`cauchyModeForm`): the manuscript's (D.12). -/ +noncomputable def cauchyModeSplitting (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) : + N.ModeSplitting (cauchyValueMatrix κ ζ a t packet slot) G P + (P × Fin (2 ^ (i + 1)) × Fin p) where + side := side + group := group + packet := packet + localPart e e' := + if group (N.tgt e') = group (N.src e) then cauchyValueMatrix κ ζ a t packet slot e e' else 0 + modePacket Z := Z.1 + source Z := if (Z.2.1 : ℕ).testBit i then (sym Z.1).1 else (sym Z.1).2 + target Z := if (Z.2.1 : ℕ).testBit i then (sym Z.1).2 else (sym Z.1).1 + coeff := cauchyModeCoeff κ ζ a i t packet slot sym + form := cauchyModeForm R i packet slot sym + side_src := hsrc + side_tgt := htgt + apply_eq := cauchyValueMatrix_eq_add_sum horient hslot hia κ ζ hp ht hti hti' + localPart_ne_zero e e' h := by + simp only [ne_eq, ite_eq_right_iff, not_forall, cauchyValueMatrix] at h + obtain ⟨hg, hq, -⟩ := h + exact ⟨hq.symm, hg⟩ + coeff_ne_zero e Z h := by + unfold cauchyModeCoeff at h + obtain ⟨hq, -, hb⟩ := not_not.mp fun hc => h (ite_eq_right hc) + refine ⟨hq.symm, ?_⟩ + rw [group_src_eq horient e, hb, hq] + form_ne_zero Z e h := by + unfold cauchyModeForm at h + obtain ⟨hq, -, hy⟩ := not_not.mp fun hc => h (ite_eq_right hc) + refine ⟨hq.symm, ?_⟩ + have hb : (slot e).testBit i = (Z.2.1 : ℕ).testBit i := by + rw [← hy, Nat.testBit_mod_two_pow] + simp + rw [group_tgt_eq horient e, hb, hq] + +omit [Fintype E] [Fintype P] in +/-- **The modes have weight `b + 1`:** the coefficient of the mode `(q, y₀, b)` is divisible by +`2 ^ (b + 1)`, so the child identity (D.16) applies with these weights. -/ +theorem two_pow_dvd_cauchyModeCoeff {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) + (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (e : E) + (Z : P × Fin (2 ^ (i + 1)) × Fin p) : + (2 : R) ^ ((Z.2.2 : ℕ) + 1) ∣ cauchyModeCoeff κ ζ a i t packet slot sym e Z := by + unfold cauchyModeCoeff + split_ifs with h + · have h2a : 2 ^ (i + 1) ≤ 2 ^ a := Nat.pow_le_pow_right two_pos hia + have hx : ((slot e : ℕ) : ℤ) < 2 ^ a := by exact_mod_cast hslot e + have hy₀ : ((Z.2.1 : ℕ) : ℤ) < 2 ^ a := by exact_mod_cast lt_of_lt_of_le Z.2.1.isLt h2a + refine two_pow_dvd_kernelValue_mul_crossRatio_pow κ ζ ht ?_ + (not_dvd_sub_sub_of_testBit_eq hti hti' Z.2.1.isLt h.2.2) _ + rw [mem_cauchyGrid] + push_cast + constructor <;> omega + · exact dvd_zero _ + +/-- **The child identity for the Cauchy matrix** ((D.16) of [OpenAI2026ETH] at an interpolation +value). With `2 ^ p = 0` in `R`, the deformed contraction along the Cauchy matrix of a packing at +`T = t + α ζ`, for a grid point `t` of valuation `i`, is the sum over the exponent tuples of total +weight `∑ (b + 1) β_{(q, y₀, b)} < p` of the products over the groups of the contractions of the +children of `cauchyModeSplitting`. -/ +theorem deformedContraction_cauchyValueMatrix_eq_sum [Fintype V] [DecidableEq V] [Fintype G] + [DecidableEq E] (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) : + N.deformedContraction side (cauchyValueMatrix κ ζ a t packet slot) = + ∑ β ∈ modeBudget (fun Z : P × Fin (2 ^ (i + 1)) × Fin p => (Z.2.2 : ℕ) + 1) p, + ∏ g, ((cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').child β + g).contraction := + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti + hti').deformedContraction_eq_sum_prod_contraction_child _ (fun _ => Nat.succ_pos _) hp + fun e Z => two_pow_dvd_cauchyModeCoeff hslot hia κ ζ ht hti hti' e Z + +end Modes + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCauchyModes.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCauchyModes.lean new file mode 100644 index 0000000000..eb5731330c --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCauchyModes.lean @@ -0,0 +1,91 @@ +/- +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.CauchyModes +public import Complexity.Foundations.NumberTheory.Padics.RatValuation + +/-! +# Tests for the modes of the Cauchy kernel + +One packet of two slots (`a = 1`), oriented in coordinate `i = 0`, with groups `false` and `true`: +edge `0`, in slot `0`, runs from row `0` of group `false` to column `2` of group `true`, and edge +`1`, in slot `1`, from row `1` of group `true` to column `3` of group `false`. At the grid point +`t = 1`, of valuation `0`, over `ℤ / 4` (`p = 2`), the Cauchy matrix at `T = 1 + 4 ζ` has four +modes `(packet, tile, b)`: the tiles are `y₀ ∈ {0, 1}` and `b < 2`, of weight `b + 1`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.CauchyModes + +open Cslib.Circuits.Frontier TensorNetwork + +/-- The two-edge network over `ℤ / 4`, with tensor `(1, v + 1)` on the bit of the edge at `v`. -/ +def net : TensorNetwork (ZMod (2 ^ 2)) Bool (Fin 4) (Fin 2) where + src e := ⟨e.val, by omega⟩ + tgt e := ⟨e.val + 2, by omega⟩ + tensor v x := if x ⟨v.val % 2, Nat.mod_lt _ (by decide)⟩ then (v.val + 1 : ZMod (2 ^ 2)) else 1 + tensor_congr v x y h := by rw [h _ (by fin_cases v <;> decide)] + +/-- The rows `0, 1`. -/ +def side (v : Fin 4) : Bool := decide (v.val < 2) + +/-- Groups: rows `0` and column `3` are `false`, row `1` and column `2` are `true`. -/ +def group (v : Fin 4) : Bool := decide (v = 1 ∨ v = 2) + +/-- Edge `e` sits in slot `e` of the single packet. -/ +def slot (e : Fin 2) : ℕ := e.val + +/-- The packet's pair of groups, oriented as at slot `0`. -/ +def sym (_ : Unit) : Bool × Bool := (false, true) + +theorem horient (e : Fin 2) : (group (net.src e), group (net.tgt e)) = + if (slot e).testBit 0 then (sym ((fun _ => ()) e)).swap else sym ((fun _ => ()) e) := by + fin_cases e <;> decide + +/-- Reduction of `ℤ_(2)` modulo `4`. -/ +noncomputable abbrev κ := Rat.padicIntegerToZModPow 2 2 + +/-- **The child identity (D.16)** at `T = 1 + 4 ζ`: the deformed contraction along the Cauchy matrix +is a sum over the mode tuples of total weight below two of products of child contractions. -/ +example (ζ : ZMod (2 ^ 2)) : + net.deformedContraction side (cauchyValueMatrix κ ζ 1 1 (fun _ => ()) slot) = + ∑ β ∈ modeBudget (fun Z : Unit × Fin (2 ^ (0 + 1)) × Fin 2 => (Z.2.2 : ℕ) + 1) 2, + ∏ g, ((cauchyModeSplitting (sym := sym) (i := 0) side (by decide) (by decide) horient + (a := 1) (fun e => by fin_cases e <;> decide) (by decide) κ ζ (p := 2) (by decide) + (t := 1) (by decide) (by decide) (by decide)).child β g).contraction := + deformedContraction_cauchyValueMatrix_eq_sum side (by decide) (by decide) horient + (fun e => by fin_cases e <;> decide) (by decide) κ ζ (by decide) (by decide) (by decide) + (by decide) + +/-- The budget keeps the zero tuple and the two weight-one modes `b = 0`. -/ +example : + (modeBudget (fun Z : Unit × Fin (2 ^ (0 + 1)) × Fin 2 => (Z.2.2 : ℕ) + 1) 2).card = 3 := by + decide + +/-- Column `1`, in slot `1`, is in tile `1` with `j = 0`, so the mode `((), 1, 0)` reads it with +coefficient `j ^ 0 = 1`, and the mode of tile `0` does not read it. -/ +example : cauchyModeForm (ZMod (2 ^ 2)) 0 (fun _ : Fin 2 => ()) slot sym (p := 2) ((), 1, 0) 1 = 1 ∧ + cauchyModeForm (ZMod (2 ^ 2)) 0 (fun _ : Fin 2 => ()) slot sym (p := 2) ((), 0, 0) 1 = 0 := by + decide + +/-- Row `0` has digit `0`, so it only carries the modes of tile `0`, whose columns cross from the +other group. -/ +example (ζ : ZMod (2 ^ 2)) : + cauchyModeCoeff κ ζ 1 0 1 (fun _ : Fin 2 => ()) slot sym (p := 2) 0 ((), 1, 0) = 0 := by + simp [cauchyModeCoeff, slot] + +/-- A crossing coefficient has weight `b + 1`. -/ +example (ζ : ZMod (2 ^ 2)) : + (2 : ZMod (2 ^ 2)) ^ 2 ∣ cauchyModeCoeff κ ζ 1 0 1 (fun _ : Fin 2 => ()) slot sym (p := 2) 0 + ((), 0, 1) := + two_pow_dvd_cauchyModeCoeff (fun e => by fin_cases e <;> decide) (by decide) κ ζ (by decide) + (by decide) (by decide) 0 ((), 0, 1) + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.CauchyModes From 02ef13c857b9b8fd1f5aa83e5093c6fcb25e1f24 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:19:45 -0400 Subject: [PATCH 08/33] feat(GenericRAM/Native): depth-first stack recursion with native bounds A generic recursion scheme (leaf test, leaf step, start/next/absorb/finish) run by an explicit stack machine. Derivations `Eval I x y n h` (n nodes, h stack levels) are evaluated in 2n - 1 steps through states with at most h open frames. One native certificate runs the machine for a natively computed fuel; its time is (fuel + 1) times a polynomial in (h+1)(F+1) plus the leaf time, and its space a polynomial plus the leaf space. Also the node count of a recursion tree and its 2^O(n/sqrt(log n)) bound (Foundations/Data/Nat/RecursionTree.lean), for the call count of [OpenAI2026ETH, Appendix D.4.7]. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../GenericRAM/Native/StackRecursion.lean | 778 ++++++++++++++++++ .../Foundations/Data/Nat/RecursionTree.lean | 170 ++++ ComplexityTests/StackRecursion.lean | 257 ++++++ 4 files changed, 1207 insertions(+) create mode 100644 Complexity/Computability/GenericRAM/Native/StackRecursion.lean create mode 100644 Complexity/Foundations/Data/Nat/RecursionTree.lean create mode 100644 ComplexityTests/StackRecursion.lean diff --git a/Complexity.lean b/Complexity.lean index 69ba190535..e3af2ca51c 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1457,6 +1457,7 @@ public import Complexity.Computability.GenericRAM.Native.HeavyBranchFold public import Complexity.Computability.GenericRAM.Native.IndexFold public import Complexity.Computability.GenericRAM.Native.MeasurementFold public import Complexity.Computability.GenericRAM.Native.Nat +public import Complexity.Computability.GenericRAM.Native.StackRecursion public import Complexity.Computability.GenericRAM.Native.SubsetPairFold public import Complexity.Computability.GenericRAM.Signed public import Complexity.Computability.GenericRAM.Signed.Fits @@ -2726,6 +2727,7 @@ public import Complexity.Foundations.Data.Nat.Prime public import Complexity.Foundations.Data.Nat.Primrec public import Complexity.Foundations.Data.Nat.Radix public import Complexity.Foundations.Data.Nat.Radix.Measurement +public import Complexity.Foundations.Data.Nat.RecursionTree public import Complexity.Foundations.Data.Nat.ResidueSum public import Complexity.Foundations.Data.Nat.SqrtLog public import Complexity.Foundations.Data.Nat.SqrtRatio diff --git a/Complexity/Computability/GenericRAM/Native/StackRecursion.lean b/Complexity/Computability/GenericRAM/Native/StackRecursion.lean new file mode 100644 index 0000000000..c2fd37dd11 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Native/StackRecursion.lean @@ -0,0 +1,778 @@ +/- +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.GenericRAM.Native.Combinators +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.List +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Option +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives + +/-! +# Depth-first recursion with an explicit stack + +A *recursion scheme* (`StackRecursion.Scheme`) evaluates node descriptions `x : X` to values +`y : Y`. A node is either a leaf, evaluated by `leaf`, or a call, which keeps an accumulator +`a : A`: starting from `start x`, it asks `next x a` for its next child, folds the child's value +into the accumulator with `absorb`, and when `next` returns `none` produces its value with +`finish`. Children may depend on the values of earlier children through the accumulator. + +**The machine** (`Scheme.step`). Its state is the stack of open calls, innermost first, each +with its accumulator, and a register holding a pending call or a returned value. Each step +performs one call or one return. A derivation `Scheme.Eval I x y n h`, with `n` nodes and `h` +stack levels, makes the machine reach `y` in `2 * n - 1` steps (`Scheme.Eval.run`), through +states with at most `h` open calls whose nodes, frames and values satisfy the invariant `I` +(`Scheme.Eval.good_iterate`). + +**The native certificate** (`Scheme.certificate`). The leaf step is an arbitrary native +certificate; the other components are polynomial-time. One fixed machine runs the stack machine +for a natively computed number `fuel a` of steps (`NativeCertificate.iterate`) and returns the +register. When `2 * n - 1 ≤ fuel a` the result is `y` (`Scheme.run_eq_some`). If every node, +frame and value on the derivation has encoded length at most `F` and every leaf is evaluated +within time `TL` and space `SL`, the time is at most `fuel a + 1` times +`K ((h + 1) (F + 1)) ^ K + TL` and the workspace at most `K ((h + 1) (F + 1)) ^ K + SL`, up to +the cost of computing the fuel and the root and of the binary clock, for one constant `K` of the +scheme (`Scheme.certificate_le`). Only the stack is kept, so the workspace grows with the depth, +not with the number of nodes. The number of nodes of a recursion tree is bounded in +`Foundations/Data/Nat/RecursionTree.lean`. + +This is the depth-first evaluation of the recursion in the proof of Theorem D.1 of +[OpenAI2026ETH] (Appendix D.4.7, p. 64), which keeps one sum over mode tuples and one product +over groups per stack level. The scheme is generic: the recursion of Appendix D.4 is one +instance, with its leaves evaluated by direct summation. + +## Main definitions + +* `Cslib.GenericRAM.StackRecursion.Scheme`: a recursion scheme. +* `Cslib.GenericRAM.StackRecursion.Invariant`: predicates on the nodes, frames and values met. +* `Cslib.GenericRAM.StackRecursion.Scheme.Loop`, `Cslib.GenericRAM.StackRecursion.Scheme.Eval`: + derivations of the loop of a call and of the value of a node, with their node counts and + stack depths. +* `Cslib.GenericRAM.StackRecursion.Scheme.step`, `Cslib.GenericRAM.StackRecursion.Scheme.run`: + the stack machine and its clocked run. +* `Cslib.GenericRAM.StackRecursion.Scheme.PolyTime`: the polynomial-time components. +* `Cslib.GenericRAM.StackRecursion.Scheme.certificate`: one native machine for the clocked run. + +## Main results + +* `Cslib.GenericRAM.StackRecursion.Scheme.Eval.run`: a derivation with `n` nodes is evaluated in + `2 * n - 1` steps. +* `Cslib.GenericRAM.StackRecursion.Scheme.run_eq_some`: enough fuel gives the value. +* `Cslib.GenericRAM.StackRecursion.Scheme.certificate_le`: time `fuel + 1` times a polynomial in + `(h + 1) * (F + 1)` plus the leaf time, and workspace a polynomial plus the leaf space. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.7 (p. 64). +-/ + +@[expose] public section + +namespace Cslib.GenericRAM.StackRecursion + +open Turing.MultiTapeTM + +/-- A *recursion scheme*. A node `x` is a leaf when `isLeaf x`, with value `leaf x`. Otherwise +it is a call: its accumulator starts at `start x`; while `next x a` returns a child, the child is +evaluated and its value folded into the accumulator by `absorb`; when `next x a` is `none` the +call returns `finish x a`. -/ +structure Scheme (X A Y : Type) where + /-- Whether a node is a leaf. -/ + isLeaf : X → Bool + /-- The value of a leaf. -/ + leaf : X → Y + /-- The initial accumulator of a call. -/ + start : X → A + /-- The next child of a call with the given accumulator, or `none` when it is done. -/ + next : X → A → Option X + /-- Fold the value of the current child into the accumulator. -/ + absorb : X → A → Y → A + /-- The value of a call from its final accumulator. -/ + finish : X → A → Y + +/-- Predicates on what a derivation meets: the nodes evaluated, the frames pushed (a call with +its accumulator while one of its children is evaluated) and the values returned. -/ +structure Invariant (X A Y : Type) where + /-- Every node evaluated satisfies `node`. -/ + node : X → Prop + /-- Every pushed frame satisfies `frame`. -/ + frame : X → A → Prop + /-- Every returned value satisfies `value`. -/ + value : Y → Prop + +/-- A state of the stack machine: the open calls with their accumulators, innermost first, a +pending call and a returned value. -/ +abbrev State (X A Y : Type) := List (X × A) × Option X × Option Y + +namespace Scheme + +variable {X A Y : Type} (R : Scheme X A Y) + +/-- Continue the call `x` with accumulator `a` above the stack `st`: push its frame and call its +next child, or return its value when it has none. -/ +def resume (x : X) (a : A) (st : List (X × A)) : State X A Y := + match R.next x a with + | none => (st, none, some (R.finish x a)) + | some c => ((x, a) :: st, some c, none) + +/-- **One step of the stack machine.** A pending leaf is evaluated; a pending call is started; a +returned value is absorbed by the innermost open call, which then continues. A value returned +with no open call is final. -/ +def step : State X A Y → State X A Y + | (st, some x, _) => if R.isLeaf x then (st, none, some (R.leaf x)) else R.resume x (R.start x) st + | ((x, a) :: st, none, some y) => R.resume x (R.absorb x a y) st + | σ => σ + +theorem resume_of_next_none {x : X} {a : A} (st : List (X × A)) (h : R.next x a = none) : + R.resume x a st = (st, none, some (R.finish x a)) := by + simp [resume, h] + +theorem resume_of_next_some {x c : X} {a : A} (st : List (X × A)) (h : R.next x a = some c) : + R.resume x a st = ((x, a) :: st, some c, none) := by + simp [resume, h] + +theorem step_leaf {x : X} (st : List (X × A)) (r : Option Y) (h : R.isLeaf x = true) : + R.step (st, some x, r) = (st, none, some (R.leaf x)) := by + simp [step, h] + +theorem step_call {x : X} (st : List (X × A)) (r : Option Y) (h : R.isLeaf x = false) : + R.step (st, some x, r) = R.resume x (R.start x) st := by + simp [step, h] + +theorem step_return (x : X) (a : A) (st : List (X × A)) (y : Y) : + R.step ((x, a) :: st, none, some y) = R.resume x (R.absorb x a y) st := rfl + +/-- A value returned with no open call is final. -/ +@[simp] theorem step_final (y : Y) : R.step ([], none, some y) = ([], none, some y) := rfl + +/-- **The loop of a call.** `R.Loop I x a a' n h`: the call `x`, from accumulator `a`, evaluates +its remaining children with `n` nodes in total and ends with accumulator `a'`, using at most `h` +stack levels above its own frame. Every frame pushed, child evaluated and value returned +satisfies `I`. -/ +inductive Loop (I : Invariant X A Y) : X → A → A → ℕ → ℕ → Prop + /-- The call has no further child. -/ + | done {x : X} {a : A} : R.next x a = none → Loop I x a a 0 0 + /-- The next child is a leaf. -/ + | leaf {x c : X} {a a' : A} {n h : ℕ} : I.frame x a → R.next x a = some c → I.node c → + R.isLeaf c = true → I.value (R.leaf c) → Loop I x (R.absorb x a (R.leaf c)) a' n h → + Loop I x a a' (n + 1) h + /-- The next child is a call, whose own loop ends with accumulator `b`. -/ + | call {x c : X} {a b a' : A} {n₁ h₁ n₂ h₂ : ℕ} : I.frame x a → R.next x a = some c → + I.node c → R.isLeaf c = false → Loop I c (R.start c) b n₁ h₁ → I.value (R.finish c b) → + Loop I x (R.absorb x a (R.finish c b)) a' n₂ h₂ → + Loop I x a a' (n₁ + 1 + n₂) (max (h₁ + 1) h₂) + +/-- **A derivation of the value of a node.** `R.Eval I x y n h`: the node `x` has value `y`, +computed with `n` nodes and at most `h` open calls, and every node, frame and value met satisfies +`I`. A leaf has one node and no open call; a call has one more node and one more level than its +loop. -/ +def Eval (I : Invariant X A Y) (x : X) (y : Y) (n h : ℕ) : Prop := + I.node x ∧ I.value y ∧ + ((R.isLeaf x = true ∧ y = R.leaf x ∧ n = 1 ∧ h = 0) ∨ + (R.isLeaf x = false ∧ ∃ b n' h', R.Loop I x (R.start x) b n' h' ∧ y = R.finish x b ∧ + n = n' + 1 ∧ h = h' + 1)) + +variable {R} {I : Invariant X A Y} + +/-- A leaf is evaluated by `leaf`. -/ +theorem Eval.leaf {x : X} (hx : I.node x) (hl : R.isLeaf x = true) (hv : I.value (R.leaf x)) : + R.Eval I x (R.leaf x) 1 0 := + ⟨hx, hv, .inl ⟨hl, rfl, rfl, rfl⟩⟩ + +/-- A call is evaluated by its loop, from `start`, and `finish`. -/ +theorem Eval.call {x : X} {b : A} {n h : ℕ} (hx : I.node x) (hl : R.isLeaf x = false) + (hloop : R.Loop I x (R.start x) b n h) (hv : I.value (R.finish x b)) : + R.Eval I x (R.finish x b) (n + 1) (h + 1) := + ⟨hx, hv, .inr ⟨hl, b, n, h, hloop, rfl, rfl, rfl⟩⟩ + +/-- **The loop absorbs an evaluated child.** -/ +theorem Loop.child {x c : X} {a a' : A} {y : Y} {n₁ h₁ n₂ h₂ : ℕ} (hf : I.frame x a) + (hn : R.next x a = some c) (hc : R.Eval I c y n₁ h₁) + (hrest : R.Loop I x (R.absorb x a y) a' n₂ h₂) : + R.Loop I x a a' (n₁ + n₂) (max h₁ h₂) := by + obtain ⟨hnode, hval, ⟨hl, rfl, rfl, rfl⟩ | ⟨hl, b, n', h', hloop, rfl, rfl, rfl⟩⟩ := hc + · simpa [Nat.add_comm] using Loop.leaf hf hn hnode hl hval hrest + · exact Loop.call hf hn hnode hl hloop hval hrest + +/-- Every derivation has at least one node. -/ +theorem Eval.one_le {x : X} {y : Y} {n h : ℕ} (he : R.Eval I x y n h) : 1 ≤ n := by + obtain ⟨-, -, ⟨-, -, rfl, -⟩ | ⟨-, -, -, -, -, -, rfl, -⟩⟩ := he <;> omega + +/-! ### The machine follows the derivation -/ + +/-- **A loop is run in two steps per node**, ending with the value of the call returned to the +stack below it. -/ +theorem Loop.run {x : X} {a a' : A} {n h : ℕ} (hl : R.Loop I x a a' n h) (st : List (X × A)) : + R.step^[2 * n] (R.resume x a st) = (st, none, some (R.finish x a')) := by + induction hl generalizing st with + | done hn => exact R.resume_of_next_none st hn + | @leaf x c a a' n h _ hn _ hl _ _ ih => + rw [show 2 * (n + 1) = 2 * n + 1 + 1 by ring] + simp only [Function.iterate_add_apply, Function.iterate_one] + rw [R.resume_of_next_some st hn, R.step_leaf _ _ hl, R.step_return, ih] + | @call x c a b a' n₁ h₁ n₂ h₂ _ hn _ hl _ _ _ ih₁ ih₂ => + rw [show 2 * (n₁ + 1 + n₂) = 2 * n₂ + 1 + 2 * n₁ + 1 by ring] + simp only [Function.iterate_add_apply, Function.iterate_one] + rw [R.resume_of_next_some st hn, R.step_call _ _ hl, ih₁, R.step_return, ih₂] + +/-- **A node with `n` nodes is evaluated in `2 * n - 1` steps**, above any stack. -/ +theorem Eval.run {x : X} {y : Y} {n h : ℕ} (he : R.Eval I x y n h) (st : List (X × A)) : + R.step^[2 * n - 1] (st, some x, none) = (st, none, some y) := by + obtain ⟨-, -, ⟨hl, rfl, rfl, rfl⟩ | ⟨hl, b, n', h', hloop, rfl, rfl, rfl⟩⟩ := he + · exact R.step_leaf st none hl + · rw [show 2 * (n' + 1) - 1 = 2 * n' + 1 by omega, Function.iterate_add_apply, + Function.iterate_one, R.step_call _ _ hl, hloop.run] + +/-! ### The states met -/ + +/-- A state is *good* for `I` and a depth `H` when at most `H` calls are open, every open frame +satisfies `I`, and the register holds either a pending node or a returned value satisfying +`I`. -/ +def Good (I : Invariant X A Y) (H : ℕ) (σ : State X A Y) : Prop := + σ.1.length ≤ H ∧ (∀ f ∈ σ.1, I.frame f.1 f.2) ∧ + ((∃ x, σ.2 = (some x, none) ∧ I.node x) ∨ (∃ y, σ.2 = (none, some y) ∧ I.value y)) + +theorem Good.mono {H H' : ℕ} {σ : State X A Y} (hσ : Good I H σ) (h : H ≤ H') : Good I H' σ := + ⟨hσ.1.trans h, hσ.2⟩ + +private theorem good_push {x : X} {a : A} {st : List (X × A)} {H : ℕ} {r : Option X × Option Y} + (hst : ∀ f ∈ st, I.frame f.1 f.2) (hf : I.frame x a) (hH : st.length + 1 ≤ H) + (hr : (∃ x, r = (some x, none) ∧ I.node x) ∨ (∃ y, r = (none, some y) ∧ I.value y)) : + Good I H ((x, a) :: st, r) := by + refine ⟨by simpa using hH, ?_, hr⟩ + intro f hf' + rcases List.mem_cons.mp hf' with rfl | hf' + · exact hf + · exact hst f hf' + +/-- **The states of a loop are good**, strictly before it returns. -/ +theorem Loop.good {x : X} {a a' : A} {n h : ℕ} (hl : R.Loop I x a a' n h) {st : List (X × A)} + (hst : ∀ f ∈ st, I.frame f.1 f.2) : + ∀ j < 2 * n, Good I (st.length + h + 1) (R.step^[j] (R.resume x a st)) := by + induction hl generalizing st with + | done _ => intro j hj; omega + | @leaf x c a a' n h hf hn hc hl hv _ ih => + intro j hj + rw [R.resume_of_next_some st hn] + rcases j with _ | _ | j + · exact good_push hst hf (by omega) (.inl ⟨c, rfl, hc⟩) + · rw [Function.iterate_one, R.step_leaf _ _ hl] + exact good_push hst hf (by omega) (.inr ⟨_, rfl, hv⟩) + · rw [show j + 1 + 1 = j + 2 by ring, Function.iterate_add_apply, + show R.step^[2] ((x, a) :: st, some c, none) = R.resume x (R.absorb x a (R.leaf c)) st by + simp only [show (2 : ℕ) = 1 + 1 from rfl, Function.iterate_add_apply, + Function.iterate_one] + rw [R.step_leaf _ _ hl, R.step_return]] + exact ih hst j (by omega) + | @call x c a b a' n₁ h₁ n₂ h₂ hf hn hc hl hloop₁ hv _ ih₁ ih₂ => + intro j hj + rw [R.resume_of_next_some st hn] + have hst' : ∀ f ∈ (x, a) :: st, I.frame f.1 f.2 := fun f hf' => by + rcases List.mem_cons.mp hf' with rfl | hf' + · exact hf + · exact hst f hf' + have hbegin : R.step^[1] ((x, a) :: st, some c, none) = R.resume c (R.start c) ((x, a) :: st) := + by rw [Function.iterate_one, R.step_call _ _ hl] + rcases j with _ | j + · exact good_push hst hf (by omega) (.inl ⟨c, rfl, hc⟩) + rcases Nat.lt_or_ge j (2 * n₁) with hj₁ | hj₁ + · rw [show j + 1 = j + 1 from rfl, Function.iterate_add_apply, hbegin] + exact (ih₁ hst' j hj₁).mono (by simp only [List.length_cons]; omega) + obtain ⟨k, rfl⟩ := Nat.exists_eq_add_of_le hj₁ + rw [show 2 * n₁ + k + 1 = k + 2 * n₁ + 1 by ring, Function.iterate_add_apply, + Function.iterate_add_apply, hbegin, hloop₁.run] + rcases k with _ | k + · exact good_push hst hf (by omega) (.inr ⟨_, rfl, hv⟩) + · rw [show k + 1 = k + 1 from rfl, Function.iterate_add_apply, Function.iterate_one, + R.step_return] + exact (ih₂ hst k (by omega)).mono (by omega) + +/-- **The states of an evaluation are good**, up to and including its returned value. -/ +theorem Eval.good {x : X} {y : Y} {n h : ℕ} (he : R.Eval I x y n h) {st : List (X × A)} + (hst : ∀ f ∈ st, I.frame f.1 f.2) : + ∀ j ≤ 2 * n - 1, Good I (st.length + h) (R.step^[j] (st, some x, none)) := by + obtain ⟨hx, hy, ⟨hl, rfl, rfl, rfl⟩ | ⟨hl, b, n', h', hloop, rfl, rfl, rfl⟩⟩ := he + · intro j hj + rcases j with _ | j + · exact ⟨by simp, hst, .inl ⟨x, rfl, hx⟩⟩ + · rw [show j = 0 by omega, Function.iterate_one, R.step_leaf _ _ hl] + exact ⟨by simp, hst, .inr ⟨_, rfl, hy⟩⟩ + · intro j hj + rcases j with _ | j + · exact ⟨by simp, hst, .inl ⟨x, rfl, hx⟩⟩ + rw [Function.iterate_add_apply, Function.iterate_one, R.step_call _ _ hl] + rcases Nat.lt_or_ge j (2 * n') with hj' | hj' + · exact (hloop.good hst j hj').mono (by omega) + · rw [show j = 2 * n' by omega, hloop.run] + exact ⟨by simp, hst, .inr ⟨_, rfl, hy⟩⟩ + +/-- **Every state of a run from an empty stack is good**, including the final state, which no +step changes. -/ +theorem Eval.good_iterate {x : X} {y : Y} {n h : ℕ} (he : R.Eval I x y n h) (j : ℕ) : + Good I h (R.step^[j] ([], some x, none)) := by + rcases le_or_gt j (2 * n - 1) with hj | hj + · simpa using he.good (st := []) (by simp) j hj + · obtain ⟨k, rfl⟩ := Nat.exists_eq_add_of_lt hj + rw [show 2 * n - 1 + k + 1 = (k + 1) + (2 * n - 1) by omega, Function.iterate_add_apply, + he.run, Function.iterate_fixed (R.step_final y)] + exact ⟨by simp, by simp, .inr ⟨y, rfl, he.2.1⟩⟩ + +/-! ### The native machine -/ + +section Native + +variable (R) {encX : X ↪ List Bool} {encA : A ↪ List Bool} {encY : Y ↪ List Bool} + +/-- The encoding of a machine state: the list of open frames, each a pair, then the optional +pending node and the optional returned value. -/ +abbrev encState (encX : X ↪ List Bool) (encA : A ↪ List Bool) (encY : Y ↪ List Bool) : + State X A Y ↪ List Bool := + encPair (encList (encPair encX encA)) (encPair (encOption encX) (encOption encY)) + +/-- **The polynomial-time components** of a scheme: everything but the leaf step. -/ +structure PolyTime (encX : X ↪ List Bool) (encA : A ↪ List Bool) (encY : Y ↪ List Bool) : + Prop where + /-- The leaf test. -/ + isLeaf : ComputableInPolyTime R.isLeaf encX encBool + /-- The initial accumulator. -/ + start : ComputableInPolyTime R.start encX encA + /-- Child generation. -/ + next : ComputableInPolyTime (fun p : X × A => R.next p.1 p.2) (encPair encX encA) + (encOption encX) + /-- Absorbing a child's value. -/ + absorb : ComputableInPolyTime (fun p : (X × A) × Y => R.absorb p.1.1 p.1.2 p.2) + (encPair (encPair encX encA) encY) encA + /-- The value of a call. -/ + finish : ComputableInPolyTime (fun p : X × A => R.finish p.1 p.2) (encPair encX encA) encY + +/-- Whether the register holds a pending leaf. -/ +def isLeafCall (σ : State X A Y) : Bool := σ.2.1.elim false R.isLeaf + +/-- Evaluate a pending node as a leaf, keeping the stack. -/ +def leafStep (σ : State X A Y) : State X A Y := + (σ.1, none, σ.2.1.elim none fun x => some (R.leaf x)) + +/-- Every step but the evaluation of a leaf. -/ +def polyStep (σ : State X A Y) : State X A Y := + σ.2.1.elim + (σ.2.2.elim σ fun y => σ.1.head?.elim σ fun f => R.resume f.1 (R.absorb f.1 f.2 y) σ.1.tail) + (fun x => R.resume x (R.start x) σ.1) + +/-- A step evaluates a leaf, or takes a polynomial-time step. -/ +theorem step_eq_ite (σ : State X A Y) : + R.step σ = if R.isLeafCall σ then R.leafStep σ else R.polyStep σ := by + rcases σ with ⟨_ | ⟨⟨x, a⟩, st⟩, _ | c, _ | y⟩ <;> + simp only [step, isLeafCall, leafStep, polyStep, Option.elim, List.head?, List.tail, + Bool.false_eq_true, ite_false] <;> rfl + +variable {R} + +/-- Continuing a call is polynomial-time. -/ +theorem resume_polyTime (h : R.PolyTime encX encA encY) : + ComputableInPolyTime (fun q : (X × A) × List (X × A) => R.resume q.1.1 q.1.2 q.2) + (encPair (encPair encX encA) (encList (encPair encX encA))) (encState encX encA encY) := by + have hfr := ComputableInPolyTime.fst (encPair encX encA) (encList (encPair encX encA)) + have hst := ComputableInPolyTime.snd (encPair encX encA) (encList (encPair encX encA)) + have hfallback : ComputableInPolyTime + (fun q : (X × A) × List (X × A) => (q.2, (none : Option X), some (R.finish q.1.1 q.1.2))) + (encPair (encPair encX encA) (encList (encPair encX encA))) (encState encX encA encY) := + hst.prod ((ComputableInPolyTime.const none).prod + ((ComputableInPolyTime.optionSome encY).comp (h.finish.comp hfr))) + have hbody : ComputableInPolyTime + (fun p : ((X × A) × List (X × A)) × X => (p.1.1 :: p.1.2, some p.2, (none : Option Y))) + (encPair (encPair (encPair encX encA) (encList (encPair encX encA))) encX) + (encState encX encA encY) := by + have hq := ComputableInPolyTime.fst + (encPair (encPair encX encA) (encList (encPair encX encA))) encX + have hc := ComputableInPolyTime.snd + (encPair (encPair encX encA) (encList (encPair encX encA))) encX + exact (ComputableInPolyTime.cons' (hfr.comp hq) (hst.comp hq)).prod + (((ComputableInPolyTime.optionSome encX).comp hc).prod (ComputableInPolyTime.const none)) + have helim := ComputableInPolyTime.optionElimWithInput + (value := fun q : (X × A) × List (X × A) => R.next q.1.1 q.1.2) + (fallback := fun q => (q.2, (none : Option X), some (R.finish q.1.1 q.1.2))) + (body := fun q c => (q.1 :: q.2, some c, (none : Option Y))) (h.next.comp hfr) hfallback hbody + refine helim.of_enc_eq fun q => ?_ + rcases q with ⟨⟨x, a⟩, st⟩ + change encState encX encA encY (R.resume x a st) = + encState encX encA encY ((R.next x a).elim (st, none, some (R.finish x a)) + fun c => ((x, a) :: st, some c, none)) + rw [resume] + cases R.next x a <;> rfl + +/-- The leaf test on the register is polynomial-time. -/ +theorem isLeafCall_polyTime (h : R.PolyTime encX encA encY) : + ComputableInPolyTime R.isLeafCall (encState encX encA encY) encBool := by + have hreg := (ComputableInPolyTime.fst (encOption encX) (encOption encY)).comp + (ComputableInPolyTime.snd (encList (encPair encX encA)) + (encPair (encOption encX) (encOption encY))) + exact ComputableInPolyTime.optionElimWithInput (body := fun _ x => R.isLeaf x) hreg + (ComputableInPolyTime.const false) + (h.isLeaf.comp (ComputableInPolyTime.snd (encState encX encA encY) encX)) + +/-- Every step but the evaluation of a leaf is polynomial-time. -/ +theorem polyStep_polyTime (h : R.PolyTime encX encA encY) : + ComputableInPolyTime R.polyStep (encState encX encA encY) (encState encX encA encY) := by + have hstack : ComputableInPolyTime (fun σ : State X A Y => σ.1) (encState encX encA encY) + (encList (encPair encX encA)) := + ComputableInPolyTime.fst (encList (encPair encX encA)) + (encPair (encOption encX) (encOption encY)) + have hreg : ComputableInPolyTime (fun σ : State X A Y => σ.2) (encState encX encA encY) + (encPair (encOption encX) (encOption encY)) := + ComputableInPolyTime.snd (encList (encPair encX encA)) + (encPair (encOption encX) (encOption encY)) + have hpend : ComputableInPolyTime (fun σ : State X A Y => σ.2.1) (encState encX encA encY) + (encOption encX) := (ComputableInPolyTime.fst (encOption encX) (encOption encY)).comp hreg + have hret : ComputableInPolyTime (fun σ : State X A Y => σ.2.2) (encState encX encA encY) + (encOption encY) := (ComputableInPolyTime.snd (encOption encX) (encOption encY)).comp hreg + -- A returned value is absorbed by the innermost open frame. + have hframe : ComputableInPolyTime + (fun q : (State X A Y × Y) × (X × A) => + R.resume q.2.1 (R.absorb q.2.1 q.2.2 q.1.2) q.1.1.1.tail) + (encPair (encPair (encState encX encA encY) encY) (encPair encX encA)) + (encState encX encA encY) := by + have hq := ComputableInPolyTime.fst (encPair (encState encX encA encY) encY) + (encPair encX encA) + have hf := ComputableInPolyTime.snd (encPair (encState encX encA encY) encY) + (encPair encX encA) + have hy := (ComputableInPolyTime.snd (encState encX encA encY) encY).comp hq + have hσ := (ComputableInPolyTime.fst (encState encX encA encY) encY).comp hq + have habs := h.absorb.comp (hf.prod hy) + exact (resume_polyTime h).comp + ((((ComputableInPolyTime.fst encX encA).comp hf).prod habs).prod + ((ComputableInPolyTime.tail (encA := encPair encX encA)).comp (hstack.comp hσ))) + have hreturn : ComputableInPolyTime + (fun p : State X A Y × Y => + p.1.1.head?.elim p.1 fun f => R.resume f.1 (R.absorb f.1 f.2 p.2) p.1.1.tail) + (encPair (encState encX encA encY) encY) (encState encX encA encY) := by + have hσ := ComputableInPolyTime.fst (encState encX encA encY) encY + have hhead : ComputableInPolyTime (fun p : State X A Y × Y => p.1.1.head?) + (encPair (encState encX encA encY) encY) (encOption (encPair encX encA)) := + (ComputableInPolyTime.head? (encPair encX encA) (encL := encList (encPair encX encA))).comp + (hstack.comp hσ) + exact ComputableInPolyTime.optionElimWithInput + (value := fun p : State X A Y × Y => p.1.1.head?) (fallback := fun p => p.1) + (body := fun p f => R.resume f.1 (R.absorb f.1 f.2 p.2) p.1.1.tail) hhead hσ hframe + have hnoCall : ComputableInPolyTime + (fun σ : State X A Y => + σ.2.2.elim σ fun y => σ.1.head?.elim σ fun f => R.resume f.1 (R.absorb f.1 f.2 y) σ.1.tail) + (encState encX encA encY) (encState encX encA encY) := + ComputableInPolyTime.optionElimWithInput (value := fun σ : State X A Y => σ.2.2) + (fallback := fun σ => σ) + (body := fun σ y => σ.1.head?.elim σ fun f => R.resume f.1 (R.absorb f.1 f.2 y) σ.1.tail) + hret ComputableInPolyTime.id' hreturn + have hcall : ComputableInPolyTime + (fun p : State X A Y × X => R.resume p.2 (R.start p.2) p.1.1) + (encPair (encState encX encA encY) encX) (encState encX encA encY) := by + have hx : ComputableInPolyTime (fun p : State X A Y × X => p.2) + (encPair (encState encX encA encY) encX) encX := + ComputableInPolyTime.snd (encState encX encA encY) encX + have hst : ComputableInPolyTime (fun p : State X A Y × X => p.1.1) + (encPair (encState encX encA encY) encX) (encList (encPair encX encA)) := + hstack.comp (ComputableInPolyTime.fst (encState encX encA encY) encX) + exact (resume_polyTime h).comp ((hx.prod (h.start.comp hx)).prod hst) + exact ComputableInPolyTime.optionElimWithInput (value := fun σ : State X A Y => σ.2.1) + (fallback := fun σ => σ.2.2.elim σ fun y => + σ.1.head?.elim σ fun f => R.resume f.1 (R.absorb f.1 f.2 y) σ.1.tail) + (body := fun σ x => R.resume x (R.start x) σ.1) hpend hnoCall hcall + +end Native + +section Certificate + +variable {encX : X ↪ List Bool} {encA : A ↪ List Bool} {encY : Y ↪ List Bool} + +theorem stack_polyTime : + ComputableInPolyTime (fun σ : State X A Y => σ.1) (encState encX encA encY) + (encList (encPair encX encA)) := + ComputableInPolyTime.fst _ _ + +theorem pending_polyTime : + ComputableInPolyTime (fun σ : State X A Y => σ.2.1) (encState encX encA encY) + (encOption encX) := + (ComputableInPolyTime.fst (encOption encX) (encOption encY)).comp (ComputableInPolyTime.snd _ _) + +variable (R) in +/-- **The leaf step**: the leaf certificate on the pending node, keeping the stack. -/ +noncomputable def leafStepCertificate (leaf : NativeCertificate R.leaf encX encY) : + NativeCertificate R.leafStep (encState encX encA encY) (encState encX encA encY) := + (NativeCertificate.ofPolyTime stack_polyTime).prod ((NativeCertificate.const none).prod + (NativeCertificate.option (value := fun σ : State X A Y => σ.2.1) (fallback := fun _ => none) + (body := fun p : State X A Y × X => some (R.leaf p.2)) + (NativeCertificate.ofPolyTime pending_polyTime) (NativeCertificate.const none) + (NativeCertificate.some (leaf.comp (NativeCertificate.payload _ encX))))) + +/-- **One step of the machine**: the leaf certificate on a pending leaf, and a polynomial-time +step otherwise. -/ +noncomputable def stepCertificate (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) : + NativeCertificate R.step (encState encX encA encY) (encState encX encA encY) := + (NativeCertificate.branch (NativeCertificate.ofPolyTime (isLeafCall_polyTime h)) + (leafStepCertificate R leaf) (NativeCertificate.ofPolyTime (polyStep_polyTime h))).congr + fun σ => (R.step_eq_ite σ).symm + +theorem stepCertificate_time (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) (σ : State X A Y) : + (stepCertificate h leaf).time σ = + (NativeCertificate.ofPolyTime (isLeafCall_polyTime h)).time σ + + if R.isLeafCall σ then (leafStepCertificate R (encA := encA) leaf).time σ + else (NativeCertificate.ofPolyTime (polyStep_polyTime h)).time σ := rfl + +theorem stepCertificate_space (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) (σ : State X A Y) : + (stepCertificate h leaf).space σ = + (NativeCertificate.ofPolyTime (isLeafCall_polyTime h)).space σ + + if R.isLeafCall σ then (leafStepCertificate R (encA := encA) leaf).space σ + else (NativeCertificate.ofPolyTime (polyStep_polyTime h)).space σ := rfl + +theorem leafStepCertificate_time (leaf : NativeCertificate R.leaf encX encY) (st : List (X × A)) + (x : X) (v : Option Y) : + (leafStepCertificate R (encA := encA) leaf).time (st, some x, v) = + (NativeCertificate.ofPolyTime (stack_polyTime (encX := encX) (encA := encA) + (encY := encY))).time (st, some x, v) + + ((encState encX encA encY (st, some x, v)).length + + (2 * (NativeCertificate.ofPolyTime (pending_polyTime (encX := encX) (encA := encA) + (encY := encY))).time (st, some x, v) + + (encState encX encA encY (st, some x, v)).length + + ((encPair (encState encX encA encY) encX ((st, some x, v), x)).length + + leaf.time x))) := rfl + +theorem leafStepCertificate_space (leaf : NativeCertificate R.leaf encX encY) + (st : List (X × A)) (x : X) (v : Option Y) : + (leafStepCertificate R (encA := encA) leaf).space (st, some x, v) = + (NativeCertificate.ofPolyTime (stack_polyTime (encX := encX) (encA := encA) + (encY := encY))).space (st, some x, v) + + ((encState encX encA encY (st, some x, v)).length + + (2 * (NativeCertificate.ofPolyTime (pending_polyTime (encX := encX) (encA := encA) + (encY := encY))).space (st, some x, v) + + (encState encX encA encY (st, some x, v)).length + + ((encPair (encState encX encA encY) encX ((st, some x, v), x)).length + + leaf.space x))) := rfl + +variable (R) in +/-- The leaf cost of a state: the leaf certificate's bound at a pending leaf, and zero +otherwise. -/ +def leafCost (cost : X → ℕ) (σ : State X A Y) : ℕ := + σ.2.1.elim 0 fun x => if R.isLeaf x then cost x else 0 + +private theorem length_pending_le {σ : State X A Y} {x : X} (hx : σ.2.1 = some x) : + (encX x).length ≤ (encState encX encA encY σ).length := by + rcases σ with ⟨st, r, v⟩ + simp only at hx + subst hx + simp only [length_encPair, encOption_some, List.length_cons] + omega + +/-- **The cost of one step**: a polynomial in the state length, plus the leaf certificate at a +pending leaf. -/ +theorem exists_stepCertificate_le (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) : + ∃ K : ℕ, ∀ σ : State X A Y, + (stepCertificate h leaf).time σ ≤ + K * ((encState encX encA encY σ).length + 1) ^ K + R.leafCost leaf.time σ ∧ + (stepCertificate h leaf).space σ ≤ + K * ((encState encX encA encY σ).length + 1) ^ K + R.leafCost leaf.space σ := by + have ht := NativeCertificate.ofPolyTime_isPolynomial (isLeafCall_polyTime h) + have hp := NativeCertificate.ofPolyTime_isPolynomial (polyStep_polyTime h) + have hs := NativeCertificate.ofPolyTime_isPolynomial (stack_polyTime (encX := encX) + (encA := encA) (encY := encY)) + have hc := NativeCertificate.ofPolyTime_isPolynomial (pending_polyTime (encX := encX) + (encA := encA) (encY := encY)) + -- Everything but the leaf certificate is polynomial in the state length. + have hT := ((((ht.1.add hp.1).add hs.1).add ((PolyBounded.const 2).mul hc.1)).add + ((PolyBounded.const 6).mul PolyBounded.self)).add (PolyBounded.const 1) + have hS := ((((ht.2.add hp.2).add hs.2).add ((PolyBounded.const 2).mul hc.2)).add + ((PolyBounded.const 6).mul PolyBounded.self)).add (PolyBounded.const 1) + obtain ⟨cT, dT, hcT⟩ := hT.exists_le + obtain ⟨cS, dS, hcS⟩ := hS.exists_le + refine ⟨cT + cS + dT + dS, fun σ => ?_⟩ + have hmono {c d : ℕ} (hc : c ≤ cT + cS + dT + dS) (hd : d ≤ cT + cS + dT + dS) : + c * ((encState encX encA encY σ).length + 1) ^ d ≤ + (cT + cS + dT + dS) * ((encState encX encA encY σ).length + 1) ^ (cT + cS + dT + dS) := + Nat.mul_le_mul hc (Nat.pow_le_pow_right (Nat.succ_pos _) hd) + have hT' := (hcT σ).trans (hmono (by omega) (by omega)) + have hS' := (hcS σ).trans (hmono (by omega) (by omega)) + rcases σ with ⟨st, _ | x, v⟩ + · rw [stepCertificate_time, stepCertificate_space] + simp only [leafCost, isLeafCall, Option.elim, Bool.false_eq_true, ite_false, Nat.add_zero] + constructor <;> omega + · have hx := length_pending_le (encX := encX) (encA := encA) (encY := encY) + (σ := (st, some x, v)) rfl + rw [stepCertificate_time, stepCertificate_space] + by_cases hl : R.isLeaf x = true + · simp only [leafCost, isLeafCall, Option.elim, hl, ite_true] + have hpair := length_encPair (enc₁ := encState encX encA encY) (enc₂ := encX) + ((st, some x, v), x) + dsimp only at hpair + rw [leafStepCertificate_time, leafStepCertificate_space] + constructor <;> omega + · simp only [Bool.not_eq_true] at hl + simp only [leafCost, isLeafCall, Option.elim, hl, Bool.false_eq_true, ite_false, + Nat.add_zero] + constructor <;> omega + +variable (R) in +/-- **The clocked run**: the register after `fuel a` steps from the root `root a` on an empty +stack. -/ +def run {I : Type} (fuel : I → ℕ) (root : I → X) (a : I) : Option Y := + (R.step^[fuel a] ([], some (root a), none)).2.2 + +/-- **Enough fuel gives the value.** -/ +theorem run_eq_some {I : Type} {fuel : I → ℕ} {root : I → X} {Inv : Invariant X A Y} {a : I} + {y : Y} {N H : ℕ} (he : R.Eval Inv (root a) y N H) (hfuel : 2 * N - 1 ≤ fuel a) : + R.run fuel root a = some y := by + obtain ⟨k, hk⟩ := Nat.exists_eq_add_of_le hfuel + rw [run, hk, Nat.add_comm, Function.iterate_add_apply, he.run, + Function.iterate_fixed (R.step_final y)] + +/-- **The native machine** for the clocked run: one fixed machine, from native certificates for +the fuel and the root. -/ +noncomputable def certificate (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) {I : Type} {encIn : I ↪ List Bool} + {fuel : I → ℕ} {root : I → X} (cn : NativeCertificate fuel encIn encNat) + (croot : NativeCertificate root encIn encX) : + NativeCertificate (R.run fuel root) encIn (encOption encY) := + NativeCertificate.snd (NativeCertificate.snd (NativeCertificate.iterate cn + ((NativeCertificate.const ([] : List (X × A))).prod + ((NativeCertificate.some croot).prod (NativeCertificate.const (none : Option Y)))) + (stepCertificate h leaf))) + +/-- A good state is at most `5 (H + 1) (F + 1)` long when the nodes, frames and values it holds +are at most `F` long. -/ +theorem length_encState_le {Inv : Invariant X A Y} {H F : ℕ} {σ : State X A Y} + (hσ : Good Inv H σ) (hnode : ∀ x, Inv.node x → (encX x).length ≤ F) + (hframe : ∀ x a, Inv.frame x a → (encPair encX encA (x, a)).length ≤ F) + (hvalue : ∀ y, Inv.value y → (encY y).length ≤ F) : + (encState encX encA encY σ).length ≤ 5 * (H + 1) * (F + 1) := by + obtain ⟨hlen, hfr, hreg⟩ := hσ + have hstack : (encList (encPair encX encA) σ.1).length ≤ H * (2 * F + 1) := by + rw [length_encList] + calc (σ.1.map fun f => 2 * (encPair encX encA f).length + 1).sum + ≤ (σ.1.map fun f => 2 * (encPair encX encA f).length + 1).length • (2 * F + 1) := + List.sum_le_length_nsmul _ _ fun n hn => by + obtain ⟨⟨x, a⟩, hf, rfl⟩ := List.mem_map.mp hn + have := hframe x a (hfr _ hf) + omega + _ ≤ H * (2 * F + 1) := by + rw [List.length_map, smul_eq_mul] + exact Nat.mul_le_mul_right _ hlen + have hHF : H * (2 * F + 1) ≤ 2 * H * F + H := by rw [Nat.mul_add, Nat.mul_one]; ring_nf; omega + rcases σ with ⟨st, r, v⟩ + rcases hreg with ⟨x, hr, hx⟩ | ⟨y, hr, hy⟩ <;> simp only [Prod.mk.injEq] at hr <;> + obtain ⟨rfl, rfl⟩ := hr + · have := hnode x hx + simp only [length_encPair, encOption_some, encOption_none, List.length_cons, + List.length_nil] at hstack ⊢ + nlinarith + · have := hvalue y hy + simp only [length_encPair, encOption_some, encOption_none, List.length_cons, + List.length_nil] at hstack ⊢ + nlinarith + +private theorem pow_absorb (K₀ z : ℕ) (hz : 1 ≤ z) : + K₀ * (5 * z + 1) ^ K₀ + 5 * z ≤ (K₀ * 6 ^ K₀ + K₀ + 6) * z ^ (K₀ * 6 ^ K₀ + K₀ + 6) := by + set K := K₀ * 6 ^ K₀ + K₀ + 6 + have h6 : (5 * z + 1) ^ K₀ ≤ 6 ^ K₀ * z ^ K := by + calc (5 * z + 1) ^ K₀ ≤ (6 * z) ^ K₀ := Nat.pow_le_pow_left (by omega) _ + _ = 6 ^ K₀ * z ^ K₀ := Nat.mul_pow _ _ _ + _ ≤ 6 ^ K₀ * z ^ K := Nat.mul_le_mul_left _ (Nat.pow_le_pow_right hz (by omega)) + have hz' : z ≤ z ^ K := by + calc z = z ^ 1 := (pow_one z).symm + _ ≤ z ^ K := Nat.pow_le_pow_right hz (by omega) + calc K₀ * (5 * z + 1) ^ K₀ + 5 * z ≤ K₀ * (6 ^ K₀ * z ^ K) + 5 * z ^ K := + Nat.add_le_add (Nat.mul_le_mul_left _ h6) (Nat.mul_le_mul_left _ hz') + _ = (K₀ * 6 ^ K₀ + 5) * z ^ K := by ring + _ ≤ K * z ^ K := Nat.mul_le_mul_right _ (by omega) + +/-- **The resources of the native machine.** There is one constant `K` such that, for any fuel +and root certificates, if the root has a derivation with `N` nodes and at most `H` open calls +whose nodes, frames and values are at most `F` long, and whose leaves are evaluated by the leaf +certificate within time `TL` and space `SL`, then the time is at most the cost of the fuel and the +root plus `fuel + 1` times `K ((H + 1) (F + 1)) ^ K + TL` (and the cost of the binary clock), and +the workspace is at most the cost of the fuel and the root plus `K ((H + 1) (F + 1)) ^ K + SL`. +The bounds hold for any fuel; with `2 * N - 1 ≤ fuel` the result is the value +(`run_eq_some`). -/ +theorem certificate_le (h : R.PolyTime encX encA encY) + (leaf : NativeCertificate R.leaf encX encY) : + ∃ K : ℕ, ∀ {I : Type} {encIn : I ↪ List Bool} {fuel : I → ℕ} {root : I → X} + (cn : NativeCertificate fuel encIn encNat) (croot : NativeCertificate root encIn encX) + (Inv : Invariant X A Y) (F TL SL : ℕ), + (∀ x, Inv.node x → (encX x).length ≤ F ∧ + (R.isLeaf x = true → leaf.time x ≤ TL ∧ leaf.space x ≤ SL)) → + (∀ x a, Inv.frame x a → (encPair encX encA (x, a)).length ≤ F) → + (∀ y, Inv.value y → (encY y).length ≤ F) → + ∀ (a : I) (y : Y) (N H : ℕ), R.Eval Inv (root a) y N H → + (certificate h leaf cn croot).time a ≤ cn.time a + croot.time a + 2 * (encIn a).length + + (fuel a + 1) * (K * ((H + 1) * (F + 1)) ^ K + TL + + ((encNat (fuel a)).length + 1) ^ 2) ∧ + (certificate h leaf cn croot).space a ≤ cn.space a + croot.space a + + 2 * (encIn a).length + K * ((H + 1) * (F + 1)) ^ K + SL + + (encNat (fuel a)).length := by + obtain ⟨K₀, hK₀⟩ := exists_stepCertificate_le h leaf + refine ⟨K₀ * 6 ^ K₀ + K₀ + 6, ?_⟩ + intro I encIn fuel root cn croot Inv F TL SL hnode hframe hvalue a y N H he + set z := (H + 1) * (F + 1) + have hz : 1 ≤ z := Nat.one_le_iff_ne_zero.mpr (Nat.mul_ne_zero (by omega) (by omega)) + have habsorb := pow_absorb K₀ z hz + -- Every state of the run is good, hence short, and pays the leaf bound only at a leaf. + have hgood := he.good_iterate (R := R) + have hlen (j : ℕ) : (encState encX encA encY (R.step^[j] ([], some (root a), none))).length ≤ + 5 * z := by + have := length_encState_le (hgood j) (fun x hx => (hnode x hx).1) hframe hvalue + rwa [Nat.mul_assoc] at this + clear_value z + have hcost (cost : X → ℕ) (B : ℕ) + (hB : ∀ x, Inv.node x → R.isLeaf x = true → cost x ≤ B) (j : ℕ) : + R.leafCost cost (R.step^[j] ([], some (root a), none)) ≤ B := by + obtain ⟨-, -, ⟨x, hr, hx⟩ | ⟨y', hr, -⟩⟩ := hgood j <;> simp only [leafCost, hr, Option.elim] + · split_ifs with hl + · exact hB x hx hl + · exact Nat.zero_le _ + · exact Nat.zero_le _ + have hround (j : ℕ) : + (stepCertificate h leaf).time (R.step^[j] ([], some (root a), none)) ≤ + K₀ * (5 * z + 1) ^ K₀ + TL ∧ + (stepCertificate h leaf).space (R.step^[j] ([], some (root a), none)) ≤ + K₀ * (5 * z + 1) ^ K₀ + SL := by + obtain ⟨ht, hs⟩ := hK₀ (R.step^[j] ([], some (root a), none)) + have hp : K₀ * ((encState encX encA encY (R.step^[j] ([], some (root a), none))).length + 1) ^ + K₀ ≤ K₀ * (5 * z + 1) ^ K₀ := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by have := hlen j; omega) _) + have hct := hcost leaf.time TL (fun x hx hl => ((hnode x hx).2 hl).1) j + have hcs := hcost leaf.space SL (fun x hx hl => ((hnode x hx).2 hl).2) j + constructor <;> omega + have hmaxT := NativeCertificate.maxRoundTime_le (stepCertificate h leaf) (fuel a) + ([], some (root a), none) (R := K₀ * (5 * z + 1) ^ K₀ + TL) fun j _ => (hround j).1 + have hmaxS := NativeCertificate.maxRoundSpace_le (stepCertificate h leaf) (fuel a) + ([], some (root a), none) (Q := K₀ * (5 * z + 1) ^ K₀ + SL) fun j _ => (hround j).2 + have hmaxL := NativeCertificate.maxStateLength_le + (encPair (encList (encPair encX encA)) (encPair (encOption encX) (encOption encY))) R.step + (fuel a) ([], some (root a), none) (B := 5 * z) fun j _ => hlen j + simp only [certificate, NativeCertificate.snd_time, NativeCertificate.snd_space, + NativeCertificate.iterate_time, NativeCertificate.iterate_space, + NativeCertificate.prod_time, NativeCertificate.prod_space, NativeCertificate.const_time, + NativeCertificate.const_space, NativeCertificate.some_time, NativeCertificate.some_space] + constructor + · have hstep : NativeCertificate.maxRoundTime (stepCertificate h leaf) (fuel a) + ([], some (root a), none) + + NativeCertificate.maxStateLength + (encPair (encList (encPair encX encA)) (encPair (encOption encX) (encOption encY))) + R.step (fuel a) ([], some (root a), none) + ((encNat (fuel a)).length + 1) ^ 2 ≤ + (K₀ * 6 ^ K₀ + K₀ + 6) * z ^ (K₀ * 6 ^ K₀ + K₀ + 6) + TL + + ((encNat (fuel a)).length + 1) ^ 2 := by omega + have := Nat.mul_le_mul_left (fuel a + 1) hstep + omega + · omega + +end Certificate + +end Scheme + +end Cslib.GenericRAM.StackRecursion diff --git a/Complexity/Foundations/Data/Nat/RecursionTree.lean b/Complexity/Foundations/Data/Nat/RecursionTree.lean new file mode 100644 index 0000000000..e5f2982128 --- /dev/null +++ b/Complexity/Foundations/Data/Nat/RecursionTree.lean @@ -0,0 +1,170 @@ +/- +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.Foundations.Data.Nat.SqrtLog +public import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Order.BigOperators.Group.Finset +import Mathlib.Tactic.IntervalCases +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Counting the nodes of a recursion tree + +A recursion whose calls at depth `d` have at most `b d` children, with at most `k` levels below +depth `d`, has at most `Nat.recursionBound b k d` nodes below and including a call at depth `d`: +one for the call, plus `b d` subtrees of one level less (`Nat.recursionBound_succ`). This is at +most `k + 1` times the product of the branching factors (`Nat.recursionBound_le`). + +**Recursion trees of size `2 ^ O(n / √(log n))`.** If a call at depth `j` has at most +`2 ^ (c ⌊log₂ n⌋ + e j)` children, there are at most `⌊log₂ n⌋` levels, and the excess exponents +sum to `O(n / √⌊log₂ n⌋)`, then the tree has `2 ^ O(n / √⌊log₂ n⌋)` nodes +(`Nat.exists_recursionBound_le_two_pow_div_sqrt_log_two`), in the natural-number scale of +`Nat.exists_three_pow_mul_le_two_pow_div_sqrt_log_two`. The polynomial factors `2 ^ (c ⌊log₂ n⌋)` +contribute `2 ^ O(log² n)`, which the scale absorbs. Excess exponents halving with the depth sum to +at most twice the first one (`Nat.sum_div_two_pow_le`). + +This is the call count in the proof of Theorem D.1 of [OpenAI2026ETH] (Appendix D.4.7, p. 64): +a call at depth `d` has `s ^ O(1) exp(O(s / √λ · (√2 / 8) ^ d))` children and there are +`O(log λ)` levels. + +## Main definitions + +* `Nat.recursionBound`: the node bound of a recursion tree with given branching factors. + +## Main results + +* `Nat.recursionBound_le`: at most `k + 1` times the product of the branching factors. +* `Nat.sum_div_two_pow_le`: `∑ j < D, x / 2 ^ j ≤ 2 x`. +* `Nat.exists_recursionBound_le_two_pow_div_sqrt_log_two`: recursion trees of size + `2 ^ O(n / √⌊log₂ n⌋)`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.7 (p. 64). +-/ + +public section + +namespace Nat + +open Finset + +/-- The node bound of a recursion tree whose calls at depth `d` have at most `b d` children, +for a call at depth `d` with at most `k` levels below it. -/ +@[expose] def recursionBound (b : ℕ → ℕ) : ℕ → ℕ → ℕ + | 0, _ => 1 + | k + 1, d => 1 + b d * recursionBound b k (d + 1) + +@[simp] theorem recursionBound_zero (b : ℕ → ℕ) (d : ℕ) : recursionBound b 0 d = 1 := rfl + +/-- A call is one node above its children's subtrees. -/ +theorem recursionBound_succ (b : ℕ → ℕ) (k d : ℕ) : + recursionBound b (k + 1) d = 1 + b d * recursionBound b k (d + 1) := rfl + +/-- **The node bound is at most `k + 1` times the product of the branching factors.** -/ +theorem recursionBound_le (b : ℕ → ℕ) (k d : ℕ) : + recursionBound b k d ≤ (k + 1) * ∏ j ∈ range k, max 1 (b (d + j)) := by + induction k generalizing d with + | zero => simp + | succ k ih => + rw [recursionBound_succ, prod_range_succ'] + set P := ∏ j ∈ range k, max 1 (b (d + (j + 1))) + have hP : 1 ≤ P := one_le_prod fun _ _ => le_max_left _ _ + have hm : 1 ≤ max 1 (b d) := le_max_left _ _ + have hb : b d ≤ max 1 (b d) := le_max_right _ _ + have ih' : recursionBound b k (d + 1) ≤ (k + 1) * P := by + simpa only [Nat.add_assoc, Nat.add_comm 1] using ih (d + 1) + simp only [Nat.add_zero] + calc 1 + b d * recursionBound b k (d + 1) ≤ max 1 (b d) * P + max 1 (b d) * ((k + 1) * P) := + Nat.add_le_add (Nat.one_le_iff_ne_zero.mpr (Nat.mul_ne_zero (by omega) (by omega))) + (Nat.mul_le_mul hb ih') + _ = (k + 1 + 1) * (P * max 1 (b d)) := by ring + +/-- **Halving excess exponents**: `∑ j < D, x / 2 ^ j ≤ 2 x`. -/ +theorem sum_div_two_pow_le (x D : ℕ) : ∑ j ∈ range D, x / 2 ^ j ≤ 2 * x := by + induction D generalizing x with + | zero => simp + | succ D ih => + rw [sum_range_succ'] + have h : ∑ j ∈ range D, x / 2 ^ (j + 1) = ∑ j ∈ range D, x / 2 / 2 ^ j := + sum_congr rfl fun j _ => by rw [pow_succ, Nat.mul_comm, Nat.div_div_eq_div_mul] + rw [h, pow_zero, Nat.div_one] + have := ih (x / 2) + have := Nat.div_mul_le_self x 2 + omega + +/-- `L ^ 3 ≤ 8 * 2 ^ L`. -/ +private theorem cube_le_eight_mul_two_pow (L : ℕ) : L ^ 3 ≤ 8 * 2 ^ L := by + have hlarge : ∀ m : ℕ, (m + 9) ^ 3 ≤ 8 * 2 ^ (m + 9) := by + intro m + induction m with + | zero => norm_num + | succ m ih => + have h : (m + 1 + 9) ^ 3 ≤ 2 * (m + 9) ^ 3 := by ring_nf; nlinarith + calc (m + 1 + 9) ^ 3 ≤ 2 * (m + 9) ^ 3 := h + _ ≤ 2 * (8 * 2 ^ (m + 9)) := Nat.mul_le_mul_left 2 ih + _ = 8 * 2 ^ (m + 1 + 9) := by ring + rcases Nat.lt_or_ge L 9 with hL | hL + · interval_cases L <;> norm_num + · obtain ⟨m, rfl⟩ := Nat.exists_eq_add_of_le' hL + exact hlarge m + +/-- The square of the logarithm is `O(n / √⌊log₂ n⌋)`: `⌊log₂ n⌋ ^ 2 ≤ 16 (n / √⌊log₂ n⌋)` from +`n = 2` on, since `⌊log₂ n⌋ ^ 2 √⌊log₂ n⌋ ≤ ⌊log₂ n⌋ ^ 3 ≤ 8 n`. -/ +theorem log_two_sq_le_div_sqrt_log_two {n : ℕ} (hn : 2 ≤ n) : + log 2 n ^ 2 ≤ 16 * (n / sqrt (log 2 n)) := by + set L := log 2 n + have hr : 1 ≤ sqrt L := one_le_sqrt_log_two hn + have hS : 1 ≤ n / sqrt L := one_le_div_sqrt_log_two hn + have hcube : L ^ 2 * sqrt L ≤ 8 * n := + calc L ^ 2 * sqrt L ≤ L ^ 2 * L := Nat.mul_le_mul_left _ (sqrt_le_self L) + _ = L ^ 3 := by ring + _ ≤ 8 * 2 ^ L := cube_le_eight_mul_two_pow L + _ ≤ 8 * n := Nat.mul_le_mul_left 8 (pow_log_le_self 2 (by omega)) + have h1 : L ^ 2 ≤ 8 * n / sqrt L := (Nat.le_div_iff_mul_le hr).2 hcube + have h2 := mul_div_le_mul_div_add 8 n (sqrt L) + omega + +/-- **Recursion trees of size `2 ^ O(n / √⌊log₂ n⌋)`.** For constants `c` and `E` there is `K` +such that, from `n = 2` on: if a call at depth `j < D` has at most `2 ^ (c ⌊log₂ n⌋ + e j)` +children, `D ≤ ⌊log₂ n⌋`, and `∑ j < D, e j ≤ E (n / √⌊log₂ n⌋)`, then +`(D + 1) ∏ j < D, max 1 (b j) ≤ 2 ^ (K n / √⌊log₂ n⌋)`, which bounds the nodes of the recursion +tree by `recursionBound_le`. -/ +theorem exists_recursionBound_le_two_pow_div_sqrt_log_two (c E : ℕ) : + ∃ K, ∀ (n D : ℕ) (b e : ℕ → ℕ), 2 ≤ n → D ≤ log 2 n → + (∀ j < D, b j ≤ 2 ^ (c * log 2 n + e j)) → + ∑ j ∈ range D, e j ≤ E * (n / sqrt (log 2 n)) → + (D + 1) * ∏ j ∈ range D, max 1 (b j) ≤ 2 ^ (K * n / sqrt (log 2 n)) := by + refine ⟨1 + 16 * c + E, fun n D b e hn hD hb he => ?_⟩ + set L := log 2 n + set S := n / sqrt L + have hL : L ≤ S := log_two_le_div_sqrt_log_two n + have hL2 : L ^ 2 ≤ 16 * S := log_two_sq_le_div_sqrt_log_two hn + have hD1 : D + 1 ≤ 2 ^ L := (Nat.add_le_add_right hD 1).trans Nat.lt_two_pow_self + have hprod : ∏ j ∈ range D, max 1 (b j) ≤ 2 ^ (D * (c * L) + ∑ j ∈ range D, e j) := by + calc ∏ j ∈ range D, max 1 (b j) ≤ ∏ j ∈ range D, 2 ^ (c * L + e j) := + prod_le_prod fun j hj => max_le Nat.one_le_two_pow (hb j (mem_range.mp hj)) + _ = 2 ^ (∑ j ∈ range D, (c * L + e j)) := (Finset.prod_pow_eq_pow_sum _ _ _) + _ = 2 ^ (D * (c * L) + ∑ j ∈ range D, e j) := by + rw [sum_add_distrib, sum_const, card_range, smul_eq_mul] + have hexp : L + (D * (c * L) + ∑ j ∈ range D, e j) ≤ (1 + 16 * c + E) * S := by + have h1 : D * (c * L) ≤ c * (16 * S) := + calc D * (c * L) ≤ L * (c * L) := Nat.mul_le_mul_right _ hD + _ = c * L ^ 2 := by ring + _ ≤ c * (16 * S) := Nat.mul_le_mul_left _ hL2 + nlinarith + calc (D + 1) * ∏ j ∈ range D, max 1 (b j) + ≤ 2 ^ L * 2 ^ (D * (c * L) + ∑ j ∈ range D, e j) := Nat.mul_le_mul hD1 hprod + _ = 2 ^ (L + (D * (c * L) + ∑ j ∈ range D, e j)) := (pow_add _ _ _).symm + _ ≤ 2 ^ ((1 + 16 * c + E) * S) := Nat.pow_le_pow_right (by norm_num) hexp + _ ≤ 2 ^ ((1 + 16 * c + E) * n / sqrt L) := + Nat.pow_le_pow_right (by norm_num) (mul_div_le_mul_div_assoc _ _ _) + +end Nat diff --git a/ComplexityTests/StackRecursion.lean b/ComplexityTests/StackRecursion.lean new file mode 100644 index 0000000000..bcca97e434 --- /dev/null +++ b/ComplexityTests/StackRecursion.lean @@ -0,0 +1,257 @@ +/- +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.GenericRAM.Native.StackRecursion +import Complexity.Foundations.Data.Nat.RecursionTree +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives + +/-! # Depth-first stack recursion on a toy scheme + +The halving recursion: a node `x ≥ 2` has two children `x / 2`, a leaf has value `1`, and a call +adds its children's values. The value of `x` is its number of leaves. The tests run the stack +machine, derive `Eval` for every node, instantiate the polynomial-time components (so the +`PolyTime` hypotheses of the native certificate are satisfiable) and check the node-count +arithmetic. -/ + +namespace ComplexityTests.StackRecursion + +open Cslib.GenericRAM Cslib.GenericRAM.StackRecursion Turing.MultiTapeTM + +/-- The halving scheme. The accumulator counts the children done and sums their values. -/ +def halving : Scheme ℕ (ℕ × ℕ) ℕ where + isLeaf x := decide (x < 2) + leaf _ := 1 + start _ := (0, 0) + next x a := if a.1 < 2 then some (x / 2) else none + absorb _ a y := (a.1 + 1, a.2 + y) + finish _ a := a.2 + +/-- The number of leaves below `x`. -/ +def leaves (x : ℕ) : ℕ := if x < 2 then 1 else 2 * leaves (x / 2) +decreasing_by omega + +/-- The number of nodes below `x`. -/ +def nodes (x : ℕ) : ℕ := if x < 2 then 1 else 2 * nodes (x / 2) + 1 +decreasing_by omega + +/-- The number of open calls while evaluating `x`. -/ +def height (x : ℕ) : ℕ := if x < 2 then 0 else height (x / 2) + 1 +decreasing_by omega + +/-- No invariant. -/ +def none : Invariant ℕ (ℕ × ℕ) ℕ := ⟨fun _ => True, fun _ _ => True, fun _ => True⟩ + +-- The machine on `8`: four levels, eight leaves, fifteen nodes, so `2 * 15 - 1 = 29` steps. +example : halving.run (fun _ : Unit => 29) (fun _ => 8) () = some 8 := by decide + +-- One step fewer, the value `4` of the second child is still waiting to be absorbed by the root. +example : halving.run (fun _ : Unit => 28) (fun _ => 8) () = some 4 := by decide + +-- Extra fuel changes nothing: the final state is fixed. +example : halving.run (fun _ : Unit => 100) (fun _ => 8) () = some 8 := by decide + +/-- **Every node has a derivation**, with its leaves as value. -/ +theorem eval_halving (x : ℕ) : halving.Eval none x (leaves x) (nodes x) (height x) := by + induction x using Nat.strong_induction_on with + | _ x ih => + by_cases hx : x < 2 + · have hl : leaves x = 1 := by rw [leaves]; simp [hx] + have hn : nodes x = 1 := by rw [nodes]; simp [hx] + have hh : height x = 0 := by rw [height]; simp [hx] + rw [hl, hn, hh] + exact Scheme.Eval.leaf (R := halving) trivial (decide_eq_true hx) trivial + · have hl : leaves x = leaves (x / 2) + leaves (x / 2) := by rw [leaves]; simp [hx]; ring + have hn : nodes x = (nodes (x / 2) + (nodes (x / 2) + 0)) + 1 := by + rw [nodes]; simp [hx]; ring + have hh : height x = max (height (x / 2)) (max (height (x / 2)) 0) + 1 := by + rw [height]; simp [hx] + have hc := ih (x / 2) (by omega) + have hloop : halving.Loop none x (halving.start x) + (2, 0 + leaves (x / 2) + leaves (x / 2)) (nodes (x / 2) + (nodes (x / 2) + 0)) + (max (height (x / 2)) (max (height (x / 2)) 0)) := + Scheme.Loop.child trivial (by simp [halving]) hc + (Scheme.Loop.child trivial (by simp [halving]) hc (Scheme.Loop.done (by simp [halving]))) + have := Scheme.Eval.call (R := halving) (I := none) trivial (decide_eq_false hx) hloop + trivial + rw [hl, hn, hh] + simpa [halving] using this + +/-- `nodes x + 1 ≤ 2 max x 1`. -/ +theorem nodes_add_one_le (x : ℕ) : nodes x + 1 ≤ 2 * max x 1 := by + induction x using Nat.strong_induction_on with + | _ x ih => + by_cases hx : x < 2 + · rw [nodes]; simp only [hx, ite_true]; omega + · have := ih (x / 2) (by omega) + rw [nodes]; simp only [hx, ite_false] + omega + +/-- `2 * nodes x - 1 ≤ 4 * x + 1`: linear fuel suffices. -/ +theorem two_mul_nodes_le (x : ℕ) : 2 * nodes x - 1 ≤ 4 * x + 1 := by + have := nodes_add_one_le x + omega + +/-- With fuel `4 x + 1`, the machine returns the number of leaves, on every input. -/ +theorem run_halving (x : ℕ) : + halving.run (fun x => 4 * x + 1) id x = some (leaves x) := + Scheme.run_eq_some (eval_halving x) (two_mul_nodes_le x) + +/-- **The polynomial-time components** of the halving scheme, in binary. -/ +theorem halving_polyTime : halving.PolyTime encNat (encPair encNat encNat) encNat where + isLeaf := ComputableInPolyTime.binary_lt' ComputableInPolyTime.id' + (ComputableInPolyTime.const 2) + start := ComputableInPolyTime.const (0, 0) + next := by + have hk : ComputableInPolyTime (fun p : ℕ × (ℕ × ℕ) => p.2.1) + (encPair encNat (encPair encNat encNat)) encNat := + (ComputableInPolyTime.fst encNat encNat).comp (ComputableInPolyTime.snd _ _) + have hhalf : ComputableInPolyTime (fun p : ℕ × (ℕ × ℕ) => p.1 / 2) + (encPair encNat (encPair encNat encNat)) encNat := + (ComputableInPolyTime.binary_shiftRight' (ComputableInPolyTime.fst _ _) + (ComputableInPolyTime.const 1)).of_enc_eq fun p => by + rw [Nat.shiftRight_eq_div_pow, pow_one] + refine (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hk + (ComputableInPolyTime.const 2)) ((ComputableInPolyTime.optionSome encNat).comp hhalf) + (ComputableInPolyTime.const Option.none)).of_enc_eq fun p => ?_ + by_cases h : p.2.1 < 2 + · have h' : p.2.1 ≤ 1 := by omega + simp [halving, h, h'] + · have h' : ¬p.2.1 ≤ 1 := by omega + simp [halving, h, h'] + absorb := by + have hk : ComputableInPolyTime (fun p : (ℕ × (ℕ × ℕ)) × ℕ => p.1.2.1) + (encPair (encPair encNat (encPair encNat encNat)) encNat) encNat := + (ComputableInPolyTime.fst encNat encNat).comp + ((ComputableInPolyTime.snd encNat _).comp (ComputableInPolyTime.fst _ _)) + have hs : ComputableInPolyTime (fun p : (ℕ × (ℕ × ℕ)) × ℕ => p.1.2.2) + (encPair (encPair encNat (encPair encNat encNat)) encNat) encNat := + (ComputableInPolyTime.snd encNat encNat).comp + ((ComputableInPolyTime.snd encNat _).comp (ComputableInPolyTime.fst _ _)) + exact (ComputableInPolyTime.binary_add.comp (hk.prod (ComputableInPolyTime.const 1))).prod + (ComputableInPolyTime.binary_add.comp (hs.prod (ComputableInPolyTime.snd _ _))) + finish := (ComputableInPolyTime.snd encNat encNat).comp (ComputableInPolyTime.snd _ _) + +/-- The fuel `4 x + 1`, in binary. -/ +noncomputable def fuelCertificate : NativeCertificate (fun x : ℕ => 4 * x + 1) encNat encNat := + NativeCertificate.ofPolyTime (ComputableInPolyTime.binary_add.comp + ((ComputableInPolyTime.binary_mul' (ComputableInPolyTime.const 4) + ComputableInPolyTime.id').prod (ComputableInPolyTime.const 1))) + +/-- The root is the input. -/ +noncomputable def rootCertificate : NativeCertificate (id : ℕ → ℕ) encNat encNat := + NativeCertificate.ofPolyTime ComputableInPolyTime.id' + +/-- The leaf step: the constant `1`. -/ +def leafCertificate : NativeCertificate halving.leaf encNat encNat := NativeCertificate.const 1 + +/-- **One fixed machine computes the number of leaves** of every input. -/ +noncomputable def halvingCertificate : + NativeCertificate (halving.run (fun x => 4 * x + 1) id) encNat (encOption encNat) := + Scheme.certificate halving_polyTime leafCertificate fuelCertificate rootCertificate + +/-- The size invariant below a root `x₀`: nodes are at most `x₀`, frames have counted at most two +children and summed at most `x₀ + 1`, and values are at most `x₀ + 1`. -/ +def bounded (x₀ : ℕ) : Invariant ℕ (ℕ × ℕ) ℕ := + ⟨fun y => y ≤ x₀, fun y a => y ≤ x₀ ∧ a.1 ≤ 2 ∧ a.2 ≤ x₀ + 1, fun v => v ≤ x₀ + 1⟩ + +theorem leaves_le_max (x : ℕ) : leaves x ≤ max x 1 := by + induction x using Nat.strong_induction_on with + | _ x ih => + by_cases hx : x < 2 + · rw [leaves]; simp only [hx, ite_true]; omega + · have := ih (x / 2) (by omega) + rw [leaves]; simp only [hx, ite_false] + omega + +theorem leaves_le (x : ℕ) : leaves x ≤ x + 1 := by + have := leaves_le_max x + omega + +/-- **Every node below `x₀` has a derivation keeping the size invariant.** -/ +theorem eval_halving_bounded {x₀ x : ℕ} (hx₀ : x ≤ x₀) : + halving.Eval (bounded x₀) x (leaves x) (nodes x) (height x) := by + induction x using Nat.strong_induction_on with + | _ x ih => + have hlx := leaves_le x + by_cases hx : x < 2 + · have hl : leaves x = 1 := by rw [leaves]; simp [hx] + have hn : nodes x = 1 := by rw [nodes]; simp [hx] + have hh : height x = 0 := by rw [height]; simp [hx] + rw [hl, hn, hh] + exact Scheme.Eval.leaf (R := halving) hx₀ (decide_eq_true hx) + (show 1 ≤ x₀ + 1 by omega) + · have hl : leaves x = leaves (x / 2) + leaves (x / 2) := by rw [leaves]; simp [hx]; ring + have hn : nodes x = (nodes (x / 2) + (nodes (x / 2) + 0)) + 1 := by + rw [nodes]; simp [hx]; ring + have hh : height x = max (height (x / 2)) (max (height (x / 2)) 0) + 1 := by + rw [height]; simp [hx] + have hc := ih (x / 2) (by omega) (by omega) + have hhalf := leaves_le (x / 2) + have hloop : halving.Loop (bounded x₀) x (halving.start x) + (2, 0 + leaves (x / 2) + leaves (x / 2)) (nodes (x / 2) + (nodes (x / 2) + 0)) + (max (height (x / 2)) (max (height (x / 2)) 0)) := + Scheme.Loop.child (show x ≤ x₀ ∧ 0 ≤ 2 ∧ 0 ≤ x₀ + 1 by omega) (by simp [halving]) hc + (Scheme.Loop.child (show x ≤ x₀ ∧ 1 ≤ 2 ∧ 0 + leaves (x / 2) ≤ x₀ + 1 by omega) + (by simp [halving]) hc (Scheme.Loop.done (by simp [halving]))) + have := Scheme.Eval.call (R := halving) (I := bounded x₀) hx₀ (decide_eq_false hx) hloop + (show 0 + leaves (x / 2) + leaves (x / 2) ≤ x₀ + 1 by omega) + rw [hl, hn, hh] + simpa [halving] using this + +private theorem length_encNat_mono {y z : ℕ} (h : y ≤ z) : + (encNat y).length ≤ (encNat z).length := by + simpa [Nat.size_eq_bits_len] using Nat.size_le_size h + +/-- **The resource theorem applies**: with `ℓ` the length of `x + 1` in binary, nodes, frames and +values are at most `F = 3 ℓ + 6` long, and the leaf costs at most `F`, so the time is +`4 x + 2` times a polynomial in `(height x + 1) (F + 1)`, and the workspace is polynomial in it. -/ +theorem halvingCertificate_le : ∃ K : ℕ, ∀ x : ℕ, + halvingCertificate.time x ≤ fuelCertificate.time x + rootCertificate.time x + + 2 * (encNat x).length + (4 * x + 1 + 1) * + (K * ((height x + 1) * (3 * (encNat (x + 1)).length + 6 + 1)) ^ K + + (3 * (encNat (x + 1)).length + 6) + ((encNat (4 * x + 1)).length + 1) ^ 2) ∧ + halvingCertificate.space x ≤ fuelCertificate.space x + rootCertificate.space x + + 2 * (encNat x).length + + K * ((height x + 1) * (3 * (encNat (x + 1)).length + 6 + 1)) ^ K + + (3 * (encNat (x + 1)).length + 6) + (encNat (4 * x + 1)).length := by + obtain ⟨K, hK⟩ := Scheme.certificate_le halving_polyTime leafCertificate + refine ⟨K, fun x => ?_⟩ + have h2 : (encNat 2).length ≤ 2 := by decide + refine hK fuelCertificate rootCertificate (bounded x) (3 * (encNat (x + 1)).length + 6) _ _ + (fun y hy => ⟨?_, fun _ => ⟨?_, ?_⟩⟩) (fun y a ha => ?_) (fun v hv => ?_) x (leaves x) + (nodes x) (height x) (eval_halving_bounded le_rfl) + · have := length_encNat_mono (show y ≤ x + 1 by simp only [bounded] at hy; omega) + omega + · have := length_encNat_mono (show y ≤ x + 1 by simp only [bounded] at hy; omega) + simp only [leafCertificate, NativeCertificate.const_time] + omega + · have := length_encNat_mono (show y ≤ x + 1 by simp only [bounded] at hy; omega) + simp only [leafCertificate, NativeCertificate.const_space] + omega + · obtain ⟨hy, hk, hs⟩ := ha + have h1 := length_encNat_mono (show y ≤ x + 1 by omega) + have h3 := length_encNat_mono (show a.2 ≤ x + 1 by omega) + have h4 := (length_encNat_mono hk).trans h2 + simp only [length_encPair] + omega + · have := length_encNat_mono (show v ≤ x + 1 by simp only [bounded] at hv; omega) + omega + +/-! ### Node-count arithmetic -/ + +-- Binary branching for three levels: `1 + 2 (1 + 2 (1 + 2)) = 15` nodes. +example : Nat.recursionBound (fun _ => 2) 3 0 = 15 := rfl + +-- The product bound: `15 ≤ 4 * 2 ^ 3`. +example : Nat.recursionBound (fun _ => 2) 3 0 ≤ (3 + 1) * ∏ _j ∈ Finset.range 3, max 1 2 := + Nat.recursionBound_le _ 3 0 + +-- Halving excess exponents: `100 + 50 + 25 + 12 + 6 = 193 ≤ 200`. +example : ∑ j ∈ Finset.range 5, 100 / 2 ^ j ≤ 2 * 100 := Nat.sum_div_two_pow_le 100 5 + +end ComplexityTests.StackRecursion From 99d9e0d69372a439fe89c955398c74361686e888 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:28:05 -0400 Subject: [PATCH 09/33] feat(PolyTime): arithmetic in (Z/2^p)[U]/(f) on coefficient lists Addition, multiplication by U and shift-and-add multiplication modulo 2^p and a monic f given by its lower coefficients, proved correct in AdjoinRoot (f mod 2^p) and certified polynomial-time with p in unary and coordinates in binary. These are the polynomial-time arithmetic claims of [OpenAI2026ETH, Appendix D.4.1] for addition and multiplication (l = 1). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../PolyTime/GaloisRingArithmetic.lean | 446 ++++++++++++++++++ ComplexityTests/GaloisRingArithmetic.lean | 59 +++ 3 files changed, 506 insertions(+) create mode 100644 Complexity/Computability/PolyTime/GaloisRingArithmetic.lean create mode 100644 ComplexityTests/GaloisRingArithmetic.lean diff --git a/Complexity.lean b/Complexity.lean index fdd3b220c6..566b83dcbd 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1985,6 +1985,7 @@ public import Complexity.Computability.PolyTime.CNF.SparsePolyCompile public import Complexity.Computability.PolyTime.CSP.ToCNF public import Complexity.Computability.PolyTime.CommonCountingAccum public import Complexity.Computability.PolyTime.FineGrained.ParameterCheck +public import Complexity.Computability.PolyTime.GaloisRingArithmetic public import Complexity.Computability.PolyTime.GreedyLabelSchedule public import Complexity.Computability.PolyTime.GreedySlotScore public import Complexity.Computability.PolyTime.Int.ExactDiv diff --git a/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean new file mode 100644 index 0000000000..b46df67660 --- /dev/null +++ b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean @@ -0,0 +1,446 @@ +/- +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.Init +public import Complexity.Computability.Machines.Turing.MultiTape.PolyTime +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.List +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Nat.Bits +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Unary +public import Mathlib.Algebra.BigOperators.Group.Finset.Basic +public import Mathlib.Data.ZMod.Basic +public import Mathlib.RingTheory.AdjoinRoot +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Fold +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.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryDivision +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives +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.PolyTimeLoops +import Mathlib.Algebra.BigOperators.Intervals +import Mathlib.Data.List.GetD +import Mathlib.Data.Nat.Size + +/-! +# Arithmetic modulo `2 ^ p` and a monic polynomial, on coefficient lists + +The coefficient rings of the contraction algorithm of [OpenAI2026ETH] (Appendix D.4.1, p. 57) are +`A_p = (ℤ / 2 ^ p)[U] / (f)` for a monic `f ∈ ℤ[U]` of degree `e`. An element is represented in the +basis `1, U, …, U ^ (e - 1)` by its list of coordinates, each an integer residue modulo `2 ^ p`. +The monic polynomial is given by the list `fl` of its coefficients below the leading one +(`GaloisRingArith.monicOf`), so `e` is the length of `fl`. With `f` irreducible modulo `2`, `A_p` is +the Galois ring `(ℤ / 2 ^ p)[U] / (f)`; nothing here needs irreducibility. + +The value of a coefficient list `c` is `∑ i < e, c i U ^ i` in `A_p` (`GaloisRingArith.val`). + +* **Addition** (`GaloisRingArith.add`) adds coordinates modulo `2 ^ p`. +* **Multiplication by `U`** (`GaloisRingArith.mulX`) shifts the coordinates up and subtracts the top + coordinate times `f`, reducing `U ^ e = -∑ i < e, fl i U ^ i`. +* **Multiplication** (`GaloisRingArith.mul`) is shift-and-add: `c d = ∑ i < e, c i (U ^ i d)`, + accumulating `c i` times the current `U ^ i d` and multiplying it by `U`, `e` times. + +Each operation is correct (`GaloisRingArith.val_add`, `GaloisRingArith.val_mulX`, +`GaloisRingArith.val_mul`) and polynomial-time, with the precision `p` in unary and the +coordinates and the coefficients of `f` in binary (`GaloisRingArith.computableInPolyTime_add`, +`GaloisRingArith.computableInPolyTime_mul`). Their outputs are reduced, with `e` coordinates below +`2 ^ p` (`GaloisRingArith.length_mul`, `GaloisRingArith.lt_of_mem_mul`). These are the +polynomial-time arithmetic claims of Appendix D.4.1 for addition and multiplication, in the case +`ℓ = 1` of the source, where `A = O` has no `V`-coordinates. Inversion of units, which the source +does by Newton lifting, is not covered here. + +## Main definitions + +* `Cslib.GaloisRingArith.monicOf`: the monic polynomial with given lower coefficients. +* `Cslib.GaloisRingArith.val`: the value of a coefficient list. +* `Cslib.GaloisRingArith.add`, `Cslib.GaloisRingArith.mulX`, `Cslib.GaloisRingArith.mul`: addition, + multiplication by `U` and multiplication. + +## Main results + +* `Cslib.GaloisRingArith.val_add`, `Cslib.GaloisRingArith.val_mulX`, + `Cslib.GaloisRingArith.val_mul`: correctness. +* `Cslib.GaloisRingArith.computableInPolyTime_add`, + `Cslib.GaloisRingArith.computableInPolyTime_mulX`, + `Cslib.GaloisRingArith.computableInPolyTime_mul`: polynomial time. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.1 (p. 57). +-/ + +@[expose] public section + +namespace Cslib.GaloisRingArith + +open Polynomial Finset + +/-- The monic polynomial `U ^ e + ∑ i < e, fl i U ^ i` of degree `e = fl.length`. -/ +noncomputable def monicOf (fl : List ℕ) : ℤ[X] := + X ^ fl.length + ∑ i ∈ range fl.length, C (fl.getD i 0 : ℤ) * X ^ i + +/-- `monicOf fl` is monic, of degree `fl.length`. -/ +theorem monic_monicOf (fl : List ℕ) : (monicOf fl).Monic := by + unfold monicOf + apply monic_X_pow_add + refine (degree_sum_le _ _).trans_lt ?_ + rw [Finset.sup_lt_iff (WithBot.bot_lt_coe _)] + intro i hi + exact (degree_C_mul_X_pow_le _ _).trans_lt (WithBot.coe_lt_coe.mpr (mem_range.mp hi)) + +/-- The ring `A_p = (ℤ / 2 ^ p)[U] / (f)` for `f = monicOf fl`. -/ +abbrev Ring (p : ℕ) (fl : List ℕ) : Type := + AdjoinRoot ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))) + +/-- The value `∑ i < e, c i U ^ i` of a coefficient list in `A_p`, with `e = fl.length`. +Coordinates past `e` are ignored and missing ones are zero. -/ +noncomputable def val (p : ℕ) (fl c : List ℕ) : Ring p fl := + ∑ i ∈ range fl.length, ((c.getD i 0 : ℕ) : Ring p fl) * AdjoinRoot.root _ ^ i + +/-- Coordinatewise sum modulo `2 ^ p`. -/ +def add (p : ℕ) (fl c d : List ℕ) : List ℕ := + (List.range fl.length).map fun i => (c.getD i 0 + d.getD i 0) % 2 ^ p + +/-- Add `k` times `d` to `c`, coordinatewise modulo `2 ^ p`. -/ +def addSmul (p : ℕ) (fl c : List ℕ) (k : ℕ) (d : List ℕ) : List ℕ := + (List.range fl.length).map fun i => (c.getD i 0 + k * d.getD i 0) % 2 ^ p + +/-- Multiplication by `U`: shift the coordinates up and subtract the top coordinate times the +lower coefficients of `f`, modulo `2 ^ p`. -/ +def mulX (p : ℕ) (fl c : List ℕ) : List ℕ := + (List.range fl.length).map fun i => + ((0 :: c).getD i 0 + (2 ^ p - c.getD (fl.length - 1) 0 * fl.getD i 0 % 2 ^ p)) % 2 ^ p + +/-- The zero element, with `e` coordinates. -/ +def zero (fl : List ℕ) : List ℕ := (List.range fl.length).map fun _ => 0 + +/-- One step of shift-and-add multiplication by `c`: from `(U ^ i d, ∑ j < i, c j U ^ j d)`, +add `c i U ^ i d` to the accumulator and multiply the current power by `U`. -/ +def mulStep (p : ℕ) (fl c : List ℕ) (s : List ℕ × List ℕ) (i : ℕ) : List ℕ × List ℕ := + (mulX p fl s.1, addSmul p fl s.2 (c.getD i 0) s.1) + +/-- **Multiplication** by shift-and-add. -/ +def mul (p : ℕ) (fl c d : List ℕ) : List ℕ := + ((List.range fl.length).foldl (mulStep p fl c) (d, zero fl)).2 + +/-! ### Correctness -/ + +section Correctness + +variable {p : ℕ} {fl : List ℕ} + +theorem natCast_two_pow : ((2 ^ p : ℕ) : Ring p fl) = 0 := by + rw [← map_natCast (algebraMap (ZMod (2 ^ p)) (Ring p fl)), ZMod.natCast_self, map_zero] + +theorem natCast_mod_two_pow (x : ℕ) : ((x % 2 ^ p : ℕ) : Ring p fl) = (x : Ring p fl) := by + conv_rhs => rw [← Nat.mod_add_div x (2 ^ p)] + rw [Nat.cast_add, Nat.cast_mul, natCast_two_pow, zero_mul, add_zero] + +/-- The root satisfies `U ^ e = -∑ i < e, fl i U ^ i`. -/ +theorem root_pow_length : + (AdjoinRoot.root _ : Ring p fl) ^ fl.length = + -∑ i ∈ range fl.length, ((fl.getD i 0 : ℕ) : Ring p fl) * AdjoinRoot.root _ ^ i := by + have hmap : (monicOf fl).map (Int.castRingHom (ZMod (2 ^ p))) = X ^ fl.length + + ∑ i ∈ range fl.length, C ((fl.getD i 0 : ℕ) : ZMod (2 ^ p)) * X ^ i := by + simp [monicOf, Polynomial.map_sum] + have h := AdjoinRoot.eval₂_root ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))) + conv at h => lhs; arg 3; rw [hmap] + simp only [eval₂_add, eval₂_finsetSum, eval₂_mul, eval₂_natCast, eval₂_X_pow, + map_natCast] at h + exact eq_neg_of_add_eq_zero_left h + +@[simp] theorem val_zero : val p fl (zero fl) = 0 := by + refine sum_eq_zero fun i hi => ?_ + rw [zero, List.getD_eq_getElem _ _ (by simpa using hi)] + simp + +/-- **Addition is correct.** -/ +theorem val_add (c d : List ℕ) : val p fl (add p fl c d) = val p fl c + val p fl d := by + rw [val, val, val, ← sum_add_distrib] + refine sum_congr rfl fun i hi => ?_ + rw [add, List.getD_eq_getElem _ _ (by simpa using hi)] + simp only [List.getElem_map, List.getElem_range, natCast_mod_two_pow, Nat.cast_add, add_mul] + +/-- **Adding a multiple is correct.** -/ +theorem val_addSmul (c : List ℕ) (k : ℕ) (d : List ℕ) : + val p fl (addSmul p fl c k d) = val p fl c + (k : Ring p fl) * val p fl d := by + rw [val, val, val, mul_sum, ← sum_add_distrib] + refine sum_congr rfl fun i hi => ?_ + rw [addSmul, List.getD_eq_getElem _ _ (by simpa using hi)] + simp only [List.getElem_map, List.getElem_range, natCast_mod_two_pow, Nat.cast_add, + Nat.cast_mul] + ring + +/-- Shifting coordinates and subtracting the top one times the lower coefficients of `f` is +multiplication by a root of `f`, in any commutative ring. -/ +private theorem shift_sub_mul_sum {R : Type*} [CommRing R] (r : R) (cs fs : ℕ → R) (e : ℕ) + (hroot : r ^ (e + 1) = -∑ i ∈ range (e + 1), fs i * r ^ i) : + ∑ i ∈ range (e + 1), ((if i = 0 then 0 else cs (i - 1)) - cs e * fs i) * r ^ i = + r * ∑ i ∈ range (e + 1), cs i * r ^ i := by + simp only [sub_mul, sum_sub_distrib] + rw [sum_range_succ' (fun i => (if i = 0 then 0 else cs (i - 1)) * r ^ i)] + simp only [Nat.add_sub_cancel, Nat.succ_ne_zero, ite_false, ite_true, zero_mul, add_zero] + rw [mul_sum, sum_range_succ (fun i => r * (cs i * r ^ i)), + show r * (cs e * r ^ e) = cs e * r ^ (e + 1) by ring, hroot, mul_neg, mul_sum, + ← sub_eq_add_neg] + congr 1 + · exact sum_congr rfl fun i _ => by ring + · exact sum_congr rfl fun i _ => by ring + +/-- **Multiplication by `U` is correct.** -/ +theorem val_mulX (c : List ℕ) : + val p fl (mulX p fl c) = AdjoinRoot.root _ * val p fl c := by + -- Each coordinate is the shifted coordinate minus the top coordinate times `fl i`. + have hcoord (i : ℕ) (hi : i < fl.length) : (((mulX p fl c).getD i 0 : ℕ) : Ring p fl) = + (if i = 0 then 0 else (c.getD (i - 1) 0 : Ring p fl)) - + (c.getD (fl.length - 1) 0 : Ring p fl) * (fl.getD i 0 : Ring p fl) := by + rw [mulX, List.getD_eq_getElem _ _ (by simpa using hi)] + simp only [List.getElem_map, List.getElem_range, natCast_mod_two_pow, Nat.cast_add] + rw [Nat.cast_sub (Nat.mod_lt _ (Nat.two_pow_pos p)).le, natCast_two_pow, natCast_mod_two_pow, + Nat.cast_mul] + rcases i with _ | i <;> simp [sub_eq_add_neg] + rcases Nat.eq_zero_or_pos fl.length with he | he + · simp [val, he] + obtain ⟨e, he'⟩ := Nat.exists_eq_add_of_lt he + rw [Nat.zero_add] at he' + have hroot := root_pow_length (p := p) (fl := fl) + rw [val, val, sum_congr rfl fun i hi => by rw [hcoord i (mem_range.mp hi)]] + rw [he', Nat.add_sub_cancel] at * + exact shift_sub_mul_sum _ (fun i => (c.getD i 0 : Ring p fl)) (fun i => (fl.getD i 0 : Ring p fl)) + e hroot + +/-- **Multiplication is correct.** -/ +theorem val_mul (c d : List ℕ) : val p fl (mul p fl c d) = val p fl c * val p fl d := by + -- After `j` steps the state is `(U ^ j d, ∑ i < j, c i U ^ i d)`. + have hinv (j : ℕ) : + val p fl ((List.range j).foldl (mulStep p fl c) (d, zero fl)).1 = + AdjoinRoot.root _ ^ j * val p fl d ∧ + val p fl ((List.range j).foldl (mulStep p fl c) (d, zero fl)).2 = + (∑ i ∈ range j, ((c.getD i 0 : ℕ) : Ring p fl) * AdjoinRoot.root _ ^ i) * val p fl d := by + induction j with + | zero => simp + | succ j ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, mulStep] + refine ⟨?_, ?_⟩ + · rw [val_mulX, ih.1, pow_succ]; ring + · rw [val_addSmul, ih.1, ih.2, sum_range_succ]; ring + rw [mul, (hinv fl.length).2] + rfl + +end Correctness + +/-! ### Reduced outputs -/ + +@[simp] theorem length_add (p : ℕ) (fl c d : List ℕ) : (add p fl c d).length = fl.length := by + simp [add] + +@[simp] theorem length_addSmul (p : ℕ) (fl c : List ℕ) (k : ℕ) (d : List ℕ) : + (addSmul p fl c k d).length = fl.length := by + simp [addSmul] + +@[simp] theorem length_mulX (p : ℕ) (fl c : List ℕ) : (mulX p fl c).length = fl.length := by + simp [mulX] + +@[simp] theorem length_zero (fl : List ℕ) : (zero fl).length = fl.length := by simp [zero] + +theorem lt_of_mem_add {p : ℕ} {fl c d : List ℕ} {x : ℕ} (hx : x ∈ add p fl c d) : x < 2 ^ p := by + obtain ⟨i, -, rfl⟩ := List.mem_map.mp hx + exact Nat.mod_lt _ (Nat.two_pow_pos p) + +theorem lt_of_mem_addSmul {p : ℕ} {fl c : List ℕ} {k : ℕ} {d : List ℕ} {x : ℕ} + (hx : x ∈ addSmul p fl c k d) : x < 2 ^ p := by + obtain ⟨i, -, rfl⟩ := List.mem_map.mp hx + exact Nat.mod_lt _ (Nat.two_pow_pos p) + +theorem lt_of_mem_mulX {p : ℕ} {fl c : List ℕ} {x : ℕ} (hx : x ∈ mulX p fl c) : x < 2 ^ p := by + obtain ⟨i, -, rfl⟩ := List.mem_map.mp hx + exact Nat.mod_lt _ (Nat.two_pow_pos p) + +theorem length_mul (p : ℕ) (fl c d : List ℕ) : (mul p fl c d).length = fl.length := by + rcases Nat.eq_zero_or_pos fl.length with he | he + · simp [mul, he, zero] + obtain ⟨e, he'⟩ := Nat.exists_eq_add_of_lt he + rw [mul, Nat.zero_add] at * + rw [he', List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, mulStep, + length_addSmul, he'] + +theorem lt_of_mem_mul {p : ℕ} {fl c d : List ℕ} {x : ℕ} (hx : x ∈ mul p fl c d) : x < 2 ^ p := by + rcases Nat.eq_zero_or_pos fl.length with he | he + · simp [mul, he, zero] at hx + obtain ⟨e, he'⟩ := Nat.exists_eq_add_of_lt he + rw [Nat.zero_add] at he' + rw [mul, he', List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + mulStep] at hx + exact lt_of_mem_addSmul hx + +/-! ### Polynomial time -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {α : Type} {encIn : α ↪ List Bool} + +/-- Reduction modulo `2 ^ p`, with `p` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_mod_two_pow {x p : α → ℕ} + (hx : ComputableInPolyTime x encIn encNat) (hp : ComputableInPolyTime p encIn encUnary) : + ComputableInPolyTime (fun a => x a % 2 ^ p a) encIn encNat := + ComputableInPolyTime.binary_mod.comp (hx.prod (ComputableInPolyTime.binary_two_pow.comp hp)) + +/-- A coordinate, at a unary index. -/ +theorem computableInPolyTime_getD {c : α → List ℕ} {i : α → ℕ} + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hi : ComputableInPolyTime i encIn encUnary) : + ComputableInPolyTime (fun a => (c a).getD (i a) 0) encIn encNat := + (ComputableInPolyTime.listGetD (encA := encNat) 0).comp (hc.prod hi) + +/-- Tabulating `e` coordinates, for a unary `e`. -/ +theorem computableInPolyTime_tabulate {n : α → ℕ} {g : α → ℕ → ℕ} + (hn : ComputableInPolyTime n encIn encUnary) + (hg : ComputableInPolyTime (fun q : α × ℕ => g q.1 q.2) (encPair encIn encUnary) encNat) : + ComputableInPolyTime (fun x => (List.range (n x)).map (g x)) encIn (encList encNat) := + ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp hn) hg + +private theorem length_polyTime {fl : α → List ℕ} + (hfl : ComputableInPolyTime fl encIn (encList encNat)) : + ComputableInPolyTime (fun x => (fl x).length) encIn encUnary := + (ComputableInPolyTime.length_list encNat).comp hfl + +/-- **Addition is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_add {p : α → ℕ} {fl c d : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hd : ComputableInPolyTime d encIn (encList encNat)) : + ComputableInPolyTime (fun x => add (p x) (fl x) (c x) (d x)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact computableInPolyTime_tabulate (length_polyTime hfl) + (computableInPolyTime_mod_two_pow (ComputableInPolyTime.binary_add.comp + ((computableInPolyTime_getD (hc.comp h1) h2).prod + (computableInPolyTime_getD (hd.comp h1) h2))) (hp.comp h1)) + +/-- **Adding a multiple is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_addSmul {p k : α → ℕ} {fl c d : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hk : ComputableInPolyTime k encIn encNat) + (hd : ComputableInPolyTime d encIn (encList encNat)) : + ComputableInPolyTime (fun x => addSmul (p x) (fl x) (c x) (k x) (d x)) encIn + (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact computableInPolyTime_tabulate (length_polyTime hfl) + (computableInPolyTime_mod_two_pow (ComputableInPolyTime.binary_add.comp + ((computableInPolyTime_getD (hc.comp h1) h2).prod + (ComputableInPolyTime.binary_mul' (hk.comp h1) + (computableInPolyTime_getD (hd.comp h1) h2)))) (hp.comp h1)) + +/-- **Multiplication by `U` is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_mulX {p : α → ℕ} {fl c : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) : + ComputableInPolyTime (fun x => mulX (p x) (fl x) (c x)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have hshift := computableInPolyTime_getD + (ComputableInPolyTime.cons' (ComputableInPolyTime.const 0) (hc.comp h1)) h2 + have htop := computableInPolyTime_getD (hc.comp h1) + (ComputableInPolyTime.unary_sub' (length_polyTime (hfl.comp h1)) + (ComputableInPolyTime.const 1)) + have hsub := computableInPolyTime_mod_two_pow + (ComputableInPolyTime.binary_mul' htop (computableInPolyTime_getD (hfl.comp h1) h2)) + (hp.comp h1) + exact computableInPolyTime_tabulate (length_polyTime hfl) + (computableInPolyTime_mod_two_pow (ComputableInPolyTime.binary_add.comp (hshift.prod + (ComputableInPolyTime.binary_sub' (ComputableInPolyTime.binary_two_pow.comp (hp.comp h1)) + hsub))) (hp.comp h1)) + +/-- A reduced coefficient list, with `e` coordinates below `2 ^ p`, is at most +`e (2 p + 1)` long in binary. -/ +theorem length_encList_le {p e : ℕ} {c : List ℕ} (hlen : c.length = e) + (hlt : ∀ x ∈ c, x < 2 ^ p) : (encList encNat c).length ≤ e * (2 * p + 1) := by + rw [length_encList] + calc (c.map fun x => 2 * (encNat x).length + 1).sum + ≤ (c.map fun x => 2 * (encNat x).length + 1).length • (2 * p + 1) := + List.sum_le_length_nsmul _ _ fun n hn => by + obtain ⟨x, hx, rfl⟩ := List.mem_map.mp hn + have : (encNat x).length ≤ p := by + change x.bits.length ≤ p + rw [Nat.size_eq_bits_len] at * + exact (Nat.size_eq_bits_len x ▸ Nat.size_le.mpr (hlt x hx)) + omega + _ = e * (2 * p + 1) := by rw [List.length_map, hlen, smul_eq_mul] + +/-- **Multiplication is polynomial-time**, with the precision in unary. Every intermediate pair is +reduced after its first step, so its length is bounded by the precision and the degree. -/ +@[fun_prop] +theorem computableInPolyTime_mul {p : α → ℕ} {fl c d : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hd : ComputableInPolyTime d encIn (encList encNat)) : + ComputableInPolyTime (fun x => mul (p x) (fl x) (c x) (d x)) encIn (encList encNat) := by + let encS := encPair (encList encNat) (encList encNat) + have hinit : ComputableInPolyTime (fun x => (d x, zero (fl x))) encIn encS := + hd.prod (computableInPolyTime_tabulate (g := fun _ _ => 0) (length_polyTime hfl) + (ComputableInPolyTime.const 0)) + have hrange : ComputableInPolyTime (fun x => List.range (fl x).length) encIn + (encList encUnary) := + ComputableInPolyTime.range.comp (length_polyTime hfl) + -- The step, on the input, the state and the unary index. + have hstep : ComputableInPolyTime + (fun q : (α × (List ℕ × List ℕ)) × ℕ => mulStep (p q.1.1) (fl q.1.1) (c q.1.1) q.1.2 q.2) + (encPair (encPair encIn encS) encUnary) encS := by + have hq := ComputableInPolyTime.fst (encPair encIn encS) encUnary + have hx := (ComputableInPolyTime.fst encIn encS).comp hq + have hs := (ComputableInPolyTime.snd encIn encS).comp hq + have hi := ComputableInPolyTime.snd (encPair encIn encS) encUnary + have hcur := (ComputableInPolyTime.fst (encList encNat) (encList encNat)).comp hs + have hacc := (ComputableInPolyTime.snd (encList encNat) (encList encNat)).comp hs + exact (computableInPolyTime_mulX (hp.comp hx) (hfl.comp hx) hcur).prod + (computableInPolyTime_addSmul (hp.comp hx) (hfl.comp hx) hacc + (computableInPolyTime_getD (hc.comp hx) hi) hcur) + -- Every step's output is reduced, hence of bounded length. + have hgrowth : PolyBounded (fun x => (encIn x).length) + (fun x => 3 * ((fl x).length * (2 * p x + 1)) + 1) := by + have hpl := (ComputableInPolyTime.polyBounded_length hp) + have hfll := (ComputableInPolyTime.polyBounded_length hfl) + refine (((PolyBounded.const 3).mul (hfll.mul (((PolyBounded.const 2).mul hpl).add + (PolyBounded.const 1)))).add (PolyBounded.const 1)).mono fun x => ?_ + have h1 : p x ≤ (encUnary (p x)).length := by simp + have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by + rw [length_encList] + calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp + _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := + List.sum_le_sum fun a _ => by omega + have := Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) + omega + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x s i => mulStep (p x) (fl x) (c x) s i) hinit + ⟨hrange, hstep, inferInstance, inferInstance⟩ ⟨hgrowth, fun x s i _ => ?_⟩ + · exact (ComputableInPolyTime.snd (encList encNat) (encList encNat)).comp hfold + · have hcur := length_encList_le (length_mulX (p x) (fl x) s.1) fun _ h => lt_of_mem_mulX h + have hacc := length_encList_le (length_addSmul (p x) (fl x) s.2 ((c x).getD i 0) s.1) + fun _ h => lt_of_mem_addSmul h + simp only [mulStep, encS, length_encPair] + omega + +end PolyTime + +end Cslib.GaloisRingArith diff --git a/ComplexityTests/GaloisRingArithmetic.lean b/ComplexityTests/GaloisRingArithmetic.lean new file mode 100644 index 0000000000..566fca586f --- /dev/null +++ b/ComplexityTests/GaloisRingArithmetic.lean @@ -0,0 +1,59 @@ +/- +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.PolyTime.GaloisRingArithmetic + +/-! # Arithmetic in `(ℤ / 2 ^ p)[U] / (f)` on coefficient lists + +Concrete products in the Galois ring `(ℤ / 8)[U] / (U ^ 2 + U + 1)` (`p = 3`, `fl = [1, 1]`), the +correctness statements, and the polynomial-time certificates in closed form, with the precision +in unary and the coordinates in binary. -/ + +namespace ComplexityTests.GaloisRingArithmetic + +open Cslib.GaloisRingArith Turing.MultiTapeTM + +-- `U * U = U ^ 2 = -1 - U`, that is `7 + 7 U` modulo `8`. +example : mulX 3 [1, 1] [0, 1] = [7, 7] := by decide + +-- `(1 + 2 U) (3 + U) = 3 + 7 U + 2 U ^ 2 = 1 + 5 U` modulo `8` and `U ^ 2 + U + 1`. +example : mul 3 [1, 1] [1, 2] [3, 1] = [1, 5] := by decide + +-- Addition reduces each coordinate: `(7 + 5 U) + (3 + 6 U) = 2 + 3 U` modulo `8`. +example : add 3 [1, 1] [7, 5] [3, 6] = [2, 3] := by decide + +-- `U` has order `3` here: `U ^ 3 = 1`. +example : mul 3 [1, 1] [0, 1] (mul 3 [1, 1] [0, 1] [0, 1]) = [1, 0] := by decide + +-- Unreduced inputs are reduced: missing coordinates are zero, extra ones ignored. +example : mul 3 [1, 1] [9] [3, 1, 4] = [3, 1] := by decide + +-- Degree three, `f = U ^ 3 + U + 1`, modulo `16`: `U ^ 2 * U = U ^ 3 = -1 - U`. +example : mulX 4 [1, 1, 0] [0, 0, 1] = [15, 15, 0] := by decide + +/-- The defining polynomial is monic. -/ +example : (monicOf [1, 1]).Monic := monic_monicOf _ + +/-- Multiplication is correct in the ring itself. -/ +example (c d : List ℕ) : val 3 [1, 1] (mul 3 [1, 1] c d) = val 3 [1, 1] c * val 3 [1, 1] d := + val_mul c d + +/-- **Multiplication in polynomial time**, on the ring description `(p, fl)` with `p` in unary +and the two coefficient lists in binary. -/ +example : ComputableInPolyTime + (fun q : (ℕ × List ℕ) × (List ℕ × List ℕ) => mul q.1.1 q.1.2 q.2.1 q.2.2) + (encPair (encPair encUnary (encList encNat)) (encPair (encList encNat) (encList encNat))) + (encList encNat) := by + fun_prop + +/-- **Addition in polynomial time**, in the same encoding. -/ +example : ComputableInPolyTime + (fun q : (ℕ × List ℕ) × (List ℕ × List ℕ) => add q.1.1 q.1.2 q.2.1 q.2.2) + (encPair (encPair encUnary (encList encNat)) (encPair (encList encNat) (encList encNat))) + (encList encNat) := by + fun_prop + +end ComplexityTests.GaloisRingArithmetic From 27d031198052460748576f5c64eee82e3c841af6 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:29:37 -0400 Subject: [PATCH 10/33] doc(GenericRAM/Native): qualify the module path in the stack recursion docstring Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity/Computability/GenericRAM/Native/StackRecursion.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Complexity/Computability/GenericRAM/Native/StackRecursion.lean b/Complexity/Computability/GenericRAM/Native/StackRecursion.lean index c2fd37dd11..fadf0610d0 100644 --- a/Complexity/Computability/GenericRAM/Native/StackRecursion.lean +++ b/Complexity/Computability/GenericRAM/Native/StackRecursion.lean @@ -41,7 +41,7 @@ within time `TL` and space `SL`, the time is at most `fuel a + 1` times the cost of computing the fuel and the root and of the binary clock, for one constant `K` of the scheme (`Scheme.certificate_le`). Only the stack is kept, so the workspace grows with the depth, not with the number of nodes. The number of nodes of a recursion tree is bounded in -`Foundations/Data/Nat/RecursionTree.lean`. +`Complexity/Foundations/Data/Nat/RecursionTree.lean`. This is the depth-first evaluation of the recursion in the proof of Theorem D.1 of [OpenAI2026ETH] (Appendix D.4.7, p. 64), which keeps one sum over mode tuples and one product From 70f4cd51d4fae5ef1cd772bdca7f609ed8894e77 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:34:00 -0400 Subject: [PATCH 11/33] feat(PolyTime/Nat): the clock 2^(K n / sqrt(floor(log2 n))) in polynomial time The fuel of the stack recursion on the scale of ContractionAlgorithm, computed in binary from n in unary. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Complexity/PolyTime/Nat/SqrtLog.lean | 53 +++++++++++++++++++ ComplexityTests/StackRecursion.lean | 14 +++++ 3 files changed, 68 insertions(+) create mode 100644 Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Nat/SqrtLog.lean diff --git a/Complexity.lean b/Complexity.lean index e3af2ca51c..c3020b1ba0 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1631,6 +1631,7 @@ public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Poly public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Prime public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Search public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Sqrt +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.SqrtLog public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Option public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Pi public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Plain diff --git a/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Nat/SqrtLog.lean b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Nat/SqrtLog.lean new file mode 100644 index 0000000000..7b6a566ec2 --- /dev/null +++ b/Complexity/Computability/Machines/Turing/MultiTape/Complexity/PolyTime/Nat/SqrtLog.lean @@ -0,0 +1,53 @@ +/- +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.Init +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Nat.Bits +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Unary +public import Complexity.Computability.Machines.Turing.MultiTape.PolyTime +public import Mathlib.Data.Nat.Log +public import Mathlib.Data.Nat.Sqrt +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +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.Sqrt +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends + +/-! +# The clock `2 ^ (K n / √⌊log₂ n⌋)` in polynomial time + +For a fixed `K`, the number `2 ^ (K n / √⌊log₂ n⌋)` (in the natural-number scale of +`Complexity/Foundations/Data/Nat/SqrtLog.lean`) is computable in binary in polynomial time from +`n` in unary. Its binary length is at most `K n + 1`. It serves as the fuel of clocked +subexponential algorithms, such as the depth-first recursion of the contraction theorem of +[OpenAI2026ETH] (Appendix D.4.7, p. 64), whose number of calls is `2 ^ O(n / √(log n))`. + +## Main results + +* `Turing.MultiTapeTM.ComputableInPolyTime.unary_two_pow_div_sqrt_log_two`: the clock. +-/ + +@[expose] public section + +namespace Turing.MultiTapeTM.ComputableInPolyTime + +/-- **The clock `2 ^ (K n / √⌊log₂ n⌋)`**, in binary from `n` in unary. -/ +theorem unary_two_pow_div_sqrt_log_two (K : ℕ) : + ComputableInPolyTime (fun n : ℕ => 2 ^ (K * n / Nat.sqrt (Nat.log 2 n))) encUnary encNat := by + have hb := unary_to_binary + have hlog : ComputableInPolyTime (fun n : ℕ => Nat.log 2 n) encUnary encUnary := + binary_log_two.comp hb + have hsq : ComputableInPolyTime (fun n : ℕ => Nat.sqrt (Nat.log 2 n)) encUnary encUnary := + binary_to_unary_bounded (binary_sqrt.comp (hb.comp hlog)) id' + (fun n => (Nat.sqrt_le_self _).trans (Nat.log_le_self 2 n)) + exact binary_two_pow.comp (unary_div.comp ((unary_mul' (const K) id').prod hsq)) + +end Turing.MultiTapeTM.ComputableInPolyTime diff --git a/ComplexityTests/StackRecursion.lean b/ComplexityTests/StackRecursion.lean index bcca97e434..34fbd89200 100644 --- a/ComplexityTests/StackRecursion.lean +++ b/ComplexityTests/StackRecursion.lean @@ -6,6 +6,7 @@ Authors: Samuel Schlesinger import Complexity.Computability.GenericRAM.Native.StackRecursion import Complexity.Foundations.Data.Nat.RecursionTree +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.SqrtLog import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat.Binary import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeBinaryPrimitives import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives @@ -254,4 +255,17 @@ example : Nat.recursionBound (fun _ => 2) 3 0 ≤ (3 + 1) * ∏ _j ∈ Finset.ra -- Halving excess exponents: `100 + 50 + 25 + 12 + 6 = 193 ≤ 200`. example : ∑ j ∈ Finset.range 5, 100 / 2 ^ j ≤ 2 * 100 := Nat.sum_div_two_pow_le 100 5 +/-! ### The clock -/ + +-- The fuel `2 ^ (3 n / √⌊log₂ n⌋)` of a subexponential recursion, from `n` in unary. +example : ComputableInPolyTime (fun n : ℕ => 2 ^ (3 * n / Nat.sqrt (Nat.log 2 n))) encUnary + encNat := + ComputableInPolyTime.unary_two_pow_div_sqrt_log_two 3 + +-- At `n = 16`: `⌊log₂ 16⌋ = 4`, `√4 = 2`, so the clock is `2 ^ 24`. +example : 2 ^ (3 * 16 / Nat.sqrt (Nat.log 2 16)) = 2 ^ 24 := by + have h1 : Nat.log 2 16 = 4 := by rw [show (16 : ℕ) = 2 ^ 4 by rfl, Nat.log_pow one_lt_two] + have h2 : Nat.sqrt 4 = 2 := (Nat.eq_sqrt.2 ⟨by norm_num, by norm_num⟩).symm + rw [h1, h2] + end ComplexityTests.StackRecursion From b9caee50441cd14c22ce4d3f0285c1f20bed8e32 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:36:29 -0400 Subject: [PATCH 12/33] feat(PolyTime): unit and powers in (Z/2^p)[U]/(f) Powers c^k by k multiplications, with k in unary (the mode exponents of [OpenAI2026ETH, Appendix D.4.6] are below the precision). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../PolyTime/GaloisRingArithmetic.lean | 93 ++++++++++++++++++- ComplexityTests/GaloisRingArithmetic.lean | 17 ++++ 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean index b46df67660..8a9bfcd2a5 100644 --- a/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean +++ b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean @@ -47,6 +47,9 @@ The value of a coefficient list `c` is `∑ i < e, c i U ^ i` in `A_p` (`GaloisR coordinate times `f`, reducing `U ^ e = -∑ i < e, fl i U ^ i`. * **Multiplication** (`GaloisRingArith.mul`) is shift-and-add: `c d = ∑ i < e, c i (U ^ i d)`, accumulating `c i` times the current `U ^ i d` and multiplying it by `U`, `e` times. +* **Powers** (`GaloisRingArith.pow`) `c ^ k` take `k` multiplications from `GaloisRingArith.one`, + with `k` in unary. In the source the exponents of the mode substitutions are below the + precision (Appendix D.4.6, p. 63), so they are polynomial. Each operation is correct (`GaloisRingArith.val_add`, `GaloisRingArith.val_mulX`, `GaloisRingArith.val_mul`) and polynomial-time, with the precision `p` in unary and the @@ -61,16 +64,19 @@ does by Newton lifting, is not covered here. * `Cslib.GaloisRingArith.monicOf`: the monic polynomial with given lower coefficients. * `Cslib.GaloisRingArith.val`: the value of a coefficient list. -* `Cslib.GaloisRingArith.add`, `Cslib.GaloisRingArith.mulX`, `Cslib.GaloisRingArith.mul`: addition, - multiplication by `U` and multiplication. +* `Cslib.GaloisRingArith.add`, `Cslib.GaloisRingArith.mulX`, `Cslib.GaloisRingArith.mul`, + `Cslib.GaloisRingArith.one`, `Cslib.GaloisRingArith.pow`: addition, multiplication by `U`, + multiplication, the unit and powers. ## Main results * `Cslib.GaloisRingArith.val_add`, `Cslib.GaloisRingArith.val_mulX`, - `Cslib.GaloisRingArith.val_mul`: correctness. + `Cslib.GaloisRingArith.val_mul`, `Cslib.GaloisRingArith.val_one`, + `Cslib.GaloisRingArith.val_pow`: correctness. * `Cslib.GaloisRingArith.computableInPolyTime_add`, `Cslib.GaloisRingArith.computableInPolyTime_mulX`, - `Cslib.GaloisRingArith.computableInPolyTime_mul`: polynomial time. + `Cslib.GaloisRingArith.computableInPolyTime_mul`, + `Cslib.GaloisRingArith.computableInPolyTime_pow`: polynomial time. ## References @@ -131,6 +137,13 @@ def mulStep (p : ℕ) (fl c : List ℕ) (s : List ℕ × List ℕ) (i : ℕ) : L def mul (p : ℕ) (fl c d : List ℕ) : List ℕ := ((List.range fl.length).foldl (mulStep p fl c) (d, zero fl)).2 +/-- The unit element `1`, with `e` coordinates. -/ +def one (fl : List ℕ) : List ℕ := (List.range fl.length).map fun i => if i = 0 then 1 else 0 + +/-- **Powers** `c ^ k`, by `k` multiplications; the exponent is read in unary. -/ +def pow (p : ℕ) (fl c : List ℕ) (k : ℕ) : List ℕ := + (List.range k).foldl (fun acc _ => mul p fl acc c) (one fl) + /-! ### Correctness -/ section Correctness @@ -235,6 +248,34 @@ theorem val_mul (c d : List ℕ) : val p fl (mul p fl c d) = val p fl c * val p rw [mul, (hinv fl.length).2] rfl +/-- **The unit is correct.** -/ +@[simp] theorem val_one : val p fl (one fl) = 1 := by + rcases Nat.eq_zero_or_pos fl.length with he | he + · -- The defining polynomial is `1`, so the ring is trivial. + have h1 : (monicOf fl).map (Int.castRingHom (ZMod (2 ^ p))) = 1 := by + simp [monicOf, he] + have h : (1 : Ring p fl) = 0 := by + rw [← map_one (AdjoinRoot.mk ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ p))))), + AdjoinRoot.mk_eq_zero, h1] + simp [val, he, h] + · obtain ⟨e, he'⟩ := Nat.exists_eq_add_of_lt he + rw [Nat.zero_add] at he' + rw [val, he', sum_range_succ'] + simp only [one, he'] + rw [sum_eq_zero fun i hi => by + rw [List.getD_eq_getElem _ _ (by simp at hi ⊢; omega)] + simp] + rw [List.getD_eq_getElem _ _ (by simp)] + simp + +/-- **Powers are correct.** -/ +theorem val_pow (c : List ℕ) (k : ℕ) : val p fl (pow p fl c k) = val p fl c ^ k := by + rw [pow] + induction k with + | zero => simp + | succ k ih => rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + val_mul, ih, pow_succ] + end Correctness /-! ### Reduced outputs -/ @@ -441,6 +482,50 @@ theorem computableInPolyTime_mul {p : α → ℕ} {fl c d : α → List ℕ} simp only [mulStep, encS, length_encPair] omega +/-- **Powers are polynomial-time**, with the precision and the exponent in unary. -/ +@[fun_prop] +theorem computableInPolyTime_pow {p k : α → ℕ} {fl c : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => pow (p x) (fl x) (c x) (k x)) encIn (encList encNat) := by + have hone : ComputableInPolyTime (fun x => one (fl x)) encIn (encList encNat) := by + have h2 := ComputableInPolyTime.snd encIn encUnary + refine computableInPolyTime_tabulate (g := fun _ i => if i = 0 then 1 else 0) + (length_polyTime hfl) ?_ + refine (ComputableInPolyTime.ite (encMid := encNat) (c := fun q : α × ℕ => q.2 == 0) + ((ComputableInPolyTime.beq (h2) (ComputableInPolyTime.const 0))) + (ComputableInPolyTime.const 1) (ComputableInPolyTime.const 0)).of_enc_eq fun q => ?_ + by_cases h : q.2 = 0 <;> simp [h] + have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × ℕ => mul (p q.1.1) (fl q.1.1) q.1.2 + (c q.1.1)) (encPair (encPair encIn (encList encNat)) encUnary) (encList encNat) := by + have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) encUnary + have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq + have hacc := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + exact computableInPolyTime_mul (hp.comp hx) (hfl.comp hx) hacc (hc.comp hx) + have hgrowth : PolyBounded (fun x => (encIn x).length) + (fun x => (fl x).length * (2 * p x + 1)) := by + have hpl := (ComputableInPolyTime.polyBounded_length hp) + have hfll := (ComputableInPolyTime.polyBounded_length hfl) + refine (hfll.mul (((PolyBounded.const 2).mul hpl).add (PolyBounded.const 1))).mono + fun x => ?_ + have h1 : p x ≤ (encUnary (p x)).length := by simp + have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by + rw [length_encList] + calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp + _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := + List.sum_le_sum fun a _ => by omega + exact Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x acc _ => mul (p x) (fl x) acc (c x)) hone + ⟨ComputableInPolyTime.range.comp hk, hstep, inferInstance, inferInstance⟩ + ⟨hgrowth, fun x acc _ _ => ?_⟩ + · exact hfold + · have := length_encList_le (length_mul (p x) (fl x) acc (c x)) fun _ h => lt_of_mem_mul h + omega + end PolyTime end Cslib.GaloisRingArith diff --git a/ComplexityTests/GaloisRingArithmetic.lean b/ComplexityTests/GaloisRingArithmetic.lean index 566fca586f..f0e7710ba4 100644 --- a/ComplexityTests/GaloisRingArithmetic.lean +++ b/ComplexityTests/GaloisRingArithmetic.lean @@ -34,6 +34,16 @@ example : mul 3 [1, 1] [9] [3, 1, 4] = [3, 1] := by decide -- Degree three, `f = U ^ 3 + U + 1`, modulo `16`: `U ^ 2 * U = U ^ 3 = -1 - U`. example : mulX 4 [1, 1, 0] [0, 0, 1] = [15, 15, 0] := by decide +-- The unit, and `U ^ 3 = 1` as a power. +example : one [1, 1] = [1, 0] ∧ pow 3 [1, 1] [0, 1] 3 = [1, 0] := by decide + +-- `(1 + U) ^ 2 = 1 + 2 U + U ^ 2 = U`. +example : pow 3 [1, 1] [1, 1] 2 = [0, 1] := by decide + +/-- Powers are correct. -/ +example (c : List ℕ) (k : ℕ) : val 3 [1, 1] (pow 3 [1, 1] c k) = val 3 [1, 1] c ^ k := + val_pow c k + /-- The defining polynomial is monic. -/ example : (monicOf [1, 1]).Monic := monic_monicOf _ @@ -56,4 +66,11 @@ example : ComputableInPolyTime (encList encNat) := by fun_prop +/-- **Powers in polynomial time**, with the exponent in unary. -/ +example : ComputableInPolyTime + (fun q : (ℕ × List ℕ) × (List ℕ × ℕ) => pow q.1.1 q.1.2 q.2.1 q.2.2) + (encPair (encPair encUnary (encList encNat)) (encPair (encList encNat) encUnary)) + (encList encNat) := by + fun_prop + end ComplexityTests.GaloisRingArithmetic From dc4824e4e3a12d05ae3527e90eb88b985f9c6525 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:41:30 -0400 Subject: [PATCH 13/33] feat(PolyTime): negation, subtraction and Newton inversion in (Z/2^p)[U]/(f) The seed x^(2^e - 2) by e - 1 squarings, then p Newton steps y(2 - xy); correct whenever the seed inverts x modulo 2, which holds for units of a Galois ring. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../PolyTime/GaloisRingArithmetic.lean | 287 +++++++++++++++--- ComplexityTests/GaloisRingArithmetic.lean | 20 ++ 2 files changed, 266 insertions(+), 41 deletions(-) diff --git a/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean index 8a9bfcd2a5..123a3cfdb2 100644 --- a/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean +++ b/Complexity/Computability/PolyTime/GaloisRingArithmetic.lean @@ -50,6 +50,12 @@ The value of a coefficient list `c` is `∑ i < e, c i U ^ i` in `A_p` (`GaloisR * **Powers** (`GaloisRingArith.pow`) `c ^ k` take `k` multiplications from `GaloisRingArith.one`, with `k` in unary. In the source the exponents of the mode substitutions are below the precision (Appendix D.4.6, p. 63), so they are polynomial. +* **Inversion** (`GaloisRingArith.inv`) starts from the seed `x ^ (2 ^ e - 2)`, a product of `e - 1` + repeated squares (`GaloisRingArith.seed`), and takes `p` Newton steps `y ↦ y (2 - x y)`, each + squaring the defect `x y - 1` (`GaloisRingArith.val_mul_newton`). When the seed inverts `x` + modulo `2`, the result is the inverse (`GaloisRingArith.val_mul_inv`). In a Galois ring, with + `f` irreducible modulo `2`, this holds for every unit, since the residue field has `2 ^ e` + elements; that bridge is not made here. Each operation is correct (`GaloisRingArith.val_add`, `GaloisRingArith.val_mulX`, `GaloisRingArith.val_mul`) and polynomial-time, with the precision `p` in unary and the @@ -57,26 +63,32 @@ coordinates and the coefficients of `f` in binary (`GaloisRingArith.computableIn `GaloisRingArith.computableInPolyTime_mul`). Their outputs are reduced, with `e` coordinates below `2 ^ p` (`GaloisRingArith.length_mul`, `GaloisRingArith.lt_of_mem_mul`). These are the polynomial-time arithmetic claims of Appendix D.4.1 for addition and multiplication, in the case -`ℓ = 1` of the source, where `A = O` has no `V`-coordinates. Inversion of units, which the source -does by Newton lifting, is not covered here. +`ℓ = 1` of the source, where `A = O` has no `V`-coordinates, together with the inversion of units +by inversion modulo two and Newton lifting that the source names (Appendix D.4.1, p. 58). ## Main definitions * `Cslib.GaloisRingArith.monicOf`: the monic polynomial with given lower coefficients. * `Cslib.GaloisRingArith.val`: the value of a coefficient list. * `Cslib.GaloisRingArith.add`, `Cslib.GaloisRingArith.mulX`, `Cslib.GaloisRingArith.mul`, - `Cslib.GaloisRingArith.one`, `Cslib.GaloisRingArith.pow`: addition, multiplication by `U`, - multiplication, the unit and powers. + `Cslib.GaloisRingArith.one`, `Cslib.GaloisRingArith.pow`, `Cslib.GaloisRingArith.neg`, + `Cslib.GaloisRingArith.sub`: addition, multiplication by `U`, multiplication, the unit, powers, + negation and subtraction. +* `Cslib.GaloisRingArith.seed`, `Cslib.GaloisRingArith.newton`, `Cslib.GaloisRingArith.inv`: + inversion. ## Main results * `Cslib.GaloisRingArith.val_add`, `Cslib.GaloisRingArith.val_mulX`, `Cslib.GaloisRingArith.val_mul`, `Cslib.GaloisRingArith.val_one`, - `Cslib.GaloisRingArith.val_pow`: correctness. + `Cslib.GaloisRingArith.val_pow`, `Cslib.GaloisRingArith.val_neg`, + `Cslib.GaloisRingArith.val_sub`, `Cslib.GaloisRingArith.val_seed`, + `Cslib.GaloisRingArith.val_mul_inv`: correctness. * `Cslib.GaloisRingArith.computableInPolyTime_add`, `Cslib.GaloisRingArith.computableInPolyTime_mulX`, `Cslib.GaloisRingArith.computableInPolyTime_mul`, - `Cslib.GaloisRingArith.computableInPolyTime_pow`: polynomial time. + `Cslib.GaloisRingArith.computableInPolyTime_pow`, + `Cslib.GaloisRingArith.computableInPolyTime_inv`: polynomial time. ## References @@ -144,6 +156,33 @@ def one (fl : List ℕ) : List ℕ := (List.range fl.length).map fun i => if i = def pow (p : ℕ) (fl c : List ℕ) (k : ℕ) : List ℕ := (List.range k).foldl (fun acc _ => mul p fl acc c) (one fl) +/-- Negation, coordinatewise modulo `2 ^ p`. -/ +def neg (p : ℕ) (fl c : List ℕ) : List ℕ := + (List.range fl.length).map fun i => (2 ^ p - c.getD i 0 % 2 ^ p) % 2 ^ p + +/-- Subtraction. -/ +def sub (p : ℕ) (fl c d : List ℕ) : List ℕ := add p fl c (neg p fl d) + +/-- One step of the seed of inversion: from `(x ^ 2 ^ j, x ^ (2 ^ (j + 1) - 2))`, square the power +and multiply it in. -/ +def seedStep (p : ℕ) (fl : List ℕ) (s : List ℕ × List ℕ) : List ℕ × List ℕ := + (mul p fl s.1 s.1, mul p fl s.2 (mul p fl s.1 s.1)) + +/-- The seed `x ^ (2 ^ e - 2) = x ^ 2 x ^ 4 ⋯ x ^ (2 ^ (e - 1))` of inversion, by `e - 1` +squarings. In a Galois ring it inverts `x` modulo `2`, since the residue field has `2 ^ e` +elements. -/ +def seed (p : ℕ) (fl x : List ℕ) : List ℕ := + ((List.range (fl.length - 1)).foldl (fun s _ => seedStep p fl s) (x, one fl)).2 + +/-- One Newton step `y (2 - x y)` towards the inverse of `x`. -/ +def newton (p : ℕ) (fl x y : List ℕ) : List ℕ := + mul p fl y (sub p fl (add p fl (one fl) (one fl)) (mul p fl x y)) + +/-- **Inversion**: `p` Newton steps from the seed. It is the inverse of `x` whenever the seed +inverts `x` modulo `2` (`val_mul_inv`), in particular for every unit of a Galois ring. -/ +def inv (p : ℕ) (fl x : List ℕ) : List ℕ := + (List.range p).foldl (fun y _ => newton p fl x y) (seed p fl x) + /-! ### Correctness -/ section Correctness @@ -276,6 +315,78 @@ theorem val_pow (c : List ℕ) (k : ℕ) : val p fl (pow p fl c k) = val p fl c | succ k ih => rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, val_mul, ih, pow_succ] +/-- **Negation is correct.** -/ +theorem val_neg (c : List ℕ) : val p fl (neg p fl c) = -val p fl c := by + rw [val, val, ← sum_neg_distrib] + refine sum_congr rfl fun i hi => ?_ + rw [neg, List.getD_eq_getElem _ _ (by simpa using hi)] + simp only [List.getElem_map, List.getElem_range, natCast_mod_two_pow] + rw [Nat.cast_sub (Nat.mod_lt _ (Nat.two_pow_pos p)).le, natCast_two_pow, natCast_mod_two_pow] + ring + +/-- **Subtraction is correct.** -/ +theorem val_sub (c d : List ℕ) : val p fl (sub p fl c d) = val p fl c - val p fl d := by + rw [sub, val_add, val_neg, sub_eq_add_neg] + +/-- **The seed is the power `x ^ (2 ^ e - 2)`.** -/ +theorem val_seed (x : List ℕ) : val p fl (seed p fl x) = val p fl x ^ (2 ^ fl.length - 2) := by + have hinv (j : ℕ) : + val p fl ((List.range j).foldl (fun s _ => seedStep p fl s) (x, one fl)).1 = + val p fl x ^ 2 ^ j ∧ + val p fl ((List.range j).foldl (fun s _ => seedStep p fl s) (x, one fl)).2 = + val p fl x ^ (2 ^ (j + 1) - 2) := by + induction j with + | zero => simp + | succ j ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, seedStep] + refine ⟨?_, ?_⟩ + · rw [val_mul, ih.1, ← pow_add, ← two_mul, ← pow_succ'] + · rw [val_mul, val_mul, ih.1, ih.2, ← pow_add, ← pow_add] + congr 1 + have : 2 ≤ 2 ^ (j + 1) := by + calc 2 = 2 ^ 1 := rfl + _ ≤ 2 ^ (j + 1) := Nat.pow_le_pow_right (by norm_num) (by omega) + rw [pow_succ 2 (j + 1)] + omega + rw [seed, (hinv _).2] + rcases Nat.eq_zero_or_pos fl.length with he | he + · simp [he] + · rw [Nat.sub_add_cancel he] + +/-- **Newton's step squares the defect**: if `x y = 1 + 2 ^ m w` then +`x (y (2 - x y)) = 1 + 2 ^ (2 m) (-w ^ 2)`. -/ +theorem val_mul_newton (x y : List ℕ) {m : ℕ} {w : Ring p fl} + (h : val p fl x * val p fl y = 1 + 2 ^ m * w) : + val p fl x * val p fl (newton p fl x y) = 1 + 2 ^ (2 * m) * -w ^ 2 := by + rw [newton, val_mul, val_sub, val_add, val_mul, val_one] + have : val p fl x * (val p fl y * (1 + 1 - val p fl x * val p fl y)) = + (val p fl x * val p fl y) * (2 - val p fl x * val p fl y) := by ring + rw [this, h, show (2 : Ring p fl) ^ (2 * m) = 2 ^ m * 2 ^ m by rw [two_mul, pow_add]] + ring + +/-- **Inversion is correct** when the seed inverts `x` modulo `2`. -/ +theorem val_mul_inv (x : List ℕ) {w : Ring p fl} + (h : val p fl x * val p fl x ^ (2 ^ fl.length - 2) = 1 + 2 * w) : + val p fl x * val p fl (inv p fl x) = 1 := by + have hstep (k : ℕ) : ∃ w' : Ring p fl, val p fl x * + val p fl ((List.range k).foldl (fun y _ => newton p fl x y) (seed p fl x)) = + 1 + 2 ^ 2 ^ k * w' := by + induction k with + | zero => exact ⟨w, by rw [List.range_zero, List.foldl_nil, val_seed, h, pow_zero, pow_one]⟩ + | succ k ih => + obtain ⟨w', hw'⟩ := ih + refine ⟨-w' ^ 2, ?_⟩ + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + val_mul_newton _ _ hw', show 2 ^ (k + 1) = 2 * 2 ^ k by ring] + obtain ⟨w', hw'⟩ := hstep p + rw [inv, hw'] + have hp : (2 : Ring p fl) ^ 2 ^ p = 0 := by + have h2p : (2 : Ring p fl) ^ p = 0 := by + rw [show (2 : Ring p fl) ^ p = ((2 ^ p : ℕ) : Ring p fl) by push_cast; rfl, + natCast_two_pow] + exact pow_eq_zero_of_le Nat.lt_two_pow_self.le h2p + rw [hp, zero_mul, add_zero] + end Correctness /-! ### Reduced outputs -/ @@ -427,6 +538,38 @@ theorem length_encList_le {p e : ℕ} {c : List ℕ} (hlen : c.length = e) omega _ = e * (2 * p + 1) := by rw [List.length_map, hlen, smul_eq_mul] +/-- The unit is polynomial-time. -/ +@[fun_prop] +theorem computableInPolyTime_one {fl : α → List ℕ} + (hfl : ComputableInPolyTime fl encIn (encList encNat)) : + ComputableInPolyTime (fun x => one (fl x)) encIn (encList encNat) := by + have h2 := ComputableInPolyTime.snd encIn encUnary + refine computableInPolyTime_tabulate (g := fun _ i => if i = 0 then 1 else 0) + (length_polyTime hfl) ?_ + refine (ComputableInPolyTime.ite (encMid := encNat) (c := fun q : α × ℕ => q.2 == 0) + ((ComputableInPolyTime.beq (h2) (ComputableInPolyTime.const 0))) + (ComputableInPolyTime.const 1) (ComputableInPolyTime.const 0)).of_enc_eq fun q => ?_ + by_cases h : q.2 = 0 <;> simp [h] + +/-- The length bound shared by the folds below: a pair of reduced lists. -/ +private theorem polyBounded_reduced {p : α → ℕ} {fl : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) : + PolyBounded (fun x => (encIn x).length) + (fun x => 3 * ((fl x).length * (2 * p x + 1)) + 1) := by + have hpl := (ComputableInPolyTime.polyBounded_length hp) + have hfll := (ComputableInPolyTime.polyBounded_length hfl) + refine (((PolyBounded.const 3).mul (hfll.mul (((PolyBounded.const 2).mul hpl).add + (PolyBounded.const 1)))).add (PolyBounded.const 1)).mono fun x => ?_ + have h1 : p x ≤ (encUnary (p x)).length := by simp + have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by + rw [length_encList] + calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp + _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := + List.sum_le_sum fun a _ => by omega + have := Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) + omega + /-- **Multiplication is polynomial-time**, with the precision in unary. Every intermediate pair is reduced after its first step, so its length is bounded by the precision and the degree. -/ @[fun_prop] @@ -457,20 +600,7 @@ theorem computableInPolyTime_mul {p : α → ℕ} {fl c d : α → List ℕ} (computableInPolyTime_addSmul (hp.comp hx) (hfl.comp hx) hacc (computableInPolyTime_getD (hc.comp hx) hi) hcur) -- Every step's output is reduced, hence of bounded length. - have hgrowth : PolyBounded (fun x => (encIn x).length) - (fun x => 3 * ((fl x).length * (2 * p x + 1)) + 1) := by - have hpl := (ComputableInPolyTime.polyBounded_length hp) - have hfll := (ComputableInPolyTime.polyBounded_length hfl) - refine (((PolyBounded.const 3).mul (hfll.mul (((PolyBounded.const 2).mul hpl).add - (PolyBounded.const 1)))).add (PolyBounded.const 1)).mono fun x => ?_ - have h1 : p x ≤ (encUnary (p x)).length := by simp - have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by - rw [length_encList] - calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp - _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := - List.sum_le_sum fun a _ => by omega - have := Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) - omega + have hgrowth := polyBounded_reduced hp hfl have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth (encA := encUnary) (encL := encList encUnary) (step := fun x s i => mulStep (p x) (fl x) (c x) s i) hinit @@ -490,33 +620,14 @@ theorem computableInPolyTime_pow {p k : α → ℕ} {fl c : α → List ℕ} (hc : ComputableInPolyTime c encIn (encList encNat)) (hk : ComputableInPolyTime k encIn encUnary) : ComputableInPolyTime (fun x => pow (p x) (fl x) (c x) (k x)) encIn (encList encNat) := by - have hone : ComputableInPolyTime (fun x => one (fl x)) encIn (encList encNat) := by - have h2 := ComputableInPolyTime.snd encIn encUnary - refine computableInPolyTime_tabulate (g := fun _ i => if i = 0 then 1 else 0) - (length_polyTime hfl) ?_ - refine (ComputableInPolyTime.ite (encMid := encNat) (c := fun q : α × ℕ => q.2 == 0) - ((ComputableInPolyTime.beq (h2) (ComputableInPolyTime.const 0))) - (ComputableInPolyTime.const 1) (ComputableInPolyTime.const 0)).of_enc_eq fun q => ?_ - by_cases h : q.2 = 0 <;> simp [h] + have hone := computableInPolyTime_one hfl have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × ℕ => mul (p q.1.1) (fl q.1.1) q.1.2 (c q.1.1)) (encPair (encPair encIn (encList encNat)) encUnary) (encList encNat) := by have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) encUnary have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq have hacc := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq exact computableInPolyTime_mul (hp.comp hx) (hfl.comp hx) hacc (hc.comp hx) - have hgrowth : PolyBounded (fun x => (encIn x).length) - (fun x => (fl x).length * (2 * p x + 1)) := by - have hpl := (ComputableInPolyTime.polyBounded_length hp) - have hfll := (ComputableInPolyTime.polyBounded_length hfl) - refine (hfll.mul (((PolyBounded.const 2).mul hpl).add (PolyBounded.const 1))).mono - fun x => ?_ - have h1 : p x ≤ (encUnary (p x)).length := by simp - have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by - rw [length_encList] - calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp - _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := - List.sum_le_sum fun a _ => by omega - exact Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) + have hgrowth := polyBounded_reduced hp hfl have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth (encA := encUnary) (encL := encList encUnary) (step := fun x acc _ => mul (p x) (fl x) acc (c x)) hone @@ -526,6 +637,100 @@ theorem computableInPolyTime_pow {p k : α → ℕ} {fl c : α → List ℕ} · have := length_encList_le (length_mul (p x) (fl x) acc (c x)) fun _ h => lt_of_mem_mul h omega +/-- **Negation is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_neg {p : α → ℕ} {fl c : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) : + ComputableInPolyTime (fun x => neg (p x) (fl x) (c x)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact computableInPolyTime_tabulate (length_polyTime hfl) + (computableInPolyTime_mod_two_pow (ComputableInPolyTime.binary_sub' + (ComputableInPolyTime.binary_two_pow.comp (hp.comp h1)) + (computableInPolyTime_mod_two_pow (computableInPolyTime_getD (hc.comp h1) h2) + (hp.comp h1))) (hp.comp h1)) + +/-- **Subtraction is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_sub {p : α → ℕ} {fl c d : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hd : ComputableInPolyTime d encIn (encList encNat)) : + ComputableInPolyTime (fun x => sub (p x) (fl x) (c x) (d x)) encIn (encList encNat) := + computableInPolyTime_add hp hfl hc (computableInPolyTime_neg hp hfl hd) + +/-- **A Newton step is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_newton {p : α → ℕ} {fl x y : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hx : ComputableInPolyTime x encIn (encList encNat)) + (hy : ComputableInPolyTime y encIn (encList encNat)) : + ComputableInPolyTime (fun a => newton (p a) (fl a) (x a) (y a)) encIn (encList encNat) := + computableInPolyTime_mul hp hfl hy (computableInPolyTime_sub hp hfl + (computableInPolyTime_add hp hfl (computableInPolyTime_one hfl) + (computableInPolyTime_one hfl)) (computableInPolyTime_mul hp hfl hx hy)) + +/-- **The seed of inversion is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_seed {p : α → ℕ} {fl x : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hx : ComputableInPolyTime x encIn (encList encNat)) : + ComputableInPolyTime (fun a => seed (p a) (fl a) (x a)) encIn (encList encNat) := by + let encS := encPair (encList encNat) (encList encNat) + have hstep : ComputableInPolyTime + (fun q : (α × (List ℕ × List ℕ)) × ℕ => seedStep (p q.1.1) (fl q.1.1) q.1.2) + (encPair (encPair encIn encS) encUnary) encS := by + have hq := ComputableInPolyTime.fst (encPair encIn encS) encUnary + have ha := (ComputableInPolyTime.fst encIn encS).comp hq + have hs := (ComputableInPolyTime.snd encIn encS).comp hq + have h1 := (ComputableInPolyTime.fst (encList encNat) (encList encNat)).comp hs + have h2 := (ComputableInPolyTime.snd (encList encNat) (encList encNat)).comp hs + have hsq := computableInPolyTime_mul (hp.comp ha) (hfl.comp ha) h1 h1 + exact hsq.prod (computableInPolyTime_mul (hp.comp ha) (hfl.comp ha) h2 hsq) + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun a s _ => seedStep (p a) (fl a) s) + (hx.prod (computableInPolyTime_one hfl)) + ⟨ComputableInPolyTime.range.comp (ComputableInPolyTime.unary_sub' (length_polyTime hfl) + (ComputableInPolyTime.const 1)), hstep, inferInstance, inferInstance⟩ + ⟨polyBounded_reduced hp hfl, fun a s _ _ => ?_⟩ + · exact (ComputableInPolyTime.snd (encList encNat) (encList encNat)).comp hfold + · have h1 := length_encList_le (length_mul (p a) (fl a) s.1 s.1) fun _ h => lt_of_mem_mul h + have h2 := length_encList_le (length_mul (p a) (fl a) s.2 (mul (p a) (fl a) s.1 s.1)) + fun _ h => lt_of_mem_mul h + simp only [seedStep, length_encPair] + omega + +/-- **Inversion is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_inv {p : α → ℕ} {fl x : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hx : ComputableInPolyTime x encIn (encList encNat)) : + ComputableInPolyTime (fun a => inv (p a) (fl a) (x a)) encIn (encList encNat) := by + have hstep : ComputableInPolyTime + (fun q : (α × List ℕ) × ℕ => newton (p q.1.1) (fl q.1.1) (x q.1.1) q.1.2) + (encPair (encPair encIn (encList encNat)) encUnary) (encList encNat) := by + have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) encUnary + have ha := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq + have hy := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + exact computableInPolyTime_newton (hp.comp ha) (hfl.comp ha) (hx.comp ha) hy + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun a y _ => newton (p a) (fl a) (x a) y) (computableInPolyTime_seed hp hfl hx) + ⟨ComputableInPolyTime.range.comp hp, hstep, inferInstance, inferInstance⟩ + ⟨polyBounded_reduced hp hfl, fun a y _ _ => by + have := length_encList_le (length_mul (p a) (fl a) y + (sub (p a) (fl a) (add (p a) (fl a) (one (fl a)) (one (fl a))) (mul (p a) (fl a) (x a) y))) + fun _ h => lt_of_mem_mul h + simp only [newton] + omega⟩ + end PolyTime end Cslib.GaloisRingArith diff --git a/ComplexityTests/GaloisRingArithmetic.lean b/ComplexityTests/GaloisRingArithmetic.lean index f0e7710ba4..c3f7705e26 100644 --- a/ComplexityTests/GaloisRingArithmetic.lean +++ b/ComplexityTests/GaloisRingArithmetic.lean @@ -44,6 +44,20 @@ example : pow 3 [1, 1] [1, 1] 2 = [0, 1] := by decide example (c : List ℕ) (k : ℕ) : val 3 [1, 1] (pow 3 [1, 1] c k) = val 3 [1, 1] c ^ k := val_pow c k +-- Inversion of units: `U⁻¹ = U ^ 2 = 7 + 7 U`, and `(1 + 2 U)⁻¹`, `(3 + 5 U)⁻¹` are inverses. +example : inv 3 [1, 1] [0, 1] = [7, 7] := by decide +example : mul 3 [1, 1] [1, 2] (inv 3 [1, 1] [1, 2]) = [1, 0] := by decide +example : mul 3 [1, 1] [3, 5] (inv 3 [1, 1] [3, 5]) = [1, 0] := by decide + +-- `2` is not a unit, and inversion does not invert it. +example : mul 3 [1, 1] [2, 0] (inv 3 [1, 1] [2, 0]) ≠ [1, 0] := by decide + +/-- Inversion is correct when the seed inverts modulo `2`. -/ +example (x : List ℕ) {w : Ring 3 [1, 1]} + (h : val 3 [1, 1] x * val 3 [1, 1] x ^ (2 ^ 2 - 2) = 1 + 2 * w) : + val 3 [1, 1] x * val 3 [1, 1] (inv 3 [1, 1] x) = 1 := + val_mul_inv x h + /-- The defining polynomial is monic. -/ example : (monicOf [1, 1]).Monic := monic_monicOf _ @@ -73,4 +87,10 @@ example : ComputableInPolyTime (encList encNat) := by fun_prop +/-- **Inversion in polynomial time.** -/ +example : ComputableInPolyTime + (fun q : (ℕ × List ℕ) × List ℕ => inv q.1.1 q.1.2 q.2) + (encPair (encPair encUnary (encList encNat)) (encList encNat)) (encList encNat) := by + fun_prop + end ComplexityTests.GaloisRingArithmetic From 792613291bce8b9d183ff5b14b4ccb2664042655 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:21:15 -0400 Subject: [PATCH 14/33] feat(circuit/frontier): direct summation over networks with described tensors Networks whose tensors are given by descriptors and an evaluator, and their direct summation as a stack recursion: one native machine for the contraction modulo 2^P from any native certificate of the evaluator, with time (2^m (k+1) + 1) (poly + 2E) and space poly + ES. This is the leaf step of [OpenAI2026ETH, Appendix D.4.7] with implicit packet tensors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../TensorNetwork/DescribedContraction.lean | 896 ++++++++++++++++++ ComplexityTests/DescribedContraction.lean | 79 ++ 3 files changed, 976 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean create mode 100644 ComplexityTests/DescribedContraction.lean diff --git a/Complexity.lean b/Complexity.lean index f7c0c0ab92..8217887b32 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -872,6 +872,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Binarize 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.DirectContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean new file mode 100644 index 0000000000..fb1320cf92 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean @@ -0,0 +1,896 @@ +/- +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.Explicit +public import Complexity.Computability.GenericRAM.Native.StackRecursion +public import Complexity.Foundations.Data.BitString.List.Binary +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Words +public import Complexity.Computability.GenericRAM.Native.BooleanFold +public import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListLookup +public import Complexity.Computability.Machines.Turing.MultiTape.Internal.DegreeOptionPrimitives +public import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Degree.Bounds +import Complexity.Foundations.Data.BitString.Binary +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.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.PolyTimeListBinaryLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListMap +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListToBits +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Mathlib.Algebra.BigOperators.Fin +import Mathlib.Algebra.BigOperators.Intervals +import Mathlib.Data.List.GetD + +/-! +# Contracting a network with described tensors by direct summation + +The leaves of the recursion in the proof of Theorem D.1 of [OpenAI2026ETH] are summed directly +over all Boolean edge assignments (Appendix D.4.7, p. 64), but their tensors are not tables: a +packet vertex has up to `2 L` ports, so its table would be superpolynomial, and its entries are +computed from a description instead (Appendix D.4.6, p. 63). A *described network* +(`DescribedNetwork T`) has, for each vertex, its ports and a descriptor `t : T` of its tensor, +whose entry at the bits `bs` on the ports is `eval t bs` for an evaluator `eval`. Explicit +networks are the case of tables (`DescribedNetwork.ofExplicit`). + +**Direct summation as a stack recursion** (`DescribedNetwork.sumScheme`). The sum over the +`2 ^ m` assignments is a call whose children are the assignments, in binary order; an assignment +is a call whose children are the entries of the vertices at its bits, multiplied modulo `2 ^ P`; +an entry is a leaf, evaluated by the evaluator. The derivation has `2 ^ m (k + 1) + 1` nodes for +`k` vertices and two open calls (`DescribedNetwork.eval_sumRoot`). So a native certificate of the +evaluator, with any bounds, gives one native machine for the contraction modulo `2 ^ P` on every +input (`DescribedNetwork.contractionCertificate`), through +`Cslib.GenericRAM.StackRecursion.Scheme.certificate`. If the evaluator takes time `E`, space `ES` +and outputs at most `Ev` bits on every vertex, the machine takes time `2 ^ m (k + 1) + 1` times +`K (n + Ev + 1) ^ K + 2 E` and space `K (n + Ev + 1) ^ K + ES` on inputs of length `n` +(`DescribedNetwork.contractionCertificate_le`). This is the leaf step of the contraction +algorithm, with implicit packet tensors as descriptors. + +## Main definitions + +* `Cslib.Circuits.Frontier.DescribedNetwork`: a network with described tensors. +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction`: its contraction. +* `Cslib.Circuits.Frontier.DescribedNetwork.sumScheme`: direct summation as a recursion scheme. +* `Cslib.Circuits.Frontier.DescribedNetwork.encDescribed`: the encoding of a described network. +* `Cslib.Circuits.Frontier.DescribedNetwork.contractionCertificate`: one native machine for the + contraction modulo `2 ^ P`. + +## 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 the + contraction modulo `2 ^ P`, with `2 ^ m (k + 1) + 1` nodes and two open calls. +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction_ofExplicit`: explicit networks. +* `Cslib.Circuits.Frontier.DescribedNetwork.run_sumScheme`: the machine computes the contraction + modulo `2 ^ P`. +* `Cslib.Circuits.Frontier.DescribedNetwork.contractionCertificate_le`: its time and space. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.6–D.4.7 + (pp. 63–64). +-/ + +@[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 + +/-- A Boolean tensor network with *described* tensors: the number of edges, numbered from zero, +and for each vertex its ordered list of ports and a descriptor of its tensor. -/ +structure DescribedNetwork (T : Type) where + /-- The number of edges; the edges are numbered `0, …, numEdges - 1`. -/ + numEdges : ℕ + /-- The vertices, each given by its ports and the descriptor of its tensor. -/ + vertices : List (List ℕ × T) + +namespace DescribedNetwork + +variable {T : Type} (eval : T → List Bool → ℕ) + +/-- The bits that a word, read as an assignment of bits to the edges, puts on a list of ports; +a port past the end of the word reads `false`. -/ +def wordBits (word : List Bool) (ports : List ℕ) : List Bool := + ports.map fun i => word.getD i false + +/-- The *contraction*: the sum over all assignments of bits to the edges of the product of the +entries of the vertices at the bits on their ports. -/ +def contraction (d : DescribedNetwork T) : ℕ := + ∑ x : Fin d.numEdges → Bool, + (d.vertices.map fun v => eval v.2 (wordBits (List.ofFn x) v.1)).prod + +/-- An explicit network, with its tables as descriptors. -/ +def ofExplicit (d : ExplicitTensorNetwork ℕ) : DescribedNetwork (List ℕ) := + ⟨d.numEdges, d.vertices⟩ + +private theorem getD_ofFn {n : ℕ} (x : Fin n → Bool) (i : ℕ) : + (List.ofFn x).getD i false = if h : i < n then x ⟨i, h⟩ else false := by + by_cases h : i < n + · rw [dite_eq_left h, List.getD_eq_getElem _ _ (by simpa using h), List.getElem_ofFn] + · rw [dite_eq_right h, List.getD_eq_default _ _ (by simp only [List.length_ofFn]; omega)] + +/-- **Explicit networks**: with table lookup as the evaluator, the contraction is that of the +explicit network. -/ +theorem contraction_ofExplicit (d : ExplicitTensorNetwork ℕ) : + (ofExplicit d).contraction ExplicitTensorNetwork.entry = d.contraction := by + refine sum_congr rfl fun x _ => ?_ + simp only [wordBits, ExplicitTensorNetwork.portBits] + congr 1 + refine List.map_congr_left fun v _ => ?_ + congr 2 + funext i + exact getD_ofFn x i + +/-! ### Direct summation as a recursion scheme -/ + +/-- A node of direct summation: its kind (`0` the sum, `1` an assignment, `2` an entry), the +precision and the network, and the current word and vertex index. -/ +abbrev Node (T : Type) := ℕ × ((ℕ × DescribedNetwork T) × (List Bool × ℕ)) + +/-- The entry of a node: the evaluator at the descriptor of its vertex and the bits of its word +on the vertex's ports, or zero for an index past the vertices. -/ +def entryOf (x : Node T) : ℕ := + (x.2.1.2.vertices[x.2.2.2]?).elim 0 fun v => eval v.2 (wordBits x.2.2.1 v.1) + +/-- **Direct summation as a recursion scheme.** The sum (kind `0`) has the `2 ^ m` assignments as +children, adding their values modulo `2 ^ P`; an assignment (kind `1`) has the entries of the +vertices as children, multiplying them modulo `2 ^ P`; an entry (kind `2`) is a leaf. The +accumulator is a counter and a residue. -/ +def sumScheme : Scheme (Node T) (ℕ × ℕ) ℕ where + isLeaf x := x.1 == 2 + leaf := entryOf eval + start x := (0, (if x.1 = 0 then 0 else 1) % 2 ^ x.2.1.1) + next x a := + if x.1 = 0 then + (if a.1 < 2 ^ x.2.1.2.numEdges then some (1, (x.2.1, (lowBits x.2.1.2.numEdges a.1, 0))) + else none) + else if x.1 = 1 then + (if a.1 < x.2.1.2.vertices.length then + some (2, (x.2.1, (x.2.2.1, min x.2.1.2.vertices.length a.1))) else none) + else none + absorb x a y := (a.1 + 1, if x.1 = 0 then (a.2 + y) % 2 ^ x.2.1.1 else a.2 * y % 2 ^ x.2.1.1) + finish _ a := a.2 + +/-- The root of direct summation on `(P, d)`. -/ +def sumRoot (q : ℕ × DescribedNetwork T) : Node T := (0, (q, ([], 0))) + +/-- The weight of a word modulo `2 ^ P`: the product of the entries at its bits, reduced after +each factor. -/ +def weightMod (P : ℕ) (d : DescribedNetwork T) (word : List Bool) : ℕ := + d.vertices.foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) (1 % 2 ^ P) + +/-- The shape of a node of direct summation on `q`: it keeps `q`, its kind is at most `2`, its +word has at most `m` bits and its index is at most the number of vertices. -/ +def NodeShape (q : ℕ × DescribedNetwork T) (x : Node T) : Prop := + x.2.1 = q ∧ x.1 ≤ 2 ∧ x.2.2.1.length ≤ q.2.numEdges ∧ x.2.2.2 ≤ q.2.vertices.length + +/-- The invariant of direct summation on `q = (P, d)`: nodes have the shape of `NodeShape`, frames +also have a counter at most `2 ^ m + k` and a residue below `2 ^ P`, and every value is a residue +below `2 ^ P` or an entry of a vertex at the bits of an `m`-bit word. -/ +def sumInvariant (q : ℕ × DescribedNetwork T) : Invariant (Node T) (ℕ × ℕ) ℕ := + ⟨NodeShape q, fun x a => NodeShape q x ∧ a.1 ≤ 2 ^ q.2.numEdges + q.2.vertices.length ∧ + a.2 < 2 ^ q.1, + fun y => y < 2 ^ q.1 ∨ ∃ v ∈ q.2.vertices, ∃ w : List Bool, w.length = q.2.numEdges ∧ + y = eval v.2 (wordBits w v.1)⟩ + +variable {eval} + +/-- An entry is a leaf. -/ +theorem eval_entry (q : ℕ × DescribedNetwork T) {word : List Bool} {v : ℕ} + (hw : word.length = q.2.numEdges) (hv : v < q.2.vertices.length) : + (sumScheme eval).Eval (sumInvariant eval q) (2, (q, (word, v))) + (entryOf eval (2, (q, (word, v)))) 1 0 := by + refine Scheme.Eval.leaf (R := sumScheme eval) ⟨rfl, by omega, hw.le, hv.le⟩ rfl + (.inr ⟨q.2.vertices[v], List.getElem_mem hv, word, hw, ?_⟩) + change (q.2.vertices[v]?).elim 0 _ = _ + rw [List.getElem?_eq_getElem hv] + rfl + +private theorem foldl_take_succ (d : DescribedNetwork T) (P : ℕ) (word : List Bool) (c : ℕ) + (hc : c < d.vertices.length) : + (d.vertices.take (c + 1)).foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) + (1 % 2 ^ P) = + (d.vertices.take c).foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) + (1 % 2 ^ P) * entryOf eval (2, ((P, d), (word, c))) % 2 ^ P := by + rw [List.take_add_one, List.getElem?_eq_getElem hc, Option.toList_some, List.foldl_append] + simp [entryOf, List.getElem?_eq_getElem hc] + +private theorem foldl_mod_lt {α : Type} (f : α → ℕ) (P : ℕ) (l : List α) : + l.foldl (fun acc v => acc * f v % 2 ^ P) (1 % 2 ^ P) < 2 ^ P := by + have h (a : ℕ) (ha : a < 2 ^ P) : l.foldl (fun acc v => acc * f v % 2 ^ P) a < 2 ^ P := by + induction l generalizing a with + | nil => exact ha + | cons v l ih => exact ih _ (Nat.mod_lt _ (Nat.two_pow_pos P)) + exact h _ (Nat.mod_lt _ (Nat.two_pow_pos P)) + +/-- **An assignment evaluates to the weight of its word**, with one node per vertex plus one +and one open call. -/ +theorem eval_assign (P : ℕ) (d : DescribedNetwork T) {word : List Bool} + (hw : word.length = d.numEdges) : + ∃ h ≤ 1, (sumScheme eval).Eval (sumInvariant eval (P, d)) (1, ((P, d), (word, 0))) + (weightMod eval P d word) (d.vertices.length + 1) h := by + set acc : ℕ → ℕ × ℕ := fun c => (c, (d.vertices.take c).foldl + (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) (1 % 2 ^ P)) + have hshape : NodeShape (P, d) ((1, ((P, d), (word, 0))) : Node T) := + ⟨rfl, by omega, hw.le, Nat.zero_le _⟩ + obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme eval) + (I := sumInvariant eval (P, d)) + (x := (1, ((P, d), (word, 0)))) (acc := acc) + (child := fun c => (2, ((P, d), (word, c)))) + (val := fun c => entryOf eval (2, ((P, d), (word, c)))) + (nodes := fun _ => 1) (height := fun _ => 0) d.vertices.length 0 + (fun c hc => by simp [sumScheme, acc, hc, min_eq_right hc.le]) + (by simp [sumScheme, acc]) + (fun c hc => by + simp only [sumScheme, acc, one_ne_zero, ite_false] + rw [foldl_take_succ d P word c hc]) + (fun c hc => eval_entry (P, d) hw hc) + (fun c hc => ⟨hshape, (Nat.le_of_lt hc).trans (Nat.le_add_left _ _), foldl_mod_lt _ _ _⟩) + (fun _ _ => le_rfl) + have hstart : (sumScheme eval).start (1, ((P, d), (word, 0))) = acc 0 := by + simp [sumScheme, acc] + rw [← hstart] at hloop + refine ⟨h + 1, by omega, ?_⟩ + have := Scheme.Eval.call (R := sumScheme eval) (I := sumInvariant eval (P, d)) + (x := (1, ((P, d), (word, 0)))) hshape rfl hloop (.inl (foldl_mod_lt _ _ _)) + simpa [sumScheme, acc, weightMod, List.take_length, Finset.sum_const, Finset.card_range] using + this + +private theorem sum_lowBits (n : ℕ) (g : List Bool → ℕ) : + ∑ j ∈ range (2 ^ n), g (lowBits n j) = ∑ x : Fin n → Bool, g (List.ofFn x) := by + rw [← Fin.sum_univ_eq_sum_range] + exact Fintype.sum_equiv (equivFin n).symm _ _ fun k => by + rw [lowBits_eq_ofFn_equivFin_symm] + +private theorem foldl_mul_mod {α : Type} (f : α → ℕ) (m : ℕ) (l : List α) (a : ℕ) : + l.foldl (fun acc v => acc * f v % m) (a % m) = a * (l.map f).prod % m := by + induction l generalizing a with + | nil => simp + | cons v l ih => + rw [List.foldl_cons, Nat.mod_mul_mod, ih, List.map_cons, List.prod_cons, Nat.mul_assoc] + +private theorem weightMod_eq (P : ℕ) (d : DescribedNetwork T) (word : List Bool) : + weightMod eval P d word = + (d.vertices.map fun v => eval v.2 (wordBits word v.1)).prod % 2 ^ P := by + rw [weightMod, foldl_mul_mod, Nat.one_mul] + +/-- **Direct summation computes the contraction modulo `2 ^ P`**, with `2 ^ m (k + 1) + 1` +nodes for `m` edges and `k` vertices, and at most two open calls. -/ +theorem eval_sumRoot (P : ℕ) (d : DescribedNetwork T) : + ∃ h ≤ 2, (sumScheme eval).Eval (sumInvariant eval (P, d)) (sumRoot (P, d)) + (d.contraction eval % 2 ^ P) (2 ^ d.numEdges * (d.vertices.length + 1) + 1) h := by + set acc : ℕ → ℕ × ℕ := fun c => (c, (∑ j ∈ range c, weightMod eval P d (lowBits d.numEdges j)) % + 2 ^ P) + have hassign (c : ℕ) := eval_assign (eval := eval) P d (length_lowBits d.numEdges c) + choose hgt hhgt heval using hassign + have hshape : NodeShape (P, d) (sumRoot (P, d)) := ⟨rfl, by simp [sumRoot], by simp [sumRoot], + by simp [sumRoot]⟩ + obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme eval) + (I := sumInvariant eval (P, d)) + (x := sumRoot (P, d)) (acc := acc) + (child := fun c => (1, ((P, d), (lowBits d.numEdges c, 0)))) + (val := fun c => weightMod eval P d (lowBits d.numEdges c)) + (nodes := fun _ => d.vertices.length + 1) (height := hgt) (2 ^ d.numEdges) 1 + (fun c hc => by simp [sumScheme, sumRoot, acc, hc]) + (by simp [sumScheme, sumRoot, acc]) + (fun c hc => by simp [sumScheme, sumRoot, acc, sum_range_succ, Nat.add_mod]) + (fun c _ => heval c) + (fun c hc => ⟨hshape, by simp only [acc]; omega, Nat.mod_lt _ (Nat.two_pow_pos P)⟩) + (fun c _ => hhgt c) + have hstart : (sumScheme eval).start (sumRoot (P, d)) = acc 0 := by + simp [sumScheme, sumRoot, acc] + rw [← hstart] at hloop + refine ⟨h + 1, by omega, ?_⟩ + have := Scheme.Eval.call (R := sumScheme eval) (I := sumInvariant eval (P, d)) + (x := sumRoot (P, d)) hshape rfl hloop (.inl (Nat.mod_lt _ (Nat.two_pow_pos P))) + have hval : (sumScheme eval).finish (sumRoot (P, d)) (acc (2 ^ d.numEdges)) = + d.contraction eval % 2 ^ P := by + simp only [sumScheme, acc, contraction] + rw [sum_lowBits d.numEdges (weightMod eval P d)] + simp only [weightMod_eq] + exact (Finset.sum_nat_mod _ _ _).symm + rw [hval, sum_const, card_range, smul_eq_mul] at this + exact this + +/-! ### The native machine -/ + +section Native + +open Turing.MultiTapeTM + +variable (encT : T ↪ List Bool) + +/-- A described network is its number of edges and its list of vertices. -/ +def equivProd : DescribedNetwork T ≃ ℕ × List (List ℕ × T) where + toFun d := (d.numEdges, d.vertices) + invFun p := ⟨p.1, p.2⟩ + left_inv _ := rfl + right_inv _ := rfl + +/-- **The encoding of a described network**: its number of edges in unary, then its vertices, +each the pair of its list of ports in binary and its descriptor. -/ +def encDescribed : DescribedNetwork T ↪ List Bool := + encOfEquiv equivProd (encPair encUnary (encList (encPair (encList encNat) encT))) + +/-- The encoding of an input `(P, d)`: the precision in unary and the network. -/ +abbrev encInput : ℕ × DescribedNetwork T ↪ List Bool := encPair encUnary (encDescribed encT) + +/-- The encoding of a node: its kind in unary, its input, its word and its vertex index in +unary. -/ +abbrev encNode : Node T ↪ List Bool := + encPair encUnary (encPair (encInput encT) (encPair encBits encUnary)) + +/-- The encoding of an accumulator: the counter and the residue, in binary. -/ +abbrev encAcc : ℕ × ℕ ↪ List Bool := encPair encNat encNat + +variable {encT} + +/-- The number of edges of a described network, in unary. -/ +theorem numEdges_polyTime : + ComputableInPolyTime (fun d : DescribedNetwork T => d.numEdges) (encDescribed encT) + encUnary := + (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.of_eq (f := equivProd) fun _ => rfl) + +/-- The vertices of a described network. -/ +theorem vertices_polyTime : + ComputableInPolyTime (fun d : DescribedNetwork T => d.vertices) (encDescribed encT) + (encList (encPair (encList encNat) encT)) := + (ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.of_eq (f := equivProd) fun _ => rfl) + +/-- The kind of a node. -/ +theorem kind_polyTime : ComputableInPolyTime (fun x : Node T => x.1) (encNode encT) + encUnary := ComputableInPolyTime.fst _ _ + +/-- The input `(P, d)` of a node. -/ +theorem input_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1) (encNode encT) + (encInput encT) := (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _) + +/-- The precision of a node. -/ +theorem precision_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1.1) + (encNode encT) encUnary := (ComputableInPolyTime.fst _ _).comp input_polyTime + +/-- The network of a node. -/ +theorem network_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1.2) + (encNode encT) (encDescribed encT) := (ComputableInPolyTime.snd _ _).comp input_polyTime + +/-- The word of a node. -/ +theorem word_polyTime : ComputableInPolyTime (fun x : Node T => x.2.2.1) + (encNode encT) encBits := + (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _).snd' + +/-- The vertex index of a node. -/ +theorem index_polyTime : ComputableInPolyTime (fun x : Node T => x.2.2.2) + (encNode encT) encUnary := + (ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _).snd' + +/-- `2 ^ P`, from `P` in unary. -/ +private theorem twoPow {α : Type} {encIn : α ↪ List Bool} {P : α → ℕ} + (hP : ComputableInPolyTime P encIn encUnary) : + ComputableInPolyTime (fun a => 2 ^ P a) encIn encNat := + ComputableInPolyTime.binary_two_pow.comp hP + +/-- `x % 2 ^ P`, from `P` in unary. -/ +private theorem modTwoPow {α : Type} {encIn : α ↪ List Bool} {x P : α → ℕ} + (hx : ComputableInPolyTime x encIn encNat) (hP : ComputableInPolyTime P encIn encUnary) : + ComputableInPolyTime (fun a => x a % 2 ^ P a) encIn encNat := + ComputableInPolyTime.binary_mod.comp (hx.prod (twoPow hP)) + +/-- **The polynomial-time components of direct summation.** -/ +theorem sumScheme_polyTime : (sumScheme eval).PolyTime (encNode encT) encAcc encNat where + isLeaf := ComputableInPolyTime.beq kind_polyTime (ComputableInPolyTime.const 2) + start := by + refine (ComputableInPolyTime.const 0).prod (modTwoPow ?_ precision_polyTime) + refine (ComputableInPolyTime.ite (encMid := encNat) (ComputableInPolyTime.beq kind_polyTime + (ComputableInPolyTime.const 0)) (ComputableInPolyTime.const 0) + (ComputableInPolyTime.const 1)).of_enc_eq fun x => ?_ + by_cases h : x.1 = 0 <;> simp [h] + next := by + have hx := ComputableInPolyTime.fst (encNode encT) encAcc + have ha := ComputableInPolyTime.snd (encNode encT) encAcc + have hk := kind_polyTime.comp hx + have hc := (ComputableInPolyTime.fst encNat encNat).comp ha + have hq := input_polyTime.comp hx + have hd := network_polyTime.comp hx + have hm := numEdges_polyTime.comp hd + have hlen := (ComputableInPolyTime.length_list _).comp (vertices_polyTime.comp hd) + have hassign : ComputableInPolyTime (fun p : Node T × (ℕ × ℕ) => + (if p.2.1 < 2 ^ p.1.2.1.2.numEdges then + some ((1, (p.1.2.1, (lowBits p.1.2.1.2.numEdges p.2.1, 0))) : Node T) else none)) + (encPair (encNode encT) encAcc) (encOption (encNode encT)) := + (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hc (twoPow hm)) + ((ComputableInPolyTime.optionSome (encNode encT)).comp + ((ComputableInPolyTime.const (encOut := encUnary) 1).prod + (hq.prod ((BooleanFold.decoder_polyTime.comp (hm.prod hc)).prod + (ComputableInPolyTime.const (encOut := encUnary) 0))))) + (ComputableInPolyTime.const none)).of_enc_eq fun p => by + by_cases h : p.2.1 < 2 ^ p.1.2.1.2.numEdges <;> simp [h] + have hentry : ComputableInPolyTime (fun p : Node T × (ℕ × ℕ) => + (if p.2.1 < p.1.2.1.2.vertices.length then + some ((2, (p.1.2.1, (p.1.2.2.1, min p.1.2.1.2.vertices.length p.2.1))) : Node T) + else none)) + (encPair (encNode encT) encAcc) (encOption (encNode encT)) := + (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hc + (ComputableInPolyTime.unary_to_binary.comp hlen)) + ((ComputableInPolyTime.optionSome (encNode encT)).comp + ((ComputableInPolyTime.const (encOut := encUnary) 2).prod + (hq.prod ((word_polyTime.comp hx).prod + (ComputableInPolyTime.binary_min_unary' hlen hc))))) + (ComputableInPolyTime.const none)).of_enc_eq fun p => by + by_cases h : p.2.1 < p.1.2.1.2.vertices.length <;> simp [h] + refine (ComputableInPolyTime.ite (ComputableInPolyTime.beq hk (ComputableInPolyTime.const 0)) + hassign (ComputableInPolyTime.ite (ComputableInPolyTime.beq hk + (ComputableInPolyTime.const 1)) hentry (ComputableInPolyTime.const none))).of_enc_eq + fun p => ?_ + by_cases h0 : p.1.1 = 0 + · simp [sumScheme, h0] + · by_cases h1 : p.1.1 = 1 <;> simp [sumScheme, h0, h1] + absorb := by + have hx := (ComputableInPolyTime.fst (encNode encT) encAcc).comp + (ComputableInPolyTime.fst (encPair (encNode encT) encAcc) encNat) + have ha := (ComputableInPolyTime.snd (encNode encT) encAcc).comp + (ComputableInPolyTime.fst (encPair (encNode encT) encAcc) encNat) + have hy := ComputableInPolyTime.snd (encPair (encNode encT) encAcc) encNat + have hc := (ComputableInPolyTime.fst encNat encNat).comp ha + have hr := (ComputableInPolyTime.snd encNat encNat).comp ha + have hP := precision_polyTime.comp hx + refine (ComputableInPolyTime.binary_add.comp (hc.prod (ComputableInPolyTime.const 1))).prod + ((ComputableInPolyTime.ite (ComputableInPolyTime.beq (kind_polyTime.comp hx) + (ComputableInPolyTime.const 0)) + (modTwoPow (ComputableInPolyTime.binary_add.comp (hr.prod hy)) hP) + (modTwoPow (ComputableInPolyTime.binary_mul' hr hy) hP)).of_enc_eq fun q => ?_) + by_cases h : q.1.1.1 = 0 <;> simp [h] + finish := (ComputableInPolyTime.snd encNat encNat).comp (ComputableInPolyTime.snd _ _) + +/-- The descriptor of a vertex and the bits of the node's word on its ports. -/ +theorem query_polyTime : + ComputableInPolyTime (fun p : Node T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) + (encPair (encNode encT) (encPair (encList encNat) encT)) (encPair encT encBits) := by + have hv := ComputableInPolyTime.snd (encNode encT) (encPair (encList encNat) encT) + have hports := (ComputableInPolyTime.fst (encList encNat) encT).comp hv + have hword := word_polyTime.comp (ComputableInPolyTime.fst (encNode encT) + (encPair (encList encNat) encT)) + let E := encPair (encNode encT) (encPair (encList encNat) encT) + have hbit : ComputableInPolyTime (fun z : (Node T × (List ℕ × T)) × ℕ => + z.1.1.2.2.1.getD z.2 false) (encPair E encNat) encBool := + ComputableInPolyTime.listGetD_binary (encA := encBool) (encL := encBits) false + (hword.comp (ComputableInPolyTime.fst E encNat)) (ComputableInPolyTime.snd E encNat) + have hbits := ComputableInPolyTime.listToBits.comp + (ComputableInPolyTime.mapWithInput (f := fun p i => p.1.2.2.1.getD i false) hports hbit) + exact ((ComputableInPolyTime.snd (encList encNat) encT).comp hv).prod hbits + +/-- The vertex of a node, if its index is in range. -/ +noncomputable def vertexCertificate : + NativeCertificate (fun x : Node T => x.2.1.2.vertices[x.2.2.2]?) (encNode encT) + (encOption (encPair (encList encNat) encT)) := + NativeCertificate.ofPolyTime (ComputableInPolyTime.getElem? + (vertices_polyTime.comp network_polyTime) index_polyTime) + +/-- The query to the evaluator: the descriptor and the bits on the ports. -/ +noncomputable def queryCertificate : + NativeCertificate (fun p : Node T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) + (encPair (encNode encT) (encPair (encList encNat) encT)) (encPair encT encBits) := + NativeCertificate.ofPolyTime query_polyTime + +variable (eval) in +/-- **The leaf step**: the evaluator at the descriptor of the node's vertex and its bits, from a +native certificate of the evaluator with any bounds. -/ +noncomputable def leafCertificate + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + NativeCertificate (sumScheme eval).leaf (encNode encT) encNat := + NativeCertificate.option (value := fun x : Node T => x.2.1.2.vertices[x.2.2.2]?) + (fallback := fun _ => 0) (body := fun p => eval p.2.2 (wordBits p.1.2.2.1 p.2.1)) + vertexCertificate (NativeCertificate.const 0) (ev.comp queryCertificate) + +/-- The time of the leaf step: the vertex lookup, and the query and the evaluator at a vertex. -/ +theorem leafCertificate_time + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) (x : Node T) : + (leafCertificate eval ev).time x = 2 * (vertexCertificate (encT := encT)).time x + + (encNode encT x).length + + (x.2.1.2.vertices[x.2.2.2]?).elim (encNode encT x).length + fun v => (queryCertificate (encT := encT)).time (x, v) + + ev.time (v.2, wordBits x.2.2.1 v.1) := rfl + +/-- The space of the leaf step. -/ +theorem leafCertificate_space + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) (x : Node T) : + (leafCertificate eval ev).space x = 2 * (vertexCertificate (encT := encT)).space x + + (encNode encT x).length + + (x.2.1.2.vertices[x.2.2.2]?).elim (encNode encT x).length + fun v => (queryCertificate (encT := encT)).space (x, v) + + ev.space (v.2, wordBits x.2.2.1 v.1) := rfl + +/-- The number of steps of direct summation: `2 (2 ^ m (k + 1) + 1) - 1`. -/ +def fuel (q : ℕ × DescribedNetwork T) : ℕ := + 2 * (2 ^ q.2.numEdges * (q.2.vertices.length + 1)) + 1 + +/-- The fuel in polynomial time. -/ +theorem fuel_polyTime : ComputableInPolyTime (fuel (T := T)) (encInput encT) encNat := by + have hd := ComputableInPolyTime.snd encUnary (encDescribed encT) + have hm := numEdges_polyTime.comp hd + have hlen := (ComputableInPolyTime.length_list _).comp (vertices_polyTime.comp hd) + exact ComputableInPolyTime.binary_add.comp ((ComputableInPolyTime.binary_mul' + (ComputableInPolyTime.const 2) (ComputableInPolyTime.binary_mul' (twoPow hm) + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.unary_add' hlen + (ComputableInPolyTime.const 1))))).prod (ComputableInPolyTime.const 1)) + +/-- The root in polynomial time. -/ +theorem sumRoot_polyTime : ComputableInPolyTime (sumRoot (T := T)) (encInput encT) + (encNode encT) := + (ComputableInPolyTime.const 0).prod (ComputableInPolyTime.id'.prod + (ComputableInPolyTime.const ([], 0))) + +variable (eval) in +/-- **One native machine for direct summation over described networks**, from a native +certificate of the evaluator. It returns `some (d.contraction eval % 2 ^ P)` +(`run_sumScheme`). -/ +noncomputable def sumCertificate + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + NativeCertificate ((sumScheme eval).run fuel sumRoot) (encInput encT) (encOption encNat) := + Scheme.certificate sumScheme_polyTime (leafCertificate eval ev) + (NativeCertificate.ofPolyTime fuel_polyTime) (NativeCertificate.ofPolyTime sumRoot_polyTime) + +/-- **The machine computes the contraction modulo `2 ^ P`.** -/ +theorem run_sumScheme (q : ℕ × DescribedNetwork T) : + (sumScheme eval).run fuel sumRoot q = some (q.2.contraction eval % 2 ^ q.1) := by + obtain ⟨h, -, he⟩ := eval_sumRoot (eval := eval) q.1 q.2 + exact Scheme.run_eq_some he (by simp [fuel]; omega) + +variable (eval) in +/-- **Direct summation over described networks, as a native certificate** for the contraction +modulo `2 ^ P` on every input `(P, d)`, from a native certificate of the evaluator. -/ +noncomputable def contractionCertificate + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + NativeCertificate (fun q : ℕ × DescribedNetwork T => q.2.contraction eval % 2 ^ q.1) + (encInput encT) encNat := + (NativeCertificate.post (sumCertificate eval ev) + (ComputableInDegree.getD (enc₁ := encNat) (enc := encOption encNat) 0).inO_one).congr + fun q => by simp [run_sumScheme] + +/-- The time is that of the stack machine. -/ +@[simp] theorem contractionCertificate_time + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + (contractionCertificate eval ev).time = (sumCertificate eval ev).time := rfl + +/-- The space is that of the stack machine. -/ +@[simp] theorem contractionCertificate_space + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + (contractionCertificate eval ev).space = (sumCertificate eval ev).space := rfl + +/-! ### Resources -/ + +/-- The number of edges and the number of vertices are at most the encoded length. -/ +theorem length_numEdges_vertices_le (d : DescribedNetwork T) : + d.numEdges + d.vertices.length ≤ (encDescribed encT d).length := by + have h := length_le_length_encList (encPair (encList encNat) encT) d.vertices + change d.numEdges + d.vertices.length ≤ + (encPair encUnary (encList (encPair (encList encNat) encT)) (d.numEdges, d.vertices)).length + simp only [length_encPair, encUnary_apply, List.length_replicate] + omega + +/-- A node of the right shape is at most `5 n + 7` long, for `n` the length of the input. -/ +theorem length_encNode_le {q : ℕ × DescribedNetwork T} {x : Node T} (hx : NodeShape q x) : + (encNode encT x).length ≤ 5 * (encInput encT q).length + 7 := by + obtain ⟨hq, hk, hw, hv⟩ := hx + obtain ⟨k, ⟨P, d⟩, w, v⟩ := x + simp only at hq hk hw hv + subst hq + dsimp only at hw hv + have hd := length_numEdges_vertices_le (encT := encT) d + simp only [length_encPair, encUnary_apply, List.length_replicate, encBits_apply] + omega + +private theorem term_le {c a b d D z : ℕ} (hd : d ≤ D) : + c * (a * z + b) ^ d ≤ c * (a + b) ^ d * (z + 1) ^ D := + calc c * (a * z + b) ^ d ≤ c * ((a + b) * (z + 1)) ^ d := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by nlinarith) _) + _ = c * (a + b) ^ d * (z + 1) ^ d := by rw [Nat.mul_pow]; ring + _ ≤ c * (a + b) ^ d * (z + 1) ^ D := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (Nat.succ_pos _) hd) + +private theorem length_encNat_lt {y P : ℕ} (h : y < 2 ^ P) : (encNat y).length ≤ P := by + have h' := Nat.size_le.mpr h + rw [← Nat.size_eq_bits_len] at h' + exact h' + +/-- **The resources of direct summation over described networks.** There is `K` such that, for +an input `(P, d)` of length `n` with `m` edges and `k` vertices, if the evaluator takes time at +most `E` and space at most `ES` and outputs at most `Ev` bits on every vertex and every list of +bits on its ports, then the time is at most `2 ^ m (k + 1) + 1` times +`K (n + Ev + 1) ^ K + 2 E`, and the space at most `K (n + Ev + 1) ^ K + ES`. -/ +theorem contractionCertificate_le + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + ∃ K : ℕ, ∀ (q : ℕ × DescribedNetwork T) (E ES Ev : ℕ), + (∀ v ∈ q.2.vertices, ∀ bs : List Bool, bs.length = v.1.length → + ev.time (v.2, bs) ≤ E ∧ ev.space (v.2, bs) ≤ ES ∧ (encNat (eval v.2 bs)).length ≤ Ev) → + (contractionCertificate eval ev).time q ≤ + (2 ^ q.2.numEdges * (q.2.vertices.length + 1) + 1) * + (K * ((encInput encT q).length + Ev + 1) ^ K + 2 * E) ∧ + (contractionCertificate eval ev).space q ≤ + K * ((encInput encT q).length + Ev + 1) ^ K + ES := by + obtain ⟨K₀, hK₀⟩ := Scheme.certificate_le (sumScheme_polyTime (eval := eval) (encT := encT)) + (leafCertificate eval ev) + have hV : (vertexCertificate (encT := encT)).IsPolynomial := + NativeCertificate.ofPolyTime_isPolynomial _ + have hQ : (queryCertificate (encT := encT)).IsPolynomial := + NativeCertificate.ofPolyTime_isPolynomial _ + have hF := NativeCertificate.ofPolyTime_isPolynomial (fuel_polyTime (encT := encT)) + have hR := NativeCertificate.ofPolyTime_isPolynomial (sumRoot_polyTime (encT := encT)) + obtain ⟨cv, dv, hvt⟩ := hV.1.exists_le + obtain ⟨cv', dv', hvs⟩ := hV.2.exists_le + obtain ⟨cq, dq, hqt⟩ := hQ.1.exists_le + obtain ⟨cq', dq', hqs⟩ := hQ.2.exists_le + obtain ⟨cf, df, hft⟩ := hF.1.exists_le + obtain ⟨cf', df', hfs⟩ := hF.2.exists_le + obtain ⟨cr, dr, hrt⟩ := hR.1.exists_le + obtain ⟨cr', dr', hrs⟩ := hR.2.exists_le + obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dv + dv' + dq + dq' + df + df' + dr + dr' + 2 := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₀ * 96 ^ K₀ + (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) + 16 := + ⟨_, rfl⟩ + obtain ⟨G', hG'⟩ : ∃ G', G' = K₀ * 96 ^ K₀ + (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') := + ⟨_, rfl⟩ + obtain ⟨C, hC⟩ : ∃ C, C = cf + cr + cf' + cr' + 2 * G + G' + 4 := ⟨_, rfl⟩ + refine ⟨C + D, fun q E ES Ev hE => ?_⟩ + obtain ⟨P, d⟩ := q + obtain ⟨n, hn⟩ : ∃ n, n = (encInput encT (P, d)).length := ⟨_, rfl⟩ + obtain ⟨z, hz⟩ : ∃ z, z = n + Ev := ⟨_, rfl⟩ + obtain ⟨W, hW⟩ : ∃ W, W = (z + 1) ^ D := ⟨_, rfl⟩ + rw [← hn] + rw [show n + Ev + 1 = z + 1 by omega] + have hnP : 2 * P + 1 + (encDescribed encT d).length = n := by + rw [hn]; simp [encInput] + have hmk := length_numEdges_vertices_le (encT := encT) d + -- Every vertex is at most `n` long. + have hvlen (v : List ℕ × T) (hv : v ∈ d.vertices) : + (encPair (encList encNat) encT v).length ≤ n := by + have h1 := List.le_sum_of_mem (List.mem_map_of_mem (f := fun a => + (encPair (encList encNat) encT a).length) hv) + have h2 := sum_length_le_length_encList (encPair (encList encNat) encT) d.vertices + have h3 : (encList (encPair (encList encNat) encT) d.vertices).length ≤ + (encDescribed encT d).length := by + change _ ≤ (encPair encUnary (encList (encPair (encList encNat) encT)) + (d.numEdges, d.vertices)).length + simp only [length_encPair]; omega + omega + have hnode (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : + (encNode encT x).length ≤ 5 * z + 7 := (length_encNode_le hx).trans (by omega) + have hleafT (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : + (leafCertificate eval ev).time x ≤ + 2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E := by + have h1 := hnode x hx + rw [leafCertificate_time] + have h2 : (vertexCertificate (encT := encT)).time x ≤ cv * (5 * z + 8) ^ dv := + (hvt x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) + have h2' : 2 * (vertexCertificate (encT := encT)).time x ≤ 2 * cv * (5 * z + 8) ^ dv := by + rw [Nat.mul_assoc]; exact Nat.mul_le_mul_left 2 h2 + cases hvx : x.2.1.2.vertices[x.2.2.2]? with + | none => simp only [Option.elim]; omega + | some v => + simp only [Option.elim] + have hmem : v ∈ x.2.1.2.vertices := List.mem_of_getElem? hvx + obtain ⟨hq, -, -, -⟩ := hx + rw [hq] at hmem + have h3 := hvlen v hmem + have h4 : (queryCertificate (encT := encT)).time (x, v) ≤ cq * (11 * z + 16) ^ dq := by + refine (hqt (x, v)).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left ?_ _)) + have := length_encPair (enc₁ := encNode encT) (enc₂ := encPair (encList encNat) encT) + (x, v) + dsimp only at this + omega + have h5 := (hE v hmem (wordBits x.2.2.1 v.1) (by simp [wordBits])).1 + omega + have hleafS (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : + (leafCertificate eval ev).space x ≤ + 2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' + ES := by + have h1 := hnode x hx + rw [leafCertificate_space] + have h2 : (vertexCertificate (encT := encT)).space x ≤ cv' * (5 * z + 8) ^ dv' := + (hvs x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) + have h2' : 2 * (vertexCertificate (encT := encT)).space x ≤ 2 * cv' * (5 * z + 8) ^ dv' := by + rw [Nat.mul_assoc]; exact Nat.mul_le_mul_left 2 h2 + cases hvx : x.2.1.2.vertices[x.2.2.2]? with + | none => simp only [Option.elim]; omega + | some v => + simp only [Option.elim] + have hmem : v ∈ x.2.1.2.vertices := List.mem_of_getElem? hvx + obtain ⟨hq, -, -, -⟩ := hx + rw [hq] at hmem + have h3 := hvlen v hmem + have h4 : (queryCertificate (encT := encT)).space (x, v) ≤ cq' * (11 * z + 16) ^ dq' := by + refine (hqs (x, v)).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left ?_ _)) + have := length_encPair (enc₁ := encNode encT) (enc₂ := encPair (encList encNat) encT) + (x, v) + dsimp only at this + omega + have h5 := (hE v hmem (wordBits x.2.2.1 v.1) (by simp [wordBits])).2.1 + omega + obtain ⟨h, hh, he⟩ := eval_sumRoot (eval := eval) P d + have hres := hK₀ (NativeCertificate.ofPolyTime fuel_polyTime) + (NativeCertificate.ofPolyTime sumRoot_polyTime) (sumInvariant eval (P, d)) (13 * z + 18) + (2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E) + (2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' + ES) + (fun x hx => ⟨(hnode x hx).trans (by omega), fun _ => ⟨hleafT x hx, hleafS x hx⟩⟩) + (fun x a ha => by + obtain ⟨hx, ha1, ha2⟩ := ha + have h1 := hnode x hx + have h2 : (encNat a.1).length ≤ d.numEdges + d.vertices.length + 1 := by + refine length_encNat_lt (lt_of_le_of_lt ha1 ?_) + calc 2 ^ d.numEdges + d.vertices.length + < 2 ^ d.numEdges + 2 ^ d.vertices.length := by + have := Nat.lt_two_pow_self (n := d.vertices.length); omega + _ ≤ 2 ^ (d.numEdges + d.vertices.length + 1) := by + rw [pow_succ, pow_add, Nat.mul_two] + exact Nat.add_le_add (Nat.le_mul_of_pos_right _ (Nat.two_pow_pos _)) + (Nat.le_mul_of_pos_left _ (Nat.two_pow_pos _)) + have h3 := length_encNat_lt ha2 + have h4 : (encPair (encNode encT) encAcc (x, a)).length = + 2 * (encNode encT x).length + 1 + (encAcc a).length := length_encPair (x, a) + have h5 : (encAcc a).length = 2 * (encNat a.1).length + 1 + (encNat a.2).length := + length_encPair a + omega) + (fun y hy => by + rcases hy with hy | ⟨v, hv, w, -, rfl⟩ + · have := length_encNat_lt hy; omega + · have := (hE v hv (wordBits w v.1) (by simp [wordBits])).2.2; omega) + (P, d) _ _ _ he + rw [← hn] at hres + -- The clock and the size of the fuel. + have hfuel : fuel (P, d) < 2 ^ (d.numEdges + d.vertices.length + 2) := by + have hk : d.vertices.length + 1 ≤ 2 ^ d.vertices.length := Nat.lt_two_pow_self + have h1 : 2 ^ d.numEdges * (d.vertices.length + 1) ≤ 2 ^ (d.numEdges + d.vertices.length) := + by rw [pow_add]; exact Nat.mul_le_mul_left _ hk + have h2 : 2 ^ (d.numEdges + d.vertices.length + 2) = 4 * 2 ^ (d.numEdges + d.vertices.length) := + by rw [pow_add]; ring + have h3 := Nat.two_pow_pos (d.numEdges + d.vertices.length) + simp only [fuel] + omega + have hfl := length_encNat_lt hfuel + obtain ⟨B, hB⟩ : ∃ B, B = 2 ^ d.numEdges * (d.vertices.length + 1) + 1 := ⟨_, rfl⟩ + have hfuel1 : fuel (P, d) + 1 = 2 * B := by rw [hB]; simp only [fuel]; ring + have hB1 : 1 ≤ B := by omega + rw [← hB] + -- The polynomial parts, each at most a constant times `W`. + have hW1 : z + 1 ≤ W := by + rw [hW] + calc z + 1 = (z + 1) ^ 1 := (pow_one _).symm + _ ≤ (z + 1) ^ D := Nat.pow_le_pow_right (Nat.succ_pos _) (by omega) + have hpoly {c e : ℕ} (he : e ≤ D) : c * (n + 1) ^ e ≤ c * W := by + rw [hW] + exact Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans + (Nat.pow_le_pow_right (Nat.succ_pos _) he)) + have hterm {c a b e : ℕ} (he : e ≤ D) : c * (a * z + b) ^ e ≤ c * (a + b) ^ e * W := by + rw [hW]; exact term_le he + have hT1 : K₀ * ((h + 1) * (13 * z + 18 + 1)) ^ K₀ ≤ K₀ * 96 ^ K₀ * W := by + have h3 : (h + 1) * (13 * z + 18 + 1) ≤ 39 * z + 57 := by + have := Nat.mul_le_mul_right (13 * z + 18 + 1) (show h + 1 ≤ 3 by omega); omega + exact (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h3 _)).trans (hterm (by omega)) + have hTL : 2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq ≤ + (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W := by + have a1 := hterm (c := 2 * cv) (a := 5) (b := 8) (show dv ≤ D by omega) + have a2 := hterm (c := cq) (a := 11) (b := 16) (show dq ≤ D by omega) + have e : (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W = + 2 * cv * (5 + 8) ^ dv * W + 24 * W + cq * (11 + 16) ^ dq * W := by ring + omega + have hSL : 2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' ≤ + (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W := by + have a1 := hterm (c := 2 * cv') (a := 5) (b := 8) (show dv' ≤ D by omega) + have a2 := hterm (c := cq') (a := 11) (b := 16) (show dq' ≤ D by omega) + have e : (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W = + 2 * cv' * (5 + 8) ^ dv' * W + 24 * W + cq' * (11 + 16) ^ dq' * W := by ring + omega + have hclock : ((encNat (fuel (P, d))).length + 1) ^ 2 ≤ 16 * W := by + have h3 : (encNat (fuel (P, d))).length + 1 ≤ 4 * (z + 1) := by omega + calc ((encNat (fuel (P, d))).length + 1) ^ 2 ≤ (4 * (z + 1)) ^ 2 := Nat.pow_le_pow_left h3 2 + _ = 16 * (z + 1) ^ 2 := by ring + _ ≤ 16 * W := by + rw [hW]; exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (Nat.succ_pos _) (by omega)) + have hCD : C * W ≤ (C + D) * (z + 1) ^ (C + D) := by + rw [hW]; exact Nat.mul_le_mul (by omega) (Nat.pow_le_pow_right (Nat.succ_pos _) (by omega)) + obtain ⟨htime, hspace⟩ := hres + have hft' := hft (P, d) + have hrt' := hrt (P, d) + have hfs' := hfs (P, d) + have hrs' := hrs (P, d) + rw [← hn] at hft' hrt' hfs' hrs' + have hcf := hft'.trans (hpoly (c := cf) (show df ≤ D by omega)) + have hcr := hrt'.trans (hpoly (c := cr) (show dr ≤ D by omega)) + have hcf' := hfs'.trans (hpoly (c := cf') (show df' ≤ D by omega)) + have hcr' := hrs'.trans (hpoly (c := cr') (show dr' ≤ D by omega)) + constructor + · rw [hfuel1] at htime + obtain ⟨S, hS⟩ : ∃ S, S = K₀ * ((h + 1) * (13 * z + 18 + 1)) ^ K₀ + + (2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E) + + ((encNat (fuel (P, d))).length + 1) ^ 2 := ⟨_, rfl⟩ + rw [← hS] at htime + have hstep : S ≤ G * W + E := by + have e : G * W = K₀ * 96 ^ K₀ * W + (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W + 16 * W := by + rw [hG]; ring + omega + have hm := Nat.mul_le_mul_left (2 * B) hstep + calc (contractionCertificate eval ev).time (P, d) + ≤ cf * W + cr * W + 2 * W + 2 * B * (G * W + E) := by + rw [contractionCertificate_time]; unfold sumCertificate; omega + _ ≤ B * ((cf + cr + 2) * W) + B * (2 * G * W) + 2 * B * E := by + have e2 : 2 * B * (G * W + E) = B * (2 * G * W) + 2 * B * E := by ring + have e3 : cf * W + cr * W + 2 * W = (cf + cr + 2) * W := by ring + have e4 : (cf + cr + 2) * W ≤ B * ((cf + cr + 2) * W) := Nat.le_mul_of_pos_left _ hB1 + omega + _ = B * ((cf + cr + 2 + 2 * G) * W) + 2 * B * E := by ring + _ ≤ B * (C * W) + 2 * B * E := + Nat.add_le_add_right (Nat.mul_le_mul_left B (Nat.mul_le_mul_right W (by omega))) _ + _ = B * (C * W + 2 * E) := by ring + _ ≤ B * ((C + D) * (z + 1) ^ (C + D) + 2 * E) := + Nat.mul_le_mul_left _ (Nat.add_le_add_right hCD _) + · have h1 : (contractionCertificate eval ev).space (P, d) ≤ C * W + ES := by + rw [contractionCertificate_space] + unfold sumCertificate + have e : C * W = cf * W + cr * W + cf' * W + cr' * W + 2 * G * W + G' * W + 4 * W := by + rw [hC]; ring + have e' : G' * W = K₀ * 96 ^ K₀ * W + (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W := by + rw [hG']; ring + have hfl' : (encNat (fuel (P, d))).length ≤ W := by omega + omega + exact h1.trans (Nat.add_le_add_right hCD _) + +end Native + +end DescribedNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/DescribedContraction.lean b/ComplexityTests/DescribedContraction.lean new file mode 100644 index 0000000000..7581ad62a3 --- /dev/null +++ b/ComplexityTests/DescribedContraction.lean @@ -0,0 +1,79 @@ +/- +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.Circuit.Frontier.TensorNetwork.DescribedContraction +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListBinaryLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives + +/-! # Direct summation over described networks + +Tables as descriptors, with table lookup as the evaluator: the stack machine on a one-edge +network, the agreement with explicit networks, and one native machine for the contraction with +its resource theorem. -/ + +namespace ComplexityTests.DescribedContraction + +open Cslib.Circuits.Frontier Cslib.Circuits.Frontier.DescribedNetwork Cslib.BitString + Turing.MultiTapeTM + +/-- One edge between two vertices with tables `[2, 3]` and `[5, 7]`: contraction +`2 * 5 + 3 * 7 = 31`. -/ +def oneEdge : DescribedNetwork (List ℕ) := ⟨1, [([0], [2, 3]), ([0], [5, 7])]⟩ + +example : oneEdge.contraction ExplicitTensorNetwork.entry = 31 := by decide + +-- The stack machine, with fuel `2 (2 ^ 1 * 3) + 1 = 13`, returns `31 % 16 = 15`. +example : (sumScheme ExplicitTensorNetwork.entry).run fuel sumRoot (4, oneEdge) = some 15 := by + decide + +-- With one step fewer, the sum still waits for the second assignment's weight +-- `3 * 7 = 21 ≡ 5`. +example : (sumScheme ExplicitTensorNetwork.entry).run (fun _ => 12) sumRoot (4, oneEdge) = + some 5 := by + decide + +-- On every input, the run is the contraction modulo `2 ^ P`, for any evaluator. +example {T : Type} (eval : T → List Bool → ℕ) (q : ℕ × DescribedNetwork T) : + (sumScheme eval).run fuel sumRoot q = some (q.2.contraction eval % 2 ^ q.1) := + run_sumScheme q + +-- Explicit networks are described networks with their tables. +example (d : ExplicitTensorNetwork ℕ) : + (ofExplicit d).contraction ExplicitTensorNetwork.entry = d.contraction := + contraction_ofExplicit d + +/-- Table lookup is polynomial-time. -/ +theorem entry_polyTime : ComputableInPolyTime (fun q : List ℕ × List Bool => + ExplicitTensorNetwork.entry q.1 q.2) (encPair (encList encNat) encBits) encNat := by + have hindex := ComputableInPolyTime.bitsBinaryValue.comp + (ComputableInPolyTime.bitsReverse.comp (ComputableInPolyTime.snd (encList encNat) encBits)) + refine (ComputableInPolyTime.listGetD_binary (encA := encNat) (encL := encList encNat) 0 + (ComputableInPolyTime.fst (encList encNat) encBits) hindex).of_enc_eq fun q => ?_ + simp only [Function.comp_def, ExplicitTensorNetwork.entry, lsbValue_eq_binaryValue_reverse] + +/-- **One native machine contracts explicit networks** given as described networks with their +tables, from the table-lookup certificate. -/ +noncomputable def tableCertificate : + Cslib.GenericRAM.NativeCertificate + (fun q : ℕ × DescribedNetwork (List ℕ) => + q.2.contraction ExplicitTensorNetwork.entry % 2 ^ q.1) + (encInput (encList encNat)) encNat := + contractionCertificate ExplicitTensorNetwork.entry + (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime) + +-- The resource theorem applies to it. +example : ∃ K : ℕ, ∀ (q : ℕ × DescribedNetwork (List ℕ)) (E ES Ev : ℕ), + (∀ v ∈ q.2.vertices, ∀ bs : List Bool, bs.length = v.1.length → + (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime).time (v.2, bs) ≤ E ∧ + (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime).space (v.2, bs) ≤ ES ∧ + (encNat (ExplicitTensorNetwork.entry v.2 bs)).length ≤ Ev) → + tableCertificate.time q ≤ (2 ^ q.2.numEdges * (q.2.vertices.length + 1) + 1) * + (K * ((encInput (encList encNat) q).length + Ev + 1) ^ K + 2 * E) ∧ + tableCertificate.space q ≤ K * ((encInput (encList encNat) q).length + Ev + 1) ^ K + ES := + contractionCertificate_le _ + +end ComplexityTests.DescribedContraction From 3685abaef55599db833f8bf8e8388dd96b6e5101 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:49:30 -0400 Subject: [PATCH 15/33] feat(circuit/frontier): explicit packet entries of the child networks (Theorem D.1, T2.9 item 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The child networks of an active call keep their packet tensors implicit (§D.4.6 of OpenAI2026ETH). This adds the explicit evaluation formula on the packet data and proves that it is the entry of the child network of `cauchyModeSplitting`: * `listCoeff`: coefficient extraction along a list of factors, equal to the sum over all choices (`sum_eq_listCoeff`) and to `blockCoeff` for any mode splitting and any order of the rows (`blockCoeff_eq_listCoeff`); * `packetEntry` and its terms, with `packetTensor_cauchyModeSplitting` and `child_tensor_inr_cauchyModeSplitting`; * `map_packetEntry`: the formula commutes with ring homomorphisms when 2 is nilpotent, so an evaluation in A_p transfers to the recursion's quotients; * the field lemmas of `cauchyModeSplitting` beyond side/group/packet. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/PacketEntry.lean | 678 ++++++++++++++++++ .../Frontier/TensorNetworkPacketEntry.lean | 75 ++ 3 files changed, 754 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEntry.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean diff --git a/Complexity.lean b/Complexity.lean index 47ca344c79..61358cafb6 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -877,6 +877,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectCont public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEntry.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEntry.lean new file mode 100644 index 0000000000..bd8a7ffe7e --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEntry.lean @@ -0,0 +1,678 @@ +/- +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.CauchyModes +import all Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes +import Mathlib.Algebra.BigOperators.Fin +import Mathlib.Data.Fintype.Fin + +/-! +# Entries of the child networks + +The child networks of an active call of the contraction algorithm of [OpenAI2026ETH] are kept +implicit: a packet vertex has up to `2 L` ports, so its table would have `2 ^ (2 L)` entries. The +source evaluates an entry by expanding the product of the at most `L` affine row factors of the +packet, enumerating the choices of a local term or a mode in each factor, keeping those with the +required exponents, and multiplying by the powers of the substituted modes (§D.4.6, p. 63). This +file gives that evaluation as an explicit formula on the data of the packet and proves that it is +the entry of the child network of `TensorNetwork.cauchyModeSplitting`. + +**Coefficient extraction along a list** (`TensorNetwork.listCoeff`). For a list of factors, each +a function `T : Option M → R` (the local term at `none` and the coefficient of the mode `Z` at +`some Z`), and exponents `γ` on the modes of a finite set `F`, the coefficient of `∏ Z ^ γ Z` in +`∏ (T none + ∑ Z ∈ F, T (some Z) Z)` is computed recursively: the first factor contributes its +local term, or one of the modes still needed. This is the coefficient of a block +(`TensorNetwork.ModeSplitting.blockCoeff_eq_listCoeff`) for any mode splitting, with the rows of +the block in any order. + +**Packet entries** (`TensorNetwork.packetEntry`). For the Cauchy modes of an active node, the +entry of the packet vertex of the child of group `g` at the packet `q` depends only on: +* the parameters `κ`, `ζ`, `a`, `i` and `t` of the node; +* whether the two symbols `sym q` of the packet are `g` and whether they differ; +* the exponents of the modes `(q, y₀, b)` of the packet; +* the slots and bits of its row ports and of its column ports, as two lists. + +The local form of a row is a sum of kernel values over the column ports set to one +(`TensorNetwork.packetLocalForm`), a mode coefficient is the geometric expansion term (D.12) +(`TensorNetwork.packetModeCoeff`), and a mode value is a sum of powers `j ^ b` over the column ports +of its tile set to one (`TensorNetwork.packetModeValue`). The main result +(`TensorNetwork.packetTensor_cauchyModeSplitting`, and for the child itself +`TensorNetwork.child_tensor_inr_cauchyModeSplitting`) is that this formula is the packet tensor. +A retained vertex keeps its tensor (`TensorNetwork.ModeSplitting.child_tensor_inl`). + +The formula commutes with ring homomorphisms (`TensorNetwork.map_packetEntry`) when `2` is +nilpotent, so an evaluation in one coefficient ring, such as the coefficient lists of +`(ℤ / 2 ^ p)[U] / (f)`, gives the entry in every ring it maps to, such as the quotients of the +recursion. + +This file also supplies the field lemmas of `TensorNetwork.cauchyModeSplitting` beyond its sides, +groups and packets (`TensorNetwork.cauchyModeSplitting_localPart`, +`TensorNetwork.cauchyModeSplitting_coeff`, `TensorNetwork.cauchyModeSplitting_form` and the mode +packets, sources and targets), through `import all`, since the definition is not exposed. + +## Main definitions + +* `Cslib.Circuits.Frontier.TensorNetwork.listCoeff`: coefficient extraction along a list. +* `Cslib.Circuits.Frontier.TensorNetwork.packetLocalForm`, + `Cslib.Circuits.Frontier.TensorNetwork.packetModeCoeff`, + `Cslib.Circuits.Frontier.TensorNetwork.packetModeValue`, + `Cslib.Circuits.Frontier.TensorNetwork.packetRowTerm`: the terms of a packet entry. +* `Cslib.Circuits.Frontier.TensorNetwork.packetEntry`: the entry of a packet vertex. + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.sum_eq_listCoeff`: the recursion sums over all choices. +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.blockCoeff_eq_listCoeff`: block + coefficients by the recursion. +* `Cslib.Circuits.Frontier.TensorNetwork.packetTensor_cauchyModeSplitting`, + `Cslib.Circuits.Frontier.TensorNetwork.child_tensor_inr_cauchyModeSplitting`: the packet entries + of the children. +* `Cslib.Circuits.Frontier.TensorNetwork.map_packetEntry`: packet entries commute with ring + homomorphisms. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.5–§D.4.6, + (D.12)–(D.16), pp. 62–63. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +namespace TensorNetwork + +/-! ### The remaining fields of the Cauchy mode splitting -/ + +section Fields + +variable {R V E G P : Type*} [CommRing R] [DecidableEq G] [DecidableEq P] + {N : TensorNetwork R Bool V E} {group : V → G} {packet : E → P} {slot : E → ℕ} + {sym : P → G × G} {i : ℕ} + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) + +/-- The local part of the Cauchy mode splitting: the Cauchy matrix between ports of the same +group. -/ +@[simp] theorem cauchyModeSplitting_localPart [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').localPart = + fun e e' => if group (N.tgt e') = group (N.src e) then + cauchyValueMatrix κ ζ a t packet slot e e' else 0 := by + unfold cauchyModeSplitting; rfl + +/-- The packet of the mode `(q, y₀, b)` is `q`. -/ +@[simp] theorem cauchyModeSplitting_modePacket [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').modePacket = + Prod.fst := by + unfold cauchyModeSplitting; rfl + +/-- The source group of the mode `(q, y₀, b)`: the group of the columns of its tile. -/ +@[simp] theorem cauchyModeSplitting_source [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').source = + fun Z => if (Z.2.1 : ℕ).testBit i then (sym Z.1).1 else (sym Z.1).2 := by + unfold cauchyModeSplitting; rfl + +/-- The target group of the mode `(q, y₀, b)`: the other group of its packet. -/ +@[simp] theorem cauchyModeSplitting_target [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').target = + fun Z => if (Z.2.1 : ℕ).testBit i then (sym Z.1).2 else (sym Z.1).1 := by + unfold cauchyModeSplitting; rfl + +/-- The mode coefficients are `cauchyModeCoeff`. -/ +@[simp] theorem cauchyModeSplitting_coeff [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').coeff = + cauchyModeCoeff κ ζ a i t packet slot sym := by + unfold cauchyModeSplitting; rfl + +/-- The mode forms are `cauchyModeForm`. -/ +@[simp] theorem cauchyModeSplitting_form [Fintype P] : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').form = + cauchyModeForm R i packet slot sym := by + unfold cauchyModeSplitting; rfl + +end Fields + +/-! ### Coefficient extraction along a list -/ + +section ListCoeff + +variable {R M : Type*} [CommRing R] [DecidableEq M] + +/-- **Coefficient extraction along a list of factors.** Each factor is a function +`T : Option M → R`, standing for `T none + ∑ Z ∈ F, T (some Z) Z`. The value at the exponents `γ` +is the coefficient of `∏ Z ∈ F, Z ^ γ Z` in the product of the factors: the first factor +contributes its local term `T none`, or the coefficient of a mode `Z` with `γ Z ≠ 0`, lowering +`γ Z` by one, and the empty product has the coefficient `1` at zero exponents. -/ +@[expose] def listCoeff (F : Finset M) : List (Option M → R) → (M → ℕ) → R + | [], γ => if ∀ Z ∈ F, γ Z = 0 then 1 else 0 + | T :: ts, γ => T none * listCoeff F ts γ + + ∑ Z ∈ F, if γ Z = 0 then 0 else + T (some Z) * listCoeff F ts (Function.update γ Z (γ Z - 1)) + +@[simp] theorem listCoeff_nil (F : Finset M) (γ : M → ℕ) : + listCoeff (R := R) F [] γ = if ∀ Z ∈ F, γ Z = 0 then 1 else 0 := + rfl + +theorem listCoeff_cons (F : Finset M) (T : Option M → R) (ts : List (Option M → R)) + (γ : M → ℕ) : + listCoeff F (T :: ts) γ = T none * listCoeff F ts γ + + ∑ Z ∈ F, if γ Z = 0 then 0 else + T (some Z) * listCoeff F ts (Function.update γ Z (γ Z - 1)) := + rfl + +/-- The count condition after choosing the mode `Z ∈ F` in the first factor. -/ +private theorem count_cons_iff {F : Finset M} {Z : M} (hZ : Z ∈ F) (c γ : M → ℕ) : + (∀ Z' ∈ F, (if Z = Z' then 1 else 0) + c Z' = γ Z') ↔ + γ Z ≠ 0 ∧ ∀ Z' ∈ F, c Z' = Function.update γ Z (γ Z - 1) Z' := by + constructor + · intro h + have hz := h Z hZ + simp only [↓reduceIte] at hz + refine ⟨by omega, fun Z' hZ' => ?_⟩ + by_cases hZZ : Z' = Z + · subst hZZ + rw [Function.update_self] + omega + · have := h Z' hZ' + simp only [Ne.symm hZZ, ↓reduceIte, zero_add] at this + rw [Function.update_of_ne hZZ, this] + · rintro ⟨h0, h⟩ Z' hZ' + by_cases hZZ : Z' = Z + · subst hZZ + simp only [↓reduceIte, h Z' hZ', Function.update_self] + omega + · simp only [Ne.symm hZZ, ↓reduceIte, zero_add, h Z' hZ', Function.update_of_ne hZZ] + +/-- Picks of `Fin.cons o φ`: those of `o`, and those of `φ`. -/ +private theorem card_cons_eq {n : ℕ} (o : Option M) (φ : Fin n → Option M) (Z : M) : + #{j | (Fin.cons o φ : Fin (n + 1) → Option M) j = some Z} = + (if o = some Z then 1 else 0) + #{j | φ j = some Z} := by + rw [Fin.card_filter_univ_succ'] + simp only [Fin.cons_zero, Fin.cons_succ] + +omit [DecidableEq M] in +/-- A sum over the functions on `Fin (n + 1)` splits off the value at `0`. -/ +private theorem sum_pi_fin_succ [Fintype M] {n : ℕ} (f : (Fin (n + 1) → Option M) → R) : + ∑ φ, f φ = ∑ o : Option M, ∑ φ : Fin n → Option M, f (Fin.cons o φ) := by + rw [← Fintype.sum_prod_type'] + exact (Fintype.sum_equiv (Fin.consEquiv fun _ => Option M) _ _ fun x => rfl).symm + +/-- **The recursion sums over all choices.** For factors `T j` indexed by `Fin n`, the sum over +the ways `φ` to pick in each factor its local term or a mode of `F`, each mode `Z` picked `γ Z` +times, of the product of the picked terms is `listCoeff`. -/ +theorem sum_eq_listCoeff [Fintype M] (F : Finset M) : + ∀ (n : ℕ) (T : Fin n → Option M → R) (γ : M → ℕ), + (∑ φ : Fin n → Option M, + if (∀ j Z, φ j = some Z → Z ∈ F) ∧ ∀ Z ∈ F, #{j | φ j = some Z} = γ Z then + ∏ j, T j (φ j) else 0) = listCoeff F (List.ofFn T) γ + | 0, T, γ => by + rw [Fintype.sum_unique, List.ofFn_zero, listCoeff_nil] + simp only [IsEmpty.forall_iff, true_and, Finset.univ_eq_empty, filter_empty, card_empty, + Finset.prod_empty] + by_cases h : ∀ Z ∈ F, γ Z = 0 + · rw [ite_eq_left h, ite_eq_left fun Z hZ => (h Z hZ).symm] + · rw [ite_eq_right h, ite_eq_right fun h' => h fun Z hZ => (h' Z hZ).symm] + | n + 1, T, γ => by + rw [sum_pi_fin_succ, Fintype.sum_option, List.ofFn_succ, listCoeff_cons] + congr 1 + · -- The local term of the first factor. + rw [← sum_eq_listCoeff F n (fun j => T j.succ) γ, mul_sum] + refine sum_congr rfl fun φ _ => ?_ + simp only [Fin.prod_univ_succ, Fin.cons_zero, Fin.cons_succ, + Fin.forall_fin_succ, card_cons_eq, reduceCtorEq, false_implies, implies_true, true_and, + ↓reduceIte, zero_add, mul_ite, mul_zero] + · -- A mode in the first factor. + rw [← sum_subset (subset_univ F) fun Z _ hZ => ?_] + · refine sum_congr rfl fun Z hZ => ?_ + rw [← sum_eq_listCoeff F n (fun j => T j.succ), mul_sum] + simp only [Fin.prod_univ_succ, Fin.cons_zero, Fin.cons_succ, + Fin.forall_fin_succ, card_cons_eq, Option.some.injEq, forall_eq', hZ, true_and, + count_cons_iff hZ] + by_cases h0 : γ Z = 0 + · simp only [h0, ne_eq, not_true_eq_false, false_and, and_false, ↓reduceIte, + sum_const_zero] + · simp only [h0, ne_eq, not_false_eq_true, true_and, ↓reduceIte, mul_ite, mul_zero] + · refine sum_eq_zero fun φ _ => ite_eq_right ?_ + simp only [Fin.forall_fin_succ, Fin.cons_zero, Option.some.injEq, forall_eq', not_and] + exact fun h => absurd h.1 hZ + +/-- `listCoeff` along the image of an injective map of modes. -/ +theorem listCoeff_map {M' : Type*} [DecidableEq M'] (f : M' ↪ M) (F : Finset M') : + ∀ (ts : List (Option M → R)) (γ : M → ℕ), + listCoeff (F.map f) ts γ = listCoeff F (ts.map fun T => T ∘ Option.map f) (γ ∘ f) + | [], γ => by simp + | T :: ts, γ => by + rw [List.map_cons, listCoeff_cons, listCoeff_cons, sum_map, listCoeff_map f F ts γ] + refine congrArg _ (sum_congr rfl fun Z _ => ?_) + rw [listCoeff_map f F ts, Function.update_comp_eq_of_injective _ f.injective] + rfl + +end ListCoeff + +/-! ### Block coefficients by the recursion -/ + +namespace ModeSplitting + +variable {R V E G P M : Type*} [CommRing R] [Fintype M] {N : TensorNetwork R Bool V E} + {W : Matrix E E R} (S : ModeSplitting N W G P M) [Fintype E] [DecidableEq E] [DecidableEq P] + [DecidableEq G] [DecidableEq M] + +/-- **Block coefficients by the recursion.** For a list `l` of the row ports of the block `k`, +without repetition and in any order, the coefficient of a mode monomial in the product of the +affine factors of the block is `listCoeff` along the terms of the rows of `l`. -/ +theorem blockCoeff_eq_listCoeff (k : P × G) (β : M → ℕ) (Y X : E → Bool) {l : List E} + (hl : l.Nodup) (hmem : ∀ e, e ∈ l ↔ S.rowBlock e = k) : + S.blockCoeff k β Y X = + listCoeff {Z | S.targetBlock Z = k} (l.map fun e => S.choiceTerm Y X e) β := by + let σ : Fin l.length ≃ {e // S.rowBlock e = k} := + Equiv.ofBijective (fun j => ⟨l.get j, (hmem _).1 (List.get_mem _ _)⟩) + ⟨fun j j' h => (hl.get_inj_iff).1 (congrArg Subtype.val h), fun e => by + obtain ⟨j, hj⟩ := List.mem_iff_get.1 ((hmem e.1).2 e.2) + exact ⟨j, Subtype.ext hj⟩⟩ + have hl' : (l.map fun e => S.choiceTerm Y X e) = + List.ofFn fun j => S.choiceTerm Y X (σ j).1 := by + conv_lhs => rw [← List.ofFn_get l, List.map_ofFn] + rfl + have hcard (φ : {e // S.rowBlock e = k} → Option M) (Z : M) : + #{j | φ (σ j) = some Z} = #{e | φ e = some Z} := + Finset.card_equiv σ fun j => by simp only [mem_filter, mem_univ, true_and] + rw [hl', ← sum_eq_listCoeff] + refine Fintype.sum_equiv (σ.arrowCongr (Equiv.refl _)).symm _ _ fun φ => ?_ + simp only [Equiv.arrowCongr_symm, Equiv.refl_symm, Equiv.arrowCongr_apply, Equiv.coe_refl, + Function.comp_apply, id_eq, Equiv.symm_symm, mem_filter, mem_univ, true_and] + refine if_congr (and_congr ⟨fun h j Z => h (σ j) Z, fun h e Z => ?_⟩ + (forall_congr' fun Z => imp_congr_right fun _ => ?_)) ?_ rfl + · simpa using h (σ.symm e) Z + · rw [hcard] + · exact (Fintype.prod_equiv σ _ _ fun j => rfl).symm + +/-- A retained vertex of a child keeps its tensor, reading the row bits on its edges when it is a +row and the column bits when it is a column. -/ +theorem child_tensor_inl [DecidableEq V] (β : M → ℕ) (g : G) (v : {v // S.group v = g}) + (y : {e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g} → Bool) : + (S.child β g).tensor (.inl v) y = + if S.side v.1 then + N.tensor v.1 fun e => if h : N.src e = v.1 then y (.inl ⟨e, h ▸ v.2⟩) else false + else + N.tensor v.1 fun e => if h : N.tgt e = v.1 then y (.inr ⟨e, h ▸ v.2⟩) else false := + rfl + +/-- A packet vertex of a child has the packet tensor at the bits of its ports. -/ +theorem child_tensor_inr [DecidableEq V] (β : M → ℕ) (g : G) (q : P) + (y : {e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g} → Bool) : + (S.child β g).tensor (.inr q) y = + S.packetTensor β q g + (fun e => if h : S.group (N.src e) = g then y (.inl ⟨e, h⟩) else false) + (fun e => if h : S.group (N.tgt e) = g then y (.inr ⟨e, h⟩) else false) := + rfl + +end ModeSplitting + +/-! ### Packet entries -/ + +section Packet + +variable {R : Type*} [CommRing R] + +/-- The local form of a row port in slot `x` of a packet: the sum of the kernel values +`W_{y - x}(T)` over its column ports `(y, X_y)` set to one. -/ +@[expose] noncomputable def packetLocalForm (κ : ℤ₍₂₎ →+* R) (ζ : R) (a : ℕ) (t : ℤ) + (cols : List (ℕ × Bool)) (x : ℕ) : R := + (cols.map fun c => if c.2 then kernelValue κ ζ a t ((c.1 : ℤ) - x) else 0).sum + +/-- The coefficient of the mode `(y₀, b)` of a packet in the form of a row port in slot `x`: when +the two symbols of the packet differ and digit `i` of `x` is that of `y₀`, the term +`W_{y₀ - x}(T) ρ ^ b` of the geometric expansion (D.12); otherwise zero. -/ +@[expose] noncomputable def packetModeCoeff (κ : ℤ₍₂₎ →+* R) (ζ : R) (a i : ℕ) (t : ℤ) + (differ : Bool) {p : ℕ} (x : ℕ) (Z : Fin (2 ^ (i + 1)) × Fin p) : R := + if differ = true ∧ x.testBit i = (Z.1 : ℕ).testBit i then + kernelValue κ ζ a t ((Z.1 : ℤ) - x) * crossRatio a i t ((Z.1 : ℤ) - x) ζ ^ (Z.2 : ℕ) + else 0 + +variable (R) in +/-- The value `L_Z(X)` of the mode `Z = (y₀, b)` of a packet on its column ports: when the two +symbols of the packet differ, the sum of `j ^ b` over the column ports in slots +`y = y₀ + 2 ^ (i + 1) j` set to one. -/ +@[expose] def packetModeValue (i : ℕ) (differ : Bool) {p : ℕ} + (cols : List (ℕ × Bool)) (Z : Fin (2 ^ (i + 1)) × Fin p) : R := + (cols.map fun c => if c.2 = true ∧ differ = true ∧ c.1 % 2 ^ (i + 1) = (Z.1 : ℕ) then + ((c.1 / 2 ^ (i + 1) : ℕ) : R) ^ (Z.2 : ℕ) else 0).sum + +/-- The terms of the affine factor of a row port `(x, Y_x)` of a packet: its local term at `none`, +and the coefficient of the mode `Z` at `some Z`. -/ +@[expose] noncomputable def packetRowTerm (κ : ℤ₍₂₎ →+* R) (ζ : R) (a i : ℕ) (t : ℤ) + (differ : Bool) {p : ℕ} (cols : List (ℕ × Bool)) (r : ℕ × Bool) : + Option (Fin (2 ^ (i + 1)) × Fin p) → R + | none => affineFactor r.2 (packetLocalForm κ ζ a t cols r.1) + | some Z => affineSign r.2 * packetModeCoeff κ ζ a i t differ r.1 Z + +/-- Whether the modes of the tile `y₀` of a packet target the group `g`, given whether the two +symbols of the packet are `g`: the target is the second symbol when digit `i` of `y₀` is `1`. -/ +@[expose] def modeTargets (i : ℕ) (e₁ e₂ : Bool) (y₀ : ℕ) : Bool := + if y₀.testBit i then e₂ else e₁ + +/-- Whether the modes of the tile `y₀` of a packet have the source `g`: the source is the first +symbol when digit `i` of `y₀` is `1`. -/ +@[expose] def modeSources (i : ℕ) (e₁ e₂ : Bool) (y₀ : ℕ) : Bool := + if y₀.testBit i then e₁ else e₂ + +/-- **The entry of a packet vertex** of a child (§D.4.6, p. 63). The packet has row ports +`rows` and column ports `cols`, each a slot and a bit; `e₁` and `e₂` say whether the two symbols +of the packet are the group of the child, and `differ` whether they differ; `β` gives the exponents +of the modes `(y₀, b)` of the packet. The entry is the coefficient, at the exponents of the modes +targeting the group, of the product of the affine row factors, times the powers of the values of +the modes with the group as source. -/ +@[expose] noncomputable def packetEntry (κ : ℤ₍₂₎ →+* R) (ζ : R) (a i : ℕ) (t : ℤ) {p : ℕ} + (e₁ e₂ differ : Bool) (β : Fin (2 ^ (i + 1)) × Fin p → ℕ) (rows cols : List (ℕ × Bool)) : R := + listCoeff {Z | modeTargets i e₁ e₂ Z.1 = true} + (rows.map (packetRowTerm κ ζ a i t differ cols)) β * + ∏ Z with modeSources i e₁ e₂ Z.1 = true, packetModeValue R i differ cols Z ^ β Z + +end Packet + +/-! ### Packet entries under ring homomorphisms -/ + +section Map + +variable {R S : Type*} [CommRing R] [CommRing S] + +/-- Kernel values commute with ring homomorphisms. -/ +theorem map_kernelValue (ψ : R →+* S) (κ : ℤ₍₂₎ →+* R) (ζ : R) (a : ℕ) (t d : ℤ) : + ψ (kernelValue κ ζ a t d) = kernelValue (ψ.comp κ) (ψ ζ) a t d := + Polynomial.hom_eval₂ _ _ _ _ + +/-- A ring homomorphism maps the inverse of a unit to the inverse of its image. -/ +private theorem map_ringInverse (ψ : R →+* S) {u : R} (hu : IsUnit u) : + ψ (Ring.inverse u) = Ring.inverse (ψ u) := by + obtain ⟨w, rfl⟩ := hu + have h : ψ (w : R) = ((Units.map (ψ : R →* S) w : Sˣ) : S) := rfl + rw [Ring.inverse_unit, h, Ring.inverse_unit, Units.coe_map_inv] + rfl + +/-- **Cross ratios commute with ring homomorphisms** when `2` is nilpotent and +`2 ^ (i + 1) ∤ t - d₀` with `i ≤ a`, so that the inverted element is a unit. -/ +theorem map_crossRatio (ψ : R →+* S) {n : ℕ} (hn : (2 : R) ^ n = 0) {a i : ℕ} (hia : i ≤ a) + {t d₀ : ℤ} (hcross : ¬ (2 : ℤ) ^ (i + 1) ∣ t - d₀) (ζ : R) : + ψ (crossRatio a i t d₀ ζ) = crossRatio a i t d₀ (ψ ζ) := by + obtain ⟨hvi, ⟨c, hc⟩, -⟩ := two_adic_split hcross + have hU : IsUnit ((((t - d₀) / 2 ^ padicValInt 2 (t - d₀) : ℤ) : R) + + 2 ^ (a + 1 - padicValInt 2 (t - d₀)) * ζ) := by + rw [hc, show a + 1 - padicValInt 2 (t - d₀) = a - padicValInt 2 (t - d₀) + 1 by omega, + pow_succ] + convert isUnit_one_add_two_mul hn ((c : R) + 2 ^ (a - padicValInt 2 (t - d₀)) * ζ) using 1 + push_cast + ring + rw [crossRatio, crossRatio, map_mul, map_pow, map_ofNat, map_ringInverse ψ hU, map_add, + map_mul, map_pow, map_ofNat, map_intCast] + +/-- Mode coefficients commute with ring homomorphisms when `2` is nilpotent, at a grid point `t` +of valuation `i ≤ a`. -/ +theorem map_packetModeCoeff (ψ : R →+* S) {n : ℕ} (hn : (2 : R) ^ n = 0) (κ : ℤ₍₂₎ →+* R) + (ζ : R) {a i : ℕ} (hia : i ≤ a) {t : ℤ} (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (differ : Bool) {p : ℕ} (x : ℕ) + (Z : Fin (2 ^ (i + 1)) × Fin p) : + ψ (packetModeCoeff κ ζ a i t differ x Z) = + packetModeCoeff (ψ.comp κ) (ψ ζ) a i t differ x Z := by + unfold packetModeCoeff + split_ifs with h + · rw [map_mul, map_pow, map_kernelValue, + map_crossRatio ψ hn hia (not_dvd_sub_sub_of_testBit_eq hti hti' Z.1.isLt h.2)] + · exact map_zero ψ + +/-- Local forms commute with ring homomorphisms. -/ +theorem map_packetLocalForm (ψ : R →+* S) (κ : ℤ₍₂₎ →+* R) (ζ : R) (a : ℕ) (t : ℤ) + (cols : List (ℕ × Bool)) (x : ℕ) : + ψ (packetLocalForm κ ζ a t cols x) = packetLocalForm (ψ.comp κ) (ψ ζ) a t cols x := by + rw [packetLocalForm, packetLocalForm, map_list_sum, List.map_map] + congr 1 + refine List.map_congr_left fun c _ => ?_ + simp only [Function.comp_apply, apply_ite ψ, map_zero, map_kernelValue] + +/-- Mode values commute with ring homomorphisms. -/ +theorem map_packetModeValue (ψ : R →+* S) (i : ℕ) (differ : Bool) {p : ℕ} + (cols : List (ℕ × Bool)) (Z : Fin (2 ^ (i + 1)) × Fin p) : + ψ (packetModeValue R i differ cols Z) = packetModeValue S i differ cols Z := by + rw [packetModeValue, packetModeValue, map_list_sum, List.map_map] + congr 1 + refine List.map_congr_left fun c _ => ?_ + simp only [Function.comp_apply, apply_ite ψ, map_zero, map_pow, map_natCast] + +/-- `listCoeff` commutes with ring homomorphisms. -/ +theorem map_listCoeff {M : Type*} [DecidableEq M] (ψ : R →+* S) (F : Finset M) : + ∀ (ts : List (Option M → R)) (γ : M → ℕ), + ψ (listCoeff F ts γ) = listCoeff F (ts.map fun T => ψ ∘ T) γ + | [], γ => by simp only [listCoeff_nil, List.map_nil, apply_ite ψ, map_one, map_zero] + | T :: ts, γ => by + rw [List.map_cons, listCoeff_cons, listCoeff_cons, map_add, map_mul, map_listCoeff ψ F ts, + map_sum] + refine congrArg _ (sum_congr rfl fun Z _ => ?_) + rw [apply_ite ψ, map_zero, map_mul, map_listCoeff ψ F ts] + rfl + +/-- **Packet entries commute with ring homomorphisms** when `2` is nilpotent, at a grid point `t` +of valuation `i ≤ a`. So a packet entry evaluated in one coefficient ring gives the entry in +every ring it maps to. -/ +theorem map_packetEntry (ψ : R →+* S) {n : ℕ} (hn : (2 : R) ^ n = 0) (κ : ℤ₍₂₎ →+* R) (ζ : R) + {a i : ℕ} (hia : i ≤ a) {t : ℤ} (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) + {p : ℕ} (e₁ e₂ differ : Bool) (β : Fin (2 ^ (i + 1)) × Fin p → ℕ) + (rows cols : List (ℕ × Bool)) : + ψ (packetEntry κ ζ a i t e₁ e₂ differ β rows cols) = + packetEntry (ψ.comp κ) (ψ ζ) a i t e₁ e₂ differ β rows cols := by + rw [packetEntry, packetEntry, map_mul, map_listCoeff, map_prod, List.map_map] + congr 1 + · congr 1 + refine List.map_congr_left fun r _ => funext fun o => ?_ + cases o with + | none => + cases hr : r.2 <;> + simp only [Function.comp_apply, packetRowTerm, affineFactor, hr, Bool.false_eq_true, + ↓reduceIte, map_sub, map_one, map_packetLocalForm] + | some Z => + cases hr : r.2 <;> + simp only [Function.comp_apply, packetRowTerm, affineSign, hr, Bool.false_eq_true, + ↓reduceIte, map_mul, map_neg, map_one, map_packetModeCoeff ψ hn κ ζ hia hti hti'] + · refine prod_congr rfl fun Z _ => ?_ + rw [map_pow, map_packetModeValue] + +end Map + +/-! ### The packet entries of the Cauchy children -/ + +section Cauchy + +variable {R V E G P : Type*} [CommRing R] [Fintype E] [DecidableEq E] [Fintype P] [DecidableEq G] + [DecidableEq P] {N : TensorNetwork R Bool V E} {group : V → G} {packet : E → P} {slot : E → ℕ} + {sym : P → G × G} {i : ℕ} + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) + +/-- The modes of the packet `q`, as modes of the splitting. -/ +private def packetModes (q : P) : Fin (2 ^ (i + 1)) × Fin p ↪ P × Fin (2 ^ (i + 1)) × Fin p := + ⟨fun Z => (q, Z), fun _ _ h => (Prod.ext_iff.1 h).2⟩ + +omit [Fintype E] [DecidableEq E] [Fintype P] [DecidableEq G] [DecidableEq P] in +private theorem packetModes_apply (q : P) (Z : Fin (2 ^ (i + 1)) × Fin p) : + packetModes q Z = (q, Z) := + rfl + +omit [Fintype E] [DecidableEq E] in +/-- The modes targeting the block `(q, g)` are the modes of `q` whose tiles target `g`. -/ +private theorem targetBlock_eq_iff (q q' : P) (g : G) (Z : Fin (2 ^ (i + 1)) × Fin p) : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').targetBlock + (q', Z) = (q, g) ↔ + q' = q ∧ modeTargets i (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) Z.1 = true := by + simp only [ModeSplitting.targetBlock, cauchyModeSplitting_modePacket, + cauchyModeSplitting_target, Prod.mk.injEq, modeTargets] + constructor + · rintro ⟨rfl, h⟩ + exact ⟨rfl, by split_ifs at h ⊢ <;> simp_all⟩ + · rintro ⟨rfl, h⟩ + exact ⟨rfl, by split_ifs at h ⊢ <;> simp_all⟩ + +omit [Fintype E] [DecidableEq E] in +/-- The modes with the source block `(q, g)` are the modes of `q` whose tiles have source `g`. -/ +private theorem sourceBlock_eq_iff (q q' : P) (g : G) (Z : Fin (2 ^ (i + 1)) × Fin p) : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').sourceBlock + (q', Z) = (q, g) ↔ + q' = q ∧ modeSources i (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) Z.1 = true := by + simp only [ModeSplitting.sourceBlock, cauchyModeSplitting_modePacket, + cauchyModeSplitting_source, Prod.mk.injEq, modeSources] + constructor + · rintro ⟨rfl, h⟩ + exact ⟨rfl, by split_ifs at h ⊢ <;> simp_all⟩ + · rintro ⟨rfl, h⟩ + exact ⟨rfl, by split_ifs at h ⊢ <;> simp_all⟩ + +omit [DecidableEq E] in +/-- The local form of a row port of the block `(q, g)` is `packetLocalForm` on the column ports +of the packet `q` in the group `g`. -/ +private theorem localForm_cauchyModeSplitting (q : P) (g : G) (X : E → Bool) {cols : List E} + (hcols : cols.Nodup) (hcols' : ∀ e, e ∈ cols ↔ packet e = q ∧ group (N.tgt e) = g) {e : E} + (he : packet e = q) (heg : group (N.src e) = g) : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').localForm X e = + packetLocalForm κ ζ a t (cols.map fun e => (slot e, X e)) (slot e) := by + classical + rw [ModeSplitting.localForm, packetLocalForm, List.map_map, ← List.sum_toFinset _ hcols, + ← sum_subset (subset_univ _) fun e' _ he' => ?_] + · refine sum_congr rfl fun e' he' => ?_ + obtain ⟨hq, hg⟩ := (hcols' e').1 (List.mem_toFinset.1 he') + simp only [cauchyModeSplitting_localPart, cauchyValueMatrix, hg, heg, he, hq, ↓reduceIte, + Function.comp_apply, mul_ite, mul_one, mul_zero] + · have h' : ¬ (packet e' = q ∧ group (N.tgt e') = g) := fun h => + he' (List.mem_toFinset.2 ((hcols' e').2 h)) + simp only [cauchyModeSplitting_localPart, cauchyValueMatrix, heg, he] + split_ifs with h₁ h₂ <;> first | simp only [zero_mul] | exact absurd ⟨h₂.symm, h₁⟩ h' + +omit [DecidableEq E] in +/-- The value of a mode `(q, Z)` with source `g` is `packetModeValue` on the column ports of the +packet `q` in the group `g`. -/ +private theorem modeValue_cauchyModeSplitting (q : P) (g : G) (X : E → Bool) {cols : List E} + (hcols : cols.Nodup) (hcols' : ∀ e, e ∈ cols ↔ packet e = q ∧ group (N.tgt e) = g) + (Z : Fin (2 ^ (i + 1)) × Fin p) + (hZ : (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').source + (q, Z) = g) : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').modeValue X (q, Z) = + packetModeValue R i (decide ((sym q).1 ≠ (sym q).2)) (cols.map fun e => (slot e, X e)) Z := by + classical + set S := cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti' + rw [ModeSplitting.modeValue, packetModeValue, List.map_map, ← List.sum_toFinset _ hcols, + ← sum_subset (subset_univ _) fun e' _ he' => ?_] + · refine sum_congr rfl fun e' he' => ?_ + obtain ⟨hq, -⟩ := (hcols' e').1 (List.mem_toFinset.1 he') + simp only [S, cauchyModeSplitting_form, cauchyModeForm, hq, true_and, Function.comp_apply, + decide_eq_true_eq] + by_cases hX : X e' <;> simp [hX] + · -- A column port outside the block contributes nothing. + have h' : ¬ (packet e' = q ∧ group (N.tgt e') = g) := fun h => + he' (List.mem_toFinset.2 ((hcols' e').2 h)) + by_cases h0 : S.form (q, Z) e' = 0 + · rw [h0, zero_mul] + · obtain ⟨hq, hg⟩ := S.form_ne_zero (q, Z) e' h0 + exact absurd ⟨hq.symm, hg.symm.trans hZ⟩ h' + +/-- **The packet tensors of the Cauchy children.** For lists `rows` and `cols` of the row and +column ports of the packet `q` in the group `g`, without repetition and in any order, the packet +tensor of `cauchyModeSplitting` is `packetEntry` at their slots and bits. -/ +theorem packetTensor_cauchyModeSplitting (β : P × Fin (2 ^ (i + 1)) × Fin p → ℕ) (q : P) (g : G) + (Y X : E → Bool) {rows cols : List E} (hrows : rows.Nodup) + (hrows' : ∀ e, e ∈ rows ↔ packet e = q ∧ group (N.src e) = g) (hcols : cols.Nodup) + (hcols' : ∀ e, e ∈ cols ↔ packet e = q ∧ group (N.tgt e) = g) : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').packetTensor β q g + Y X = + packetEntry κ ζ a i t (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) + (decide ((sym q).1 ≠ (sym q).2)) (fun Z => β (q, Z)) (rows.map fun e => (slot e, Y e)) + (cols.map fun e => (slot e, X e)) := by + set S := cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti' + have htarget : ({Z | S.targetBlock Z = (q, g)} : Finset (P × Fin (2 ^ (i + 1)) × Fin p)) = + ({Z | modeTargets i (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) Z.1 = true} : + Finset (Fin (2 ^ (i + 1)) × Fin p)).map (packetModes q) := by + ext ⟨q', Z⟩ + simp only [S, mem_filter, mem_univ, true_and, mem_map, targetBlock_eq_iff, packetModes_apply, + Prod.mk.injEq] + constructor + · rintro ⟨rfl, h⟩ + exact ⟨Z, h, rfl, rfl⟩ + · rintro ⟨Z', h, rfl, rfl⟩ + exact ⟨rfl, h⟩ + have hsource : ({Z | S.sourceBlock Z = (q, g)} : Finset (P × Fin (2 ^ (i + 1)) × Fin p)) = + ({Z | modeSources i (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) Z.1 = true} : + Finset (Fin (2 ^ (i + 1)) × Fin p)).map (packetModes q) := by + ext ⟨q', Z⟩ + simp only [S, mem_filter, mem_univ, true_and, mem_map, sourceBlock_eq_iff, packetModes_apply, + Prod.mk.injEq] + constructor + · rintro ⟨rfl, h⟩ + exact ⟨Z, h, rfl, rfl⟩ + · rintro ⟨Z', h, rfl, rfl⟩ + exact ⟨rfl, h⟩ + rw [ModeSplitting.packetTensor, packetEntry, S.blockCoeff_eq_listCoeff (q, g) β Y X hrows + (fun e => (hrows' e).trans (by rw [ModeSplitting.rowBlock, Prod.mk.injEq]; exact Iff.rfl)), + htarget, listCoeff_map, hsource, prod_map, + List.map_map, List.map_map] + congr 1 + · congr 1 + refine List.map_congr_left fun e he => funext fun o => ?_ + obtain ⟨hq, hg⟩ := (hrows' e).1 he + cases o with + | none => + simp only [Function.comp_apply, Option.map_none, ModeSplitting.choiceTerm, packetRowTerm] + rw [localForm_cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti' q g X + hcols hcols' hq hg] + | some Z => + simp only [Function.comp_apply, Option.map_some, ModeSplitting.choiceTerm, packetRowTerm, + S, cauchyModeSplitting_coeff, cauchyModeCoeff, packetModeCoeff, packetModes_apply, hq, + true_and, decide_eq_true_eq] + · refine prod_congr rfl fun Z hZ => ?_ + have hZ' : S.source (q, Z) = g := congrArg Prod.snd + ((sourceBlock_eq_iff side hsrc htgt horient hslot hia κ ζ hp ht hti hti' q q g Z).2 + ⟨rfl, (mem_filter.1 hZ).2⟩) + rw [packetModes_apply, modeValue_cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht + hti hti' q g X hcols hcols' Z hZ'] + +/-- **The packet entries of the Cauchy children** (§D.4.6, p. 63): the entry of the packet vertex +`q` of the child of group `g` at the bits `y` on its ports is `packetEntry` at the slots and bits +of its row ports and column ports, listed without repetition and in any order. -/ +theorem child_tensor_inr_cauchyModeSplitting [DecidableEq V] + (β : P × Fin (2 ^ (i + 1)) × Fin p → ℕ) (g : G) (q : P) + (y : {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.src e) = g} ⊕ + {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.tgt e) = g} → Bool) + {rows cols : List E} (hrows : rows.Nodup) + (hrows' : ∀ e, e ∈ rows ↔ packet e = q ∧ group (N.src e) = g) + (hcols : cols.Nodup) (hcols' : ∀ e, e ∈ cols ↔ packet e = q ∧ group (N.tgt e) = g) : + ((cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').child β g).tensor + (.inr q) y = + packetEntry κ ζ a i t (decide ((sym q).1 = g)) (decide ((sym q).2 = g)) + (decide ((sym q).1 ≠ (sym q).2)) (fun Z => β (q, Z)) + (rows.map fun e => (slot e, if h : _ = g then y (.inl ⟨e, h⟩) else false)) + (cols.map fun e => (slot e, if h : _ = g then y (.inr ⟨e, h⟩) else false)) := + packetTensor_cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti' β q g _ _ + hrows hrows' hcols hcols' + +end Cauchy + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean new file mode 100644 index 0000000000..fe79b79655 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean @@ -0,0 +1,75 @@ +/- +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.PacketEntry +public import ComplexityTests.Computability.Circuit.Frontier.TensorNetworkCauchyModes + +/-! +# Tests for the entries of the child networks + +The network of the Cauchy mode tests: one packet of two slots, oriented in coordinate `0`, with +groups `false` and `true`. Edge `0` runs from row `0` (group `false`) to column `2` (group +`true`), and edge `1` from row `1` (group `true`) to column `3` (group `false`). So the packet +vertex of the child of group `false` has one row port, edge `0` in slot `0`, and one column port, +edge `1` in slot `1`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.PacketEntry + +open Cslib.Circuits.Frontier TensorNetwork +open ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.CauchyModes + +/-- Coefficient extraction: the coefficient of `m` in `(2 + 3 m) (5 + 7 m)` is `2 · 7 + 3 · 5`. -/ +example : listCoeff (R := ℤ) ({()} : Finset Unit) + [fun o => o.elim 2 fun _ => 3, fun o => o.elim 5 fun _ => 7] (fun _ => 1) = 29 := by + simp [listCoeff] + +/-- The coefficient of `m ^ 2` is `3 · 7`, and there is no `m ^ 3` term. -/ +example : listCoeff (R := ℤ) ({()} : Finset Unit) + [fun o => o.elim 2 fun _ => 3, fun o => o.elim 5 fun _ => 7] (fun _ => 2) = 21 ∧ + listCoeff (R := ℤ) ({()} : Finset Unit) + [fun o => o.elim 2 fun _ => 3, fun o => o.elim 5 fun _ => 7] (fun _ => 3) = 0 := by + simp [listCoeff] + +/-- The Cauchy mode splitting of the test network at `T = 1 + 4 ζ`. -/ +noncomputable abbrev split (ζ : ZMod (2 ^ 2)) := + cauchyModeSplitting (sym := sym) (i := 0) side (by decide) (by decide) horient (a := 1) + (fun e => by fin_cases e <;> decide) (by decide) κ ζ (p := 2) (by decide) (t := 1) (by decide) + (by decide) (by decide) + +/-- **The packet entry of the child of group `false`**, at the bits `y`, is `packetEntry` on its row +port (edge `0`, slot `0`) and its column port (edge `1`, slot `1`). -/ +example (ζ : ZMod (2 ^ 2)) (β : Unit × Fin (2 ^ (0 + 1)) × Fin 2 → ℕ) + (y : {e // (split ζ).group (net.src e) = false} ⊕ {e // (split ζ).group (net.tgt e) = false} → + Bool) : + ((split ζ).child β false).tensor (.inr ()) y = + packetEntry κ ζ 1 0 1 true false true (fun Z => β ((), Z)) + ([0].map fun e => (slot e, if h : _ = false then y (.inl ⟨e, h⟩) else false)) + ([1].map fun e => (slot e, if h : _ = false then y (.inr ⟨e, h⟩) else false)) := + child_tensor_inr_cauchyModeSplitting side (by decide) (by decide) horient + (fun e => by fin_cases e <;> decide) (by decide) κ ζ (by decide) (by decide) (by decide) + (by decide) β false () y (rows := [0]) (cols := [1]) (by decide) + (fun e => by fin_cases e <;> decide) (by decide) (fun e => by fin_cases e <;> decide) + +/-- With no ports and zero exponents, a packet entry is `1`. -/ +example (ζ : ZMod (2 ^ 2)) : + packetEntry (p := 2) κ ζ 1 0 1 true false true (fun _ => 0) [] [] = 1 := by + simp [packetEntry] + +/-- Packet entries reduce along `ℤ / 4 → ℤ / 2`. -/ +example (ζ : ZMod (2 ^ 2)) (β : Fin (2 ^ (0 + 1)) × Fin 2 → ℕ) (rows cols : List (ℕ × Bool)) : + ZMod.castHom (by decide : 2 ∣ 2 ^ 2) (ZMod 2) + (packetEntry κ ζ 1 0 1 true false true β rows cols) = + packetEntry ((ZMod.castHom (by decide : 2 ∣ 2 ^ 2) (ZMod 2)).comp κ) + (ZMod.castHom (by decide : 2 ∣ 2 ^ 2) (ZMod 2) ζ) 1 0 1 true false true β rows cols := + map_packetEntry _ (n := 2) (by decide) κ ζ (by decide) (by decide) (by decide) _ _ _ β rows cols + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.PacketEntry From 4c076d5efa568f6cc7e6c15595ad61e214b373ef Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:57:33 -0400 Subject: [PATCH 16/33] feat(circuit/frontier): the local Cauchy kernel as a product of units (Lemma D.4 evaluation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `kernelValue_eq_prod`: with t - q = 2^v_q u_q, u_q odd, W_d(t + αζ) is κ(kernelUnit a) times ∏_{q ≠ d, t} (u_q + 2^(a+1-v_q) ζ), times 2^(a+1-v_d) ζ when d ≠ t, where kernelUnit a is the inverse of the odd part of C'(0). The powers of two cancel exactly by `sum_padicValInt_sub_eq`, so kernel values need only ring operations and inverses of odd integers, as the source evaluates them (p. 60). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/KernelProduct.lean | 288 ++++++++++++++++++ .../Frontier/TensorNetworkPacketEntry.lean | 14 + 3 files changed, 303 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/KernelProduct.lean diff --git a/Complexity.lean b/Complexity.lean index 61358cafb6..88a37828a3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -876,6 +876,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformatio public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectContraction public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/KernelProduct.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/KernelProduct.lean new file mode 100644 index 0000000000..8eca2827ff --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/KernelProduct.lean @@ -0,0 +1,288 @@ +/- +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.CauchyModes +import Complexity.Foundations.NumberTheory.Padics.RatValuation + +/-! +# The local Cauchy kernel as a product of units + +Lemma D.4 of [OpenAI2026ETH] (pp. 59–60) shows that the Cauchy kernel `W_d` on the grid `S` of +`L = 2 ^ a` slots has `2`-integral coefficients in the local coordinate `z`, `T = t + α z`, +`α = 2 ^ (a + 1)`. The source evaluates it by dividing each factor `t - q + α z`, `q ≠ t`, by the +known power `2 ^ v₂(t - q)` and inverting only odd integers (p. 60). This file proves the resulting +product formula, so that a kernel value is computed with ring operations and inverses of odd +integers. + +Write `t - q = 2 ^ v_q u_q` with `u_q` odd. Since `S` is a complete residue system modulo +`2 ^ (a + 1)`, the valuations `v_q`, `q ≠ t`, sum to those of the normalization +`C'(0) = ∏_{q ≠ 0} (0 - q)` (`sum_padicValInt_sub_eq`). So the powers of two cancel, and + +* `W_t(t + α z) = c ∏_{q ≠ t} (u_q + 2 ^ (a + 1 - v_q) z)`, +* `W_d(t + α z) = c 2 ^ (a + 1 - v_d) z ∏_{q ≠ d, t} (u_q + 2 ^ (a + 1 - v_q) z)` for `d ≠ t`, + +where `c = ∏_{q ≠ 0} u'_q⁻¹` is the inverse of the odd part of `C'(0)` (`kernelUnit`). Every +factor is integral, and `c` is a unit of `ℤ_(2)` (`localKernel_eq_prod`). Evaluating at `ζ` in a +ring receiving `ℤ_(2)` gives `kernelValue_eq_prod`. + +## Main definitions + +* `Cslib.Circuits.Frontier.kernelUnit`: the inverse of the odd part of `C'(0)`, in `ℤ_(2)`. + +## Main results + +* `Cslib.Circuits.Frontier.sum_padicValInt_sub_eq`: the valuations of `t - q` sum to those of + `C'(0)`. +* `Cslib.Circuits.Frontier.localKernel_eq_prod`: the local kernel as a product of units. +* `Cslib.Circuits.Frontier.kernelValue_eq_prod`: its values. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.4 and its + evaluation, pp. 59–60. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Polynomial Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-- An integer is `2 ^ v₂(n)` times its odd part `n / 2 ^ v₂(n)`. -/ +private theorem two_pow_mul_ediv (n : ℤ) : 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) = n := + Int.mul_ediv_cancel' (by exact_mod_cast padicValInt_dvd (p := 2) n) + +/-- The odd part of a nonzero integer is nonzero. -/ +private theorem ediv_ne_zero {n : ℤ} (hn : n ≠ 0) : n / 2 ^ padicValInt 2 n ≠ 0 := by + intro h + exact hn (by rw [← two_pow_mul_ediv n, h, mul_zero]) + +/-- The odd part of a nonzero integer is not divisible by `2`. -/ +private theorem padicValInt_ediv_eq_zero {n : ℤ} (hn : n ≠ 0) : + padicValInt 2 (n / 2 ^ padicValInt 2 n) = 0 := by + refine padicValInt.eq_zero_of_not_dvd fun ⟨c, hc⟩ => ?_ + have hc' : n / 2 ^ padicValInt 2 n = 2 * c := by exact_mod_cast hc + have h2 : (2 : ℤ) ^ (padicValInt 2 n + 1) ∣ n := ⟨c, by + calc n = 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) := (two_pow_mul_ediv n).symm + _ = 2 ^ (padicValInt 2 n + 1) * c := by rw [hc', pow_succ]; ring⟩ + have h := (padicValInt_dvd_iff (p := 2) (padicValInt 2 n + 1) n).mp (by exact_mod_cast h2) + omega + +/-- The valuation of the odd part of a nonzero integer is one. -/ +private theorem padicValuation_ediv {n : ℤ} (hn : n ≠ 0) : + Rat.padicValuation 2 (((n / 2 ^ padicValInt 2 n : ℤ) : ℚ)) = 1 := by + rw [Rat.padicValuation_intCast_of_ne_zero 2 (ediv_ne_zero hn), padicValInt_ediv_eq_zero hn] + rfl + +/-- The inverse of an odd part is `2`-integral. -/ +private theorem inv_ediv_mem {n : ℤ} (hn : n ≠ 0) : + (((n / 2 ^ padicValInt 2 n : ℤ) : ℚ))⁻¹ ∈ ℤ₍₂₎ := by + change Rat.padicValuation 2 _ ≤ 1 + rw [map_inv₀, padicValuation_ediv hn, inv_one] + +/-- Distinct grid points of `2 ^ a` slots differ by at most `a` factors of two. -/ +private theorem padicValInt_sub_le {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : padicValInt 2 (t - q) ≤ a := by + have hval := padicValuation_two_pow_le_sub ht hq hne + have hne' : t - q ≠ 0 := sub_ne_zero.mpr (Ne.symm hne) + have h2 : Rat.padicValuation 2 ((2 : ℚ) ^ a) = WithZero.exp (-(a : ℤ)) := by + exact_mod_cast Rat.padicValuation_pow_self 2 a + have h' : Rat.padicValuation 2 ((t : ℚ) - q) = Rat.padicValuation 2 ((t - q : ℤ) : ℚ) := by + push_cast; rfl + rw [h', Rat.padicValuation_intCast_of_ne_zero 2 hne', h2, WithZero.exp_le_exp] at hval + omega + +/-- A product of differences `s - q` over the other grid points is `2 ^ (∑ v₂(s - q))` times the +product of their odd parts. -/ +private theorem prod_sub_eq (s : ℤ) (F : Finset ℤ) : + ∏ q ∈ F, ((s : ℚ) - q) = 2 ^ (∑ q ∈ F, padicValInt 2 (s - q)) * + ∏ q ∈ F, (((s - q) / 2 ^ padicValInt 2 (s - q) : ℤ) : ℚ) := by + rw [← prod_pow_eq_pow_sum, ← prod_mul_distrib] + refine prod_congr rfl fun q _ => ?_ + exact_mod_cast (two_pow_mul_ediv (s - q)).symm + +/-- **The valuations of the differences sum to those of the normalization.** For `t` on the grid +of `2 ^ a` slots, `∑_{q ≠ t} v₂(t - q) = ∑_{q ≠ 0} v₂(0 - q)`. -/ +theorem sum_padicValInt_sub_eq {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) : + ∑ q ∈ (cauchyGrid (2 ^ a)).erase t, padicValInt 2 (t - q) = + ∑ q ∈ (cauchyGrid (2 ^ a)).erase 0, padicValInt 2 (0 - q) := by + have hv (s : ℤ) : Rat.padicValuation 2 (∏ q ∈ (cauchyGrid (2 ^ a)).erase s, ((s : ℚ) - q)) = + WithZero.exp (-((∑ q ∈ (cauchyGrid (2 ^ a)).erase s, padicValInt 2 (s - q) : ℕ) : ℤ)) := by + rw [prod_sub_eq s, map_mul, map_prod] + rw [prod_eq_one fun q hq => padicValuation_ediv (sub_ne_zero.mpr + (Ne.symm (ne_of_mem_erase hq))), mul_one] + exact_mod_cast Rat.padicValuation_pow_self 2 _ + have h := padicValuation_prod_sub_eq ht + have h0 := hv 0 + push_cast at h0 + rw [hv t, h0, WithZero.exp_inj, neg_inj] at h + exact_mod_cast h + +/-- The inverse `∏_{q ≠ 0} u'_q⁻¹` of the odd part of `C'(0) = ∏_{q ≠ 0} (0 - q)`, a unit of +`ℤ_(2)`, with `u'_q` the odd part of `0 - q`. -/ +noncomputable def kernelUnit (a : ℕ) : ℤ₍₂₎ := + ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, + if h : (0 : ℤ) - q = 0 then 1 else ⟨_, inv_ediv_mem h⟩ + +/-- The value of `kernelUnit`. -/ +theorem coe_kernelUnit (a : ℕ) : + ((kernelUnit a : ℤ₍₂₎) : ℚ) = (∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, + (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : ℚ))⁻¹ := by + change (ℤ₍₂₎).subtype (kernelUnit a) = _ + rw [kernelUnit, map_prod, ← prod_inv_distrib] + refine prod_congr rfl fun q hq => ?_ + have hne : (0 : ℤ) - q ≠ 0 := sub_ne_zero.mpr (Ne.symm (ne_of_mem_erase hq)) + rw [dite_eq_right hne] + rfl + +/-- The `2`-integral factor `u_q + 2 ^ (a + 1 - v_q) z` of the local kernel, where +`t - q = 2 ^ v_q u_q`. -/ +private noncomputable def unitFactor (a : ℕ) (t q : ℤ) : ℤ₍₂₎[X] := + C (((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) : ℤ₍₂₎) + + C ((2 : ℤ₍₂₎) ^ (a + 1 - padicValInt 2 (t - q))) * X + +private theorem map_unitFactor (a : ℕ) (t q : ℤ) : + (unitFactor a t q).map (ℤ₍₂₎).subtype = + C ((((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) : ℚ)) + + C ((2 : ℚ) ^ (a + 1 - padicValInt 2 (t - q))) * X := by + simp only [unitFactor, Polynomial.map_add, Polynomial.map_mul, + Polynomial.map_X, map_intCast, map_pow, map_ofNat, Polynomial.map_intCast, + Polynomial.map_pow, Polynomial.map_ofNat] + +/-- A shifted factor `t + α z - q`, `q ≠ t` on the grid, is `2 ^ v_q` times the unit factor. -/ +private theorem shift_eq_unitFactor {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : + C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X - C (q : ℚ) = + C ((2 : ℚ) ^ padicValInt 2 (t - q)) * (unitFactor a t q).map (ℤ₍₂₎).subtype := by + have hle := padicValInt_sub_le ht hq hne + have hsplit : ((t : ℚ) - q) = 2 ^ padicValInt 2 (t - q) * + (((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) : ℚ) := by + exact_mod_cast (two_pow_mul_ediv (t - q)).symm + have hpow : (2 : ℚ) ^ padicValInt 2 (t - q) * 2 ^ (a + 1 - padicValInt 2 (t - q)) = + 2 ^ (a + 1) := by + rw [← pow_add, Nat.add_sub_cancel' (by omega)] + rw [map_unitFactor, mul_add, ← mul_assoc, ← C_mul, ← C_mul, hpow, ← hsplit, C_sub] + ring + +/-- The shifted factors over grid points other than `t`. -/ +private theorem prod_shift_eq {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (F : Finset ℤ) + (hF : F ⊆ (cauchyGrid (2 ^ a)).erase t) : + ∏ q ∈ F, (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X - C (q : ℚ)) = + C ((2 : ℚ) ^ ∑ q ∈ F, padicValInt 2 (t - q)) * + ∏ q ∈ F, (unitFactor a t q).map (ℤ₍₂₎).subtype := by + rw [← prod_pow_eq_pow_sum, map_prod, ← prod_mul_distrib] + refine prod_congr rfl fun q hq => ?_ + obtain ⟨hqt, hqS⟩ := mem_erase.mp (hF hq) + exact shift_eq_unitFactor ht hqS hqt + +/-- **The local kernel as a product of units** (Lemma D.4 and its evaluation, p. 60). For grid +points `t` and `d` of `2 ^ a` slots, with `t - q = 2 ^ v_q u_q`, `u_q` odd, the local kernel is +`kernelUnit a` times `∏_{q ≠ d, t} (u_q + 2 ^ (a + 1 - v_q) z)`, times `2 ^ (a + 1 - v_d) z` when +`d ≠ t`. -/ +theorem localKernel_eq_prod {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) : + localKernel a t d = C (kernelUnit a) * + (if d = t then 1 else C ((2 : ℤ₍₂₎) ^ (a + 1 - padicValInt 2 (t - d))) * X) * + ∏ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, + (C (((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) : ℤ₍₂₎) + + C ((2 : ℤ₍₂₎) ^ (a + 1 - padicValInt 2 (t - q))) * X) := by + refine Polynomial.map_injective (ℤ₍₂₎).subtype Subtype.val_injective ?_ + rw [map_localKernel ht hd, cauchyKernel, Lagrange.eval_nodal, mul_comp, C_comp, Lagrange.nodal, + Polynomial.prod_comp] + simp only [sub_comp, X_comp, C_comp] + -- The normalization `C'(0) = 2 ^ S₀ w`. + set S₀ := ∑ q ∈ (cauchyGrid (2 ^ a)).erase 0, padicValInt 2 (0 - q) + set w := ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : ℚ) + have hC0 : ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, ((0 : ℚ) - (q : ℚ)) = 2 ^ S₀ * w := by + have := prod_sub_eq 0 ((cauchyGrid (2 ^ a)).erase 0) + push_cast at this + exact this + have hsum := sum_padicValInt_sub_eq ht + have hRHS : (C (kernelUnit a) * + (if d = t then 1 else C ((2 : ℤ₍₂₎) ^ (a + 1 - padicValInt 2 (t - d))) * X) * + ∏ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, + (C (((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) : ℤ₍₂₎) + + C ((2 : ℤ₍₂₎) ^ (a + 1 - padicValInt 2 (t - q))) * X)).map (ℤ₍₂₎).subtype = + C w⁻¹ * (if d = t then 1 else C ((2 : ℚ) ^ (a + 1 - padicValInt 2 (t - d))) * X) * + ∏ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, (unitFactor a t q).map (ℤ₍₂₎).subtype := by + rw [Polynomial.map_mul, Polynomial.map_mul, Polynomial.map_C, Polynomial.map_prod] + congr 2 + · exact congrArg C (coe_kernelUnit a) + · split_ifs + · exact Polynomial.map_one _ + · simp only [Polynomial.map_mul, Polynomial.map_X, map_pow, map_ofNat, Polynomial.map_pow, + Polynomial.map_ofNat] + rw [hRHS, hC0] + rcases eq_or_ne d t with rfl | hdt + · rw [ite_eq_left rfl, mul_one, erase_idem, prod_shift_eq ht _ subset_rfl, hsum, ← mul_assoc, + ← C_mul] + congr 2 + rw [mul_inv] + change ((2 : ℚ) ^ S₀)⁻¹ * w⁻¹ * 2 ^ S₀ = w⁻¹ + rw [mul_comm, ← mul_assoc, mul_inv_cancel₀ (pow_ne_zero _ two_ne_zero), one_mul] + · rw [ite_eq_right hdt] + have htd : t ∈ (cauchyGrid (2 ^ a)).erase d := mem_erase.mpr ⟨hdt.symm, ht⟩ + have hdt' : d ∈ (cauchyGrid (2 ^ a)).erase t := mem_erase.mpr ⟨hdt, hd⟩ + have hsub : ((cauchyGrid (2 ^ a)).erase d).erase t ⊆ (cauchyGrid (2 ^ a)).erase t := + fun q hq => by + simp only [mem_erase] at hq ⊢ + exact ⟨hq.1, hq.2.2⟩ + set sv := ∑ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, padicValInt 2 (t - q) + set U := ∏ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, (unitFactor a t q).map (ℤ₍₂₎).subtype + -- `S₀ = v_d + ∑_{q ≠ d, t} v_q`. + have hsplit : S₀ = padicValInt 2 (t - d) + sv := + calc S₀ = ∑ q ∈ (cauchyGrid (2 ^ a)).erase t, padicValInt 2 (t - q) := hsum.symm + _ = padicValInt 2 (t - d) + + ∑ q ∈ ((cauchyGrid (2 ^ a)).erase t).erase d, padicValInt 2 (t - q) := + (add_sum_erase _ (fun q => padicValInt 2 (t - q)) hdt').symm + _ = padicValInt 2 (t - d) + sv := by rw [erase_right_comm] + have hvd := padicValInt_sub_le ht hd hdt + have hw : w ≠ 0 := prod_ne_zero_iff.mpr fun q hq => by + exact_mod_cast ediv_ne_zero (sub_ne_zero.mpr (Ne.symm (ne_of_mem_erase hq))) + rw [← mul_prod_erase _ _ htd, prod_shift_eq ht _ hsub, + show C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X - C (t : ℚ) = C ((2 : ℚ) ^ (a + 1)) * X by ring] + have hL : C (2 ^ S₀ * w)⁻¹ * (C ((2 : ℚ) ^ (a + 1)) * X * (C ((2 : ℚ) ^ sv) * U)) = + C ((2 ^ S₀ * w)⁻¹ * 2 ^ (a + 1) * 2 ^ sv) * (X * U) := by + rw [C_mul, C_mul]; ring + have hR : C w⁻¹ * (C ((2 : ℚ) ^ (a + 1 - padicValInt 2 (t - d))) * X) * U = + C (w⁻¹ * 2 ^ (a + 1 - padicValInt 2 (t - d))) * (X * U) := by + rw [C_mul]; ring + rw [hL, hR] + congr 2 + have h2 : (2 : ℚ) ^ (a + 1) = + 2 ^ (a + 1 - padicValInt 2 (t - d)) * 2 ^ padicValInt 2 (t - d) := by + rw [← pow_add, Nat.sub_add_cancel (by omega)] + rw [h2, hsplit, pow_add] + field_simp + +/-- **Kernel values as products of units.** In a ring `R` receiving `ℤ_(2)` through `κ`, the +kernel value `W_d(t + α ζ)` is `κ(kernelUnit a)` times `∏_{q ≠ d, t} (u_q + 2 ^ (a + 1 - v_q) ζ)`, +times `2 ^ (a + 1 - v_d) ζ` when `d ≠ t`. -/ +theorem kernelValue_eq_prod {R : Type*} [CommRing R] (κ : ℤ₍₂₎ →+* R) (ζ : R) {a : ℕ} + {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hd : d ∈ cauchyGrid (2 ^ a)) : + kernelValue κ ζ a t d = κ (kernelUnit a) * + (if d = t then 1 else 2 ^ (a + 1 - padicValInt 2 (t - d)) * ζ) * + ∏ q ∈ ((cauchyGrid (2 ^ a)).erase d).erase t, + (((t - q) / 2 ^ padicValInt 2 (t - q) : ℤ) + 2 ^ (a + 1 - padicValInt 2 (t - q)) * ζ) := by + rw [kernelValue, localKernel_eq_prod ht hd] + change eval₂RingHom κ ζ _ = _ + rw [map_mul, map_mul, map_prod] + simp only [coe_eval₂RingHom, eval₂_C] + congr 1 + · congr 1 + split_ifs + · exact eval₂_one _ _ + · rw [eval₂_mul, eval₂_C, eval₂_X, map_pow, map_ofNat] + · refine prod_congr rfl fun q _ => ?_ + rw [eval₂_add, eval₂_mul, eval₂_C, eval₂_C, eval₂_X, map_intCast, map_pow, map_ofNat] + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean index fe79b79655..cdda8d275b 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEntry.lean @@ -7,6 +7,7 @@ Authors: Samuel Schlesinger module -- upstream: local-only (test driver) +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import ComplexityTests.Computability.Circuit.Frontier.TensorNetworkCauchyModes @@ -72,4 +73,17 @@ example (ζ : ZMod (2 ^ 2)) (β : Fin (2 ^ (0 + 1)) × Fin 2 → ℕ) (rows cols (ZMod.castHom (by decide : 2 ∣ 2 ^ 2) (ZMod 2) ζ) 1 0 1 true false true β rows cols := map_packetEntry _ (n := 2) (by decide) κ ζ (by decide) (by decide) (by decide) _ _ _ β rows cols +/-- **Kernel values as products of units**, on the grid `{-2, -1, 0, 1}` of two slots: the value +`W_0(1 + 4 ζ)` is `κ(kernelUnit 1)` times `2 ^ (2 - v₂(1)) ζ` times the factors at `q = -2, -1`. -/ +example (ζ : ZMod (2 ^ 2)) : + kernelValue κ ζ 1 1 0 = κ (kernelUnit 1) * (2 ^ (1 + 1 - padicValInt 2 (1 - 0)) * ζ) * + ∏ q ∈ ((cauchyGrid (2 ^ 1)).erase 0).erase 1, + (((1 - q) / 2 ^ padicValInt 2 (1 - q) : ℤ) + 2 ^ (1 + 1 - padicValInt 2 (1 - q)) * ζ) := by + rw [kernelValue_eq_prod κ ζ (by decide) (by decide), ite_eq_right (by decide)] + +/-- The valuations of `1 - q`, `q ∈ {-2, -1, 0}`, sum to those of `0 - q`, `q ∈ {-2, -1, 1}`. -/ +example : ∑ q ∈ (cauchyGrid (2 ^ 1)).erase 1, padicValInt 2 (1 - q) = + ∑ q ∈ (cauchyGrid (2 ^ 1)).erase 0, padicValInt 2 (0 - q) := + sum_padicValInt_sub_eq (by decide) + end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.PacketEntry From 955f94ab192ec98f3e1c9d8402a69ff6ab03f420 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:14:48 -0400 Subject: [PATCH 17/33] feat(circuit/frontier): direct summation over described networks with ring operations on values Generalize the leaf of #1914 from natural numbers modulo 2^P to any ring operations on values (SumOps), with their reduced values, homomorphisms and polynomial-time certificates. The scheme computes the fold of the operations (sumValue); a homomorphism takes it to the contraction of the mapped entries. Instances: natural numbers modulo 2^P (SumOps.mod, the previous behaviour, with sumValue_mod and modContractionCertificate) and coefficient lists of (Z/2^p)[U]/(f) with GaloisRingArith (SumOps.galois, val_sumValue_galois). The resource theorem takes a bound Rv on reduced values. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/DescribedContraction.lean | 902 ++++++++++++------ ComplexityTests/DescribedContraction.lean | 69 +- 2 files changed, 639 insertions(+), 332 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean index fb1320cf92..9a511b6bca 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean @@ -9,6 +9,7 @@ module public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.GenericRAM.Native.StackRecursion +public import Complexity.Computability.PolyTime.GaloisRingArithmetic public import Complexity.Foundations.Data.BitString.List.Binary public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Words public import Complexity.Computability.GenericRAM.Native.BooleanFold @@ -44,43 +45,60 @@ computed from a description instead (Appendix D.4.6, p. 63). A *described networ whose entry at the bits `bs` on the ports is `eval t bs` for an evaluator `eval`. Explicit networks are the case of tables (`DescribedNetwork.ofExplicit`). +**Values and their operations** (`SumOps`). The entries are values `Y` combined by ring +operations that depend on a precision `q : Q`: a zero, a one, a sum and a product, whose results +are *reduced* (of bounded size). The two instances are natural numbers modulo `2 ^ P` +(`SumOps.mod`) and the coefficient lists of `A_p = (ℤ / 2 ^ p)[U] / (f)` at the precision +`(p, fl)` (`SumOps.galois`), the coefficient rings of the recursion (Appendix D.4.1, p. 57). A map +of the values into a commutative semiring that takes the operations at `q` to the semiring +operations (`SumOps.IsHom`) takes the computed sum to the contraction of the mapped entries. + **Direct summation as a stack recursion** (`DescribedNetwork.sumScheme`). The sum over the -`2 ^ m` assignments is a call whose children are the assignments, in binary order; an assignment -is a call whose children are the entries of the vertices at its bits, multiplied modulo `2 ^ P`; -an entry is a leaf, evaluated by the evaluator. The derivation has `2 ^ m (k + 1) + 1` nodes for -`k` vertices and two open calls (`DescribedNetwork.eval_sumRoot`). So a native certificate of the -evaluator, with any bounds, gives one native machine for the contraction modulo `2 ^ P` on every -input (`DescribedNetwork.contractionCertificate`), through +`2 ^ m` assignments is a call whose children are the assignments, in binary order, added with the +operations; an assignment is a call whose children are the entries of the vertices at its bits, +multiplied with the operations; an entry is a leaf, evaluated by the evaluator. The derivation has +`2 ^ m (k + 1) + 1` nodes for `k` vertices and two open calls, and its value is the fold of the +operations (`DescribedNetwork.sumValue`, `DescribedNetwork.eval_sumRoot`). So a native certificate +of the evaluator, with any bounds, gives one native machine for the sum on every input +(`DescribedNetwork.contractionCertificate`), through `Cslib.GenericRAM.StackRecursion.Scheme.certificate`. If the evaluator takes time `E`, space `ES` -and outputs at most `Ev` bits on every vertex, the machine takes time `2 ^ m (k + 1) + 1` times -`K (n + Ev + 1) ^ K + 2 E` and space `K (n + Ev + 1) ^ K + ES` on inputs of length `n` -(`DescribedNetwork.contractionCertificate_le`). This is the leaf step of the contraction -algorithm, with implicit packet tensors as descriptors. +and outputs at most `Ev` bits on every vertex, and reduced values are at most `Rv` bits, the +machine takes time `2 ^ m (k + 1) + 1` times `K (n + Ev + Rv + 1) ^ K + 2 E` and space +`K (n + Ev + Rv + 1) ^ K + ES` on inputs of length `n` +(`DescribedNetwork.contractionCertificate_le`). Under a homomorphism the value is the contraction +(`DescribedNetwork.SumOps.IsHom.map_sumValue`): modulo `2 ^ P` for natural numbers +(`DescribedNetwork.sumValue_mod`, `DescribedNetwork.modContractionCertificate`), and in `A_p` for +coefficient lists (`DescribedNetwork.val_sumValue_galois`). This is the leaf step of the +contraction algorithm, with implicit packet tensors as descriptors. ## Main definitions * `Cslib.Circuits.Frontier.DescribedNetwork`: a network with described tensors. * `Cslib.Circuits.Frontier.DescribedNetwork.contraction`: its contraction. +* `Cslib.Circuits.Frontier.SumOps`: ring operations on values, with `SumOps.IsHom` and + `SumOps.PolyTime`; the instances `SumOps.mod` and `SumOps.galois`. * `Cslib.Circuits.Frontier.DescribedNetwork.sumScheme`: direct summation as a recursion scheme. +* `Cslib.Circuits.Frontier.DescribedNetwork.sumValue`: its value. * `Cslib.Circuits.Frontier.DescribedNetwork.encDescribed`: the encoding of a described network. * `Cslib.Circuits.Frontier.DescribedNetwork.contractionCertificate`: one native machine for the - contraction modulo `2 ^ P`. + sum. ## 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 the - contraction modulo `2 ^ P`, with `2 ^ m (k + 1) + 1` nodes and two open calls. +* `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 + contraction; `DescribedNetwork.sumValue_mod` and `DescribedNetwork.val_sumValue_galois`. * `Cslib.Circuits.Frontier.DescribedNetwork.contraction_ofExplicit`: explicit networks. -* `Cslib.Circuits.Frontier.DescribedNetwork.run_sumScheme`: the machine computes the contraction - modulo `2 ^ P`. +* `Cslib.Circuits.Frontier.DescribedNetwork.run_sumScheme`: the machine computes `sumValue`. * `Cslib.Circuits.Frontier.DescribedNetwork.contractionCertificate_le`: its time and space. ## References -* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.6–D.4.7 - (pp. 63–64). +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.1 (p. 57) + and Appendix D.4.6–D.4.7 (pp. 63–64). -/ @[expose] public section @@ -124,7 +142,80 @@ end Cslib.GenericRAM.StackRecursion.Scheme namespace Cslib.Circuits.Frontier -open Finset Cslib.BitString Cslib.GenericRAM Cslib.GenericRAM.StackRecursion +open Finset Cslib.BitString Cslib.GenericRAM Cslib.GenericRAM.StackRecursion Turing.MultiTapeTM + +/-! ### Values and their operations -/ + +/-- **Ring operations on values** for direct summation: at each precision `q : Q`, a zero, a +one, a sum and a product on the values `Y`, and the *reduced* values at `q`, among which are the +zero, the one and every sum and product. -/ +structure SumOps (Q Y : Type) where + /-- The zero at a precision. -/ + zero : Q → Y + /-- The one at a precision. -/ + one : Q → Y + /-- The sum at a precision. -/ + add : Q → Y → Y → Y + /-- The product at a precision. -/ + mul : Q → Y → Y → Y + /-- The reduced values at a precision. -/ + Reduced : Q → Y → Prop + reduced_zero (q : Q) : Reduced q (zero q) + reduced_one (q : Q) : Reduced q (one q) + reduced_add (q : Q) (a b : Y) : Reduced q (add q a b) + reduced_mul (q : Q) (a b : Y) : Reduced q (mul q a b) + +namespace SumOps + +variable {Q Y : Type} (ops : SumOps Q Y) + +/-- A *homomorphism* of the operations at `q`: a map of the values into a commutative semiring +taking the zero, the one, sums and products at `q` to those of the semiring. -/ +structure IsHom (q : Q) {R : Type*} [CommSemiring R] (φ : Y → R) : Prop where + map_zero : φ (ops.zero q) = 0 + map_one : φ (ops.one q) = 1 + map_add (a b : Y) : φ (ops.add q a b) = φ a + φ b + map_mul (a b : Y) : φ (ops.mul q a b) = φ a * φ b + +/-- **Polynomial-time operations**, for encodings of the precisions and the values. -/ +structure PolyTime (encQ : Q ↪ List Bool) (encY : Y ↪ List Bool) : Prop where + zero : ComputableInPolyTime ops.zero encQ encY + one : ComputableInPolyTime ops.one encQ encY + add : ComputableInPolyTime (fun z : Q × Y × Y => ops.add z.1 z.2.1 z.2.2) + (encPair encQ (encPair encY encY)) encY + mul : ComputableInPolyTime (fun z : Q × Y × Y => ops.mul z.1 z.2.1 z.2.2) + (encPair encQ (encPair encY encY)) encY + +/-- A left fold of the sum or the product from a reduced start is reduced. -/ +theorem reduced_foldl {α : Type} (q : Q) (op : Y → Y → Y) (hop : ∀ a b, ops.Reduced q (op a b)) + (f : α → Y) (l : List α) {a : Y} (ha : ops.Reduced q a) : + ops.Reduced q (l.foldl (fun acc v => op acc (f v)) a) := by + induction l generalizing a with + | nil => exact ha + | cons v l ih => exact ih (hop _ _) + +variable {ops} + +/-- A homomorphism takes a fold of products to the product of the images. -/ +theorem IsHom.map_foldl_mul {q : Q} {R : Type*} [CommSemiring R] {φ : Y → R} (h : ops.IsHom q φ) + {α : Type} (f : α → Y) (l : List α) (a : Y) : + φ (l.foldl (fun acc v => ops.mul q acc (f v)) a) = φ a * (l.map fun v => φ (f v)).prod := by + induction l generalizing a with + | nil => simp + | cons v l ih => rw [List.foldl_cons, ih, h.map_mul, List.map_cons, List.prod_cons, mul_assoc] + +/-- A homomorphism takes a fold of sums over a range to the sum of the images. -/ +theorem IsHom.map_foldl_add {q : Q} {R : Type*} [CommSemiring R] {φ : Y → R} (h : ops.IsHom q φ) + (g : ℕ → Y) (n : ℕ) (a : Y) : + φ ((List.range n).foldl (fun acc j => ops.add q acc (g j)) a) = + φ a + ∑ j ∈ range n, φ (g j) := by + induction n with + | zero => simp + | succ n ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, h.map_add, ih, + sum_range_succ, add_assoc] + +end SumOps /-- A Boolean tensor network with *described* tensors: the number of edges, numbered from zero, and for each vertex its ordered list of ports and a descriptor of its tensor. -/ @@ -136,7 +227,7 @@ structure DescribedNetwork (T : Type) where namespace DescribedNetwork -variable {T : Type} (eval : T → List Bool → ℕ) +variable {T : Type} /-- The bits that a word, read as an assignment of bits to the edges, puts on a list of ports; a port past the end of the word reads `false`. -/ @@ -144,8 +235,9 @@ def wordBits (word : List Bool) (ports : List ℕ) : List Bool := ports.map fun i => word.getD i false /-- The *contraction*: the sum over all assignments of bits to the edges of the product of the -entries of the vertices at the bits on their ports. -/ -def contraction (d : DescribedNetwork T) : ℕ := +entries of the vertices at the bits on their ports, for entries in a commutative semiring. -/ +def contraction {R : Type*} [CommSemiring R] (eval : T → List Bool → R) + (d : DescribedNetwork T) : R := ∑ x : Fin d.numEdges → Bool, (d.vertices.map fun v => eval v.2 (wordBits (List.ofFn x) v.1)).prod @@ -171,25 +263,36 @@ theorem contraction_ofExplicit (d : ExplicitTensorNetwork ℕ) : funext i exact getD_ofFn x i +/-- The contraction commutes with maps of semirings. -/ +theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) + (eval : T → List Bool → R) (d : DescribedNetwork T) : + ψ (d.contraction eval) = d.contraction fun t bs => ψ (eval t bs) := by + rw [contraction, contraction, map_sum] + refine sum_congr rfl fun x _ => ?_ + rw [map_list_prod, List.map_map] + rfl + /-! ### Direct summation as a recursion scheme -/ /-- A node of direct summation: its kind (`0` the sum, `1` an assignment, `2` an entry), the precision and the network, and the current word and vertex index. -/ -abbrev Node (T : Type) := ℕ × ((ℕ × DescribedNetwork T) × (List Bool × ℕ)) +abbrev Node (Q T : Type) := ℕ × ((Q × DescribedNetwork T) × (List Bool × ℕ)) + +variable {Q Y : Type} (ops : SumOps Q Y) (eval : T → List Bool → Y) /-- The entry of a node: the evaluator at the descriptor of its vertex and the bits of its word -on the vertex's ports, or zero for an index past the vertices. -/ -def entryOf (x : Node T) : ℕ := - (x.2.1.2.vertices[x.2.2.2]?).elim 0 fun v => eval v.2 (wordBits x.2.2.1 v.1) +on the vertex's ports, or the zero for an index past the vertices. -/ +def entryOf (x : Node Q T) : Y := + (x.2.1.2.vertices[x.2.2.2]?).elim (ops.zero x.2.1.1) fun v => eval v.2 (wordBits x.2.2.1 v.1) /-- **Direct summation as a recursion scheme.** The sum (kind `0`) has the `2 ^ m` assignments as -children, adding their values modulo `2 ^ P`; an assignment (kind `1`) has the entries of the -vertices as children, multiplying them modulo `2 ^ P`; an entry (kind `2`) is a leaf. The -accumulator is a counter and a residue. -/ -def sumScheme : Scheme (Node T) (ℕ × ℕ) ℕ where +children, adding their values; an assignment (kind `1`) has the entries of the vertices as +children, multiplying them; an entry (kind `2`) is a leaf. The operations are those at the +precision of the node, and the accumulator is a counter and a value. -/ +def sumScheme : Scheme (Node Q T) (ℕ × Y) Y where isLeaf x := x.1 == 2 - leaf := entryOf eval - start x := (0, (if x.1 = 0 then 0 else 1) % 2 ^ x.2.1.1) + leaf := entryOf ops eval + start x := (0, if x.1 = 0 then ops.zero x.2.1.1 else ops.one x.2.1.1) next x a := if x.1 = 0 then (if a.1 < 2 ^ x.2.1.2.numEdges then some (1, (x.2.1, (lowBits x.2.1.2.numEdges a.1, 0))) @@ -198,157 +301,242 @@ def sumScheme : Scheme (Node T) (ℕ × ℕ) ℕ where (if a.1 < x.2.1.2.vertices.length then some (2, (x.2.1, (x.2.2.1, min x.2.1.2.vertices.length a.1))) else none) else none - absorb x a y := (a.1 + 1, if x.1 = 0 then (a.2 + y) % 2 ^ x.2.1.1 else a.2 * y % 2 ^ x.2.1.1) + absorb x a y := + (a.1 + 1, if x.1 = 0 then ops.add x.2.1.1 a.2 y else ops.mul x.2.1.1 a.2 y) finish _ a := a.2 -/-- The root of direct summation on `(P, d)`. -/ -def sumRoot (q : ℕ × DescribedNetwork T) : Node T := (0, (q, ([], 0))) +/-- The root of direct summation on `(q, d)`. -/ +def sumRoot (q : Q × DescribedNetwork T) : Node Q T := (0, (q, ([], 0))) + +/-- The weight of a word: the product of the entries at its bits, folded with the operations at +`q`. -/ +def weight (q : Q) (d : DescribedNetwork T) (word : List Bool) : Y := + d.vertices.foldl (fun acc v => ops.mul q acc (eval v.2 (wordBits word v.1))) (ops.one q) -/-- The weight of a word modulo `2 ^ P`: the product of the entries at its bits, reduced after -each factor. -/ -def weightMod (P : ℕ) (d : DescribedNetwork T) (word : List Bool) : ℕ := - d.vertices.foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) (1 % 2 ^ P) +/-- **The value of direct summation**: the weights of the `2 ^ m` words in binary order, folded +with the sum at `q`. -/ +def sumValue (q : Q) (d : DescribedNetwork T) : Y := + (List.range (2 ^ d.numEdges)).foldl + (fun acc j => ops.add q acc (weight ops eval q d (lowBits d.numEdges j))) (ops.zero q) /-- The shape of a node of direct summation on `q`: it keeps `q`, its kind is at most `2`, its word has at most `m` bits and its index is at most the number of vertices. -/ -def NodeShape (q : ℕ × DescribedNetwork T) (x : Node T) : Prop := +def NodeShape (q : Q × DescribedNetwork T) (x : Node Q T) : Prop := x.2.1 = q ∧ x.1 ≤ 2 ∧ x.2.2.1.length ≤ q.2.numEdges ∧ x.2.2.2 ≤ q.2.vertices.length -/-- The invariant of direct summation on `q = (P, d)`: nodes have the shape of `NodeShape`, frames -also have a counter at most `2 ^ m + k` and a residue below `2 ^ P`, and every value is a residue -below `2 ^ P` or an entry of a vertex at the bits of an `m`-bit word. -/ -def sumInvariant (q : ℕ × DescribedNetwork T) : Invariant (Node T) (ℕ × ℕ) ℕ := +/-- The invariant of direct summation on `q`: nodes have the shape of `NodeShape`, frames also +have a counter at most `2 ^ m + k` and a reduced value, and every value is reduced or an entry of +a vertex at the bits of an `m`-bit word. -/ +def sumInvariant (q : Q × DescribedNetwork T) : Invariant (Node Q T) (ℕ × Y) Y := ⟨NodeShape q, fun x a => NodeShape q x ∧ a.1 ≤ 2 ^ q.2.numEdges + q.2.vertices.length ∧ - a.2 < 2 ^ q.1, - fun y => y < 2 ^ q.1 ∨ ∃ v ∈ q.2.vertices, ∃ w : List Bool, w.length = q.2.numEdges ∧ + ops.Reduced q.1 a.2, + fun y => ops.Reduced q.1 y ∨ ∃ v ∈ q.2.vertices, ∃ w : List Bool, w.length = q.2.numEdges ∧ y = eval v.2 (wordBits w v.1)⟩ -variable {eval} +variable {ops eval} /-- An entry is a leaf. -/ -theorem eval_entry (q : ℕ × DescribedNetwork T) {word : List Bool} {v : ℕ} +theorem eval_entry (q : Q × DescribedNetwork T) {word : List Bool} {v : ℕ} (hw : word.length = q.2.numEdges) (hv : v < q.2.vertices.length) : - (sumScheme eval).Eval (sumInvariant eval q) (2, (q, (word, v))) - (entryOf eval (2, (q, (word, v)))) 1 0 := by - refine Scheme.Eval.leaf (R := sumScheme eval) ⟨rfl, by omega, hw.le, hv.le⟩ rfl + (sumScheme ops eval).Eval (sumInvariant ops eval q) (2, (q, (word, v))) + (entryOf ops eval (2, (q, (word, v)))) 1 0 := by + refine Scheme.Eval.leaf (R := sumScheme ops eval) ⟨rfl, by omega, hw.le, hv.le⟩ rfl (.inr ⟨q.2.vertices[v], List.getElem_mem hv, word, hw, ?_⟩) - change (q.2.vertices[v]?).elim 0 _ = _ + change (q.2.vertices[v]?).elim _ _ = _ rw [List.getElem?_eq_getElem hv] rfl -private theorem foldl_take_succ (d : DescribedNetwork T) (P : ℕ) (word : List Bool) (c : ℕ) +private theorem foldl_take_succ (q : Q) (d : DescribedNetwork T) (word : List Bool) (c : ℕ) (hc : c < d.vertices.length) : - (d.vertices.take (c + 1)).foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) - (1 % 2 ^ P) = - (d.vertices.take c).foldl (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) - (1 % 2 ^ P) * entryOf eval (2, ((P, d), (word, c))) % 2 ^ P := by + (d.vertices.take (c + 1)).foldl (fun acc v => ops.mul q acc (eval v.2 (wordBits word v.1))) + (ops.one q) = + ops.mul q ((d.vertices.take c).foldl + (fun acc v => ops.mul q acc (eval v.2 (wordBits word v.1))) (ops.one q)) + (entryOf ops eval (2, ((q, d), (word, c)))) := by rw [List.take_add_one, List.getElem?_eq_getElem hc, Option.toList_some, List.foldl_append] simp [entryOf, List.getElem?_eq_getElem hc] -private theorem foldl_mod_lt {α : Type} (f : α → ℕ) (P : ℕ) (l : List α) : - l.foldl (fun acc v => acc * f v % 2 ^ P) (1 % 2 ^ P) < 2 ^ P := by - have h (a : ℕ) (ha : a < 2 ^ P) : l.foldl (fun acc v => acc * f v % 2 ^ P) a < 2 ^ P := by - induction l generalizing a with - | nil => exact ha - | cons v l ih => exact ih _ (Nat.mod_lt _ (Nat.two_pow_pos P)) - exact h _ (Nat.mod_lt _ (Nat.two_pow_pos P)) - /-- **An assignment evaluates to the weight of its word**, with one node per vertex plus one and one open call. -/ -theorem eval_assign (P : ℕ) (d : DescribedNetwork T) {word : List Bool} +theorem eval_assign (q : Q) (d : DescribedNetwork T) {word : List Bool} (hw : word.length = d.numEdges) : - ∃ h ≤ 1, (sumScheme eval).Eval (sumInvariant eval (P, d)) (1, ((P, d), (word, 0))) - (weightMod eval P d word) (d.vertices.length + 1) h := by - set acc : ℕ → ℕ × ℕ := fun c => (c, (d.vertices.take c).foldl - (fun acc v => acc * eval v.2 (wordBits word v.1) % 2 ^ P) (1 % 2 ^ P)) - have hshape : NodeShape (P, d) ((1, ((P, d), (word, 0))) : Node T) := + ∃ h ≤ 1, (sumScheme ops eval).Eval (sumInvariant ops eval (q, d)) (1, ((q, d), (word, 0))) + (weight ops eval q d word) (d.vertices.length + 1) h := by + set acc : ℕ → ℕ × Y := fun c => (c, (d.vertices.take c).foldl + (fun acc v => ops.mul q acc (eval v.2 (wordBits word v.1))) (ops.one q)) + have hshape : NodeShape (q, d) ((1, ((q, d), (word, 0))) : Node Q T) := ⟨rfl, by omega, hw.le, Nat.zero_le _⟩ - obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme eval) - (I := sumInvariant eval (P, d)) - (x := (1, ((P, d), (word, 0)))) (acc := acc) - (child := fun c => (2, ((P, d), (word, c)))) - (val := fun c => entryOf eval (2, ((P, d), (word, c)))) + have hred (c : ℕ) : ops.Reduced q (acc c).2 := + ops.reduced_foldl q _ (ops.reduced_mul q) _ _ (ops.reduced_one q) + obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme ops eval) + (I := sumInvariant ops eval (q, d)) + (x := (1, ((q, d), (word, 0)))) (acc := acc) + (child := fun c => (2, ((q, d), (word, c)))) + (val := fun c => entryOf ops eval (2, ((q, d), (word, c)))) (nodes := fun _ => 1) (height := fun _ => 0) d.vertices.length 0 (fun c hc => by simp [sumScheme, acc, hc, min_eq_right hc.le]) (by simp [sumScheme, acc]) (fun c hc => by simp only [sumScheme, acc, one_ne_zero, ite_false] - rw [foldl_take_succ d P word c hc]) - (fun c hc => eval_entry (P, d) hw hc) - (fun c hc => ⟨hshape, (Nat.le_of_lt hc).trans (Nat.le_add_left _ _), foldl_mod_lt _ _ _⟩) + rw [foldl_take_succ q d word c hc]) + (fun c hc => eval_entry (q, d) hw hc) + (fun c hc => ⟨hshape, (Nat.le_of_lt hc).trans (Nat.le_add_left _ _), hred c⟩) (fun _ _ => le_rfl) - have hstart : (sumScheme eval).start (1, ((P, d), (word, 0))) = acc 0 := by + have hstart : (sumScheme ops eval).start (1, ((q, d), (word, 0))) = acc 0 := by simp [sumScheme, acc] rw [← hstart] at hloop refine ⟨h + 1, by omega, ?_⟩ - have := Scheme.Eval.call (R := sumScheme eval) (I := sumInvariant eval (P, d)) - (x := (1, ((P, d), (word, 0)))) hshape rfl hloop (.inl (foldl_mod_lt _ _ _)) - simpa [sumScheme, acc, weightMod, List.take_length, Finset.sum_const, Finset.card_range] using + have := Scheme.Eval.call (R := sumScheme ops eval) (I := sumInvariant ops eval (q, d)) + (x := (1, ((q, d), (word, 0)))) hshape rfl hloop (.inl (hred d.vertices.length)) + simpa [sumScheme, acc, weight, List.take_length, Finset.sum_const, Finset.card_range] using this -private theorem sum_lowBits (n : ℕ) (g : List Bool → ℕ) : +private theorem sum_lowBits {M : Type*} [AddCommMonoid M] (n : ℕ) (g : List Bool → M) : ∑ j ∈ range (2 ^ n), g (lowBits n j) = ∑ x : Fin n → Bool, g (List.ofFn x) := by rw [← Fin.sum_univ_eq_sum_range] exact Fintype.sum_equiv (equivFin n).symm _ _ fun k => by rw [lowBits_eq_ofFn_equivFin_symm] -private theorem foldl_mul_mod {α : Type} (f : α → ℕ) (m : ℕ) (l : List α) (a : ℕ) : - l.foldl (fun acc v => acc * f v % m) (a % m) = a * (l.map f).prod % m := by - induction l generalizing a with - | nil => simp - | cons v l ih => - rw [List.foldl_cons, Nat.mod_mul_mod, ih, List.map_cons, List.prod_cons, Nat.mul_assoc] - -private theorem weightMod_eq (P : ℕ) (d : DescribedNetwork T) (word : List Bool) : - weightMod eval P d word = - (d.vertices.map fun v => eval v.2 (wordBits word v.1)).prod % 2 ^ P := by - rw [weightMod, foldl_mul_mod, Nat.one_mul] - -/-- **Direct summation computes the contraction modulo `2 ^ P`**, with `2 ^ m (k + 1) + 1` -nodes for `m` edges and `k` vertices, and at most two open calls. -/ -theorem eval_sumRoot (P : ℕ) (d : DescribedNetwork T) : - ∃ h ≤ 2, (sumScheme eval).Eval (sumInvariant eval (P, d)) (sumRoot (P, d)) - (d.contraction eval % 2 ^ P) (2 ^ d.numEdges * (d.vertices.length + 1) + 1) h := by - set acc : ℕ → ℕ × ℕ := fun c => (c, (∑ j ∈ range c, weightMod eval P d (lowBits d.numEdges j)) % - 2 ^ P) - have hassign (c : ℕ) := eval_assign (eval := eval) P d (length_lowBits d.numEdges c) +/-- **Direct summation computes `sumValue`**, with `2 ^ m (k + 1) + 1` nodes for `m` edges and +`k` vertices, and at most two open calls. -/ +theorem eval_sumRoot (q : Q) (d : DescribedNetwork T) : + ∃ h ≤ 2, (sumScheme ops eval).Eval (sumInvariant ops eval (q, d)) (sumRoot (q, d)) + (sumValue ops eval q d) (2 ^ d.numEdges * (d.vertices.length + 1) + 1) h := by + set acc : ℕ → ℕ × Y := fun c => (c, (List.range c).foldl + (fun acc j => ops.add q acc (weight ops eval q d (lowBits d.numEdges j))) (ops.zero q)) + have hassign (c : ℕ) := eval_assign (ops := ops) (eval := eval) q d + (length_lowBits d.numEdges c) choose hgt hhgt heval using hassign - have hshape : NodeShape (P, d) (sumRoot (P, d)) := ⟨rfl, by simp [sumRoot], by simp [sumRoot], + have hshape : NodeShape (q, d) (sumRoot (q, d)) := ⟨rfl, by simp [sumRoot], by simp [sumRoot], by simp [sumRoot]⟩ - obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme eval) - (I := sumInvariant eval (P, d)) - (x := sumRoot (P, d)) (acc := acc) - (child := fun c => (1, ((P, d), (lowBits d.numEdges c, 0)))) - (val := fun c => weightMod eval P d (lowBits d.numEdges c)) + have hred (c : ℕ) : ops.Reduced q (acc c).2 := + ops.reduced_foldl q _ (ops.reduced_add q) _ _ (ops.reduced_zero q) + obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := sumScheme ops eval) + (I := sumInvariant ops eval (q, d)) + (x := sumRoot (q, d)) (acc := acc) + (child := fun c => (1, ((q, d), (lowBits d.numEdges c, 0)))) + (val := fun c => weight ops eval q d (lowBits d.numEdges c)) (nodes := fun _ => d.vertices.length + 1) (height := hgt) (2 ^ d.numEdges) 1 (fun c hc => by simp [sumScheme, sumRoot, acc, hc]) (by simp [sumScheme, sumRoot, acc]) - (fun c hc => by simp [sumScheme, sumRoot, acc, sum_range_succ, Nat.add_mod]) + (fun c hc => by + simp only [sumScheme, sumRoot, acc, ite_true, List.range_succ, List.foldl_append, + List.foldl_cons, List.foldl_nil]) (fun c _ => heval c) - (fun c hc => ⟨hshape, by simp only [acc]; omega, Nat.mod_lt _ (Nat.two_pow_pos P)⟩) + (fun c hc => ⟨hshape, by simp only [acc]; omega, hred c⟩) (fun c _ => hhgt c) - have hstart : (sumScheme eval).start (sumRoot (P, d)) = acc 0 := by + have hstart : (sumScheme ops eval).start (sumRoot (q, d)) = acc 0 := by simp [sumScheme, sumRoot, acc] rw [← hstart] at hloop refine ⟨h + 1, by omega, ?_⟩ - have := Scheme.Eval.call (R := sumScheme eval) (I := sumInvariant eval (P, d)) - (x := sumRoot (P, d)) hshape rfl hloop (.inl (Nat.mod_lt _ (Nat.two_pow_pos P))) - have hval : (sumScheme eval).finish (sumRoot (P, d)) (acc (2 ^ d.numEdges)) = - d.contraction eval % 2 ^ P := by - simp only [sumScheme, acc, contraction] - rw [sum_lowBits d.numEdges (weightMod eval P d)] - simp only [weightMod_eq] - exact (Finset.sum_nat_mod _ _ _).symm - rw [hval, sum_const, card_range, smul_eq_mul] at this + have := Scheme.Eval.call (R := sumScheme ops eval) (I := sumInvariant ops eval (q, d)) + (x := sumRoot (q, d)) hshape rfl hloop (.inl (hred (2 ^ d.numEdges))) + rw [sum_const, card_range, smul_eq_mul] at this exact this +/-- `sumValue` is reduced. -/ +theorem reduced_sumValue (q : Q) (d : DescribedNetwork T) : ops.Reduced q (sumValue ops eval q d) := + ops.reduced_foldl q _ (ops.reduced_add q) _ _ (ops.reduced_zero q) + +/-- **Under a homomorphism, the value is the contraction** of the mapped entries. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.IsHom.map_sumValue {q : Q} {R : Type*} + [CommSemiring R] {φ : Y → R} (h : ops.IsHom q φ) (d : DescribedNetwork T) : + φ (sumValue ops eval q d) = d.contraction fun t bs => φ (eval t bs) := by + rw [sumValue, h.map_foldl_add, h.map_zero, zero_add, contraction, + sum_lowBits d.numEdges (fun w => φ (weight ops eval q d w))] + refine sum_congr rfl fun x _ => ?_ + rw [weight, h.map_foldl_mul, h.map_one, one_mul] + +/-! ### Natural numbers modulo a power of two -/ + +/-- **Natural numbers modulo `2 ^ P`**, with the precision `P`; the reduced values are those +below `2 ^ P`. -/ +def _root_.Cslib.Circuits.Frontier.SumOps.mod : SumOps ℕ ℕ where + zero _ := 0 + one P := 1 % 2 ^ P + add P a b := (a + b) % 2 ^ P + mul P a b := a * b % 2 ^ P + Reduced P y := y < 2 ^ P + reduced_zero P := Nat.two_pow_pos P + reduced_one P := Nat.mod_lt _ (Nat.two_pow_pos P) + reduced_add P _ _ := Nat.mod_lt _ (Nat.two_pow_pos P) + reduced_mul P _ _ := Nat.mod_lt _ (Nat.two_pow_pos P) + +/-- The cast into `ZMod (2 ^ P)` is a homomorphism of the operations at `P`. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.mod_isHom (P : ℕ) : + SumOps.mod.IsHom P (Nat.cast : ℕ → ZMod (2 ^ P)) where + map_zero := Nat.cast_zero + map_one := by simp only [SumOps.mod, ZMod.natCast_mod, Nat.cast_one] + map_add a b := by simp only [SumOps.mod, ZMod.natCast_mod, Nat.cast_add] + map_mul a b := by simp only [SumOps.mod, ZMod.natCast_mod, Nat.cast_mul] + +/-- **Modulo `2 ^ P`, the value is the contraction modulo `2 ^ P`.** -/ +theorem sumValue_mod (eval : T → List Bool → ℕ) (P : ℕ) (d : DescribedNetwork T) : + sumValue SumOps.mod eval P d = d.contraction eval % 2 ^ P := by + have h := (SumOps.mod_isHom P).map_sumValue (eval := eval) d + have h2 : ((d.contraction eval : ℕ) : ZMod (2 ^ P)) = + d.contraction fun t bs => ((eval t bs : ℕ) : ZMod (2 ^ P)) := + map_contraction (Nat.castRingHom (ZMod (2 ^ P))) eval d + have h' := congrArg ZMod.val (h.trans h2.symm) + rwa [ZMod.val_cast_of_lt (reduced_sumValue (ops := SumOps.mod) P d), ZMod.val_natCast] at h' + +/-! ### Coefficient lists of `(ℤ / 2 ^ p)[U] / (f)` -/ + +/-- **Coefficient lists of `A_p = (ℤ / 2 ^ p)[U] / (f)`**, with the precision `(p, fl)` and the +operations of `GaloisRingArith`; the reduced values have `e = fl.length` coordinates, each at most +`2 ^ p`. -/ +def _root_.Cslib.Circuits.Frontier.SumOps.galois : SumOps (ℕ × List ℕ) (List ℕ) where + zero q := GaloisRingArith.zero q.2 + one q := GaloisRingArith.one q.2 + add q := GaloisRingArith.add q.1 q.2 + mul q := GaloisRingArith.mul q.1 q.2 + Reduced q c := c.length = q.2.length ∧ ∀ x ∈ c, x ≤ 2 ^ q.1 + reduced_zero q := ⟨GaloisRingArith.length_zero _, fun x hx => by + simp only [GaloisRingArith.zero, List.mem_map] at hx + obtain ⟨_, -, rfl⟩ := hx + exact Nat.zero_le _⟩ + reduced_one q := ⟨by simp [GaloisRingArith.one], fun x hx => by + simp only [GaloisRingArith.one, List.mem_map] at hx + obtain ⟨i, -, rfl⟩ := hx + split_ifs + · exact Nat.one_le_two_pow + · exact Nat.zero_le _⟩ + reduced_add q _ _ := ⟨GaloisRingArith.length_add _ _ _ _, + fun _ hx => (GaloisRingArith.lt_of_mem_add hx).le⟩ + reduced_mul q _ _ := ⟨GaloisRingArith.length_mul _ _ _ _, + fun _ hx => (GaloisRingArith.lt_of_mem_mul hx).le⟩ + +/-- The value in `A_p` is a homomorphism of the operations at `(p, fl)`. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.galois_isHom (q : ℕ × List ℕ) : + SumOps.galois.IsHom q (GaloisRingArith.val q.1 q.2) where + map_zero := GaloisRingArith.val_zero + map_one := GaloisRingArith.val_one + map_add := GaloisRingArith.val_add + map_mul := GaloisRingArith.val_mul + +/-- **In `A_p`, the value is the contraction** of the values of the entries. -/ +theorem val_sumValue_galois (eval : T → List Bool → List ℕ) (q : ℕ × List ℕ) + (d : DescribedNetwork T) : + GaloisRingArith.val q.1 q.2 (sumValue SumOps.galois eval q d) = + d.contraction fun t bs => GaloisRingArith.val q.1 q.2 (eval t bs) := + (SumOps.galois_isHom q).map_sumValue d + +/-- A reduced coefficient list at `(p, fl)` is at most `e (2 p + 3)` long in binary, with +`e = fl.length`. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.length_encList_le_of_galois_reduced + {q : ℕ × List ℕ} {c : List ℕ} (hc : SumOps.galois.Reduced q c) : + (encList encNat c).length ≤ q.2.length * (2 * q.1 + 3) := by + obtain ⟨hlen, hle⟩ := hc + have := GaloisRingArith.length_encList_le (p := q.1 + 1) hlen fun x hx => + (hle x hx).trans_lt (Nat.pow_lt_pow_right (by norm_num) (Nat.lt_succ_self _)) + calc (encList encNat c).length ≤ q.2.length * (2 * (q.1 + 1) + 1) := this + _ = q.2.length * (2 * q.1 + 3) := by ring + /-! ### The native machine -/ section Native -open Turing.MultiTapeTM - -variable (encT : T ↪ List Bool) +variable (encQ : Q ↪ List Bool) (encY : Y ↪ List Bool) (encT : T ↪ List Bool) /-- A described network is its number of edges and its list of vertices. -/ def equivProd : DescribedNetwork T ≃ ℕ × List (List ℕ × T) where @@ -362,18 +550,18 @@ each the pair of its list of ports in binary and its descriptor. -/ def encDescribed : DescribedNetwork T ↪ List Bool := encOfEquiv equivProd (encPair encUnary (encList (encPair (encList encNat) encT))) -/-- The encoding of an input `(P, d)`: the precision in unary and the network. -/ -abbrev encInput : ℕ × DescribedNetwork T ↪ List Bool := encPair encUnary (encDescribed encT) +/-- The encoding of an input `(q, d)`: the precision and the network. -/ +abbrev encInput : Q × DescribedNetwork T ↪ List Bool := encPair encQ (encDescribed encT) /-- The encoding of a node: its kind in unary, its input, its word and its vertex index in unary. -/ -abbrev encNode : Node T ↪ List Bool := - encPair encUnary (encPair (encInput encT) (encPair encBits encUnary)) +abbrev encNode : Node Q T ↪ List Bool := + encPair encUnary (encPair (encInput encQ encT) (encPair encBits encUnary)) -/-- The encoding of an accumulator: the counter and the residue, in binary. -/ -abbrev encAcc : ℕ × ℕ ↪ List Bool := encPair encNat encNat +/-- The encoding of an accumulator: the counter in binary and the value. -/ +abbrev encAcc : ℕ × Y ↪ List Bool := encPair encNat encY -variable {encT} +variable {encQ encY encT} /-- The number of edges of a described network, in unary. -/ theorem numEdges_polyTime : @@ -388,29 +576,29 @@ theorem vertices_polyTime : (ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.of_eq (f := equivProd) fun _ => rfl) /-- The kind of a node. -/ -theorem kind_polyTime : ComputableInPolyTime (fun x : Node T => x.1) (encNode encT) +theorem kind_polyTime : ComputableInPolyTime (fun x : Node Q T => x.1) (encNode encQ encT) encUnary := ComputableInPolyTime.fst _ _ -/-- The input `(P, d)` of a node. -/ -theorem input_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1) (encNode encT) - (encInput encT) := (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _) +/-- The input `(q, d)` of a node. -/ +theorem input_polyTime : ComputableInPolyTime (fun x : Node Q T => x.2.1) (encNode encQ encT) + (encInput encQ encT) := (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _) /-- The precision of a node. -/ -theorem precision_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1.1) - (encNode encT) encUnary := (ComputableInPolyTime.fst _ _).comp input_polyTime +theorem precision_polyTime : ComputableInPolyTime (fun x : Node Q T => x.2.1.1) + (encNode encQ encT) encQ := (ComputableInPolyTime.fst _ _).comp input_polyTime /-- The network of a node. -/ -theorem network_polyTime : ComputableInPolyTime (fun x : Node T => x.2.1.2) - (encNode encT) (encDescribed encT) := (ComputableInPolyTime.snd _ _).comp input_polyTime +theorem network_polyTime : ComputableInPolyTime (fun x : Node Q T => x.2.1.2) + (encNode encQ encT) (encDescribed encT) := (ComputableInPolyTime.snd _ _).comp input_polyTime /-- The word of a node. -/ -theorem word_polyTime : ComputableInPolyTime (fun x : Node T => x.2.2.1) - (encNode encT) encBits := +theorem word_polyTime : ComputableInPolyTime (fun x : Node Q T => x.2.2.1) + (encNode encQ encT) encBits := (ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _).snd' /-- The vertex index of a node. -/ -theorem index_polyTime : ComputableInPolyTime (fun x : Node T => x.2.2.2) - (encNode encT) encUnary := +theorem index_polyTime : ComputableInPolyTime (fun x : Node Q T => x.2.2.2) + (encNode encQ encT) encUnary := (ComputableInPolyTime.snd _ _).comp (ComputableInPolyTime.snd _ _).snd' /-- `2 ^ P`, from `P` in unary. -/ @@ -419,49 +607,44 @@ private theorem twoPow {α : Type} {encIn : α ↪ List Bool} {P : α → ℕ} ComputableInPolyTime (fun a => 2 ^ P a) encIn encNat := ComputableInPolyTime.binary_two_pow.comp hP -/-- `x % 2 ^ P`, from `P` in unary. -/ -private theorem modTwoPow {α : Type} {encIn : α ↪ List Bool} {x P : α → ℕ} - (hx : ComputableInPolyTime x encIn encNat) (hP : ComputableInPolyTime P encIn encUnary) : - ComputableInPolyTime (fun a => x a % 2 ^ P a) encIn encNat := - ComputableInPolyTime.binary_mod.comp (hx.prod (twoPow hP)) - -/-- **The polynomial-time components of direct summation.** -/ -theorem sumScheme_polyTime : (sumScheme eval).PolyTime (encNode encT) encAcc encNat where +/-- **The polynomial-time components of direct summation**, from polynomial-time operations. -/ +theorem sumScheme_polyTime (hops : ops.PolyTime encQ encY) : + (sumScheme ops eval).PolyTime (encNode encQ encT) (encAcc encY) encY where isLeaf := ComputableInPolyTime.beq kind_polyTime (ComputableInPolyTime.const 2) start := by - refine (ComputableInPolyTime.const 0).prod (modTwoPow ?_ precision_polyTime) - refine (ComputableInPolyTime.ite (encMid := encNat) (ComputableInPolyTime.beq kind_polyTime - (ComputableInPolyTime.const 0)) (ComputableInPolyTime.const 0) - (ComputableInPolyTime.const 1)).of_enc_eq fun x => ?_ - by_cases h : x.1 = 0 <;> simp [h] + refine (ComputableInPolyTime.const 0).prod ?_ + exact (ComputableInPolyTime.ite (ComputableInPolyTime.beq kind_polyTime + (ComputableInPolyTime.const 0)) (hops.zero.comp precision_polyTime) + (hops.one.comp precision_polyTime)).of_enc_eq fun x => by + by_cases h : x.1 = 0 <;> simp [h] next := by - have hx := ComputableInPolyTime.fst (encNode encT) encAcc - have ha := ComputableInPolyTime.snd (encNode encT) encAcc + have hx := ComputableInPolyTime.fst (encNode encQ encT) (encAcc encY) + have ha := ComputableInPolyTime.snd (encNode encQ encT) (encAcc encY) have hk := kind_polyTime.comp hx - have hc := (ComputableInPolyTime.fst encNat encNat).comp ha + have hc := (ComputableInPolyTime.fst encNat encY).comp ha have hq := input_polyTime.comp hx have hd := network_polyTime.comp hx have hm := numEdges_polyTime.comp hd have hlen := (ComputableInPolyTime.length_list _).comp (vertices_polyTime.comp hd) - have hassign : ComputableInPolyTime (fun p : Node T × (ℕ × ℕ) => + have hassign : ComputableInPolyTime (fun p : Node Q T × (ℕ × Y) => (if p.2.1 < 2 ^ p.1.2.1.2.numEdges then - some ((1, (p.1.2.1, (lowBits p.1.2.1.2.numEdges p.2.1, 0))) : Node T) else none)) - (encPair (encNode encT) encAcc) (encOption (encNode encT)) := + some ((1, (p.1.2.1, (lowBits p.1.2.1.2.numEdges p.2.1, 0))) : Node Q T) else none)) + (encPair (encNode encQ encT) (encAcc encY)) (encOption (encNode encQ encT)) := (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hc (twoPow hm)) - ((ComputableInPolyTime.optionSome (encNode encT)).comp + ((ComputableInPolyTime.optionSome (encNode encQ encT)).comp ((ComputableInPolyTime.const (encOut := encUnary) 1).prod (hq.prod ((BooleanFold.decoder_polyTime.comp (hm.prod hc)).prod (ComputableInPolyTime.const (encOut := encUnary) 0))))) (ComputableInPolyTime.const none)).of_enc_eq fun p => by by_cases h : p.2.1 < 2 ^ p.1.2.1.2.numEdges <;> simp [h] - have hentry : ComputableInPolyTime (fun p : Node T × (ℕ × ℕ) => + have hentry : ComputableInPolyTime (fun p : Node Q T × (ℕ × Y) => (if p.2.1 < p.1.2.1.2.vertices.length then - some ((2, (p.1.2.1, (p.1.2.2.1, min p.1.2.1.2.vertices.length p.2.1))) : Node T) + some ((2, (p.1.2.1, (p.1.2.2.1, min p.1.2.1.2.vertices.length p.2.1))) : Node Q T) else none)) - (encPair (encNode encT) encAcc) (encOption (encNode encT)) := + (encPair (encNode encQ encT) (encAcc encY)) (encOption (encNode encQ encT)) := (ComputableInPolyTime.ite (ComputableInPolyTime.binary_lt' hc (ComputableInPolyTime.unary_to_binary.comp hlen)) - ((ComputableInPolyTime.optionSome (encNode encT)).comp + ((ComputableInPolyTime.optionSome (encNode encQ encT)).comp ((ComputableInPolyTime.const (encOut := encUnary) 2).prod (hq.prod ((word_polyTime.comp hx).prod (ComputableInPolyTime.binary_min_unary' hlen hc))))) @@ -475,32 +658,32 @@ theorem sumScheme_polyTime : (sumScheme eval).PolyTime (encNode encT) encAcc enc · simp [sumScheme, h0] · by_cases h1 : p.1.1 = 1 <;> simp [sumScheme, h0, h1] absorb := by - have hx := (ComputableInPolyTime.fst (encNode encT) encAcc).comp - (ComputableInPolyTime.fst (encPair (encNode encT) encAcc) encNat) - have ha := (ComputableInPolyTime.snd (encNode encT) encAcc).comp - (ComputableInPolyTime.fst (encPair (encNode encT) encAcc) encNat) - have hy := ComputableInPolyTime.snd (encPair (encNode encT) encAcc) encNat - have hc := (ComputableInPolyTime.fst encNat encNat).comp ha - have hr := (ComputableInPolyTime.snd encNat encNat).comp ha + have hx := (ComputableInPolyTime.fst (encNode encQ encT) (encAcc encY)).comp + (ComputableInPolyTime.fst (encPair (encNode encQ encT) (encAcc encY)) encY) + have ha := (ComputableInPolyTime.snd (encNode encQ encT) (encAcc encY)).comp + (ComputableInPolyTime.fst (encPair (encNode encQ encT) (encAcc encY)) encY) + have hy := ComputableInPolyTime.snd (encPair (encNode encQ encT) (encAcc encY)) encY + have hc := (ComputableInPolyTime.fst encNat encY).comp ha + have hr := (ComputableInPolyTime.snd encNat encY).comp ha have hP := precision_polyTime.comp hx refine (ComputableInPolyTime.binary_add.comp (hc.prod (ComputableInPolyTime.const 1))).prod ((ComputableInPolyTime.ite (ComputableInPolyTime.beq (kind_polyTime.comp hx) (ComputableInPolyTime.const 0)) - (modTwoPow (ComputableInPolyTime.binary_add.comp (hr.prod hy)) hP) - (modTwoPow (ComputableInPolyTime.binary_mul' hr hy) hP)).of_enc_eq fun q => ?_) + (hops.add.comp (hP.prod (hr.prod hy))) + (hops.mul.comp (hP.prod (hr.prod hy)))).of_enc_eq fun q => ?_) by_cases h : q.1.1.1 = 0 <;> simp [h] - finish := (ComputableInPolyTime.snd encNat encNat).comp (ComputableInPolyTime.snd _ _) + finish := (ComputableInPolyTime.snd encNat encY).comp (ComputableInPolyTime.snd _ _) /-- The descriptor of a vertex and the bits of the node's word on its ports. -/ theorem query_polyTime : - ComputableInPolyTime (fun p : Node T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) - (encPair (encNode encT) (encPair (encList encNat) encT)) (encPair encT encBits) := by - have hv := ComputableInPolyTime.snd (encNode encT) (encPair (encList encNat) encT) + ComputableInPolyTime (fun p : Node Q T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) + (encPair (encNode encQ encT) (encPair (encList encNat) encT)) (encPair encT encBits) := by + have hv := ComputableInPolyTime.snd (encNode encQ encT) (encPair (encList encNat) encT) have hports := (ComputableInPolyTime.fst (encList encNat) encT).comp hv - have hword := word_polyTime.comp (ComputableInPolyTime.fst (encNode encT) + have hword := word_polyTime.comp (ComputableInPolyTime.fst (encNode encQ encT) (encPair (encList encNat) encT)) - let E := encPair (encNode encT) (encPair (encList encNat) encT) - have hbit : ComputableInPolyTime (fun z : (Node T × (List ℕ × T)) × ℕ => + let E := encPair (encNode encQ encT) (encPair (encList encNat) encT) + have hbit : ComputableInPolyTime (fun z : (Node Q T × (List ℕ × T)) × ℕ => z.1.1.2.2.1.getD z.2 false) (encPair E encNat) encBool := ComputableInPolyTime.listGetD_binary (encA := encBool) (encL := encBits) false (hword.comp (ComputableInPolyTime.fst E encNat)) (ComputableInPolyTime.snd E encNat) @@ -510,55 +693,61 @@ theorem query_polyTime : /-- The vertex of a node, if its index is in range. -/ noncomputable def vertexCertificate : - NativeCertificate (fun x : Node T => x.2.1.2.vertices[x.2.2.2]?) (encNode encT) + NativeCertificate (fun x : Node Q T => x.2.1.2.vertices[x.2.2.2]?) (encNode encQ encT) (encOption (encPair (encList encNat) encT)) := NativeCertificate.ofPolyTime (ComputableInPolyTime.getElem? (vertices_polyTime.comp network_polyTime) index_polyTime) /-- The query to the evaluator: the descriptor and the bits on the ports. -/ noncomputable def queryCertificate : - NativeCertificate (fun p : Node T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) - (encPair (encNode encT) (encPair (encList encNat) encT)) (encPair encT encBits) := + NativeCertificate (fun p : Node Q T × (List ℕ × T) => (p.2.2, wordBits p.1.2.2.1 p.2.1)) + (encPair (encNode encQ encT) (encPair (encList encNat) encT)) (encPair encT encBits) := NativeCertificate.ofPolyTime query_polyTime +/-- The zero at the precision of a node. -/ +noncomputable def zeroCertificate (hops : ops.PolyTime encQ encY) : + NativeCertificate (fun x : Node Q T => ops.zero x.2.1.1) (encNode encQ encT) encY := + NativeCertificate.ofPolyTime (hops.zero.comp precision_polyTime) + variable (eval) in /-- **The leaf step**: the evaluator at the descriptor of the node's vertex and its bits, from a native certificate of the evaluator with any bounds. -/ -noncomputable def leafCertificate - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - NativeCertificate (sumScheme eval).leaf (encNode encT) encNat := - NativeCertificate.option (value := fun x : Node T => x.2.1.2.vertices[x.2.2.2]?) - (fallback := fun _ => 0) (body := fun p => eval p.2.2 (wordBits p.1.2.2.1 p.2.1)) - vertexCertificate (NativeCertificate.const 0) (ev.comp queryCertificate) - -/-- The time of the leaf step: the vertex lookup, and the query and the evaluator at a vertex. -/ -theorem leafCertificate_time - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) (x : Node T) : - (leafCertificate eval ev).time x = 2 * (vertexCertificate (encT := encT)).time x + - (encNode encT x).length + - (x.2.1.2.vertices[x.2.2.2]?).elim (encNode encT x).length - fun v => (queryCertificate (encT := encT)).time (x, v) + +noncomputable def leafCertificate (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + NativeCertificate (sumScheme ops eval).leaf (encNode encQ encT) encY := + NativeCertificate.option (value := fun x : Node Q T => x.2.1.2.vertices[x.2.2.2]?) + (fallback := fun x => ops.zero x.2.1.1) (body := fun p => eval p.2.2 (wordBits p.1.2.2.1 p.2.1)) + vertexCertificate (zeroCertificate hops) (ev.comp queryCertificate) + +/-- The time of the leaf step: the vertex lookup, and the zero or the query and the evaluator at +a vertex. -/ +theorem leafCertificate_time (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) + (x : Node Q T) : + (leafCertificate eval hops ev).time x = + 2 * (vertexCertificate (encQ := encQ) (encT := encT)).time x + (encNode encQ encT x).length + + (x.2.1.2.vertices[x.2.2.2]?).elim ((zeroCertificate (encT := encT) hops).time x) + fun v => (queryCertificate (encQ := encQ) (encT := encT)).time (x, v) + ev.time (v.2, wordBits x.2.2.1 v.1) := rfl /-- The space of the leaf step. -/ -theorem leafCertificate_space - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) (x : Node T) : - (leafCertificate eval ev).space x = 2 * (vertexCertificate (encT := encT)).space x + - (encNode encT x).length + - (x.2.1.2.vertices[x.2.2.2]?).elim (encNode encT x).length - fun v => (queryCertificate (encT := encT)).space (x, v) + +theorem leafCertificate_space (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) + (x : Node Q T) : + (leafCertificate eval hops ev).space x = + 2 * (vertexCertificate (encQ := encQ) (encT := encT)).space x + (encNode encQ encT x).length + + (x.2.1.2.vertices[x.2.2.2]?).elim ((zeroCertificate (encT := encT) hops).space x) + fun v => (queryCertificate (encQ := encQ) (encT := encT)).space (x, v) + ev.space (v.2, wordBits x.2.2.1 v.1) := rfl /-- The number of steps of direct summation: `2 (2 ^ m (k + 1) + 1) - 1`. -/ -def fuel (q : ℕ × DescribedNetwork T) : ℕ := +def fuel (q : Q × DescribedNetwork T) : ℕ := 2 * (2 ^ q.2.numEdges * (q.2.vertices.length + 1)) + 1 /-- The fuel in polynomial time. -/ -theorem fuel_polyTime : ComputableInPolyTime (fuel (T := T)) (encInput encT) encNat := by - have hd := ComputableInPolyTime.snd encUnary (encDescribed encT) +theorem fuel_polyTime : ComputableInPolyTime (fuel (Q := Q) (T := T)) (encInput encQ encT) + encNat := by + have hd := ComputableInPolyTime.snd encQ (encDescribed encT) have hm := numEdges_polyTime.comp hd have hlen := (ComputableInPolyTime.length_list _).comp (vertices_polyTime.comp hd) exact ComputableInPolyTime.binary_add.comp ((ComputableInPolyTime.binary_mul' @@ -567,51 +756,49 @@ theorem fuel_polyTime : ComputableInPolyTime (fuel (T := T)) (encInput encT) enc (ComputableInPolyTime.const 1))))).prod (ComputableInPolyTime.const 1)) /-- The root in polynomial time. -/ -theorem sumRoot_polyTime : ComputableInPolyTime (sumRoot (T := T)) (encInput encT) - (encNode encT) := +theorem sumRoot_polyTime : ComputableInPolyTime (sumRoot (Q := Q) (T := T)) (encInput encQ encT) + (encNode encQ encT) := (ComputableInPolyTime.const 0).prod (ComputableInPolyTime.id'.prod (ComputableInPolyTime.const ([], 0))) variable (eval) in -/-- **One native machine for direct summation over described networks**, from a native -certificate of the evaluator. It returns `some (d.contraction eval % 2 ^ P)` +/-- **One native machine for direct summation over described networks**, from polynomial-time +operations and a native certificate of the evaluator. It returns `some (sumValue ops eval q d)` (`run_sumScheme`). -/ -noncomputable def sumCertificate - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - NativeCertificate ((sumScheme eval).run fuel sumRoot) (encInput encT) (encOption encNat) := - Scheme.certificate sumScheme_polyTime (leafCertificate eval ev) +noncomputable def sumCertificate (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + NativeCertificate ((sumScheme ops eval).run fuel sumRoot) (encInput encQ encT) + (encOption encY) := + Scheme.certificate (sumScheme_polyTime hops) (leafCertificate eval hops ev) (NativeCertificate.ofPolyTime fuel_polyTime) (NativeCertificate.ofPolyTime sumRoot_polyTime) -/-- **The machine computes the contraction modulo `2 ^ P`.** -/ -theorem run_sumScheme (q : ℕ × DescribedNetwork T) : - (sumScheme eval).run fuel sumRoot q = some (q.2.contraction eval % 2 ^ q.1) := by - obtain ⟨h, -, he⟩ := eval_sumRoot (eval := eval) q.1 q.2 +/-- **The machine computes `sumValue`.** -/ +theorem run_sumScheme (q : Q × DescribedNetwork T) : + (sumScheme ops eval).run fuel sumRoot q = some (sumValue ops eval q.1 q.2) := by + obtain ⟨h, -, he⟩ := eval_sumRoot (ops := ops) (eval := eval) q.1 q.2 exact Scheme.run_eq_some he (by simp [fuel]; omega) variable (eval) in -/-- **Direct summation over described networks, as a native certificate** for the contraction -modulo `2 ^ P` on every input `(P, d)`, from a native certificate of the evaluator. -/ -noncomputable def contractionCertificate - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - NativeCertificate (fun q : ℕ × DescribedNetwork T => q.2.contraction eval % 2 ^ q.1) - (encInput encT) encNat := - (NativeCertificate.post (sumCertificate eval ev) - (ComputableInDegree.getD (enc₁ := encNat) (enc := encOption encNat) 0).inO_one).congr +/-- **Direct summation over described networks, as a native certificate** for `sumValue` on +every input `(q, d)`, from polynomial-time operations and a native certificate of the +evaluator. -/ +noncomputable def contractionCertificate [Inhabited Y] (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + NativeCertificate (fun q : Q × DescribedNetwork T => sumValue ops eval q.1 q.2) + (encInput encQ encT) encY := + (NativeCertificate.post (sumCertificate eval hops ev) + (ComputableInDegree.getD (enc₁ := encY) (enc := encOption encY) default).inO_one).congr fun q => by simp [run_sumScheme] /-- The time is that of the stack machine. -/ -@[simp] theorem contractionCertificate_time - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - (contractionCertificate eval ev).time = (sumCertificate eval ev).time := rfl +@[simp] theorem contractionCertificate_time [Inhabited Y] (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + (contractionCertificate eval hops ev).time = (sumCertificate eval hops ev).time := rfl /-- The space is that of the stack machine. -/ -@[simp] theorem contractionCertificate_space - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - (contractionCertificate eval ev).space = (sumCertificate eval ev).space := rfl +@[simp] theorem contractionCertificate_space [Inhabited Y] (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + (contractionCertificate eval hops ev).space = (sumCertificate eval hops ev).space := rfl /-! ### Resources -/ @@ -625,8 +812,8 @@ theorem length_numEdges_vertices_le (d : DescribedNetwork T) : omega /-- A node of the right shape is at most `5 n + 7` long, for `n` the length of the input. -/ -theorem length_encNode_le {q : ℕ × DescribedNetwork T} {x : Node T} (hx : NodeShape q x) : - (encNode encT x).length ≤ 5 * (encInput encT q).length + 7 := by +theorem length_encNode_le {q : Q × DescribedNetwork T} {x : Node Q T} (hx : NodeShape q x) : + (encNode encQ encT x).length ≤ 5 * (encInput encQ encT q).length + 7 := by obtain ⟨hq, hk, hw, hv⟩ := hx obtain ⟨k, ⟨P, d⟩, w, v⟩ := x simp only at hq hk hw hv @@ -650,51 +837,59 @@ private theorem length_encNat_lt {y P : ℕ} (h : y < 2 ^ P) : (encNat y).length exact h' /-- **The resources of direct summation over described networks.** There is `K` such that, for -an input `(P, d)` of length `n` with `m` edges and `k` vertices, if the evaluator takes time at +an input `(q, d)` of length `n` with `m` edges and `k` vertices, if the evaluator takes time at most `E` and space at most `ES` and outputs at most `Ev` bits on every vertex and every list of -bits on its ports, then the time is at most `2 ^ m (k + 1) + 1` times -`K (n + Ev + 1) ^ K + 2 E`, and the space at most `K (n + Ev + 1) ^ K + ES`. -/ -theorem contractionCertificate_le - (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) - encNat) : - ∃ K : ℕ, ∀ (q : ℕ × DescribedNetwork T) (E ES Ev : ℕ), +bits on its ports, and the reduced values at `q` are at most `Rv` bits, then the time is at most +`2 ^ m (k + 1) + 1` times `K (n + Ev + Rv + 1) ^ K + 2 E`, and the space at most +`K (n + Ev + Rv + 1) ^ K + ES`. -/ +theorem contractionCertificate_le [Inhabited Y] (hops : ops.PolyTime encQ encY) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) encY) : + ∃ K : ℕ, ∀ (q : Q × DescribedNetwork T) (E ES Ev Rv : ℕ), (∀ v ∈ q.2.vertices, ∀ bs : List Bool, bs.length = v.1.length → - ev.time (v.2, bs) ≤ E ∧ ev.space (v.2, bs) ≤ ES ∧ (encNat (eval v.2 bs)).length ≤ Ev) → - (contractionCertificate eval ev).time q ≤ + ev.time (v.2, bs) ≤ E ∧ ev.space (v.2, bs) ≤ ES ∧ (encY (eval v.2 bs)).length ≤ Ev) → + (∀ y, ops.Reduced q.1 y → (encY y).length ≤ Rv) → + (contractionCertificate eval hops ev).time q ≤ (2 ^ q.2.numEdges * (q.2.vertices.length + 1) + 1) * - (K * ((encInput encT q).length + Ev + 1) ^ K + 2 * E) ∧ - (contractionCertificate eval ev).space q ≤ - K * ((encInput encT q).length + Ev + 1) ^ K + ES := by - obtain ⟨K₀, hK₀⟩ := Scheme.certificate_le (sumScheme_polyTime (eval := eval) (encT := encT)) - (leafCertificate eval ev) - have hV : (vertexCertificate (encT := encT)).IsPolynomial := + (K * ((encInput encQ encT q).length + Ev + Rv + 1) ^ K + 2 * E) ∧ + (contractionCertificate eval hops ev).space q ≤ + K * ((encInput encQ encT q).length + Ev + Rv + 1) ^ K + ES := by + obtain ⟨K₀, hK₀⟩ := Scheme.certificate_le + (sumScheme_polyTime (ops := ops) (eval := eval) (encT := encT) hops) + (leafCertificate eval hops ev) + have hV : (vertexCertificate (encQ := encQ) (encT := encT)).IsPolynomial := + NativeCertificate.ofPolyTime_isPolynomial _ + have hQ : (queryCertificate (encQ := encQ) (encT := encT)).IsPolynomial := NativeCertificate.ofPolyTime_isPolynomial _ - have hQ : (queryCertificate (encT := encT)).IsPolynomial := + have hZ : (zeroCertificate (encT := encT) hops).IsPolynomial := NativeCertificate.ofPolyTime_isPolynomial _ - have hF := NativeCertificate.ofPolyTime_isPolynomial (fuel_polyTime (encT := encT)) - have hR := NativeCertificate.ofPolyTime_isPolynomial (sumRoot_polyTime (encT := encT)) + have hF := NativeCertificate.ofPolyTime_isPolynomial (fuel_polyTime (encQ := encQ) (encT := encT)) + have hR := NativeCertificate.ofPolyTime_isPolynomial + (sumRoot_polyTime (encQ := encQ) (encT := encT)) obtain ⟨cv, dv, hvt⟩ := hV.1.exists_le obtain ⟨cv', dv', hvs⟩ := hV.2.exists_le obtain ⟨cq, dq, hqt⟩ := hQ.1.exists_le obtain ⟨cq', dq', hqs⟩ := hQ.2.exists_le + obtain ⟨cz, dz, hzt⟩ := hZ.1.exists_le + obtain ⟨cz', dz', hzs⟩ := hZ.2.exists_le obtain ⟨cf, df, hft⟩ := hF.1.exists_le obtain ⟨cf', df', hfs⟩ := hF.2.exists_le obtain ⟨cr, dr, hrt⟩ := hR.1.exists_le obtain ⟨cr', dr', hrs⟩ := hR.2.exists_le - obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dv + dv' + dq + dq' + df + df' + dr + dr' + 2 := ⟨_, rfl⟩ - obtain ⟨G, hG⟩ : ∃ G, G = K₀ * 96 ^ K₀ + (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) + 16 := - ⟨_, rfl⟩ - obtain ⟨G', hG'⟩ : ∃ G', G' = K₀ * 96 ^ K₀ + (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') := + obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dv + dv' + dq + dq' + dz + dz' + df + df' + dr + dr' + 2 := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₀ * 96 ^ K₀ + + (2 * cv * 13 ^ dv + 12 + cz * 13 ^ dz + cq * 27 ^ dq) + 16 := ⟨_, rfl⟩ + obtain ⟨G', hG'⟩ : ∃ G', G' = K₀ * 96 ^ K₀ + + (2 * cv' * 13 ^ dv' + 12 + cz' * 13 ^ dz' + cq' * 27 ^ dq') := ⟨_, rfl⟩ obtain ⟨C, hC⟩ : ∃ C, C = cf + cr + cf' + cr' + 2 * G + G' + 4 := ⟨_, rfl⟩ - refine ⟨C + D, fun q E ES Ev hE => ?_⟩ + refine ⟨C + D, fun q E ES Ev Rv hE hRv => ?_⟩ obtain ⟨P, d⟩ := q - obtain ⟨n, hn⟩ : ∃ n, n = (encInput encT (P, d)).length := ⟨_, rfl⟩ - obtain ⟨z, hz⟩ : ∃ z, z = n + Ev := ⟨_, rfl⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encInput encQ encT (P, d)).length := ⟨_, rfl⟩ + obtain ⟨z, hz⟩ : ∃ z, z = n + Ev + Rv := ⟨_, rfl⟩ obtain ⟨W, hW⟩ : ∃ W, W = (z + 1) ^ D := ⟨_, rfl⟩ rw [← hn] - rw [show n + Ev + 1 = z + 1 by omega] - have hnP : 2 * P + 1 + (encDescribed encT d).length = n := by + rw [show n + Ev + Rv + 1 = z + 1 by omega] + have hnP : 2 * (encQ P).length + 1 + (encDescribed encT d).length = n := by rw [hn]; simp [encInput] have hmk := length_numEdges_vertices_le (encT := encT) d -- Every vertex is at most `n` long. @@ -709,63 +904,79 @@ theorem contractionCertificate_le (d.numEdges, d.vertices)).length simp only [length_encPair]; omega omega - have hnode (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : - (encNode encT x).length ≤ 5 * z + 7 := (length_encNode_le hx).trans (by omega) - have hleafT (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : - (leafCertificate eval ev).time x ≤ - 2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E := by + have hnode (x : Node Q T) (hx : (sumInvariant ops eval (P, d)).node x) : + (encNode encQ encT x).length ≤ 5 * z + 7 := (length_encNode_le hx).trans (by omega) + have hleafT (x : Node Q T) (hx : (sumInvariant ops eval (P, d)).node x) : + (leafCertificate eval hops ev).time x ≤ 2 * cv * (5 * z + 8) ^ dv + (5 * z + 7) + + cz * (5 * z + 8) ^ dz + cq * (11 * z + 16) ^ dq + E := by have h1 := hnode x hx rw [leafCertificate_time] - have h2 : (vertexCertificate (encT := encT)).time x ≤ cv * (5 * z + 8) ^ dv := + have h2 : (vertexCertificate (encQ := encQ) (encT := encT)).time x ≤ + cv * (5 * z + 8) ^ dv := (hvt x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) - have h2' : 2 * (vertexCertificate (encT := encT)).time x ≤ 2 * cv * (5 * z + 8) ^ dv := by + have h2' : 2 * (vertexCertificate (encQ := encQ) (encT := encT)).time x ≤ + 2 * cv * (5 * z + 8) ^ dv := by rw [Nat.mul_assoc]; exact Nat.mul_le_mul_left 2 h2 cases hvx : x.2.1.2.vertices[x.2.2.2]? with - | none => simp only [Option.elim]; omega + | none => + simp only [Option.elim] + have h3 : (zeroCertificate (encT := encT) hops).time x ≤ cz * (5 * z + 8) ^ dz := + (hzt x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) + omega | some v => simp only [Option.elim] have hmem : v ∈ x.2.1.2.vertices := List.mem_of_getElem? hvx obtain ⟨hq, -, -, -⟩ := hx rw [hq] at hmem have h3 := hvlen v hmem - have h4 : (queryCertificate (encT := encT)).time (x, v) ≤ cq * (11 * z + 16) ^ dq := by + have h4 : (queryCertificate (encQ := encQ) (encT := encT)).time (x, v) ≤ + cq * (11 * z + 16) ^ dq := by refine (hqt (x, v)).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left ?_ _)) - have := length_encPair (enc₁ := encNode encT) (enc₂ := encPair (encList encNat) encT) - (x, v) + have := length_encPair (enc₁ := encNode encQ encT) + (enc₂ := encPair (encList encNat) encT) (x, v) dsimp only at this omega have h5 := (hE v hmem (wordBits x.2.2.1 v.1) (by simp [wordBits])).1 omega - have hleafS (x : Node T) (hx : (sumInvariant eval (P, d)).node x) : - (leafCertificate eval ev).space x ≤ - 2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' + ES := by + have hleafS (x : Node Q T) (hx : (sumInvariant ops eval (P, d)).node x) : + (leafCertificate eval hops ev).space x ≤ 2 * cv' * (5 * z + 8) ^ dv' + (5 * z + 7) + + cz' * (5 * z + 8) ^ dz' + cq' * (11 * z + 16) ^ dq' + ES := by have h1 := hnode x hx rw [leafCertificate_space] - have h2 : (vertexCertificate (encT := encT)).space x ≤ cv' * (5 * z + 8) ^ dv' := + have h2 : (vertexCertificate (encQ := encQ) (encT := encT)).space x ≤ + cv' * (5 * z + 8) ^ dv' := (hvs x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) - have h2' : 2 * (vertexCertificate (encT := encT)).space x ≤ 2 * cv' * (5 * z + 8) ^ dv' := by + have h2' : 2 * (vertexCertificate (encQ := encQ) (encT := encT)).space x ≤ + 2 * cv' * (5 * z + 8) ^ dv' := by rw [Nat.mul_assoc]; exact Nat.mul_le_mul_left 2 h2 cases hvx : x.2.1.2.vertices[x.2.2.2]? with - | none => simp only [Option.elim]; omega + | none => + simp only [Option.elim] + have h3 : (zeroCertificate (encT := encT) hops).space x ≤ cz' * (5 * z + 8) ^ dz' := + (hzs x).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) _)) + omega | some v => simp only [Option.elim] have hmem : v ∈ x.2.1.2.vertices := List.mem_of_getElem? hvx obtain ⟨hq, -, -, -⟩ := hx rw [hq] at hmem have h3 := hvlen v hmem - have h4 : (queryCertificate (encT := encT)).space (x, v) ≤ cq' * (11 * z + 16) ^ dq' := by + have h4 : (queryCertificate (encQ := encQ) (encT := encT)).space (x, v) ≤ + cq' * (11 * z + 16) ^ dq' := by refine (hqs (x, v)).trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left ?_ _)) - have := length_encPair (enc₁ := encNode encT) (enc₂ := encPair (encList encNat) encT) - (x, v) + have := length_encPair (enc₁ := encNode encQ encT) + (enc₂ := encPair (encList encNat) encT) (x, v) dsimp only at this omega have h5 := (hE v hmem (wordBits x.2.2.1 v.1) (by simp [wordBits])).2.1 omega - obtain ⟨h, hh, he⟩ := eval_sumRoot (eval := eval) P d + obtain ⟨h, hh, he⟩ := eval_sumRoot (ops := ops) (eval := eval) P d have hres := hK₀ (NativeCertificate.ofPolyTime fuel_polyTime) - (NativeCertificate.ofPolyTime sumRoot_polyTime) (sumInvariant eval (P, d)) (13 * z + 18) - (2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E) - (2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' + ES) + (NativeCertificate.ofPolyTime sumRoot_polyTime) (sumInvariant ops eval (P, d)) (13 * z + 18) + (2 * cv * (5 * z + 8) ^ dv + (5 * z + 7) + cz * (5 * z + 8) ^ dz + + cq * (11 * z + 16) ^ dq + E) + (2 * cv' * (5 * z + 8) ^ dv' + (5 * z + 7) + cz' * (5 * z + 8) ^ dz' + + cq' * (11 * z + 16) ^ dq' + ES) (fun x hx => ⟨(hnode x hx).trans (by omega), fun _ => ⟨hleafT x hx, hleafS x hx⟩⟩) (fun x a ha => by obtain ⟨hx, ha1, ha2⟩ := ha @@ -779,15 +990,15 @@ theorem contractionCertificate_le rw [pow_succ, pow_add, Nat.mul_two] exact Nat.add_le_add (Nat.le_mul_of_pos_right _ (Nat.two_pow_pos _)) (Nat.le_mul_of_pos_left _ (Nat.two_pow_pos _)) - have h3 := length_encNat_lt ha2 - have h4 : (encPair (encNode encT) encAcc (x, a)).length = - 2 * (encNode encT x).length + 1 + (encAcc a).length := length_encPair (x, a) - have h5 : (encAcc a).length = 2 * (encNat a.1).length + 1 + (encNat a.2).length := + have h3 := hRv a.2 ha2 + have h4 : (encPair (encNode encQ encT) (encAcc encY) (x, a)).length = + 2 * (encNode encQ encT x).length + 1 + (encAcc encY a).length := length_encPair (x, a) + have h5 : (encAcc encY a).length = 2 * (encNat a.1).length + 1 + (encY a.2).length := length_encPair a omega) (fun y hy => by rcases hy with hy | ⟨v, hv, w, -, rfl⟩ - · have := length_encNat_lt hy; omega + · have := hRv y hy; omega · have := (hE v hv (wordBits w v.1) (by simp [wordBits])).2.2; omega) (P, d) _ _ _ he rw [← hn] at hres @@ -821,19 +1032,25 @@ theorem contractionCertificate_le have h3 : (h + 1) * (13 * z + 18 + 1) ≤ 39 * z + 57 := by have := Nat.mul_le_mul_right (13 * z + 18 + 1) (show h + 1 ≤ 3 by omega); omega exact (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h3 _)).trans (hterm (by omega)) - have hTL : 2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq ≤ - (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W := by + have hTL : 2 * cv * (5 * z + 8) ^ dv + (5 * z + 7) + cz * (5 * z + 8) ^ dz + + cq * (11 * z + 16) ^ dq ≤ (2 * cv * 13 ^ dv + 12 + cz * 13 ^ dz + cq * 27 ^ dq) * W := by have a1 := hterm (c := 2 * cv) (a := 5) (b := 8) (show dv ≤ D by omega) - have a2 := hterm (c := cq) (a := 11) (b := 16) (show dq ≤ D by omega) - have e : (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W = - 2 * cv * (5 + 8) ^ dv * W + 24 * W + cq * (11 + 16) ^ dq * W := by ring + have a2 := hterm (c := cz) (a := 5) (b := 8) (show dz ≤ D by omega) + have a3 := hterm (c := cq) (a := 11) (b := 16) (show dq ≤ D by omega) + have e : (2 * cv * 13 ^ dv + 12 + cz * 13 ^ dz + cq * 27 ^ dq) * W = + 2 * cv * (5 + 8) ^ dv * W + 12 * W + cz * (5 + 8) ^ dz * W + cq * (11 + 16) ^ dq * W := by + ring omega - have hSL : 2 * cv' * (5 * z + 8) ^ dv' + 2 * (5 * z + 7) + cq' * (11 * z + 16) ^ dq' ≤ - (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W := by + have hSL : 2 * cv' * (5 * z + 8) ^ dv' + (5 * z + 7) + cz' * (5 * z + 8) ^ dz' + + cq' * (11 * z + 16) ^ dq' ≤ + (2 * cv' * 13 ^ dv' + 12 + cz' * 13 ^ dz' + cq' * 27 ^ dq') * W := by have a1 := hterm (c := 2 * cv') (a := 5) (b := 8) (show dv' ≤ D by omega) - have a2 := hterm (c := cq') (a := 11) (b := 16) (show dq' ≤ D by omega) - have e : (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W = - 2 * cv' * (5 + 8) ^ dv' * W + 24 * W + cq' * (11 + 16) ^ dq' * W := by ring + have a2 := hterm (c := cz') (a := 5) (b := 8) (show dz' ≤ D by omega) + have a3 := hterm (c := cq') (a := 11) (b := 16) (show dq' ≤ D by omega) + have e : (2 * cv' * 13 ^ dv' + 12 + cz' * 13 ^ dz' + cq' * 27 ^ dq') * W = + 2 * cv' * (5 + 8) ^ dv' * W + 12 * W + cz' * (5 + 8) ^ dz' * W + + cq' * (11 + 16) ^ dq' * W := by + ring omega have hclock : ((encNat (fuel (P, d))).length + 1) ^ 2 ≤ 16 * W := by have h3 : (encNat (fuel (P, d))).length + 1 ≤ 4 * (z + 1) := by omega @@ -856,15 +1073,17 @@ theorem contractionCertificate_le constructor · rw [hfuel1] at htime obtain ⟨S, hS⟩ : ∃ S, S = K₀ * ((h + 1) * (13 * z + 18 + 1)) ^ K₀ + - (2 * cv * (5 * z + 8) ^ dv + 2 * (5 * z + 7) + cq * (11 * z + 16) ^ dq + E) + + (2 * cv * (5 * z + 8) ^ dv + (5 * z + 7) + cz * (5 * z + 8) ^ dz + + cq * (11 * z + 16) ^ dq + E) + ((encNat (fuel (P, d))).length + 1) ^ 2 := ⟨_, rfl⟩ rw [← hS] at htime have hstep : S ≤ G * W + E := by - have e : G * W = K₀ * 96 ^ K₀ * W + (2 * cv * 13 ^ dv + 24 + cq * 27 ^ dq) * W + 16 * W := by + have e : G * W = K₀ * 96 ^ K₀ * W + + (2 * cv * 13 ^ dv + 12 + cz * 13 ^ dz + cq * 27 ^ dq) * W + 16 * W := by rw [hG]; ring omega have hm := Nat.mul_le_mul_left (2 * B) hstep - calc (contractionCertificate eval ev).time (P, d) + calc (contractionCertificate eval hops ev).time (P, d) ≤ cf * W + cr * W + 2 * W + 2 * B * (G * W + E) := by rw [contractionCertificate_time]; unfold sumCertificate; omega _ ≤ B * ((cf + cr + 2) * W) + B * (2 * G * W) + 2 * B * E := by @@ -878,17 +1097,70 @@ theorem contractionCertificate_le _ = B * (C * W + 2 * E) := by ring _ ≤ B * ((C + D) * (z + 1) ^ (C + D) + 2 * E) := Nat.mul_le_mul_left _ (Nat.add_le_add_right hCD _) - · have h1 : (contractionCertificate eval ev).space (P, d) ≤ C * W + ES := by + · have h1 : (contractionCertificate eval hops ev).space (P, d) ≤ C * W + ES := by rw [contractionCertificate_space] unfold sumCertificate have e : C * W = cf * W + cr * W + cf' * W + cr' * W + 2 * G * W + G' * W + 4 * W := by rw [hC]; ring - have e' : G' * W = K₀ * 96 ^ K₀ * W + (2 * cv' * 13 ^ dv' + 24 + cq' * 27 ^ dq') * W := by + have e' : G' * W = K₀ * 96 ^ K₀ * W + + (2 * cv' * 13 ^ dv' + 12 + cz' * 13 ^ dz' + cq' * 27 ^ dq') * W := by rw [hG']; ring have hfl' : (encNat (fuel (P, d))).length ≤ W := by omega omega exact h1.trans (Nat.add_le_add_right hCD _) +/-! ### The two instances -/ + +/-- Natural numbers modulo `2 ^ P` are polynomial-time, with `P` in unary. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.mod_polyTime : + SumOps.mod.PolyTime encUnary encNat where + zero := ComputableInPolyTime.const 0 + one := GaloisRingArith.computableInPolyTime_mod_two_pow (ComputableInPolyTime.const 1) + ComputableInPolyTime.id' + add := GaloisRingArith.computableInPolyTime_mod_two_pow + (ComputableInPolyTime.binary_add.comp ((ComputableInPolyTime.fst encNat encNat).comp + (ComputableInPolyTime.snd _ _) |>.prod ((ComputableInPolyTime.snd encNat encNat).comp + (ComputableInPolyTime.snd _ _)))) (ComputableInPolyTime.fst _ _) + mul := GaloisRingArith.computableInPolyTime_mod_two_pow + (ComputableInPolyTime.binary_mul' ((ComputableInPolyTime.fst encNat encNat).comp + (ComputableInPolyTime.snd _ _)) ((ComputableInPolyTime.snd encNat encNat).comp + (ComputableInPolyTime.snd _ _))) (ComputableInPolyTime.fst _ _) + +/-- Coefficient lists are polynomial-time, with `p` in unary and the coefficients of `f` in +binary. -/ +theorem _root_.Cslib.Circuits.Frontier.SumOps.galois_polyTime : + SumOps.galois.PolyTime (encPair encUnary (encList encNat)) (encList encNat) where + zero := GaloisRingArith.computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp (ComputableInPolyTime.snd _ _)) + (ComputableInPolyTime.const 0) + one := GaloisRingArith.computableInPolyTime_one (ComputableInPolyTime.snd _ _) + add := by + have hq := ComputableInPolyTime.fst (encPair encUnary (encList encNat)) + (encPair (encList encNat) (encList encNat)) + have hcd := ComputableInPolyTime.snd (encPair encUnary (encList encNat)) + (encPair (encList encNat) (encList encNat)) + exact GaloisRingArith.computableInPolyTime_add ((ComputableInPolyTime.fst _ _).comp hq) + ((ComputableInPolyTime.snd _ _).comp hq) ((ComputableInPolyTime.fst _ _).comp hcd) + ((ComputableInPolyTime.snd _ _).comp hcd) + mul := by + have hq := ComputableInPolyTime.fst (encPair encUnary (encList encNat)) + (encPair (encList encNat) (encList encNat)) + have hcd := ComputableInPolyTime.snd (encPair encUnary (encList encNat)) + (encPair (encList encNat) (encList encNat)) + exact GaloisRingArith.computableInPolyTime_mul ((ComputableInPolyTime.fst _ _).comp hq) + ((ComputableInPolyTime.snd _ _).comp hq) ((ComputableInPolyTime.fst _ _).comp hcd) + ((ComputableInPolyTime.snd _ _).comp hcd) + +/-- **Direct summation modulo `2 ^ P`**: one native machine for `d.contraction eval % 2 ^ P` on +every input `(P, d)`, with `P` in unary, from a native certificate of a natural-number +evaluator. -/ +noncomputable def modContractionCertificate (eval : T → List Bool → ℕ) + (ev : NativeCertificate (fun q : T × List Bool => eval q.1 q.2) (encPair encT encBits) + encNat) : + NativeCertificate (fun q : ℕ × DescribedNetwork T => q.2.contraction eval % 2 ^ q.1) + (encInput encUnary encT) encNat := + (contractionCertificate eval SumOps.mod_polyTime ev).congr fun q => sumValue_mod eval q.1 q.2 + end Native end DescribedNetwork diff --git a/ComplexityTests/DescribedContraction.lean b/ComplexityTests/DescribedContraction.lean index 7581ad62a3..ac5647045b 100644 --- a/ComplexityTests/DescribedContraction.lean +++ b/ComplexityTests/DescribedContraction.lean @@ -12,8 +12,8 @@ import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListP /-! # Direct summation over described networks Tables as descriptors, with table lookup as the evaluator: the stack machine on a one-edge -network, the agreement with explicit networks, and one native machine for the contraction with -its resource theorem. -/ +network modulo `2 ^ P` and over coefficient lists of `(ℤ / 4)[U] / (U ^ 2 + U + 1)`, the agreement +with explicit networks, and one native machine for the contraction with its resource theorem. -/ namespace ComplexityTests.DescribedContraction @@ -26,20 +26,44 @@ def oneEdge : DescribedNetwork (List ℕ) := ⟨1, [([0], [2, 3]), ([0], [5, 7]) example : oneEdge.contraction ExplicitTensorNetwork.entry = 31 := by decide --- The stack machine, with fuel `2 (2 ^ 1 * 3) + 1 = 13`, returns `31 % 16 = 15`. -example : (sumScheme ExplicitTensorNetwork.entry).run fuel sumRoot (4, oneEdge) = some 15 := by +-- The stack machine modulo `2 ^ 4`, with fuel `2 (2 ^ 1 * 3) + 1 = 13`, returns `31 % 16 = 15`. +example : (sumScheme SumOps.mod ExplicitTensorNetwork.entry).run fuel sumRoot (4, oneEdge) = + some 15 := by decide -- With one step fewer, the sum still waits for the second assignment's weight -- `3 * 7 = 21 ≡ 5`. -example : (sumScheme ExplicitTensorNetwork.entry).run (fun _ => 12) sumRoot (4, oneEdge) = - some 5 := by +example : (sumScheme SumOps.mod ExplicitTensorNetwork.entry).run (fun _ => 12) sumRoot + (4, oneEdge) = some 5 := by decide --- On every input, the run is the contraction modulo `2 ^ P`, for any evaluator. +-- On every input, the run modulo `2 ^ P` is the contraction modulo `2 ^ P`, for any evaluator. example {T : Type} (eval : T → List Bool → ℕ) (q : ℕ × DescribedNetwork T) : - (sumScheme eval).run fuel sumRoot q = some (q.2.contraction eval % 2 ^ q.1) := - run_sumScheme q + (sumScheme SumOps.mod eval).run fuel sumRoot q = some (q.2.contraction eval % 2 ^ q.1) := by + rw [run_sumScheme, sumValue_mod] + +/-- One edge between two vertices whose entries are coefficient lists of +`(ℤ / 4)[U] / (U ^ 2 + U + 1)`: `U, 1` and `U, 3`. The contraction is +`U ^ 2 + 3 = 2 - U`, the list `[2, 3]`. -/ +def galoisEdge : DescribedNetwork (List (List ℕ)) := + ⟨1, [([0], [[0, 1], [1, 0]]), ([0], [[0, 1], [3, 0]])]⟩ + +/-- Table lookup on coefficient lists. -/ +def galoisEntry (t : List (List ℕ)) (bs : List Bool) : List ℕ := + t.getD (lsbValue bs) [] + +-- The stack machine over coefficient lists at the precision `(2, [1, 1])`. +example : (sumScheme SumOps.galois galoisEntry).run fuel sumRoot ((2, [1, 1]), galoisEdge) = + some [2, 3] := by + decide + +-- On every input, the value of the run in `A_p` is the contraction of the values of the entries. +example {T : Type} (eval : T → List Bool → List ℕ) (q : (ℕ × List ℕ) × DescribedNetwork T) : + (sumScheme SumOps.galois eval).run fuel sumRoot q = + some (sumValue SumOps.galois eval q.1 q.2) ∧ + Cslib.GaloisRingArith.val q.1.1 q.1.2 (sumValue SumOps.galois eval q.1 q.2) = + q.2.contraction fun t bs => Cslib.GaloisRingArith.val q.1.1 q.1.2 (eval t bs) := + ⟨run_sumScheme q, val_sumValue_galois eval q.1 q.2⟩ -- Explicit networks are described networks with their tables. example (d : ExplicitTensorNetwork ℕ) : @@ -55,25 +79,36 @@ theorem entry_polyTime : ComputableInPolyTime (fun q : List ℕ × List Bool => (ComputableInPolyTime.fst (encList encNat) encBits) hindex).of_enc_eq fun q => ?_ simp only [Function.comp_def, ExplicitTensorNetwork.entry, lsbValue_eq_binaryValue_reverse] -/-- **One native machine contracts explicit networks** given as described networks with their -tables, from the table-lookup certificate. -/ +/-- **One native machine contracts explicit networks** modulo `2 ^ P`, given as described +networks with their tables, from the table-lookup certificate. -/ noncomputable def tableCertificate : Cslib.GenericRAM.NativeCertificate (fun q : ℕ × DescribedNetwork (List ℕ) => q.2.contraction ExplicitTensorNetwork.entry % 2 ^ q.1) - (encInput (encList encNat)) encNat := - contractionCertificate ExplicitTensorNetwork.entry + (encInput encUnary (encList encNat)) encNat := + modContractionCertificate ExplicitTensorNetwork.entry (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime) --- The resource theorem applies to it. +-- The resource theorem applies to it, with residues of at most `P` bits. example : ∃ K : ℕ, ∀ (q : ℕ × DescribedNetwork (List ℕ)) (E ES Ev : ℕ), (∀ v ∈ q.2.vertices, ∀ bs : List Bool, bs.length = v.1.length → (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime).time (v.2, bs) ≤ E ∧ (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime).space (v.2, bs) ≤ ES ∧ (encNat (ExplicitTensorNetwork.entry v.2 bs)).length ≤ Ev) → tableCertificate.time q ≤ (2 ^ q.2.numEdges * (q.2.vertices.length + 1) + 1) * - (K * ((encInput (encList encNat) q).length + Ev + 1) ^ K + 2 * E) ∧ - tableCertificate.space q ≤ K * ((encInput (encList encNat) q).length + Ev + 1) ^ K + ES := - contractionCertificate_le _ + (K * ((encInput encUnary (encList encNat) q).length + Ev + q.1 + 1) ^ K + 2 * E) ∧ + tableCertificate.space q ≤ + K * ((encInput encUnary (encList encNat) q).length + Ev + q.1 + 1) ^ K + ES := by + obtain ⟨K, hK⟩ := contractionCertificate_le (ops := SumOps.mod) + (encT := encList encNat) SumOps.mod_polyTime + (Cslib.GenericRAM.NativeCertificate.ofPolyTime entry_polyTime) + refine ⟨K, fun q E ES Ev hE => hK q E ES Ev q.1 hE fun y hy => ?_⟩ + have h := Nat.size_le.mpr (show y < 2 ^ q.1 from hy) + rw [← Nat.size_eq_bits_len] at h + exact h + +-- Coefficient lists are polynomial-time operations, with `p` in unary. +example : SumOps.galois.PolyTime (encPair encUnary (encList encNat)) (encList encNat) := + SumOps.galois_polyTime end ComplexityTests.DescribedContraction From 62dc6f5b477b45ee214b469849e0c8181c6ef6cc Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:27:53 -0400 Subject: [PATCH 18/33] feat(circuit/frontier): evaluate packet entries on coefficient lists of A_p Native evaluation of the packet entries of the child networks (Theorem D.1, section D.4.6) on GaloisRingArith coefficient lists: kernel values by the product formula of Lemma D.4's evaluation, cross ratios by Newton inversion of an element that is one modulo two, local forms, mode coefficients and values, row terms, and coefficient extraction along a list of modes. val_packetEntryList proves the value in A_p is packetEntry at the reduction of Z_(2); map_val_packetEntryList transports it along any ring map, using that ring maps from Z_(2) are unique when 2 is nilpotent (padicInteger_ringHom_ext). The 2-adic valuation is computed by halving (twoVal), so the functions reduce; tests check tiny packets by decide. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../TensorNetwork/PacketEvaluator.lean | 639 ++++++++++++++++++ .../TensorNetworkPacketEvaluator.lean | 80 +++ 3 files changed, 720 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean diff --git a/Complexity.lean b/Complexity.lean index faf65580db..652a35a911 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -884,6 +884,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCo public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean new file mode 100644 index 0000000000..93db7912a8 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean @@ -0,0 +1,639 @@ +/- +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.KernelProduct +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry +public import Complexity.Computability.PolyTime.GaloisRingArithmetic +public import Complexity.Foundations.NumberTheory.Padics.RatValuation +import Mathlib.Algebra.BigOperators.Group.List.Basic +import Mathlib.Data.List.GetD + +/-! +# Evaluating packet entries on coefficient lists + +The packet tensors of the child networks of the contraction algorithm of [OpenAI2026ETH] are +implicit: an entry is computed from the data of its packet (§D.4.6, p. 63). This file gives that +computation on the coefficient lists of `A_p = (ℤ / 2 ^ p)[U] / (f)`, with the arithmetic of +`Cslib.GaloisRingArith`, and proves that it is `TensorNetwork.packetEntry` in `A_p`, so in every +ring that `A_p` maps to. + +* **Scalars.** `ℤ_(2)` reduces to `A_p` (`NativePacket.scalarHom`), and any two ring maps from + `ℤ_(2)` to a ring in which `2` is nilpotent agree (`padicInteger_ringHom_ext`). An integer is a + constant list (`NativePacket.intConst`). +* **Kernel values** (`NativePacket.kernelValueList`) follow Lemma D.4's evaluation + (`kernelValue_eq_prod`, p. 60): the inverse of the odd part of `C'(0)`, times + `2 ^ (a + 1 - v_d) ζ` when `d ≠ t`, times the `2 ^ (a + 1) - 2` linear factors + `u_q + 2 ^ (a + 1 - v_q) ζ`. The odd part of `C'(0)` is a product of odd integers, so it is `1` + modulo `2` and `GaloisRingArith.inv` inverts it. +* **Cross ratios** (`NativePacket.crossRatioList`) invert `u₀ + 2 ^ (a + 1 - v) ζ`, which is `1` + modulo `2` since `u₀` is odd and `v ≤ i ≤ a`. +* **Local forms, mode coefficients, mode values and row terms** follow + `TensorNetwork.packetLocalForm`, `TensorNetwork.packetModeCoeff`, + `TensorNetwork.packetModeValue` and `TensorNetwork.packetRowTerm`, with the modes `(y₀, b)` as + pairs of natural numbers. +* **Coefficient extraction** (`NativePacket.listCoeffList`) is the recursion + `TensorNetwork.listCoeff` on coefficient lists, along a duplicate-free list of modes. + +The main result (`NativePacket.val_packetEntryList`) is that the value of +`NativePacket.packetEntryList` in `A_p` is `TensorNetwork.packetEntry` at the reduction of +`ℤ_(2)` and the value of `ζ`. Transported along a ring map `ψ : A_p → S` with `2` nilpotent in +`S` (`NativePacket.map_val_packetEntryList`), it is the packet entry over `S` at any ring map +`ℤ_(2) → S`; with `TensorNetwork.child_tensor_inr_cauchyModeSplitting`, it is the entry of a +packet vertex of a child network, for instance over `B ⧸ (2 ^ p)`. + +These are functions on lists of natural numbers with explicit recursion; their certificates of +time `exp(O(L log s))` and polynomial space are separate. + +## Main definitions + +* `Cslib.Circuits.Frontier.NativePacket.scalarHom`: the reduction `ℤ_(2) → A_p`. +* `Cslib.Circuits.Frontier.NativePacket.kernelValueList`, + `Cslib.Circuits.Frontier.NativePacket.crossRatioList`: kernel values and cross ratios. +* `Cslib.Circuits.Frontier.NativePacket.listCoeffList`: coefficient extraction. +* `Cslib.Circuits.Frontier.NativePacket.packetEntryList`: the entry of a packet vertex. + +## Main results + +* `Cslib.Circuits.Frontier.padicInteger_ringHom_ext`: ring maps from `ℤ_(2)` are unique when `2` + is nilpotent. +* `Cslib.Circuits.Frontier.NativePacket.val_kernelValueList`, + `Cslib.Circuits.Frontier.NativePacket.val_crossRatioList`, + `Cslib.Circuits.Frontier.NativePacket.val_listCoeffList`: correctness of the parts. +* `Cslib.Circuits.Frontier.NativePacket.val_packetEntryList`, + `Cslib.Circuits.Frontier.NativePacket.map_val_packetEntryList`: packet entries. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.4 and its + evaluation (pp. 59–60), §D.4.1 (p. 57) and §D.4.6 (p. 63). +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +open Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-- An odd natural number is a unit of a ring in which `2` is nilpotent. -/ +private theorem isUnit_natCast_of_odd {S : Type*} [CommRing S] {n : ℕ} (hn : (2 : S) ^ n = 0) + {m : ℕ} (hm : Odd m) : IsUnit (m : S) := by + obtain ⟨k, rfl⟩ := hm + rw [Nat.cast_add, Nat.cast_mul, Nat.cast_one, Nat.cast_ofNat, add_comm] + exact IsNilpotent.isUnit_one_add ⟨n, by rw [mul_pow, hn, zero_mul]⟩ + +/-- **Ring maps from `ℤ_(2)` are unique** into a ring in which `2` is nilpotent: an element +times its odd denominator is its numerator. -/ +theorem padicInteger_ringHom_ext {S : Type*} [CommRing S] {n : ℕ} (hn : (2 : S) ^ n = 0) + (φ ψ : ℤ₍₂₎ →+* S) : φ = ψ := by + ext x + have hden : ¬ 2 ∣ (x : ℚ).den := Rat.padicValuation_le_one_iff.mp x.2 + have h : x * ((x : ℚ).den : ℤ₍₂₎) = ((x : ℚ).num : ℤ₍₂₎) := + Subtype.ext (by push_cast; exact Rat.mul_den_eq_num _) + have h1 := congrArg φ h + have h2 := congrArg ψ h + rw [map_mul, map_natCast, map_intCast] at h1 h2 + exact (isUnit_natCast_of_odd hn (Nat.odd_iff.mpr (Nat.two_dvd_ne_zero.mp hden))).mul_right_cancel + (h1.trans h2.symm) + +namespace NativePacket + +open GaloisRingArith TensorNetwork + +variable {p : ℕ} {fl : List ℕ} + +/-! ### Scalars -/ + +/-- The reduction `ℤ_(2) → A_p`, through `ℤ / 2 ^ p`. -/ +noncomputable def scalarHom (p : ℕ) (fl : List ℕ) : ℤ₍₂₎ →+* Ring p fl := + (algebraMap (ZMod (2 ^ p)) (Ring p fl)).comp (Rat.padicIntegerToZModPow 2 p) + +/-- `2 ^ p = 0` in `A_p`. -/ +theorem two_pow_eq_zero : (2 : Ring p fl) ^ p = 0 := by + have h := natCast_two_pow (p := p) (fl := fl) + push_cast at h + exact h + +/-- With no coordinates, `A_p` is the zero ring. -/ +theorem subsingleton_of_length_eq_zero (h : fl.length = 0) : Subsingleton (Ring p fl) := by + set f := (monicOf fl).map (Int.castRingHom (ZMod (2 ^ p))) + have hf : f = 1 := by + simp only [f, List.length_eq_zero_iff.mp h, monicOf, List.length_nil, pow_zero, range_zero, + sum_empty, add_zero, Polynomial.map_one] + have h1 : AdjoinRoot.mk f f = AdjoinRoot.mk f 1 := congrArg _ hf + rw [AdjoinRoot.mk_self, map_one] at h1 + exact subsingleton_of_zero_eq_one h1 + +/-- The value of a tabulated list. -/ +theorem val_tabulate (g : ℕ → ℕ) : + val p fl ((List.range fl.length).map g) = + ∑ i ∈ range fl.length, (g i : Ring p fl) * AdjoinRoot.root _ ^ i := by + refine sum_congr rfl fun i hi => ?_ + rw [List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, List.getElem_range] + +/-- An integer as a constant coefficient list: its residue modulo `2 ^ p` in the first of the `e` +coordinates. -/ +def intConst (p : ℕ) (fl : List ℕ) (n : ℤ) : List ℕ := + (List.range fl.length).map fun j => if j = 0 then (n % 2 ^ p).toNat else 0 + +/-- The value of a constant list is the integer. -/ +@[simp] theorem val_intConst (n : ℤ) : val p fl (intConst p fl n) = n := by + rcases Nat.eq_zero_or_pos fl.length with h | h + · have := subsingleton_of_length_eq_zero (p := p) h + exact Subsingleton.elim _ _ + rw [intConst, val_tabulate, sum_eq_single 0 (fun j _ hj => by simp [hj]) + (fun h0 => absurd (mem_range.mpr h) h0)] + have hnn : 0 ≤ n % 2 ^ p := Int.emod_nonneg _ (pow_ne_zero _ two_ne_zero) + have h2 : ((2 ^ p : ℤ) : Ring p fl) = 0 := by + rw [Int.cast_pow, Int.cast_ofNat]; exact two_pow_eq_zero + simp only [↓reduceIte, pow_zero, mul_one] + rw [show (((n % 2 ^ p).toNat : ℕ) : Ring p fl) = ((n % 2 ^ p : ℤ) : Ring p fl) by + rw [← Int.cast_natCast, Int.toNat_of_nonneg hnn]] + rw [Int.emod_def, Int.cast_sub, Int.cast_mul, h2, zero_mul, sub_zero] + +/-- The product of a list of coefficient lists. -/ +def prodList (p : ℕ) (fl : List ℕ) (cs : List (List ℕ)) : List ℕ := + cs.foldl (mul p fl) (one fl) + +/-- The sum of a list of coefficient lists. -/ +def sumList (p : ℕ) (fl : List ℕ) (cs : List (List ℕ)) : List ℕ := + cs.foldl (add p fl) (zero fl) + +private theorem val_foldl_mul (cs : List (List ℕ)) (c : List ℕ) : + val p fl (cs.foldl (mul p fl) c) = val p fl c * (cs.map (val p fl)).prod := by + induction cs generalizing c with + | nil => simp + | cons d cs ih => rw [List.foldl_cons, ih, val_mul, List.map_cons, List.prod_cons, mul_assoc] + +private theorem val_foldl_add (cs : List (List ℕ)) (c : List ℕ) : + val p fl (cs.foldl (add p fl) c) = val p fl c + (cs.map (val p fl)).sum := by + induction cs generalizing c with + | nil => simp + | cons d cs ih => rw [List.foldl_cons, ih, val_add, List.map_cons, List.sum_cons, add_assoc] + +@[simp] theorem val_prodList (cs : List (List ℕ)) : + val p fl (prodList p fl cs) = (cs.map (val p fl)).prod := by + rw [prodList, val_foldl_mul, val_one, one_mul] + +@[simp] theorem val_sumList (cs : List (List ℕ)) : + val p fl (sumList p fl cs) = (cs.map (val p fl)).sum := by + rw [sumList, val_foldl_add, val_zero, zero_add] + +/-! ### Inverting elements that are one modulo two -/ + +/-- Products of elements `1 + 2 x` have this form. -/ +private theorem exists_prod_eq_one_add_two_mul {R : Type*} [CommRing R] (l : List R) + (hl : ∀ y ∈ l, ∃ w, y = 1 + 2 * w) : ∃ w, l.prod = 1 + 2 * w := by + induction l with + | nil => exact ⟨0, by simp⟩ + | cons y l ih => + obtain ⟨u, hu⟩ := hl y List.mem_cons_self + obtain ⟨w, hw⟩ := ih fun z hz => hl z (List.mem_cons_of_mem _ hz) + exact ⟨u + w + 2 * u * w, by rw [List.prod_cons, hu, hw]; ring⟩ + +/-- **Inversion of an element that is one modulo two**: Newton's inversion needs no further +hypothesis. -/ +theorem val_mul_inv_of_eq (c : List ℕ) {w : Ring p fl} (h : val p fl c = 1 + 2 * w) : + val p fl c * val p fl (inv p fl c) = 1 := by + obtain ⟨y, hy⟩ := exists_prod_eq_one_add_two_mul + (List.replicate (2 ^ fl.length - 2) (val p fl c)) fun z hz => ⟨w, by + rw [List.eq_of_mem_replicate hz, h]⟩ + rw [List.prod_replicate] at hy + refine val_mul_inv c (w := w + y + 2 * w * y) ?_ + rw [hy, h] + ring + +/-- An inverse is unique. -/ +private theorem eq_of_mul_eq_one {R : Type*} [CommRing R] {a b c : R} (hb : a * b = 1) + (hc : c * a = 1) : b = c := by + calc b = (c * a) * b := by rw [hc, one_mul] + _ = c * (a * b) := mul_assoc _ _ _ + _ = c := by rw [hb, mul_one] + +/-- An odd integer is `1 + 2 w`. -/ +private theorem intCast_odd {R : Type*} [CommRing R] {u : ℤ} (hu : Odd u) : + ∃ w : R, (u : R) = 1 + 2 * w := by + obtain ⟨m, rfl⟩ := hu + exact ⟨m, by push_cast; ring⟩ + +/-! ### Odd parts -/ + +/-- An integer is `2 ^ v₂(n)` times its odd part. -/ +private theorem two_pow_mul_ediv (n : ℤ) : 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) = n := + Int.mul_ediv_cancel' (by exact_mod_cast padicValInt_dvd (p := 2) n) + +/-- The odd part of a nonzero integer is odd. -/ +theorem odd_ediv_two_pow {n : ℤ} (hn : n ≠ 0) : Odd (n / 2 ^ padicValInt 2 n) := by + rw [← Int.not_even_iff_odd, even_iff_two_dvd] + rintro ⟨c, hc⟩ + have h2 : (2 : ℤ) ^ (padicValInt 2 n + 1) ∣ n := ⟨c, by + calc n = 2 ^ padicValInt 2 n * (n / 2 ^ padicValInt 2 n) := (two_pow_mul_ediv n).symm + _ = 2 ^ (padicValInt 2 n + 1) * c := by rw [hc, pow_succ]; ring⟩ + have h := (padicValInt_dvd_iff (p := 2) (padicValInt 2 n + 1) n).mp (by exact_mod_cast h2) + omega + +/-- The odd part of a nonzero integer is nonzero. -/ +private theorem ediv_two_pow_ne_zero {n : ℤ} (hn : n ≠ 0) : n / 2 ^ padicValInt 2 n ≠ 0 := by + intro h + exact hn (by rw [← two_pow_mul_ediv n, h, mul_zero]) + +/-- An integer not divisible by `2 ^ (i + 1)` has valuation at most `i`. -/ +theorem padicValInt_le_of_not_dvd {n : ℤ} {i : ℕ} (hn : ¬ (2 : ℤ) ^ (i + 1) ∣ n) : + padicValInt 2 n ≤ i := by + by_contra h + exact hn ((pow_dvd_pow (2 : ℤ) (show i + 1 ≤ padicValInt 2 n by omega)).trans + (by exact_mod_cast padicValInt_dvd (p := 2) n)) + +/-! ### The `2`-adic valuation, computably -/ + +/-- The `2`-adic valuation of `m`, halving at most `k` times. -/ +def twoValAux : ℕ → ℕ → ℕ + | 0, _ => 0 + | k + 1, m => if m ≠ 0 ∧ m % 2 = 0 then twoValAux k (m / 2) + 1 else 0 + +/-- The `2`-adic valuation of an integer, by repeated halving: `padicValInt 2` (`twoVal_eq`). -/ +def twoVal (n : ℤ) : ℕ := twoValAux n.natAbs n.natAbs + +theorem twoValAux_eq : ∀ (k m : ℕ), m ≤ k → twoValAux k m = padicValNat 2 m + | 0, m, h => by rw [Nat.le_zero.mp h, twoValAux]; simp + | k + 1, m, h => by + rw [twoValAux] + split_ifs with hm + · obtain ⟨hm0, hm2⟩ := hm + have hm' : m = 2 * (m / 2) := by omega + rw [twoValAux_eq k (m / 2) (by omega)] + conv_rhs => rw [hm', padicValNat.mul two_ne_zero (by omega), padicValNat_self, add_comm] + · by_cases hm0 : m = 0 + · rw [hm0]; simp + · exact (padicValNat.eq_zero_of_not_dvd fun h2 => hm ⟨hm0, by omega⟩).symm + +/-- **The valuation by halving is `padicValInt 2`.** -/ +@[simp] theorem twoVal_eq (n : ℤ) : twoVal n = padicValInt 2 n := + twoValAux_eq _ _ le_rfl + +/-! ### Kernel values -/ + +/-- The linear factor `u + 2 ^ k ζ`. -/ +def linFactor (p : ℕ) (fl : List ℕ) (u : ℤ) (k : ℕ) (ζ : List ℕ) : List ℕ := + add p fl (intConst p fl u) (mul p fl (intConst p fl (2 ^ k)) ζ) + +@[simp] theorem val_linFactor (u : ℤ) (k : ℕ) (ζ : List ℕ) : + val p fl (linFactor p fl u k ζ) = (u : Ring p fl) + 2 ^ k * val p fl ζ := by + simp only [linFactor, val_add, val_mul, val_intConst] + push_cast + ring + +/-- A linear factor with an odd constant and `k ≥ 1` is one modulo two. -/ +theorem exists_val_linFactor_eq {u : ℤ} (hu : Odd u) {k : ℕ} (hk : 1 ≤ k) (ζ : List ℕ) : + ∃ w : Ring p fl, val p fl (linFactor p fl u k ζ) = 1 + 2 * w := by + obtain ⟨w, hw⟩ := intCast_odd (R := Ring p fl) hu + obtain ⟨k', rfl⟩ := Nat.exists_eq_add_of_le hk + exact ⟨w + 2 ^ k' * val p fl ζ, by rw [val_linFactor, hw, pow_add]; ring⟩ + +/-- The grid `[-2 ^ a, 2 ^ a)` of `2 ^ a` slots, in increasing order. -/ +def gridList (a : ℕ) : List ℤ := + (List.range (2 ^ (a + 1))).map fun j : ℕ => (j : ℤ) - ((2 ^ a : ℕ) : ℤ) + +theorem nodup_gridList (a : ℕ) : (gridList a).Nodup := + List.Nodup.map (fun x y h => by simpa using h) List.nodup_range + +theorem mem_gridList {a : ℕ} {q : ℤ} : q ∈ gridList a ↔ q ∈ cauchyGrid (2 ^ a) := by + rw [gridList, List.mem_map, mem_cauchyGrid, pow_succ] + generalize 2 ^ a = L + constructor + · rintro ⟨j, hj, rfl⟩ + rw [List.mem_range] at hj + omega + · rintro ⟨h1, h2⟩ + refine ⟨(q + L).toNat, List.mem_range.mpr ?_, ?_⟩ <;> omega + +/-- The odd part of `C'(0) = ∏_{q ≠ 0} (0 - q)`, as a coefficient list. -/ +def kernelUnitDen (p : ℕ) (fl : List ℕ) (a : ℕ) : List ℕ := + prodList p fl (((gridList a).filter (· ≠ 0)).map fun q => + intConst p fl ((0 - q) / 2 ^ twoVal (0 - q))) + +/-- `κ (kernelUnit a)`, the inverse of the odd part of `C'(0)`. -/ +def kernelUnitList (p : ℕ) (fl : List ℕ) (a : ℕ) : List ℕ := + inv p fl (kernelUnitDen p fl a) + +/-- The value of `kernelUnitDen`. -/ +private theorem val_kernelUnitDen (a : ℕ) : + val p fl (kernelUnitDen p fl a) = ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, + (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : Ring p fl) := by + simp only [kernelUnitDen, twoVal_eq] + rw [val_prodList, List.map_map, ← List.prod_toFinset _ + ((nodup_gridList a).filter _)] + refine prod_congr ?_ fun q _ => val_intConst _ + ext q + simp only [List.mem_toFinset, List.mem_filter, mem_gridList, mem_erase, decide_eq_true_eq] + tauto + +/-- **The kernel unit**: `kernelUnitList` is the reduction of `kernelUnit a`. -/ +theorem val_kernelUnitList (a : ℕ) : + val p fl (kernelUnitList p fl a) = scalarHom p fl (kernelUnit a) := by + have hne (q : ℤ) (hq : q ∈ (cauchyGrid (2 ^ a)).erase 0) : (0 : ℤ) - q ≠ 0 := + sub_ne_zero.mpr (Ne.symm (ne_of_mem_erase hq)) + -- The odd part of `C'(0)` is one modulo two. + obtain ⟨w, hw⟩ := exists_prod_eq_one_add_two_mul + (((cauchyGrid (2 ^ a)).erase 0).toList.map fun q => + (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : Ring p fl)) fun y hy => by + obtain ⟨q, hq, rfl⟩ := List.mem_map.mp hy + exact intCast_odd (odd_ediv_two_pow (hne q (Finset.mem_toList.mp hq))) + rw [Finset.prod_map_toList] at hw + have hinv := val_mul_inv_of_eq (kernelUnitDen p fl a) (w := w) (by rw [val_kernelUnitDen, hw]) + refine eq_of_mul_eq_one hinv ?_ + -- `kernelUnit a` inverts the odd part of `C'(0)` in `ℤ_(2)`. + have hunit : kernelUnit a * ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, + (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : ℤ₍₂₎) = 1 := by + refine Subtype.val_injective ?_ + change (ℤ₍₂₎).subtype (kernelUnit a * _) = (ℤ₍₂₎).subtype 1 + rw [map_mul, map_prod, map_one] + change ((kernelUnit a : ℤ₍₂₎) : ℚ) * _ = 1 + rw [coe_kernelUnit] + simp only [map_intCast] + refine inv_mul_cancel₀ (prod_ne_zero_iff.mpr fun q hq => ?_) + exact_mod_cast ediv_two_pow_ne_zero (hne q hq) + rw [val_kernelUnitDen] + have := congrArg (scalarHom p fl) hunit + rw [map_mul, map_prod, map_one] at this + simpa only [map_intCast] using this + +/-- **Kernel values on coefficient lists** (Lemma D.4's evaluation, p. 60): the kernel unit, +times `2 ^ (a + 1 - v_d) ζ` when `d ≠ t`, times the linear factors `u_q + 2 ^ (a + 1 - v_q) ζ` +over the grid points `q ≠ d, t`. -/ +def kernelValueList (p : ℕ) (fl : List ℕ) (a : ℕ) (t d : ℤ) (ζ : List ℕ) : List ℕ := + mul p fl (mul p fl (kernelUnitList p fl a) + (if d = t then one fl else + mul p fl (intConst p fl (2 ^ (a + 1 - twoVal (t - d)))) ζ)) + (prodList p fl (((gridList a).filter fun q => q ≠ d ∧ q ≠ t).map fun q => + linFactor p fl ((t - q) / 2 ^ twoVal (t - q)) (a + 1 - twoVal (t - q)) ζ)) + +/-- **Kernel values are correct**: for grid points `t` and `d`, the value is +`kernelValue` at the reduction of `ℤ_(2)` and the value of `ζ`. -/ +theorem val_kernelValueList {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) (ζ : List ℕ) : + val p fl (kernelValueList p fl a t d ζ) = kernelValue (scalarHom p fl) (val p fl ζ) a t d := by + simp only [kernelValueList, twoVal_eq] + rw [kernelValue_eq_prod _ _ ht hd, val_mul, val_mul, val_kernelUnitList, + val_prodList, List.map_map, ← List.prod_toFinset _ ((nodup_gridList a).filter _)] + congr 1 + · congr 1 + split_ifs + · exact val_one + · rw [val_mul, val_intConst]; push_cast; rfl + · refine prod_congr ?_ fun q _ => val_linFactor _ _ _ + ext q + simp only [List.mem_toFinset, List.mem_filter, mem_gridList, mem_erase, decide_eq_true_eq] + tauto + +/-! ### Cross ratios -/ + +/-- **Cross ratios on coefficient lists**: `2 ^ (i + 1 - v)` times the inverse of +`u₀ + 2 ^ (a + 1 - v) ζ`, where `t - d₀ = 2 ^ v u₀`. -/ +def crossRatioList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t d₀ : ℤ) (ζ : List ℕ) : List ℕ := + mul p fl (intConst p fl (2 ^ (i + 1 - twoVal (t - d₀)))) + (inv p fl (linFactor p fl ((t - d₀) / 2 ^ twoVal (t - d₀)) (a + 1 - twoVal (t - d₀)) ζ)) + +/-- **Cross ratios are correct** when `2 ^ (i + 1) ∤ t - d₀` and `i ≤ a`: the inverted factor is +one modulo two. -/ +theorem val_crossRatioList {a i : ℕ} (hia : i ≤ a) {t d₀ : ℤ} + (hcross : ¬ (2 : ℤ) ^ (i + 1) ∣ t - d₀) (ζ : List ℕ) : + val p fl (crossRatioList p fl a i t d₀ ζ) = crossRatio a i t d₀ (val p fl ζ) := by + have hv := padicValInt_le_of_not_dvd hcross + have hn : t - d₀ ≠ 0 := by rintro h; exact hcross (h ▸ dvd_zero _) + obtain ⟨w, hw⟩ := exists_val_linFactor_eq (p := p) (fl := fl) (odd_ediv_two_pow hn) + (show 1 ≤ a + 1 - padicValInt 2 (t - d₀) by omega) ζ + have hinv := val_mul_inv_of_eq _ hw + have hU := IsUnit.of_mul_eq_one _ hinv + simp only [crossRatioList, twoVal_eq] + rw [crossRatio, val_mul, val_intConst, ← val_linFactor] + congr 1 + · push_cast; rfl + · calc val p fl (inv p fl _) = (Ring.inverse (val p fl _) * val p fl _) * val p fl (inv p fl _) := + by rw [Ring.inverse_mul_cancel _ hU, one_mul] + _ = Ring.inverse (val p fl _) := by rw [mul_assoc, hinv, mul_one] + +/-! ### The terms of a packet entry -/ + +/-- The local form of a row port in slot `x`: the sum of the kernel values at the column ports +set to one. -/ +def localFormList (p : ℕ) (fl : List ℕ) (a : ℕ) (t : ℤ) (cols : List (ℕ × Bool)) (x : ℕ) + (ζ : List ℕ) : List ℕ := + sumList p fl (cols.map fun c => if c.2 then kernelValueList p fl a t ((c.1 : ℤ) - x) ζ + else zero fl) + +theorem val_localFormList {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {x : ℕ} (hx : x < 2 ^ a) + (ζ : List ℕ) : + val p fl (localFormList p fl a t cols x ζ) = + packetLocalForm (scalarHom p fl) (val p fl ζ) a t cols x := by + rw [localFormList, val_sumList, List.map_map, packetLocalForm] + congr 1 + refine List.map_congr_left fun c hc => ?_ + simp only [Function.comp_apply] + split_ifs + · exact val_kernelValueList ht (sub_mem_cauchyGrid hx (hcols c hc)) ζ + · exact val_zero + +/-- The coefficient of the mode `(y₀, b)` in the form of a row port in slot `x`. -/ +def modeCoeffList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (differ : Bool) (x y₀ b : ℕ) + (ζ : List ℕ) : List ℕ := + if differ = true ∧ x.testBit i = y₀.testBit i then + mul p fl (kernelValueList p fl a t ((y₀ : ℤ) - x) ζ) + (pow p fl (crossRatioList p fl a i t ((y₀ : ℤ) - x) ζ) b) + else zero fl + +theorem val_modeCoeffList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (differ : Bool) {x : ℕ} + (hx : x < 2 ^ a) {nb : ℕ} (Z : Fin (2 ^ (i + 1)) × Fin nb) (ζ : List ℕ) : + val p fl (modeCoeffList p fl a i t differ x Z.1 Z.2 ζ) = + packetModeCoeff (scalarHom p fl) (val p fl ζ) a i t differ x Z := by + have hy : (Z.1 : ℕ) < 2 ^ a := + Z.1.isLt.trans_le (Nat.pow_le_pow_right (by norm_num) (by omega)) + rw [modeCoeffList, packetModeCoeff] + split_ifs with h + · rw [val_mul, val_pow, val_kernelValueList ht (sub_mem_cauchyGrid hx hy), + val_crossRatioList hia.le (not_dvd_sub_sub_of_testBit_eq hti hti' Z.1.isLt h.2)] + · exact val_zero + +/-- The value of the mode `(y₀, b)` on the column ports: the sum of `j ^ b` over the column ports +in slots `y₀ + 2 ^ (i + 1) j` set to one, when the symbols of the packet differ. -/ +def modeValueList (p : ℕ) (fl : List ℕ) (i : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) + (y₀ b : ℕ) : List ℕ := + sumList p fl (cols.map fun c => + if c.2 = true ∧ differ = true ∧ c.1 % 2 ^ (i + 1) = y₀ then + pow p fl (intConst p fl ((c.1 / 2 ^ (i + 1) : ℕ) : ℤ)) b + else zero fl) + +theorem val_modeValueList (i : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) {nb : ℕ} + (Z : Fin (2 ^ (i + 1)) × Fin nb) : + val p fl (modeValueList p fl i differ cols Z.1 Z.2) = + packetModeValue (Ring p fl) i differ cols Z := by + rw [modeValueList, val_sumList, List.map_map, packetModeValue] + congr 1 + refine List.map_congr_left fun c _ => ?_ + simp only [Function.comp_apply] + split_ifs + · rw [val_pow, val_intConst, Int.cast_natCast] + · exact val_zero + +/-- The terms of the affine factor of a row port `r = (x, Y_x)`: its local term at `none`, and +the coefficient of the mode `(y₀, b)` at `some (y₀, b)`. -/ +def rowTermList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (differ : Bool) + (cols : List (ℕ × Bool)) (ζ : List ℕ) (r : ℕ × Bool) : Option (ℕ × ℕ) → List ℕ + | none => if r.2 then localFormList p fl a t cols r.1 ζ + else sub p fl (one fl) (localFormList p fl a t cols r.1 ζ) + | some Z => if r.2 then modeCoeffList p fl a i t differ r.1 Z.1 Z.2 ζ + else neg p fl (modeCoeffList p fl a i t differ r.1 Z.1 Z.2 ζ) + +/-- The modes `(y₀, b)` with `y₀ < 2 ^ (i + 1)` and `b < nb`, as pairs. -/ +def modeEmbedding (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ × ℕ := + ⟨fun Z => (Z.1, Z.2), fun Z Z' h => by + simp only [Prod.mk.injEq] at h + exact Prod.ext (Fin.ext h.1) (Fin.ext h.2)⟩ + +@[simp] theorem modeEmbedding_apply (i nb : ℕ) (Z : Fin (2 ^ (i + 1)) × Fin nb) : + modeEmbedding i nb Z = ((Z.1 : ℕ), (Z.2 : ℕ)) := + rfl + +/-- Row terms are correct, at the modes of the packet. -/ +theorem val_rowTermList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (differ : Bool) + {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {r : ℕ × Bool} (hr : r.1 < 2 ^ a) + (nb : ℕ) (ζ : List ℕ) : + (fun o => val p fl (rowTermList p fl a i t differ cols ζ r o)) ∘ + Option.map (modeEmbedding i nb) = + packetRowTerm (p := nb) (scalarHom p fl) (val p fl ζ) a i t differ cols r := by + funext o + cases o with + | none => + simp only [Function.comp_apply, Option.map_none, rowTermList, packetRowTerm, affineFactor] + split_ifs + · exact val_localFormList ht hcols hr ζ + · rw [val_sub, val_one, val_localFormList ht hcols hr ζ] + | some Z => + simp only [Function.comp_apply, Option.map_some, rowTermList, packetRowTerm, affineSign, + modeEmbedding_apply] + have h := val_modeCoeffList (p := p) (fl := fl) hia ht hti hti' differ hr Z ζ + split_ifs + · rw [one_mul]; exact h + · rw [val_neg, h, neg_one_mul] + +/-! ### Coefficient extraction -/ + +/-- **Coefficient extraction on coefficient lists**, `TensorNetwork.listCoeff` along a list of +modes: the first factor contributes its local term, or one of the modes still needed. -/ +def listCoeffList (p : ℕ) (fl : List ℕ) {M : Type} [DecidableEq M] (modes : List M) : + List (Option M → List ℕ) → (M → ℕ) → List ℕ + | [], γ => if ∀ Z ∈ modes, γ Z = 0 then one fl else zero fl + | T :: ts, γ => add p fl (mul p fl (T none) (listCoeffList p fl modes ts γ)) + (sumList p fl (modes.map fun Z => if γ Z = 0 then zero fl else + mul p fl (T (some Z)) (listCoeffList p fl modes ts (Function.update γ Z (γ Z - 1))))) + +/-- **Coefficient extraction is correct**, along a duplicate-free list of modes. -/ +theorem val_listCoeffList {M : Type} [DecidableEq M] {modes : List M} (hm : modes.Nodup) : + ∀ (ts : List (Option M → List ℕ)) (γ : M → ℕ), + val p fl (listCoeffList p fl modes ts γ) = + TensorNetwork.listCoeff modes.toFinset (ts.map fun T o => val p fl (T o)) γ + | [], γ => by + rw [listCoeffList, List.map_nil, TensorNetwork.listCoeff_nil] + simp only [List.mem_toFinset] + split_ifs + · exact val_one + · exact val_zero + | T :: ts, γ => by + rw [listCoeffList, List.map_cons, TensorNetwork.listCoeff_cons, val_add, val_mul, + val_listCoeffList hm ts γ, val_sumList, List.map_map, ← List.sum_toFinset _ hm] + congr 1 + refine sum_congr rfl fun Z _ => ?_ + simp only [Function.comp_apply] + split_ifs + · exact val_zero + · rw [val_mul, val_listCoeffList hm ts] + +/-! ### Packet entries -/ + +/-- The modes `(y₀, b)` with `y₀ < 2 ^ (i + 1)` and `b < nb`, in lexicographic order. -/ +def modeList (i nb : ℕ) : List (ℕ × ℕ) := + List.range (2 ^ (i + 1)) ×ˢ List.range nb + +theorem nodup_modeList (i nb : ℕ) : (modeList i nb).Nodup := + List.nodup_range.product List.nodup_range + +theorem mem_modeList {i nb : ℕ} {Z : ℕ × ℕ} : Z ∈ modeList i nb ↔ Z.1 < 2 ^ (i + 1) ∧ Z.2 < nb := by + rw [modeList, List.mem_product, List.mem_range, List.mem_range] + +/-- The modes of `modeList` with a property of the tile, as the image of those of the packet. -/ +private theorem toFinset_filter_modeList (i nb : ℕ) (P : ℕ → Bool) : + ((modeList i nb).filter fun Z => P Z.1).toFinset = + ({Z | P Z.1 = true} : Finset (Fin (2 ^ (i + 1)) × Fin nb)).map (modeEmbedding i nb) := by + ext ⟨y, b⟩ + simp only [List.mem_toFinset, List.mem_filter, mem_modeList, mem_map, mem_filter, mem_univ, + true_and, modeEmbedding_apply] + constructor + · rintro ⟨⟨hy, hb⟩, h⟩ + exact ⟨(⟨y, hy⟩, ⟨b, hb⟩), h, rfl⟩ + · rintro ⟨Z, h, hZ⟩ + simp only [Prod.mk.injEq] at hZ + obtain ⟨rfl, rfl⟩ := hZ + exact ⟨⟨Z.1.isLt, Z.2.isLt⟩, h⟩ + +/-- **The entry of a packet vertex on coefficient lists** (§D.4.6, p. 63): coefficient extraction +along the row terms, at the exponents `β` of the modes targeting the group, times the powers of +the values of the modes with the group as source. The modes are the pairs `(y₀, b)`. -/ +def packetEntryList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) (e₁ e₂ differ : Bool) + (β : ℕ × ℕ → ℕ) (rows cols : List (ℕ × Bool)) (ζ : List ℕ) : List ℕ := + mul p fl + (listCoeffList p fl ((modeList i nb).filter fun Z => modeTargets i e₁ e₂ Z.1) + (rows.map (rowTermList p fl a i t differ cols ζ)) β) + (prodList p fl (((modeList i nb).filter fun Z => modeSources i e₁ e₂ Z.1).map fun Z => + pow p fl (modeValueList p fl i differ cols Z.1 Z.2) (β Z))) + +/-- **Packet entries are correct.** For a grid point `t` of valuation `i < a` and ports in slots +below `2 ^ a`, the value of `packetEntryList` in `A_p` is `packetEntry` at the reduction of +`ℤ_(2)`, the value of `ζ` and the exponents `β` on the modes of the packet. -/ +theorem val_packetEntryList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (e₁ e₂ differ : Bool) + (β : ℕ × ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) + (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : + val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ) = + packetEntry (p := nb) (scalarHom p fl) (val p fl ζ) a i t e₁ e₂ differ + (fun Z => β (Z.1, Z.2)) rows cols := by + rw [packetEntryList, packetEntry, val_mul] + congr 1 + · rw [val_listCoeffList ((nodup_modeList i nb).filter _), toFinset_filter_modeList, + TensorNetwork.listCoeff_map, List.map_map, List.map_map] + refine congrArg₂ _ (List.map_congr_left fun r hr => ?_) rfl + exact val_rowTermList hia ht hti hti' differ hcols (hrows r hr) nb ζ + · rw [val_prodList, List.map_map, ← List.prod_toFinset _ ((nodup_modeList i nb).filter _), + toFinset_filter_modeList, prod_map] + refine prod_congr rfl fun Z _ => ?_ + simp only [Function.comp_apply, modeEmbedding_apply] + rw [val_pow, val_modeValueList] + +/-- **Packet entries in every ring receiving `A_p`.** Along a ring map `ψ : A_p → S` with `2` +nilpotent in `S`, the value of `packetEntryList` is `packetEntry` over `S` at any ring map +`κ : ℤ_(2) → S` and at the image of `ζ`. -/ +theorem map_val_packetEntryList {S : Type*} [CommRing S] (ψ : Ring p fl →+* S) {n : ℕ} + (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) {a i : ℕ} (hia : i < a) {t : ℤ} + (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) + (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ × ℕ → ℕ) {rows cols : List (ℕ × Bool)} + (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : + ψ (val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ)) = + packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (Z.1, Z.2)) + rows cols := by + rw [val_packetEntryList hia ht hti hti' nb e₁ e₂ differ β hrows hcols ζ, + map_packetEntry ψ two_pow_eq_zero _ _ hia.le hti hti', + padicInteger_ringHom_ext hn (ψ.comp (scalarHom p fl)) κ] + +end NativePacket + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean new file mode 100644 index 0000000000..2094374c50 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean @@ -0,0 +1,80 @@ +/- +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.PacketEvaluator + +/-! +# Tests for packet entries on coefficient lists + +Tiny packets of `2 ^ 1` slots over `A_3 = (ℤ / 8)[U] / (U ^ 2 + U + 1)`, at the grid point `t = 1` +of valuation `i = 0`. The kernel unit and one kernel value are checked by hand; the packet entry +with one row port and zero exponents is the local form, a single kernel value; and the +correctness theorem applies to these packets. +-/ + +@[expose] public section + +namespace ComplexityTests.TensorNetworkPacketEvaluator + +open Cslib Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativePacket + Cslib.Circuits.Frontier.TensorNetwork + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +-- The valuation by halving. +example : twoVal 12 = 2 ∧ twoVal (-8) = 3 ∧ twoVal 0 = 0 := by decide + +-- The grid of `2 ^ 1` slots is `-2, -1, 0, 1`; the odd parts of `0 - q` for `q ≠ 0` are +-- `1, 1, -1`, so `C'(0)` has odd part `-1`, and its inverse is `-1 ≡ 7`. +example : kernelUnitList 3 [1, 1] 1 = [7, 0] := by decide + +-- `W_1(1 + 4 U)`: the factors for `q = -2, -1, 0` are `3 + 4 U`, `1 + 2 U` and `1 + 4 U`, whose +-- product is `3 + 6 U` modulo `8`; times `-1` it is `5 + 2 U`. +example : kernelValueList 3 [1, 1] 1 1 1 [0, 1] = [5, 2] := by decide + +-- For `d ≠ t` the kernel value has the factor `2 ^ (a + 1 - v_d) ζ`, which vanishes at `ζ = 0`. +example : kernelValueList 3 [1, 1] 1 1 0 [0, 0] = [0, 0] := by decide + +-- The cross ratio at `d₀ = 0` and `ζ = 1`: `2` times the inverse of `1 + 4 = 5`, which is `5` +-- modulo `8`, so `10 ≡ 2`. +example : crossRatioList 3 [1, 1] 1 0 1 0 [1, 0] = [2, 0] := by decide + +-- One row port in slot `0` set to one, one column port in slot `1` set to one, and zero +-- exponents: the entry is the local form `W_{1 - 0}(T)`. +example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun _ => 0) [(0, true)] [(1, true)] + [0, 1] = kernelValueList 3 [1, 1] 1 1 1 [0, 1] := by + decide + +-- With the row port set to zero, the entry is `1 - W_1(T)`. +example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun _ => 0) [(0, false)] [(1, true)] + [0, 1] = GaloisRingArith.sub 3 [1, 1] (GaloisRingArith.one [1, 1]) + (kernelValueList 3 [1, 1] 1 1 1 [0, 1]) := by + decide + +/-- The hypotheses of the correctness theorem hold for these packets. -/ +theorem val_packetEntryList_tiny (β : ℕ × ℕ → ℕ) (ζ : List ℕ) : + GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β + [(0, true), (1, false)] [(1, true), (0, true)] ζ) = + packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] ζ) 1 0 1 true false + true (fun Z => β (Z.1, Z.2)) [(0, true), (1, false)] [(1, true), (0, true)] := + val_packetEntryList (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true + false true β (by simp) (by simp) ζ + +-- Transported to any ring with `2` nilpotent, at any ring map from `ℤ_(2)`. +example {S : Type} [CommRing S] (ψ : GaloisRingArith.Ring 3 [1, 1] →+* S) {n : ℕ} + (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) (β : ℕ × ℕ → ℕ) (ζ : List ℕ) : + ψ (GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β + [(0, true)] [(1, true)] ζ)) = + packetEntry (p := 2) κ (ψ (GaloisRingArith.val 3 [1, 1] ζ)) 1 0 1 true false true + (fun Z => β (Z.1, Z.2)) [(0, true)] [(1, true)] := + map_val_packetEntryList ψ hn κ (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) + (by norm_num) 2 true false true β (by simp) (by simp) ζ + +end ComplexityTests.TensorNetworkPacketEvaluator From 04fe069c8e48cbd5351a146c239d22540cce8a07 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:30:50 -0400 Subject: [PATCH 19/33] refactor(circuit/frontier): packet modes by dense index y0 * nb + b Read the exponents of the packet modes at dense indices, matching a dense beta-slice in the packet descriptor, so coefficient extraction runs over a list of indices with a dense exponent list. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/PacketEvaluator.lean | 134 ++++++++++-------- .../TensorNetworkPacketEvaluator.lean | 15 +- 2 files changed, 83 insertions(+), 66 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean index 93db7912a8..e9e8449c56 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean @@ -35,8 +35,8 @@ ring that `A_p` maps to. modulo `2` since `u₀` is odd and `v ≤ i ≤ a`. * **Local forms, mode coefficients, mode values and row terms** follow `TensorNetwork.packetLocalForm`, `TensorNetwork.packetModeCoeff`, - `TensorNetwork.packetModeValue` and `TensorNetwork.packetRowTerm`, with the modes `(y₀, b)` as - pairs of natural numbers. + `TensorNetwork.packetModeValue` and `TensorNetwork.packetRowTerm`, with a mode `(y₀, b)` given + by its dense index `y₀ nb + b` (`NativePacket.modeIndex`). * **Coefficient extraction** (`NativePacket.listCoeffList`) is the recursion `TensorNetwork.listCoeff` on coefficient lists, along a duplicate-free list of modes. @@ -485,32 +485,45 @@ theorem val_modeValueList (i : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) · rw [val_pow, val_intConst, Int.cast_natCast] · exact val_zero +/-- The tile of a dense mode index `y₀ nb + b`. -/ +theorem modeIndex_div {nb y b : ℕ} (hb : b < nb) : (y * nb + b) / nb = y := by + rw [Nat.add_comm, Nat.add_mul_div_right _ _ (by omega), Nat.div_eq_of_lt hb, Nat.zero_add] + +/-- The exponent of a dense mode index `y₀ nb + b`. -/ +theorem modeIndex_mod {nb y b : ℕ} (hb : b < nb) : (y * nb + b) % nb = b := by + rw [Nat.add_comm, Nat.add_mul_mod_self_right, Nat.mod_eq_of_lt hb] + +/-- The dense index `y₀ nb + b` of the mode `(y₀, b)` of a packet with `nb` exponents per +tile. -/ +def modeIndex (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ := + ⟨fun Z => Z.1 * nb + Z.2, fun Z Z' h => by + have h1 := modeIndex_div (y := Z.1) Z.2.isLt + have h2 := modeIndex_mod (y := Z.1) Z.2.isLt + simp only at h + rw [h, modeIndex_div Z'.2.isLt] at h1 + rw [h, modeIndex_mod Z'.2.isLt] at h2 + exact Prod.ext (Fin.ext h1.symm) (Fin.ext h2.symm)⟩ + +@[simp] theorem modeIndex_apply (i nb : ℕ) (Z : Fin (2 ^ (i + 1)) × Fin nb) : + modeIndex i nb Z = (Z.1 : ℕ) * nb + Z.2 := + rfl + /-- The terms of the affine factor of a row port `r = (x, Y_x)`: its local term at `none`, and -the coefficient of the mode `(y₀, b)` at `some (y₀, b)`. -/ -def rowTermList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (differ : Bool) - (cols : List (ℕ × Bool)) (ζ : List ℕ) (r : ℕ × Bool) : Option (ℕ × ℕ) → List ℕ +the coefficient of the mode `(k / nb, k % nb)` at `some k`. -/ +def rowTermList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) (differ : Bool) + (cols : List (ℕ × Bool)) (ζ : List ℕ) (r : ℕ × Bool) : Option ℕ → List ℕ | none => if r.2 then localFormList p fl a t cols r.1 ζ else sub p fl (one fl) (localFormList p fl a t cols r.1 ζ) - | some Z => if r.2 then modeCoeffList p fl a i t differ r.1 Z.1 Z.2 ζ - else neg p fl (modeCoeffList p fl a i t differ r.1 Z.1 Z.2 ζ) - -/-- The modes `(y₀, b)` with `y₀ < 2 ^ (i + 1)` and `b < nb`, as pairs. -/ -def modeEmbedding (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ × ℕ := - ⟨fun Z => (Z.1, Z.2), fun Z Z' h => by - simp only [Prod.mk.injEq] at h - exact Prod.ext (Fin.ext h.1) (Fin.ext h.2)⟩ - -@[simp] theorem modeEmbedding_apply (i nb : ℕ) (Z : Fin (2 ^ (i + 1)) × Fin nb) : - modeEmbedding i nb Z = ((Z.1 : ℕ), (Z.2 : ℕ)) := - rfl + | some k => if r.2 then modeCoeffList p fl a i t differ r.1 (k / nb) (k % nb) ζ + else neg p fl (modeCoeffList p fl a i t differ r.1 (k / nb) (k % nb) ζ) -/-- Row terms are correct, at the modes of the packet. -/ +/-- Row terms are correct, at the dense indices of the modes of the packet. -/ theorem val_rowTermList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) - (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (differ : Bool) + (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (differ : Bool) {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {r : ℕ × Bool} (hr : r.1 < 2 ^ a) - (nb : ℕ) (ζ : List ℕ) : - (fun o => val p fl (rowTermList p fl a i t differ cols ζ r o)) ∘ - Option.map (modeEmbedding i nb) = + (ζ : List ℕ) : + (fun o => val p fl (rowTermList p fl a i t nb differ cols ζ r o)) ∘ + Option.map (modeIndex i nb) = packetRowTerm (p := nb) (scalarHom p fl) (val p fl ζ) a i t differ cols r := by funext o cases o with @@ -521,7 +534,7 @@ theorem val_rowTermList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGr · rw [val_sub, val_one, val_localFormList ht hcols hr ζ] | some Z => simp only [Function.comp_apply, Option.map_some, rowTermList, packetRowTerm, affineSign, - modeEmbedding_apply] + modeIndex_apply, modeIndex_div Z.2.isLt, modeIndex_mod Z.2.isLt] have h := val_modeCoeffList (p := p) (fl := fl) hia ht hti hti' differ hr Z ζ split_ifs · rw [one_mul]; exact h @@ -561,62 +574,59 @@ theorem val_listCoeffList {M : Type} [DecidableEq M] {modes : List M} (hm : mode /-! ### Packet entries -/ -/-- The modes `(y₀, b)` with `y₀ < 2 ^ (i + 1)` and `b < nb`, in lexicographic order. -/ -def modeList (i nb : ℕ) : List (ℕ × ℕ) := - List.range (2 ^ (i + 1)) ×ˢ List.range nb - -theorem nodup_modeList (i nb : ℕ) : (modeList i nb).Nodup := - List.nodup_range.product List.nodup_range - -theorem mem_modeList {i nb : ℕ} {Z : ℕ × ℕ} : Z ∈ modeList i nb ↔ Z.1 < 2 ^ (i + 1) ∧ Z.2 < nb := by - rw [modeList, List.mem_product, List.mem_range, List.mem_range] - -/-- The modes of `modeList` with a property of the tile, as the image of those of the packet. -/ -private theorem toFinset_filter_modeList (i nb : ℕ) (P : ℕ → Bool) : - ((modeList i nb).filter fun Z => P Z.1).toFinset = - ({Z | P Z.1 = true} : Finset (Fin (2 ^ (i + 1)) × Fin nb)).map (modeEmbedding i nb) := by - ext ⟨y, b⟩ - simp only [List.mem_toFinset, List.mem_filter, mem_modeList, mem_map, mem_filter, mem_univ, - true_and, modeEmbedding_apply] +/-- The dense indices below `2 ^ (i + 1) nb` whose tile has a property, as the image of the +modes of the packet. -/ +private theorem toFinset_filter_range (i nb : ℕ) (P : ℕ → Bool) : + ((List.range (2 ^ (i + 1) * nb)).filter fun k => P (k / nb)).toFinset = + ({Z | P Z.1 = true} : Finset (Fin (2 ^ (i + 1)) × Fin nb)).map (modeIndex i nb) := by + ext k + simp only [List.mem_toFinset, List.mem_filter, List.mem_range, mem_map, mem_filter, mem_univ, + true_and, modeIndex_apply] constructor - · rintro ⟨⟨hy, hb⟩, h⟩ - exact ⟨(⟨y, hy⟩, ⟨b, hb⟩), h, rfl⟩ - · rintro ⟨Z, h, hZ⟩ - simp only [Prod.mk.injEq] at hZ - obtain ⟨rfl, rfl⟩ := hZ - exact ⟨⟨Z.1.isLt, Z.2.isLt⟩, h⟩ + · rintro ⟨hk, h⟩ + have hnb : 0 < nb := Nat.pos_of_ne_zero fun h0 => by simp [h0] at hk + exact ⟨(⟨k / nb, (Nat.div_lt_iff_lt_mul hnb).mpr hk⟩, ⟨k % nb, Nat.mod_lt _ hnb⟩), h, + Nat.div_add_mod' k nb⟩ + · rintro ⟨Z, h, rfl⟩ + refine ⟨?_, by rw [modeIndex_div Z.2.isLt]; exact h⟩ + calc (Z.1 : ℕ) * nb + Z.2 < (Z.1 + 1) * nb := by rw [Nat.add_mul, Nat.one_mul]; omega + _ ≤ 2 ^ (i + 1) * nb := Nat.mul_le_mul_right _ Z.1.isLt /-- **The entry of a packet vertex on coefficient lists** (§D.4.6, p. 63): coefficient extraction along the row terms, at the exponents `β` of the modes targeting the group, times the powers of -the values of the modes with the group as source. The modes are the pairs `(y₀, b)`. -/ +the values of the modes with the group as source. A mode `(y₀, b)` is its dense index +`y₀ nb + b`, so `β` is read at dense indices, as from a dense list of exponents. -/ def packetEntryList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) (e₁ e₂ differ : Bool) - (β : ℕ × ℕ → ℕ) (rows cols : List (ℕ × Bool)) (ζ : List ℕ) : List ℕ := + (β : ℕ → ℕ) (rows cols : List (ℕ × Bool)) (ζ : List ℕ) : List ℕ := mul p fl - (listCoeffList p fl ((modeList i nb).filter fun Z => modeTargets i e₁ e₂ Z.1) - (rows.map (rowTermList p fl a i t differ cols ζ)) β) - (prodList p fl (((modeList i nb).filter fun Z => modeSources i e₁ e₂ Z.1).map fun Z => - pow p fl (modeValueList p fl i differ cols Z.1 Z.2) (β Z))) + (listCoeffList p fl ((List.range (2 ^ (i + 1) * nb)).filter fun k => + modeTargets i e₁ e₂ (k / nb)) + (rows.map (rowTermList p fl a i t nb differ cols ζ)) β) + (prodList p fl (((List.range (2 ^ (i + 1) * nb)).filter fun k => + modeSources i e₁ e₂ (k / nb)).map fun k => + pow p fl (modeValueList p fl i differ cols (k / nb) (k % nb)) (β k))) /-- **Packet entries are correct.** For a grid point `t` of valuation `i < a` and ports in slots below `2 ^ a`, the value of `packetEntryList` in `A_p` is `packetEntry` at the reduction of -`ℤ_(2)`, the value of `ζ` and the exponents `β` on the modes of the packet. -/ +`ℤ_(2)`, the value of `ζ` and the exponents `β` at the dense indices of the modes. -/ theorem val_packetEntryList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (e₁ e₂ differ : Bool) - (β : ℕ × ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) + (β : ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ) = packetEntry (p := nb) (scalarHom p fl) (val p fl ζ) a i t e₁ e₂ differ - (fun Z => β (Z.1, Z.2)) rows cols := by + (fun Z => β (Z.1 * nb + Z.2)) rows cols := by rw [packetEntryList, packetEntry, val_mul] congr 1 - · rw [val_listCoeffList ((nodup_modeList i nb).filter _), toFinset_filter_modeList, + · rw [val_listCoeffList (List.nodup_range.filter _), toFinset_filter_range, TensorNetwork.listCoeff_map, List.map_map, List.map_map] refine congrArg₂ _ (List.map_congr_left fun r hr => ?_) rfl - exact val_rowTermList hia ht hti hti' differ hcols (hrows r hr) nb ζ - · rw [val_prodList, List.map_map, ← List.prod_toFinset _ ((nodup_modeList i nb).filter _), - toFinset_filter_modeList, prod_map] + exact val_rowTermList hia ht hti hti' nb differ hcols (hrows r hr) ζ + · rw [val_prodList, List.map_map, ← List.prod_toFinset _ (List.nodup_range.filter _), + toFinset_filter_range, prod_map] refine prod_congr rfl fun Z _ => ?_ - simp only [Function.comp_apply, modeEmbedding_apply] + simp only [Function.comp_apply, modeIndex_apply, modeIndex_div Z.2.isLt, + modeIndex_mod Z.2.isLt] rw [val_pow, val_modeValueList] /-- **Packet entries in every ring receiving `A_p`.** Along a ring map `ψ : A_p → S` with `2` @@ -625,10 +635,10 @@ nilpotent in `S`, the value of `packetEntryList` is `packetEntry` over `S` at an theorem map_val_packetEntryList {S : Type*} [CommRing S] (ψ : Ring p fl →+* S) {n : ℕ} (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) - (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ × ℕ → ℕ) {rows cols : List (ℕ × Bool)} + (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : ψ (val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ)) = - packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (Z.1, Z.2)) + packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (Z.1 * nb + Z.2)) rows cols := by rw [val_packetEntryList hia ht hti hti' nb e₁ e₂ differ β hrows hcols ζ, map_packetEntry ψ two_pow_eq_zero _ _ hia.le hti hti', diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean index 2094374c50..435359142c 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean @@ -58,22 +58,29 @@ example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun _ => 0) [(0, fal (kernelValueList 3 [1, 1] 1 1 1 [0, 1]) := by decide +-- Exponent `1` at the mode `(0, 1)`, of dense index `0 * 2 + 1 = 1`, which targets the group +-- (digit `0` of `y₀ = 0` selects the first symbol): the single row factor contributes the +-- coefficient of that mode, and no source mode has a positive exponent. +example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun k => if k = 1 then 1 else 0) + [(0, true)] [(1, true)] [0, 1] = modeCoeffList 3 [1, 1] 1 0 1 true 0 0 1 [0, 1] := by + decide + /-- The hypotheses of the correctness theorem hold for these packets. -/ -theorem val_packetEntryList_tiny (β : ℕ × ℕ → ℕ) (ζ : List ℕ) : +theorem val_packetEntryList_tiny (β : ℕ → ℕ) (ζ : List ℕ) : GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β [(0, true), (1, false)] [(1, true), (0, true)] ζ) = packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] ζ) 1 0 1 true false - true (fun Z => β (Z.1, Z.2)) [(0, true), (1, false)] [(1, true), (0, true)] := + true (fun Z => β (Z.1 * 2 + Z.2)) [(0, true), (1, false)] [(1, true), (0, true)] := val_packetEntryList (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true false true β (by simp) (by simp) ζ -- Transported to any ring with `2` nilpotent, at any ring map from `ℤ_(2)`. example {S : Type} [CommRing S] (ψ : GaloisRingArith.Ring 3 [1, 1] →+* S) {n : ℕ} - (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) (β : ℕ × ℕ → ℕ) (ζ : List ℕ) : + (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) (β : ℕ → ℕ) (ζ : List ℕ) : ψ (GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β [(0, true)] [(1, true)] ζ)) = packetEntry (p := 2) κ (ψ (GaloisRingArith.val 3 [1, 1] ζ)) 1 0 1 true false true - (fun Z => β (Z.1, Z.2)) [(0, true)] [(1, true)] := + (fun Z => β (Z.1 * 2 + Z.2)) [(0, true)] [(1, true)] := map_val_packetEntryList ψ hn κ (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true false true β (by simp) (by simp) ζ From 5317b590e65543f68faca9b3904e73810d7db10f Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:43:28 -0400 Subject: [PATCH 20/33] feat(circuit/frontier): coefficient extraction as a stack recursion Packet entries expand at most L affine row factors and keep the coefficient of a mode monomial (section D.4.6). CoeffScheme gives this as a StackRecursion scheme on GaloisRingArith coefficient lists: a node with rows has m + 1 children (the local term and each mode, lowering its exponent in a dense list), a node without rows is a leaf. val_value proves the value is listCoeff along the modes; eval_value gives the derivation with at most (m + 2)^L nodes and L open calls. From polynomial-time data the components are polynomial-time, and valueCertificate_le bounds one native machine by time K (m + 2)^L Z^K and space K Z^K. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/CoeffScheme.lean | 750 ++++++++++++++++++ .../Frontier/TensorNetworkCoeffScheme.lean | 62 ++ 3 files changed, 813 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/CoeffScheme.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCoeffScheme.lean diff --git a/Complexity.lean b/Complexity.lean index 649788fcf0..c980ba15b1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -876,6 +876,7 @@ public import Complexity.Computability.Circuit.Frontier.Sweep public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Binarize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CoeffScheme public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedContraction diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/CoeffScheme.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CoeffScheme.lean new file mode 100644 index 0000000000..16d7dbbce5 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CoeffScheme.lean @@ -0,0 +1,750 @@ +/- +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.DescribedContraction +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +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.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.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 Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives +import Mathlib.Algebra.BigOperators.Intervals +import Mathlib.Data.List.GetD + +/-! +# Coefficient extraction as a stack recursion + +An entry of a packet tensor of the contraction algorithm of [OpenAI2026ETH] expands a product of +at most `L` affine row factors and keeps the coefficient of a mode monomial. The source evaluates +it in time `exp(O(L log s))` and polynomial space (§D.4.6, p. 63). This file gives that +evaluation as an instance of `Cslib.GenericRAM.StackRecursion.Scheme`, on the coefficient lists +of `A_p = (ℤ / 2 ^ p)[U] / (f)`. + +The data (`CoeffScheme.Terms`) are, at a context `x : C`, the precision `(p, fl)`, the list of +the (dense indices of the) modes, and the terms of the row factors: `term x r none` is the local +term of the row `r`, and `term x r (some k)` the coefficient of the mode `k`. A node of the +recursion is a context, the rows still to expand and the exponents still needed, as a dense list. +A node with rows `r :: rs` has `m + 1` children for `m` modes: the local term (`c = 0`), and the +mode `modes[c - 1]` with its exponent lowered (`c ≥ 1`); it adds the products of their values +with the terms of `r`, skipping the modes with exponent zero. A node without rows is a leaf: one +when every exponent is zero, else zero. + +* `CoeffScheme.Terms.value` is the value the recursion computes, and + `CoeffScheme.Terms.val_value` proves that its value in `A_p` is `TensorNetwork.listCoeff` along + the modes, at the values of the terms. +* `CoeffScheme.Terms.eval_value`: the derivation has `CoeffScheme.count m L ≤ (m + 2) ^ L` nodes + for `L` rows (`CoeffScheme.count_le`) and at most `L` open calls, with nodes whose rows are a + suffix of the root's and whose exponents are below the root's, and reduced values. + +So the run has `exp(O(L log m))` steps on a stack of at most `L + 1` frames of polynomial size: +the time and space of §D.4.6. With polynomial-time data (`CoeffScheme.Terms.PolyTime`: the +precision with `p` in unary, the modes and the terms), every component of the scheme is +polynomial-time (`CoeffScheme.Terms.scheme_polyTime`), and one native machine computes `value` +on every node (`CoeffScheme.Terms.valueCertificate`), within time `K (m + 2) ^ L Z ^ K` and space +`K Z ^ K` for `Z = n + m + Rv + 2`, where `n` is the length of the node and `Rv` bounds the +reduced coefficient lists (`CoeffScheme.Terms.valueCertificate_le`). + +## Main definitions + +* `Cslib.Circuits.Frontier.CoeffScheme.Terms`: the data of coefficient extraction. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.value`: its value. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.scheme`: the recursion scheme. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.valueCertificate`: one native machine for `value`. + +## Main results + +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.val_value`: the value is `listCoeff`. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.eval_value`: the derivation, its nodes and height. +* `Cslib.Circuits.Frontier.CoeffScheme.count_le`: the node count. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.scheme_polyTime`: polynomial-time components. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.run_scheme`: the machine computes `value`. +* `Cslib.Circuits.Frontier.CoeffScheme.Terms.valueCertificate_le`: its time and space. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.6 (p. 63). +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +namespace CoeffScheme + +open Finset GaloisRingArith NativePacket Cslib.GenericRAM Cslib.GenericRAM.StackRecursion + +/-- **The data of coefficient extraction**: at a context `x : C`, the precision `(p, fl)` of the +coefficient lists, the list of the modes, and the terms of the row factors (`term x r none` the +local term of the row `r`, `term x r (some k)` the coefficient of the mode `k`). -/ +structure Terms (C Rw : Type) where + /-- The precision `(p, fl)`. -/ + prec : C → ℕ × List ℕ + /-- The modes, as dense indices. -/ + modes : C → List ℕ + /-- The terms of the row factors. -/ + term : C → Rw → Option ℕ → List ℕ + +/-- A node: a context, the rows still to expand, and the exponents still needed, as a dense +list. -/ +abbrev Node (C Rw : Type) := C × (List Rw × List ℕ) + +/-- The number of nodes of the derivation for `m` modes and `L` rows. -/ +def count (m : ℕ) : ℕ → ℕ + | 0 => 1 + | L + 1 => (m + 1) * count m L + 1 + +/-- **At most `(m + 2) ^ L` nodes.** -/ +theorem count_le (m : ℕ) : ∀ L, count m L ≤ (m + 2) ^ L + | 0 => le_rfl + | L + 1 => by + have h := count_le m L + have h1 := Nat.one_le_two_pow.trans (Nat.pow_le_pow_left (show 2 ≤ m + 2 by omega) L) + rw [count, pow_succ] + nlinarith + +namespace Terms + +variable {C Rw : Type} (S : Terms C Rw) + +/-- The mode of child `c ≥ 1`: `modes[c - 1]`. -/ +def modeOf (x : C) (c : ℕ) : ℕ := (S.modes x).getD (c - 1) 0 + +/-- The exponents of child `c`: unchanged for the local term `c = 0`, and with the exponent of +the mode `modeOf c` lowered by one for `c ≥ 1`. -/ +def childExps (x : C) (γ : List ℕ) (c : ℕ) : List ℕ := + (List.range γ.length).map fun j => + if c ≠ 0 ∧ j = S.modeOf x c then γ.getD j 0 - 1 else γ.getD j 0 + +/-- The contribution of child `c` with value `y` at the row `r`: the local term times `y` for +`c = 0`; for `c ≥ 1`, zero when the mode has exponent zero, else its coefficient times `y`. -/ +def contrib (x : C) (r : Rw) (γ : List ℕ) (c : ℕ) (y : List ℕ) : List ℕ := + if c = 0 then mul (S.prec x).1 (S.prec x).2 (S.term x r none) y + else if γ.getD (S.modeOf x c) 0 = 0 then zero (S.prec x).2 + else mul (S.prec x).1 (S.prec x).2 (S.term x r (some (S.modeOf x c))) y + +/-- The leaf: one when every mode has exponent zero, else zero. -/ +def leafValue (x : C) (γ : List ℕ) : List ℕ := + if (S.modes x).all (fun k => γ.getD k 0 == 0) then one (S.prec x).2 else zero (S.prec x).2 + +/-- **The value of coefficient extraction**, as the recursion computes it: the sum over the +children of their contributions. -/ +def value (x : C) : List Rw → List ℕ → List ℕ + | [], γ => S.leafValue x γ + | r :: rs, γ => (List.range ((S.modes x).length + 1)).foldl + (fun s c => add (S.prec x).1 (S.prec x).2 s + (S.contrib x r γ c (value x rs (S.childExps x γ c)))) (zero (S.prec x).2) + +/-! ### Correctness -/ + +theorem length_childExps (x : C) (γ : List ℕ) (c : ℕ) : (S.childExps x γ c).length = γ.length := by + simp [childExps] + +theorem getD_childExps (x : C) (γ : List ℕ) (c j : ℕ) : + (S.childExps x γ c).getD j 0 = + if c ≠ 0 ∧ j = S.modeOf x c then γ.getD j 0 - 1 else γ.getD j 0 := by + by_cases hj : j < γ.length + · rw [childExps, List.getD_eq_getElem _ _ (by simpa using hj), List.getElem_map, + List.getElem_range] + · rw [List.getD_eq_default _ _ (by simp [childExps]; omega), + List.getD_eq_default _ _ (by omega)] + split_ifs <;> simp + +/-- The exponents of child `c ≥ 1` are those of the node with the exponent of its mode lowered. -/ +theorem getD_childExps_succ (x : C) (γ : List ℕ) (c : ℕ) : + (fun j => (S.childExps x γ (c + 1)).getD j 0) = + Function.update (fun j => γ.getD j 0) (S.modeOf x (c + 1)) + (γ.getD (S.modeOf x (c + 1)) 0 - 1) := by + funext j + rw [getD_childExps] + by_cases hj : j = S.modeOf x (c + 1) + · subst hj; simp + · simp [hj] + +private theorem val_foldl_range_add {p : ℕ} {fl : List ℕ} (g : ℕ → List ℕ) (n : ℕ) + (a : List ℕ) : + val p fl ((List.range n).foldl (fun s c => add p fl s (g c)) a) = + val p fl a + ∑ c ∈ range n, val p fl (g c) := by + induction n with + | zero => simp + | succ n ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, val_add, ih, + sum_range_succ, add_assoc] + +private theorem sum_range_getD {M : Type*} [AddCommMonoid M] (f : ℕ → M) : + ∀ l : List ℕ, ∑ c ∈ range l.length, f (l.getD c 0) = (l.map f).sum + | [] => by simp + | Z :: l => by + rw [List.length_cons, sum_range_succ', List.map_cons, List.sum_cons, add_comm] + simp only [List.getD_cons_succ, List.getD_cons_zero, sum_range_getD f l] + +/-- **The value is `listCoeff`**: along duplicate-free modes, the value in `A_p` of `value` is +`TensorNetwork.listCoeff` at the values of the terms and the exponents of the dense list. -/ +theorem val_value (x : C) (hm : (S.modes x).Nodup) : + ∀ (rows : List Rw) (γ : List ℕ), + val (S.prec x).1 (S.prec x).2 (S.value x rows γ) = + TensorNetwork.listCoeff (S.modes x).toFinset + (rows.map fun r o => val (S.prec x).1 (S.prec x).2 (S.term x r o)) + (fun k => γ.getD k 0) + | [], γ => by + rw [value, leafValue, List.map_nil, TensorNetwork.listCoeff_nil] + simp only [List.all_eq_true, beq_iff_eq, List.mem_toFinset] + split_ifs + · exact val_one + · exact val_zero + | r :: rs, γ => by + rw [value, val_foldl_range_add, val_zero, zero_add, sum_range_succ', List.map_cons, + TensorNetwork.listCoeff_cons, add_comm] + congr 1 + · simp only [contrib, ↓reduceIte, val_mul, childExps, Ne, not_true_eq_false, false_and] + rw [val_value x hm rs] + congr 2 + funext j + by_cases hj : j < γ.length + · rw [List.getD_eq_getElem _ _ (by simpa using hj), List.getElem_map, List.getElem_range] + · rw [List.getD_eq_default _ _ (by simp; omega), List.getD_eq_default _ _ (by omega)] + · rw [List.sum_toFinset _ hm, ← sum_range_getD] + refine sum_congr rfl fun c hc => ?_ + simp only [contrib, Nat.add_one_ne_zero, ↓reduceIte, modeOf, Nat.add_sub_cancel] + split_ifs with h0 + · exact val_zero + · rw [val_mul, val_value x hm rs, getD_childExps_succ S x γ c] + rfl + +/-! ### The recursion scheme -/ + +/-- **Coefficient extraction as a recursion scheme.** A node with rows has `m + 1` children, +counted by the accumulator, and adds their contributions at its first row; a node without rows +is a leaf. -/ +def scheme : Scheme (Node C Rw) (ℕ × List ℕ) (List ℕ) where + isLeaf x := x.2.1.isEmpty + leaf x := S.leafValue x.1 x.2.2 + start x := (0, zero (S.prec x.1).2) + next x a := + if x.2.1 ≠ [] ∧ a.1 ≤ (S.modes x.1).length then + some (x.1, (x.2.1.tail, S.childExps x.1 x.2.2 a.1)) + else none + absorb x a y := (a.1 + 1, (x.2.1.head?).elim a.2 fun r => + add (S.prec x.1).1 (S.prec x.1).2 a.2 (S.contrib x.1 r x.2.2 a.1 y)) + finish _ a := a.2 + +/-- The invariant at the root `(x₀, (rows₀, γ₀))`: nodes keep the context, have a suffix of the +rows and exponents of the same length below those of the root; frames also have a counter at +most `m + 1` and a reduced value; values are reduced. -/ +def invariant (x₀ : C) (rows₀ : List Rw) (γ₀ : List ℕ) : + Invariant (Node C Rw) (ℕ × List ℕ) (List ℕ) := + ⟨fun x => x.1 = x₀ ∧ x.2.1 <:+ rows₀ ∧ x.2.2.length = γ₀.length ∧ + ∀ j, x.2.2.getD j 0 ≤ γ₀.getD j 0, + fun x a => (x.1 = x₀ ∧ x.2.1 <:+ rows₀ ∧ x.2.2.length = γ₀.length ∧ + ∀ j, x.2.2.getD j 0 ≤ γ₀.getD j 0) ∧ a.1 ≤ (S.modes x₀).length + 1 ∧ + SumOps.galois.Reduced (S.prec x₀) a.2, + fun y => SumOps.galois.Reduced (S.prec x₀) y⟩ + +/-- The leaf value is reduced. -/ +theorem reduced_leafValue (x : C) (γ : List ℕ) : + SumOps.galois.Reduced (S.prec x) (S.leafValue x γ) := by + unfold leafValue + split_ifs + · exact SumOps.galois.reduced_one (S.prec x) + · exact SumOps.galois.reduced_zero (S.prec x) + +/-- **The derivation of coefficient extraction.** From a node whose rows are a suffix of the +root's and whose exponents are below the root's, the recursion computes `value`, with +`count m L` nodes for `L` rows and at most `L` open calls. -/ +theorem eval_value (x₀ : C) (rows₀ : List Rw) (γ₀ : List ℕ) : + ∀ (rows : List Rw) (γ : List ℕ), rows <:+ rows₀ → γ.length = γ₀.length → + (∀ j, γ.getD j 0 ≤ γ₀.getD j 0) → + ∃ h ≤ rows.length, S.scheme.Eval (S.invariant x₀ rows₀ γ₀) (x₀, (rows, γ)) + (S.value x₀ rows γ) (count (S.modes x₀).length rows.length) h + | [], γ, hs, hl, hγ => + ⟨0, le_rfl, Scheme.Eval.leaf (R := S.scheme) ⟨rfl, hs, hl, hγ⟩ rfl + (S.reduced_leafValue x₀ γ)⟩ + | r :: rs, γ, hs, hl, hγ => by + set m := (S.modes x₀).length + set acc : ℕ → ℕ × List ℕ := fun c => (c, (List.range c).foldl + (fun s c => add (S.prec x₀).1 (S.prec x₀).2 s + (S.contrib x₀ r γ c (S.value x₀ rs (S.childExps x₀ γ c)))) (zero (S.prec x₀).2)) + have hred (c : ℕ) : SumOps.galois.Reduced (S.prec x₀) (acc c).2 := + SumOps.galois.reduced_foldl (S.prec x₀) _ (SumOps.galois.reduced_add (S.prec x₀)) _ _ + (SumOps.galois.reduced_zero (S.prec x₀)) + have hchild (c : ℕ) := eval_value x₀ rows₀ γ₀ rs (S.childExps x₀ γ c) + ((List.suffix_cons r rs).trans hs) (by rw [length_childExps, hl]) + (fun j => by have := hγ j; rw [getD_childExps]; split_ifs <;> omega) + choose hgt hhgt heval using hchild + have hnode : (S.invariant x₀ rows₀ γ₀).node (x₀, (r :: rs, γ)) := ⟨rfl, hs, hl, hγ⟩ + obtain ⟨h, hh, hloop⟩ := Scheme.Loop.counted (R := S.scheme) + (I := S.invariant x₀ rows₀ γ₀) (x := (x₀, (r :: rs, γ))) (acc := acc) + (child := fun c => (x₀, (rs, S.childExps x₀ γ c))) + (val := fun c => S.value x₀ rs (S.childExps x₀ γ c)) + (nodes := fun _ => count m rs.length) (height := hgt) (m + 1) rs.length + (fun c hc => by + simp only [scheme, acc, ne_eq, reduceCtorEq, not_false_eq_true, true_and, + List.tail_cons, ite_eq_left_iff, not_le] + omega) + (by simp [scheme, acc, m]) + (fun c hc => by + simp only [scheme, acc, List.head?_cons, Option.elim_some, List.range_succ, + List.foldl_append, List.foldl_cons, List.foldl_nil]) + (fun c _ => heval c) + (fun c hc => ⟨hnode, by simp only [acc]; omega, hred c⟩) + (fun c _ => hhgt c) + have hstart : S.scheme.start (x₀, (r :: rs, γ)) = acc 0 := by simp [scheme, acc] + rw [← hstart] at hloop + refine ⟨h + 1, by simp only [List.length_cons]; omega, ?_⟩ + have := Scheme.Eval.call (R := S.scheme) (I := S.invariant x₀ rows₀ γ₀) + (x := (x₀, (r :: rs, γ))) hnode rfl hloop (hred (m + 1)) + rw [sum_const, card_range, smul_eq_mul] at this + exact this + +/-- **The root**: from `(x, (rows, γ))`, the recursion computes `value x rows γ` with at most +`(m + 2) ^ L` nodes for `m` modes and `L` rows. -/ +theorem eval_root (x : C) (rows : List Rw) (γ : List ℕ) : + ∃ h ≤ rows.length, S.scheme.Eval (S.invariant x rows γ) (x, (rows, γ)) (S.value x rows γ) + (count (S.modes x).length rows.length) h := + S.eval_value x rows γ rows γ (List.suffix_refl _) rfl fun _ => le_rfl + + +/-! ### Polynomial-time components -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {encC : C ↪ List Bool} {encRw : Rw ↪ List Bool} + +/-- The encoding of a node: the context, the rows and the exponents in binary. -/ +abbrev encNode (encC : C ↪ List Bool) (encRw : Rw ↪ List Bool) : Node C Rw ↪ List Bool := + encPair encC (encPair (encList encRw) (encList encNat)) + +/-- The encoding of an accumulator: the counter and the sum, in binary. -/ +abbrev encAcc : ℕ × List ℕ ↪ List Bool := encPair encNat (encList encNat) + +/-- **Polynomial-time data**: the precision with `p` in unary, the modes, and the terms. -/ +structure PolyTime (encC : C ↪ List Bool) (encRw : Rw ↪ List Bool) : Prop where + prec : ComputableInPolyTime S.prec encC (encPair encUnary (encList encNat)) + modes : ComputableInPolyTime S.modes encC (encList encNat) + term : ComputableInPolyTime (fun z : C × Rw × Option ℕ => S.term z.1 z.2.1 z.2.2) + (encPair encC (encPair encRw (encOption encNat))) (encList encNat) + +variable {S} (hS : S.PolyTime encC encRw) +include hS + +section Lift + +variable {α : Type} {encIn : α ↪ List Bool} {x : α → C} + (hx : ComputableInPolyTime x encIn encC) +include hx + +theorem precision_polyTime : ComputableInPolyTime (fun a => (S.prec (x a)).1) encIn encUnary := + (ComputableInPolyTime.fst _ _).comp (hS.prec.comp hx) + +theorem coeffs_polyTime : ComputableInPolyTime (fun a => (S.prec (x a)).2) encIn + (encList encNat) := + (ComputableInPolyTime.snd _ _).comp (hS.prec.comp hx) + +theorem zero_polyTime : ComputableInPolyTime (fun a => zero (S.prec (x a)).2) encIn + (encList encNat) := + computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp (coeffs_polyTime hS hx)) + (ComputableInPolyTime.const 0) + +theorem modeOf_polyTime {c : α → ℕ} (hc : ComputableInPolyTime c encIn encNat) : + ComputableInPolyTime (fun a => S.modeOf (x a) (c a)) encIn encNat := + ComputableInPolyTime.listGetD_binary (encA := encNat) 0 (hS.modes.comp hx) + (ComputableInPolyTime.binary_sub' hc (ComputableInPolyTime.const 1)) + +theorem childExps_polyTime {γ : α → List ℕ} {c : α → ℕ} + (hγ : ComputableInPolyTime γ encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn encNat) : + ComputableInPolyTime (fun a => S.childExps (x a) (γ a) (c a)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have hget := computableInPolyTime_getD (hγ.comp h1) h2 + have hf : ComputableInPolyTime (fun q : α × ℕ => + if c q.1 = 0 then (γ q.1).getD q.2 0 else + if q.2 = S.modeOf (x q.1) (c q.1) then (γ q.1).getD q.2 0 - 1 else (γ q.1).getD q.2 0) + (encPair encIn encUnary) encNat := + (ComputableInPolyTime.ite (ComputableInPolyTime.beq (hc.comp h1) + (ComputableInPolyTime.const 0)) hget + (ComputableInPolyTime.ite (ComputableInPolyTime.beq + (ComputableInPolyTime.unary_to_binary.comp h2) (modeOf_polyTime hS (hx.comp h1) + (hc.comp h1))) + (ComputableInPolyTime.binary_sub' hget (ComputableInPolyTime.const 1)) hget)).of_enc_eq + fun q => by + by_cases h0 : c q.1 = 0 + · simp [h0] + · by_cases hj : q.2 = S.modeOf (x q.1) (c q.1) <;> simp [h0, hj] + refine (ComputableInPolyTime.mapWithInput (f := fun a j => + if c a = 0 then (γ a).getD j 0 else + if j = S.modeOf (x a) (c a) then (γ a).getD j 0 - 1 else (γ a).getD j 0) + (ComputableInPolyTime.range.comp ((ComputableInPolyTime.length_list encNat).comp hγ)) + hf).of_enc_eq fun a => ?_ + refine congrArg _ (List.map_congr_left fun j _ => ?_) + by_cases h0 : c a = 0 + · simp [h0] + · by_cases hj : j = S.modeOf (x a) (c a) <;> simp [h0, hj] + +theorem contrib_polyTime {r : α → Rw} {γ : α → List ℕ} {c : α → ℕ} {y : α → List ℕ} + (hr : ComputableInPolyTime r encIn encRw) + (hγ : ComputableInPolyTime γ encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn encNat) + (hy : ComputableInPolyTime y encIn (encList encNat)) : + ComputableInPolyTime (fun a => S.contrib (x a) (r a) (γ a) (c a) (y a)) encIn + (encList encNat) := by + have hp := precision_polyTime hS hx + have hfl := coeffs_polyTime hS hx + have hk := modeOf_polyTime hS hx hc + have hterm (o : α → Option ℕ) (ho : ComputableInPolyTime o encIn (encOption encNat)) : + ComputableInPolyTime (fun a => S.term (x a) (r a) (o a)) encIn (encList encNat) := + hS.term.comp (hx.prod (hr.prod ho)) + exact (ComputableInPolyTime.ite (ComputableInPolyTime.beq hc (ComputableInPolyTime.const 0)) + (computableInPolyTime_mul hp hfl (hterm _ (ComputableInPolyTime.const none)) hy) + (ComputableInPolyTime.ite (ComputableInPolyTime.beq + (ComputableInPolyTime.listGetD_binary (encA := encNat) 0 hγ hk) + (ComputableInPolyTime.const 0)) + (zero_polyTime hS hx) + (computableInPolyTime_mul hp hfl + (hterm _ ((ComputableInPolyTime.optionSome encNat).comp hk)) hy))).of_enc_eq + fun a => by + by_cases h0 : c a = 0 + · simp [contrib, h0] + · simp [contrib, h0] + +end Lift + +/-- The leaf is polynomial-time. -/ +theorem leafValue_polyTime : + ComputableInPolyTime (fun x : Node C Rw => S.leafValue x.1 x.2.2) (encNode encC encRw) + (encList encNat) := by + have hx := ComputableInPolyTime.fst encC (encPair (encList encRw) (encList encNat)) + have hγ := (ComputableInPolyTime.snd (encList encRw) (encList encNat)).comp + (ComputableInPolyTime.snd encC (encPair (encList encRw) (encList encNat))) + have htest : ComputableInPolyTime (fun z : Node C Rw × ℕ => z.1.2.2.getD z.2 0 == 0) + (encPair (encNode encC encRw) encNat) encBool := + ComputableInPolyTime.beq (ComputableInPolyTime.listGetD_binary (encA := encNat) 0 + (hγ.comp (ComputableInPolyTime.fst _ _)) (ComputableInPolyTime.snd _ _)) + (ComputableInPolyTime.const 0) + exact ComputableInPolyTime.ite (ComputableInPolyTime.allWithInput (hS.modes.comp hx) htest) + (computableInPolyTime_one (coeffs_polyTime hS hx)) (zero_polyTime hS hx) + +/-- **The components of coefficient extraction are polynomial-time.** -/ +theorem scheme_polyTime : S.scheme.PolyTime (encNode encC encRw) encAcc (encList encNat) where + isLeaf := (ComputableInPolyTime.listIsEmpty (encA := encRw)).comp + ((ComputableInPolyTime.fst _ _).comp (ComputableInPolyTime.snd _ _)) + start := (ComputableInPolyTime.const 0).prod (zero_polyTime hS (ComputableInPolyTime.fst _ _)) + next := by + have hn := ComputableInPolyTime.fst (encNode encC encRw) encAcc + have hx := (ComputableInPolyTime.fst _ _).comp hn + have hrows := (ComputableInPolyTime.fst (encList encRw) (encList encNat)).comp + ((ComputableInPolyTime.snd _ _).comp hn) + have hγ := (ComputableInPolyTime.snd (encList encRw) (encList encNat)).comp + ((ComputableInPolyTime.snd _ _).comp hn) + have hc := (ComputableInPolyTime.fst encNat (encList encNat)).comp + (ComputableInPolyTime.snd (encNode encC encRw) encAcc) + have hm := ComputableInPolyTime.unary_to_binary.comp + ((ComputableInPolyTime.length_list encNat).comp (hS.modes.comp hx)) + have hsome : ComputableInPolyTime (fun z : Node C Rw × (ℕ × List ℕ) => + some (z.1.1, (z.1.2.1.tail, S.childExps z.1.1 z.1.2.2 z.2.1))) + (encPair (encNode encC encRw) encAcc) (encOption (encNode encC encRw)) := + (ComputableInPolyTime.optionSome (encNode encC encRw)).comp + (hx.prod (((ComputableInPolyTime.tail (encA := encRw)).comp hrows).prod + (childExps_polyTime hS hx hγ hc))) + refine (ComputableInPolyTime.ite ((ComputableInPolyTime.listIsEmpty (encA := encRw)).comp hrows) + (ComputableInPolyTime.const none) + (ComputableInPolyTime.ite (ComputableInPolyTime.binary_le' hc hm) hsome + (ComputableInPolyTime.const none))).of_enc_eq fun z => ?_ + by_cases he : z.1.2.1 = [] + · simp [scheme, he] + · by_cases hle : z.2.1 ≤ (S.modes z.1.1).length <;> simp [scheme, he, hle] + absorb := by + have hq := ComputableInPolyTime.fst (encPair (encNode encC encRw) encAcc) (encList encNat) + have hn := (ComputableInPolyTime.fst (encNode encC encRw) encAcc).comp hq + have ha := (ComputableInPolyTime.snd (encNode encC encRw) encAcc).comp hq + have hy := ComputableInPolyTime.snd (encPair (encNode encC encRw) encAcc) (encList encNat) + have hx := (ComputableInPolyTime.fst _ _).comp hn + have hrows := (ComputableInPolyTime.fst (encList encRw) (encList encNat)).comp + ((ComputableInPolyTime.snd _ _).comp hn) + have hγ := (ComputableInPolyTime.snd (encList encRw) (encList encNat)).comp + ((ComputableInPolyTime.snd _ _).comp hn) + have hc := (ComputableInPolyTime.fst encNat (encList encNat)).comp ha + have hs := (ComputableInPolyTime.snd encNat (encList encNat)).comp ha + let E := encPair (encPair (encNode encC encRw) encAcc) (encList encNat) + have hbody : ComputableInPolyTime + (fun w : ((Node C Rw × (ℕ × List ℕ)) × List ℕ) × Rw => + add (S.prec w.1.1.1.1).1 (S.prec w.1.1.1.1).2 w.1.1.2.2 + (S.contrib w.1.1.1.1 w.2 w.1.1.1.2.2 w.1.1.2.1 w.1.2)) (encPair E encRw) + (encList encNat) := by + have h1 := ComputableInPolyTime.fst E encRw + exact computableInPolyTime_add (precision_polyTime hS (hx.comp h1)) + (coeffs_polyTime hS (hx.comp h1)) (hs.comp h1) + (contrib_polyTime hS (hx.comp h1) (ComputableInPolyTime.snd E encRw) (hγ.comp h1) + (hc.comp h1) (hy.comp h1)) + exact (ComputableInPolyTime.binary_add.comp (hc.prod (ComputableInPolyTime.const 1))).prod + (ComputableInPolyTime.optionElimWithInput ((ComputableInPolyTime.head? encRw).comp hrows) + hs hbody) + finish := (ComputableInPolyTime.snd encNat (encList encNat)).comp (ComputableInPolyTime.snd _ _) + + +/-! ### The native machine and its resources -/ + +/-- The number of steps: `2 (m + 2) ^ L` for `m` modes and `L` rows, at least the `2 N - 1` +steps of a derivation with `N ≤ (m + 2) ^ L` nodes. -/ +def fuel (x : Node C Rw) : ℕ := 2 * ((S.modes x.1).length + 2) ^ x.2.1.length + +theorem fuel_polyTime : ComputableInPolyTime S.fuel (encNode encC encRw) encNat := by + have hx := ComputableInPolyTime.fst encC (encPair (encList encRw) (encList encNat)) + have hrows := (ComputableInPolyTime.fst (encList encRw) (encList encNat)).comp + (ComputableInPolyTime.snd encC (encPair (encList encRw) (encList encNat))) + exact ComputableInPolyTime.binary_mul' (ComputableInPolyTime.const 2) + (ComputableInPolyTime.binary_pow' (ComputableInPolyTime.binary_add.comp + ((ComputableInPolyTime.unary_to_binary.comp ((ComputableInPolyTime.length_list encNat).comp + (hS.modes.comp hx))).prod (ComputableInPolyTime.const 2))) + ((ComputableInPolyTime.length_list encRw).comp hrows)) + +/-- The leaf certificate. -/ +noncomputable def leafCertificate : + NativeCertificate S.scheme.leaf (encNode encC encRw) (encList encNat) := + NativeCertificate.ofPolyTime (leafValue_polyTime hS) + +/-- **One native machine for coefficient extraction**, from polynomial-time data: the clocked +run of the scheme from the node itself. -/ +noncomputable def runCertificate : + NativeCertificate (S.scheme.run S.fuel id) (encNode encC encRw) + (encOption (encList encNat)) := + Scheme.certificate (scheme_polyTime hS) (leafCertificate hS) + (NativeCertificate.ofPolyTime (fuel_polyTime hS)) + (NativeCertificate.ofPolyTime ComputableInPolyTime.id') + +omit hS in +/-- **The machine computes `value`.** -/ +theorem run_scheme (x : Node C Rw) : S.scheme.run S.fuel id x = some (S.value x.1 x.2.1 x.2.2) := by + obtain ⟨h, -, he⟩ := S.eval_root x.1 x.2.1 x.2.2 + refine Scheme.run_eq_some he ?_ + have := count_le (S.modes x.1).length x.2.1.length + simp only [fuel] + omega + +/-- **Coefficient extraction, as a native certificate** for `value` on every node. -/ +noncomputable def valueCertificate : + NativeCertificate (fun x : Node C Rw => S.value x.1 x.2.1 x.2.2) (encNode encC encRw) + (encList encNat) := + (NativeCertificate.post (runCertificate hS) + (ComputableInDegree.getD (enc₁ := encList encNat) (enc := encOption (encList encNat)) + []).inO_one).congr fun x => by simp [run_scheme] + +@[simp] theorem valueCertificate_time : + (valueCertificate hS).time = (runCertificate hS).time := rfl + +@[simp] theorem valueCertificate_space : + (valueCertificate hS).space = (runCertificate hS).space := rfl + +omit hS in +private theorem sum_map_le_of_getD_le {g : ℕ → ℕ} (hg : Monotone g) : + ∀ (γ γ₀ : List ℕ), γ.length = γ₀.length → (∀ j, γ.getD j 0 ≤ γ₀.getD j 0) → + (γ.map g).sum ≤ (γ₀.map g).sum + | [], [], _, _ => le_rfl + | a :: γ, b :: γ₀, hl, h => by + simp only [List.map_cons, List.sum_cons] + exact Nat.add_le_add (hg (by simpa using h 0)) + (sum_map_le_of_getD_le hg γ γ₀ (by simpa using hl) fun j => by simpa using h (j + 1)) + +omit hS in +/-- A node of the derivation from a root is at most as long as the root. -/ +theorem length_encNode_le {x₀ : C} {rows₀ : List Rw} {γ₀ : List ℕ} {x : Node C Rw} + (hx : (S.invariant x₀ rows₀ γ₀).node x) : + (encNode encC encRw x).length ≤ (encNode encC encRw (x₀, (rows₀, γ₀))).length := by + obtain ⟨hx₀, ⟨pre, hpre⟩, hl, hγ⟩ := hx + obtain ⟨x', rs, γ⟩ := x + simp only at hx₀ hpre hl hγ + subst hx₀ hpre + have h1 : (encList encRw rs).length ≤ (encList encRw (pre ++ rs)).length := by + simp only [length_encList, List.map_append, List.sum_append] + omega + have h2 : (encList encNat γ).length ≤ (encList encNat γ₀).length := by + simp only [length_encList] + exact sum_map_le_of_getD_le (fun a b hab => by + change 2 * a.bits.length + 1 ≤ 2 * b.bits.length + 1 + rw [Nat.size_eq_bits_len, Nat.size_eq_bits_len] + exact Nat.add_le_add_right (Nat.mul_le_mul_left 2 (Nat.size_le_size hab)) 1) γ γ₀ hl hγ + simp only [length_encPair] + omega + +omit hS in +private theorem length_encNat_lt {y P : ℕ} (h : y < 2 ^ P) : (encNat y).length ≤ P := by + have h' := Nat.size_le.mpr h + rw [← Nat.size_eq_bits_len] at h' + exact h' + +/-- **The resources of coefficient extraction.** There is `K` such that on a node of length `n` +with `m` modes and `L` rows, if the reduced values of its precision are at most `Rv` bits long, +then with `Z = n + m + Rv + 2` the time is at most `K (m + 2) ^ L Z ^ K` and the space at most +`K Z ^ K`: time `exp(O(L log m))` times a polynomial, in polynomial space. -/ +theorem valueCertificate_le : + ∃ K : ℕ, ∀ (x : Node C Rw) (Rv : ℕ), + (∀ y, SumOps.galois.Reduced (S.prec x.1) y → (encList encNat y).length ≤ Rv) → + (valueCertificate hS).time x ≤ K * ((S.modes x.1).length + 2) ^ x.2.1.length * + ((encNode encC encRw x).length + (S.modes x.1).length + Rv + 2) ^ K ∧ + (valueCertificate hS).space x ≤ + K * ((encNode encC encRw x).length + (S.modes x.1).length + Rv + 2) ^ K := by + obtain ⟨K₀, hK₀⟩ := Scheme.certificate_le (scheme_polyTime hS) (leafCertificate hS) + have hL := NativeCertificate.ofPolyTime_isPolynomial (leafValue_polyTime hS) + have hF := NativeCertificate.ofPolyTime_isPolynomial (fuel_polyTime hS) + have hR := NativeCertificate.ofPolyTime_isPolynomial + (ComputableInPolyTime.id' (encIn := encNode encC encRw)) + obtain ⟨cl, dl, hlt⟩ := hL.1.exists_le + obtain ⟨cl', dl', hls⟩ := hL.2.exists_le + obtain ⟨cf, df, hft⟩ := hF.1.exists_le + obtain ⟨cf', df', hfs⟩ := hF.2.exists_le + obtain ⟨cr, dr, hrt⟩ := hR.1.exists_le + obtain ⟨cr', dr', hrs⟩ := hR.2.exists_le + obtain ⟨D, hD⟩ : ∃ D, D = 2 * K₀ + dl + dl' + df + df' + dr + dr' + 4 := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = cf + cr + 2 + 3 * (K₀ * 8 ^ K₀ + cl + 9) := ⟨_, rfl⟩ + obtain ⟨G', hG'⟩ : ∃ G', G' = cf' + cr' + 2 + K₀ * 8 ^ K₀ + cl' + 2 := ⟨_, rfl⟩ + refine ⟨G + G' + D, fun x Rv hRv => ?_⟩ + obtain ⟨x₀, rows₀, γ₀⟩ := x + simp only + obtain ⟨n, hn⟩ : ∃ n, n = (encNode encC encRw (x₀, (rows₀, γ₀))).length := ⟨_, rfl⟩ + obtain ⟨m, hm⟩ : ∃ m, m = (S.modes x₀).length := ⟨_, rfl⟩ + obtain ⟨L, hLen⟩ : ∃ L, L = rows₀.length := ⟨_, rfl⟩ + obtain ⟨Z, hZ⟩ : ∃ Z, Z = n + m + Rv + 2 := ⟨_, rfl⟩ + obtain ⟨W, hW⟩ : ∃ W, W = Z ^ D := ⟨_, rfl⟩ + rw [← hn, ← hm, ← hLen, ← hZ] + have hZ2 : 2 ≤ Z := by omega + have hLn : L ≤ n := by + have := length_le_length_encList encRw rows₀ + rw [hn, hLen] + simp only [length_encPair] + omega + have hpowZ {a : ℕ} (ha : a ≤ D) : Z ^ a ≤ W := by + rw [hW]; exact Nat.pow_le_pow_right (by omega) ha + have hZW : Z ≤ W := by + calc Z = Z ^ 1 := (pow_one Z).symm + _ ≤ W := hpowZ (by omega) + have hn1 {c e : ℕ} (he : e ≤ D) : c * (n + 1) ^ e ≤ c * W := + Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans (hpowZ he)) + have hP : 1 ≤ (m + 2) ^ L := Nat.one_le_pow _ _ (by omega) + -- The derivation and the bounds on its nodes, frames and values. + obtain ⟨h, hh, he⟩ := S.eval_root x₀ rows₀ γ₀ + have hnode (x : Node C Rw) (hx : (S.invariant x₀ rows₀ γ₀).node x) : + (encNode encC encRw x).length ≤ n := hn ▸ length_encNode_le (S := S) hx + have hres := hK₀ (NativeCertificate.ofPolyTime (fuel_polyTime hS)) + (NativeCertificate.ofPolyTime ComputableInPolyTime.id') (S.invariant x₀ rows₀ γ₀) (3 * Z) + (cl * Z ^ dl) (cl' * Z ^ dl') + (fun x hx => ⟨(hnode x hx).trans (by omega), fun _ => + ⟨(hlt x).trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by + have := hnode x hx; omega) _))), + (hls x).trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by + have := hnode x hx; omega) _)))⟩⟩) + (fun x a ha => by + obtain ⟨hx, ha1, ha2⟩ := ha + have h1 := hnode x hx + have h2 : (encNat a.1).length ≤ m + 2 := + length_encNat_lt (lt_of_le_of_lt (hm ▸ ha1) (by + have := Nat.lt_two_pow_self (n := m + 2); omega)) + have h3 := hRv a.2 ha2 + have h4 : (encPair (encNode encC encRw) encAcc (x, a)).length = + 2 * (encNode encC encRw x).length + 1 + (encAcc a).length := length_encPair (x, a) + have h5 : (encAcc a).length = 2 * (encNat a.1).length + 1 + (encList encNat a.2).length := + length_encPair a + omega) + (fun y hy => by have := hRv y hy; omega) + (x₀, (rows₀, γ₀)) _ _ _ he + obtain ⟨htime, hspace⟩ := hres + -- The fuel and its clock. + have hfuel : S.fuel (x₀, (rows₀, γ₀)) = 2 * (m + 2) ^ L := by simp only [fuel, hm, hLen] + have hfuelLen : (encNat (S.fuel (x₀, (rows₀, γ₀)))).length ≤ 2 * Z ^ 2 := by + have hpow : (m + 2) ^ L < 2 ^ ((m + 2) * L + 1) := by + calc (m + 2) ^ L ≤ (2 ^ (m + 2)) ^ L := + Nat.pow_le_pow_left (Nat.lt_two_pow_self).le _ + _ = 2 ^ ((m + 2) * L) := (pow_mul _ _ _).symm + _ < 2 ^ ((m + 2) * L + 1) := Nat.pow_lt_pow_right (by norm_num) (by omega) + have h1 : S.fuel (x₀, (rows₀, γ₀)) < 2 ^ ((m + 2) * L + 2) := by + rw [hfuel, pow_succ]; omega + have h2 := length_encNat_lt h1 + have h3 : (m + 2) * L ≤ Z * Z := Nat.mul_le_mul (by omega) (by omega) + have h4 : 4 ≤ Z * Z := Nat.mul_le_mul hZ2 hZ2 + rw [sq] + omega + have hclock : ((encNat (S.fuel (x₀, (rows₀, γ₀)))).length + 1) ^ 2 ≤ 9 * W := by + have hZsq : 1 ≤ Z ^ 2 := Nat.one_le_pow _ _ (by omega) + calc ((encNat (S.fuel (x₀, (rows₀, γ₀)))).length + 1) ^ 2 ≤ (3 * Z ^ 2) ^ 2 := + Nat.pow_le_pow_left (by omega) 2 + _ = 9 * Z ^ 4 := by ring + _ ≤ 9 * W := Nat.mul_le_mul_left _ (hpowZ (by omega)) + have hstack : K₀ * ((h + 1) * (3 * Z + 1)) ^ K₀ ≤ K₀ * 8 ^ K₀ * W := by + have h1 : (h + 1) * (3 * Z + 1) ≤ 8 * Z ^ 2 := by + have hh2 : h + 1 ≤ 2 * Z := by omega + have hZZ : Z ≤ Z ^ 2 := Nat.le_self_pow (by norm_num) Z + calc (h + 1) * (3 * Z + 1) ≤ (2 * Z) * (3 * Z + 1) := Nat.mul_le_mul_right _ hh2 + _ = 6 * Z ^ 2 + 2 * Z := by ring + _ ≤ 8 * Z ^ 2 := by omega + calc K₀ * ((h + 1) * (3 * Z + 1)) ^ K₀ ≤ K₀ * (8 * Z ^ 2) ^ K₀ := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h1 _) + _ = K₀ * 8 ^ K₀ * Z ^ (2 * K₀) := by rw [Nat.mul_pow, ← pow_mul]; ring + _ ≤ K₀ * 8 ^ K₀ * W := Nat.mul_le_mul_left _ (hpowZ (by omega)) + have hTL : cl * Z ^ dl ≤ cl * W := Nat.mul_le_mul_left _ (hpowZ (by omega)) + have hSL : cl' * Z ^ dl' ≤ cl' * W := Nat.mul_le_mul_left _ (hpowZ (by omega)) + have hcf := (hft (x₀, (rows₀, γ₀))).trans (hn ▸ hn1 (c := cf) (show df ≤ D by omega)) + have hcr := (hrt (x₀, (rows₀, γ₀))).trans (hn ▸ hn1 (c := cr) (show dr ≤ D by omega)) + have hcf' := (hfs (x₀, (rows₀, γ₀))).trans (hn ▸ hn1 (c := cf') (show df' ≤ D by omega)) + have hcr' := (hrs (x₀, (rows₀, γ₀))).trans (hn ▸ hn1 (c := cr') (show dr' ≤ D by omega)) + rw [← hn] at htime hspace + have hWK : W ≤ Z ^ (G + G' + D) := hW ▸ Nat.pow_le_pow_right (by omega) (by omega) + have hnW : n ≤ W := by omega + constructor + · rw [valueCertificate_time] + unfold runCertificate + obtain ⟨P, hPdef⟩ : ∃ P, P = (m + 2) ^ L := ⟨_, rfl⟩ + rw [← hPdef] at hP ⊢ + obtain ⟨Y, hY⟩ : ∃ Y, Y = K₀ * ((h + 1) * (3 * Z + 1)) ^ K₀ + cl * Z ^ dl + + ((encNat (S.fuel (x₀, (rows₀, γ₀)))).length + 1) ^ 2 := ⟨_, rfl⟩ + rw [← hY] at htime + have hfP : (S.fuel (x₀, (rows₀, γ₀)) + 1) * Y = (2 * P + 1) * Y := by rw [hfuel, hPdef] + have hstep : Y ≤ (K₀ * 8 ^ K₀ + cl + 9) * W := by + have e : (K₀ * 8 ^ K₀ + cl + 9) * W = K₀ * 8 ^ K₀ * W + cl * W + 9 * W := by ring + omega + have h1 := Nat.mul_le_mul_left (2 * P + 1) hstep + have h2 : (2 * P + 1) * ((K₀ * 8 ^ K₀ + cl + 9) * W) ≤ + P * (3 * (K₀ * 8 ^ K₀ + cl + 9) * W) := by + have : 2 * P + 1 ≤ 3 * P := by omega + calc (2 * P + 1) * ((K₀ * 8 ^ K₀ + cl + 9) * W) + ≤ (3 * P) * ((K₀ * 8 ^ K₀ + cl + 9) * W) := Nat.mul_le_mul_right _ this + _ = P * (3 * (K₀ * 8 ^ K₀ + cl + 9) * W) := by ring + have h3 : cf * W + cr * W + 2 * W ≤ P * ((cf + cr + 2) * W) := by + have e : cf * W + cr * W + 2 * W = (cf + cr + 2) * W := by ring + rw [e]; exact Nat.le_mul_of_pos_left _ hP + have h4 : P * ((cf + cr + 2) * W) + P * (3 * (K₀ * 8 ^ K₀ + cl + 9) * W) = P * (G * W) := by + rw [hG]; ring + calc _ ≤ P * (G * W) := by omega + _ ≤ (G + G' + D) * P * Z ^ (G + G' + D) := by + rw [show (G + G' + D) * P * Z ^ (G + G' + D) = P * ((G + G' + D) * Z ^ (G + G' + D)) by + ring] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul (by omega) hWK) + · rw [valueCertificate_space] + unfold runCertificate + have h1 : (encNat (S.fuel (x₀, (rows₀, γ₀)))).length ≤ 2 * W := by + have := hpowZ (show 2 ≤ D by omega); omega + calc _ ≤ cf' * W + cr' * W + 2 * W + K₀ * 8 ^ K₀ * W + cl' * W + 2 * W := by omega + _ = G' * W := by rw [hG']; ring + _ ≤ (G + G' + D) * Z ^ (G + G' + D) := Nat.mul_le_mul (by omega) hWK + +end PolyTime + +end Terms + +end CoeffScheme + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCoeffScheme.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCoeffScheme.lean new file mode 100644 index 0000000000..4c20e833d8 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkCoeffScheme.lean @@ -0,0 +1,62 @@ +/- +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.CoeffScheme + +/-! +# Tests for coefficient extraction as a stack recursion + +Two rows `1` and `2` over `A_3 = (ℤ / 8)[U] / (U ^ 2 + U + 1)`, with the modes of dense indices +`0` and `2`. The row `r` has the local term `r` and the coefficient `k + 1 + U` at the mode `k`. +The coefficient of the mode `0` in the product of the two row factors is +`1 (1 + U) + (1 + U) 2 = 3 + 3 U`, and the machine returns it. +-/ + +@[expose] public section + +namespace ComplexityTests.TensorNetworkCoeffScheme + +open Cslib Cslib.Circuits.Frontier Cslib.Circuits.Frontier.CoeffScheme + +/-- The data of the test. -/ +def terms : Terms Unit ℕ where + prec _ := (3, [1, 1]) + modes _ := [0, 2] + term _ r o := match o with + | none => [r, 0] + | some k => [k + 1, 1] + +-- The value, at exponent `1` on the mode `0`. +example : terms.value () [1, 2] [1, 0, 0] = [3, 3] := by decide + +-- The stack machine, with fuel `2 (2 + 2) ^ 2 = 32`, returns it. +example : terms.scheme.run terms.fuel id ((), ([1, 2], [1, 0, 0])) = some [3, 3] := by decide + +-- With both exponents zero, the coefficient of the constant term is `1 * 2 = 2`. +example : terms.value () [1, 2] [0, 0, 0] = [2, 0] := by decide + +-- A mode needed twice from one row factor has coefficient zero. +example : terms.value () [1] [2, 0, 0] = [0, 0] := by decide + +-- On every node, the run is the value, and the value in `A_p` is `listCoeff`. +example (x : Node Unit ℕ) : + terms.scheme.run terms.fuel id x = some (terms.value x.1 x.2.1 x.2.2) := + terms.run_scheme x + +example (rows : List ℕ) (γ : List ℕ) : + GaloisRingArith.val 3 [1, 1] (terms.value () rows γ) = + TensorNetwork.listCoeff ({0, 2} : Finset ℕ) + (rows.map fun r o => GaloisRingArith.val 3 [1, 1] (terms.term () r o)) + (fun k => γ.getD k 0) := + terms.val_value () (by decide) rows γ + +-- At most `(m + 2) ^ L` nodes. +example : count 2 2 = 13 ∧ count 2 2 ≤ (2 + 2) ^ 2 := by decide + +end ComplexityTests.TensorNetworkCoeffScheme From 3130c30f154ebf91cc7fde8f90727f18e62a6fd6 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:47:04 -0400 Subject: [PATCH 21/33] refactor(circuit/frontier): take the number of slots L as a parameter of kernel values Kernel values iterate over the 2^(a+1) grid points, so a native certificate needs the number of slots L = 2^a in unary. The native functions now take L, and the correctness theorems assume L = 2^a. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/PacketEvaluator.lean | 114 ++++++++++-------- .../TensorNetworkPacketEvaluator.lean | 30 ++--- 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean index e9e8449c56..0d0f3f82e7 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean @@ -27,7 +27,8 @@ ring that `A_p` maps to. `ℤ_(2)` to a ring in which `2` is nilpotent agree (`padicInteger_ringHom_ext`). An integer is a constant list (`NativePacket.intConst`). * **Kernel values** (`NativePacket.kernelValueList`) follow Lemma D.4's evaluation - (`kernelValue_eq_prod`, p. 60): the inverse of the odd part of `C'(0)`, times + (`kernelValue_eq_prod`, p. 60), over the grid of `L = 2 ^ a` slots, with `L` a parameter so that + it can be read in unary: the inverse of the odd part of `C'(0)`, times `2 ^ (a + 1 - v_d) ζ` when `d ≠ t`, times the `2 ^ (a + 1) - 2` linear factors `u_q + 2 ^ (a + 1 - v_q) ζ`. The odd part of `C'(0)` is a product of odd integers, so it is `1` modulo `2` and `GaloisRingArith.inv` inverts it. @@ -298,16 +299,16 @@ theorem exists_val_linFactor_eq {u : ℤ} (hu : Odd u) {k : ℕ} (hk : 1 ≤ k) obtain ⟨k', rfl⟩ := Nat.exists_eq_add_of_le hk exact ⟨w + 2 ^ k' * val p fl ζ, by rw [val_linFactor, hw, pow_add]; ring⟩ -/-- The grid `[-2 ^ a, 2 ^ a)` of `2 ^ a` slots, in increasing order. -/ -def gridList (a : ℕ) : List ℤ := - (List.range (2 ^ (a + 1))).map fun j : ℕ => (j : ℤ) - ((2 ^ a : ℕ) : ℤ) +/-- The grid `[-L, L)` of `L` slots, in increasing order. The number of slots is a parameter, in +place of `2 ^ a`, so that it can be read in unary. -/ +def gridList (L : ℕ) : List ℤ := + (List.range (2 * L)).map fun j : ℕ => (j : ℤ) - (L : ℤ) -theorem nodup_gridList (a : ℕ) : (gridList a).Nodup := +theorem nodup_gridList (L : ℕ) : (gridList L).Nodup := List.Nodup.map (fun x y h => by simpa using h) List.nodup_range -theorem mem_gridList {a : ℕ} {q : ℤ} : q ∈ gridList a ↔ q ∈ cauchyGrid (2 ^ a) := by - rw [gridList, List.mem_map, mem_cauchyGrid, pow_succ] - generalize 2 ^ a = L +theorem mem_gridList {L : ℕ} {q : ℤ} : q ∈ gridList L ↔ q ∈ cauchyGrid L := by + rw [gridList, List.mem_map, mem_cauchyGrid] constructor · rintro ⟨j, hj, rfl⟩ rw [List.mem_range] at hj @@ -316,29 +317,30 @@ theorem mem_gridList {a : ℕ} {q : ℤ} : q ∈ gridList a ↔ q ∈ cauchyGrid refine ⟨(q + L).toNat, List.mem_range.mpr ?_, ?_⟩ <;> omega /-- The odd part of `C'(0) = ∏_{q ≠ 0} (0 - q)`, as a coefficient list. -/ -def kernelUnitDen (p : ℕ) (fl : List ℕ) (a : ℕ) : List ℕ := - prodList p fl (((gridList a).filter (· ≠ 0)).map fun q => +def kernelUnitDen (p : ℕ) (fl : List ℕ) (L : ℕ) : List ℕ := + prodList p fl (((gridList L).filter (· ≠ 0)).map fun q => intConst p fl ((0 - q) / 2 ^ twoVal (0 - q))) /-- `κ (kernelUnit a)`, the inverse of the odd part of `C'(0)`. -/ -def kernelUnitList (p : ℕ) (fl : List ℕ) (a : ℕ) : List ℕ := - inv p fl (kernelUnitDen p fl a) +def kernelUnitList (p : ℕ) (fl : List ℕ) (L : ℕ) : List ℕ := + inv p fl (kernelUnitDen p fl L) /-- The value of `kernelUnitDen`. -/ -private theorem val_kernelUnitDen (a : ℕ) : - val p fl (kernelUnitDen p fl a) = ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, +private theorem val_kernelUnitDen (L : ℕ) : + val p fl (kernelUnitDen p fl L) = ∏ q ∈ (cauchyGrid L).erase 0, (((0 - q) / 2 ^ padicValInt 2 (0 - q) : ℤ) : Ring p fl) := by simp only [kernelUnitDen, twoVal_eq] rw [val_prodList, List.map_map, ← List.prod_toFinset _ - ((nodup_gridList a).filter _)] + ((nodup_gridList L).filter _)] refine prod_congr ?_ fun q _ => val_intConst _ ext q simp only [List.mem_toFinset, List.mem_filter, mem_gridList, mem_erase, decide_eq_true_eq] tauto /-- **The kernel unit**: `kernelUnitList` is the reduction of `kernelUnit a`. -/ -theorem val_kernelUnitList (a : ℕ) : - val p fl (kernelUnitList p fl a) = scalarHom p fl (kernelUnit a) := by +theorem val_kernelUnitList {a L : ℕ} (hL : L = 2 ^ a) : + val p fl (kernelUnitList p fl L) = scalarHom p fl (kernelUnit a) := by + subst hL have hne (q : ℤ) (hq : q ∈ (cauchyGrid (2 ^ a)).erase 0) : (0 : ℤ) - q ≠ 0 := sub_ne_zero.mpr (Ne.symm (ne_of_mem_erase hq)) -- The odd part of `C'(0)` is one modulo two. @@ -348,7 +350,8 @@ theorem val_kernelUnitList (a : ℕ) : obtain ⟨q, hq, rfl⟩ := List.mem_map.mp hy exact intCast_odd (odd_ediv_two_pow (hne q (Finset.mem_toList.mp hq))) rw [Finset.prod_map_toList] at hw - have hinv := val_mul_inv_of_eq (kernelUnitDen p fl a) (w := w) (by rw [val_kernelUnitDen, hw]) + have hinv := val_mul_inv_of_eq (kernelUnitDen p fl (2 ^ a)) (w := w) + (by rw [val_kernelUnitDen, hw]) refine eq_of_mul_eq_one hinv ?_ -- `kernelUnit a` inverts the odd part of `C'(0)` in `ℤ_(2)`. have hunit : kernelUnit a * ∏ q ∈ (cauchyGrid (2 ^ a)).erase 0, @@ -369,21 +372,23 @@ theorem val_kernelUnitList (a : ℕ) : /-- **Kernel values on coefficient lists** (Lemma D.4's evaluation, p. 60): the kernel unit, times `2 ^ (a + 1 - v_d) ζ` when `d ≠ t`, times the linear factors `u_q + 2 ^ (a + 1 - v_q) ζ` over the grid points `q ≠ d, t`. -/ -def kernelValueList (p : ℕ) (fl : List ℕ) (a : ℕ) (t d : ℤ) (ζ : List ℕ) : List ℕ := - mul p fl (mul p fl (kernelUnitList p fl a) +def kernelValueList (p : ℕ) (fl : List ℕ) (a L : ℕ) (t d : ℤ) (ζ : List ℕ) : List ℕ := + mul p fl (mul p fl (kernelUnitList p fl L) (if d = t then one fl else mul p fl (intConst p fl (2 ^ (a + 1 - twoVal (t - d)))) ζ)) - (prodList p fl (((gridList a).filter fun q => q ≠ d ∧ q ≠ t).map fun q => + (prodList p fl (((gridList L).filter fun q => q ≠ d ∧ q ≠ t).map fun q => linFactor p fl ((t - q) / 2 ^ twoVal (t - q)) (a + 1 - twoVal (t - q)) ζ)) /-- **Kernel values are correct**: for grid points `t` and `d`, the value is `kernelValue` at the reduction of `ℤ_(2)` and the value of `ζ`. -/ -theorem val_kernelValueList {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem val_kernelValueList {a L : ℕ} (hL : L = 2 ^ a) {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hd : d ∈ cauchyGrid (2 ^ a)) (ζ : List ℕ) : - val p fl (kernelValueList p fl a t d ζ) = kernelValue (scalarHom p fl) (val p fl ζ) a t d := by + val p fl (kernelValueList p fl a L t d ζ) = + kernelValue (scalarHom p fl) (val p fl ζ) a t d := by + subst hL simp only [kernelValueList, twoVal_eq] - rw [kernelValue_eq_prod _ _ ht hd, val_mul, val_mul, val_kernelUnitList, - val_prodList, List.map_map, ← List.prod_toFinset _ ((nodup_gridList a).filter _)] + rw [kernelValue_eq_prod _ _ ht hd, val_mul, val_mul, val_kernelUnitList rfl, + val_prodList, List.map_map, ← List.prod_toFinset _ ((nodup_gridList _).filter _)] congr 1 · congr 1 split_ifs @@ -425,42 +430,43 @@ theorem val_crossRatioList {a i : ℕ} (hia : i ≤ a) {t d₀ : ℤ} /-- The local form of a row port in slot `x`: the sum of the kernel values at the column ports set to one. -/ -def localFormList (p : ℕ) (fl : List ℕ) (a : ℕ) (t : ℤ) (cols : List (ℕ × Bool)) (x : ℕ) +def localFormList (p : ℕ) (fl : List ℕ) (a L : ℕ) (t : ℤ) (cols : List (ℕ × Bool)) (x : ℕ) (ζ : List ℕ) : List ℕ := - sumList p fl (cols.map fun c => if c.2 then kernelValueList p fl a t ((c.1 : ℤ) - x) ζ + sumList p fl (cols.map fun c => if c.2 then kernelValueList p fl a L t ((c.1 : ℤ) - x) ζ else zero fl) -theorem val_localFormList {a : ℕ} {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem val_localFormList {a L : ℕ} (hL : L = 2 ^ a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {x : ℕ} (hx : x < 2 ^ a) (ζ : List ℕ) : - val p fl (localFormList p fl a t cols x ζ) = + val p fl (localFormList p fl a L t cols x ζ) = packetLocalForm (scalarHom p fl) (val p fl ζ) a t cols x := by rw [localFormList, val_sumList, List.map_map, packetLocalForm] congr 1 refine List.map_congr_left fun c hc => ?_ simp only [Function.comp_apply] split_ifs - · exact val_kernelValueList ht (sub_mem_cauchyGrid hx (hcols c hc)) ζ + · exact val_kernelValueList hL ht (sub_mem_cauchyGrid hx (hcols c hc)) ζ · exact val_zero /-- The coefficient of the mode `(y₀, b)` in the form of a row port in slot `x`. -/ -def modeCoeffList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (differ : Bool) (x y₀ b : ℕ) +def modeCoeffList (p : ℕ) (fl : List ℕ) (a L i : ℕ) (t : ℤ) (differ : Bool) (x y₀ b : ℕ) (ζ : List ℕ) : List ℕ := if differ = true ∧ x.testBit i = y₀.testBit i then - mul p fl (kernelValueList p fl a t ((y₀ : ℤ) - x) ζ) + mul p fl (kernelValueList p fl a L t ((y₀ : ℤ) - x) ζ) (pow p fl (crossRatioList p fl a i t ((y₀ : ℤ) - x) ζ) b) else zero fl -theorem val_modeCoeffList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem val_modeCoeffList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} + (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (differ : Bool) {x : ℕ} (hx : x < 2 ^ a) {nb : ℕ} (Z : Fin (2 ^ (i + 1)) × Fin nb) (ζ : List ℕ) : - val p fl (modeCoeffList p fl a i t differ x Z.1 Z.2 ζ) = + val p fl (modeCoeffList p fl a L i t differ x Z.1 Z.2 ζ) = packetModeCoeff (scalarHom p fl) (val p fl ζ) a i t differ x Z := by have hy : (Z.1 : ℕ) < 2 ^ a := Z.1.isLt.trans_le (Nat.pow_le_pow_right (by norm_num) (by omega)) rw [modeCoeffList, packetModeCoeff] split_ifs with h - · rw [val_mul, val_pow, val_kernelValueList ht (sub_mem_cauchyGrid hx hy), + · rw [val_mul, val_pow, val_kernelValueList hL ht (sub_mem_cauchyGrid hx hy), val_crossRatioList hia.le (not_dvd_sub_sub_of_testBit_eq hti hti' Z.1.isLt h.2)] · exact val_zero @@ -510,19 +516,20 @@ def modeIndex (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ := /-- The terms of the affine factor of a row port `r = (x, Y_x)`: its local term at `none`, and the coefficient of the mode `(k / nb, k % nb)` at `some k`. -/ -def rowTermList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) (differ : Bool) +def rowTermList (p : ℕ) (fl : List ℕ) (a L i : ℕ) (t : ℤ) (nb : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) (ζ : List ℕ) (r : ℕ × Bool) : Option ℕ → List ℕ - | none => if r.2 then localFormList p fl a t cols r.1 ζ - else sub p fl (one fl) (localFormList p fl a t cols r.1 ζ) - | some k => if r.2 then modeCoeffList p fl a i t differ r.1 (k / nb) (k % nb) ζ - else neg p fl (modeCoeffList p fl a i t differ r.1 (k / nb) (k % nb) ζ) + | none => if r.2 then localFormList p fl a L t cols r.1 ζ + else sub p fl (one fl) (localFormList p fl a L t cols r.1 ζ) + | some k => if r.2 then modeCoeffList p fl a L i t differ r.1 (k / nb) (k % nb) ζ + else neg p fl (modeCoeffList p fl a L i t differ r.1 (k / nb) (k % nb) ζ) /-- Row terms are correct, at the dense indices of the modes of the packet. -/ -theorem val_rowTermList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem val_rowTermList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} + (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (differ : Bool) {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {r : ℕ × Bool} (hr : r.1 < 2 ^ a) (ζ : List ℕ) : - (fun o => val p fl (rowTermList p fl a i t nb differ cols ζ r o)) ∘ + (fun o => val p fl (rowTermList p fl a L i t nb differ cols ζ r o)) ∘ Option.map (modeIndex i nb) = packetRowTerm (p := nb) (scalarHom p fl) (val p fl ζ) a i t differ cols r := by funext o @@ -530,12 +537,12 @@ theorem val_rowTermList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGr | none => simp only [Function.comp_apply, Option.map_none, rowTermList, packetRowTerm, affineFactor] split_ifs - · exact val_localFormList ht hcols hr ζ - · rw [val_sub, val_one, val_localFormList ht hcols hr ζ] + · exact val_localFormList hL ht hcols hr ζ + · rw [val_sub, val_one, val_localFormList hL ht hcols hr ζ] | some Z => simp only [Function.comp_apply, Option.map_some, rowTermList, packetRowTerm, affineSign, modeIndex_apply, modeIndex_div Z.2.isLt, modeIndex_mod Z.2.isLt] - have h := val_modeCoeffList (p := p) (fl := fl) hia ht hti hti' differ hr Z ζ + have h := val_modeCoeffList (p := p) (fl := fl) hL hia ht hti hti' differ hr Z ζ split_ifs · rw [one_mul]; exact h · rw [val_neg, h, neg_one_mul] @@ -596,12 +603,12 @@ private theorem toFinset_filter_range (i nb : ℕ) (P : ℕ → Bool) : along the row terms, at the exponents `β` of the modes targeting the group, times the powers of the values of the modes with the group as source. A mode `(y₀, b)` is its dense index `y₀ nb + b`, so `β` is read at dense indices, as from a dense list of exponents. -/ -def packetEntryList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) (e₁ e₂ differ : Bool) +def packetEntryList (p : ℕ) (fl : List ℕ) (a L i : ℕ) (t : ℤ) (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) (rows cols : List (ℕ × Bool)) (ζ : List ℕ) : List ℕ := mul p fl (listCoeffList p fl ((List.range (2 ^ (i + 1) * nb)).filter fun k => modeTargets i e₁ e₂ (k / nb)) - (rows.map (rowTermList p fl a i t nb differ cols ζ)) β) + (rows.map (rowTermList p fl a L i t nb differ cols ζ)) β) (prodList p fl (((List.range (2 ^ (i + 1) * nb)).filter fun k => modeSources i e₁ e₂ (k / nb)).map fun k => pow p fl (modeValueList p fl i differ cols (k / nb) (k % nb)) (β k))) @@ -609,11 +616,12 @@ def packetEntryList (p : ℕ) (fl : List ℕ) (a i : ℕ) (t : ℤ) (nb : ℕ) ( /-- **Packet entries are correct.** For a grid point `t` of valuation `i < a` and ports in slots below `2 ^ a`, the value of `packetEntryList` in `A_p` is `packetEntry` at the reduction of `ℤ_(2)`, the value of `ζ` and the exponents `β` at the dense indices of the modes. -/ -theorem val_packetEntryList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem val_packetEntryList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} + (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : - val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ) = + val p fl (packetEntryList p fl a L i t nb e₁ e₂ differ β rows cols ζ) = packetEntry (p := nb) (scalarHom p fl) (val p fl ζ) a i t e₁ e₂ differ (fun Z => β (Z.1 * nb + Z.2)) rows cols := by rw [packetEntryList, packetEntry, val_mul] @@ -621,7 +629,7 @@ theorem val_packetEntryList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauc · rw [val_listCoeffList (List.nodup_range.filter _), toFinset_filter_range, TensorNetwork.listCoeff_map, List.map_map, List.map_map] refine congrArg₂ _ (List.map_congr_left fun r hr => ?_) rfl - exact val_rowTermList hia ht hti hti' nb differ hcols (hrows r hr) ζ + exact val_rowTermList hL hia ht hti hti' nb differ hcols (hrows r hr) ζ · rw [val_prodList, List.map_map, ← List.prod_toFinset _ (List.nodup_range.filter _), toFinset_filter_range, prod_map] refine prod_congr rfl fun Z _ => ?_ @@ -633,14 +641,14 @@ theorem val_packetEntryList {a i : ℕ} (hia : i < a) {t : ℤ} (ht : t ∈ cauc nilpotent in `S`, the value of `packetEntryList` is `packetEntry` over `S` at any ring map `κ : ℤ_(2) → S` and at the image of `ζ`. -/ theorem map_val_packetEntryList {S : Type*} [CommRing S] (ψ : Ring p fl →+* S) {n : ℕ} - (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) {a i : ℕ} (hia : i < a) {t : ℤ} + (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : - ψ (val p fl (packetEntryList p fl a i t nb e₁ e₂ differ β rows cols ζ)) = + ψ (val p fl (packetEntryList p fl a L i t nb e₁ e₂ differ β rows cols ζ)) = packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (Z.1 * nb + Z.2)) rows cols := by - rw [val_packetEntryList hia ht hti hti' nb e₁ e₂ differ β hrows hcols ζ, + rw [val_packetEntryList hL hia ht hti hti' nb e₁ e₂ differ β hrows hcols ζ, map_packetEntry ψ two_pow_eq_zero _ _ hia.le hti hti', padicInteger_ringHom_ext hn (ψ.comp (scalarHom p fl)) κ] diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean index 435359142c..5115a5d231 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean @@ -33,14 +33,14 @@ example : twoVal 12 = 2 ∧ twoVal (-8) = 3 ∧ twoVal 0 = 0 := by decide -- The grid of `2 ^ 1` slots is `-2, -1, 0, 1`; the odd parts of `0 - q` for `q ≠ 0` are -- `1, 1, -1`, so `C'(0)` has odd part `-1`, and its inverse is `-1 ≡ 7`. -example : kernelUnitList 3 [1, 1] 1 = [7, 0] := by decide +example : kernelUnitList 3 [1, 1] 2 = [7, 0] := by decide -- `W_1(1 + 4 U)`: the factors for `q = -2, -1, 0` are `3 + 4 U`, `1 + 2 U` and `1 + 4 U`, whose -- product is `3 + 6 U` modulo `8`; times `-1` it is `5 + 2 U`. -example : kernelValueList 3 [1, 1] 1 1 1 [0, 1] = [5, 2] := by decide +example : kernelValueList 3 [1, 1] 1 2 1 1 [0, 1] = [5, 2] := by decide -- For `d ≠ t` the kernel value has the factor `2 ^ (a + 1 - v_d) ζ`, which vanishes at `ζ = 0`. -example : kernelValueList 3 [1, 1] 1 1 0 [0, 0] = [0, 0] := by decide +example : kernelValueList 3 [1, 1] 1 2 1 0 [0, 0] = [0, 0] := by decide -- The cross ratio at `d₀ = 0` and `ζ = 1`: `2` times the inverse of `1 + 4 = 5`, which is `5` -- modulo `8`, so `10 ≡ 2`. @@ -48,40 +48,40 @@ example : crossRatioList 3 [1, 1] 1 0 1 0 [1, 0] = [2, 0] := by decide -- One row port in slot `0` set to one, one column port in slot `1` set to one, and zero -- exponents: the entry is the local form `W_{1 - 0}(T)`. -example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun _ => 0) [(0, true)] [(1, true)] - [0, 1] = kernelValueList 3 [1, 1] 1 1 1 [0, 1] := by +example : packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun _ => 0) [(0, true)] [(1, true)] + [0, 1] = kernelValueList 3 [1, 1] 1 2 1 1 [0, 1] := by decide -- With the row port set to zero, the entry is `1 - W_1(T)`. -example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun _ => 0) [(0, false)] [(1, true)] +example : packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun _ => 0) [(0, false)] [(1, true)] [0, 1] = GaloisRingArith.sub 3 [1, 1] (GaloisRingArith.one [1, 1]) - (kernelValueList 3 [1, 1] 1 1 1 [0, 1]) := by + (kernelValueList 3 [1, 1] 1 2 1 1 [0, 1]) := by decide -- Exponent `1` at the mode `(0, 1)`, of dense index `0 * 2 + 1 = 1`, which targets the group -- (digit `0` of `y₀ = 0` selects the first symbol): the single row factor contributes the -- coefficient of that mode, and no source mode has a positive exponent. -example : packetEntryList 3 [1, 1] 1 0 1 2 true false true (fun k => if k = 1 then 1 else 0) - [(0, true)] [(1, true)] [0, 1] = modeCoeffList 3 [1, 1] 1 0 1 true 0 0 1 [0, 1] := by +example : packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun k => if k = 1 then 1 else 0) + [(0, true)] [(1, true)] [0, 1] = modeCoeffList 3 [1, 1] 1 2 0 1 true 0 0 1 [0, 1] := by decide /-- The hypotheses of the correctness theorem hold for these packets. -/ theorem val_packetEntryList_tiny (β : ℕ → ℕ) (ζ : List ℕ) : - GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β + GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 2 0 1 2 true false true β [(0, true), (1, false)] [(1, true), (0, true)] ζ) = packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] ζ) 1 0 1 true false true (fun Z => β (Z.1 * 2 + Z.2)) [(0, true), (1, false)] [(1, true), (0, true)] := - val_packetEntryList (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true - false true β (by simp) (by simp) ζ + val_packetEntryList (by norm_num) (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) + (by norm_num) 2 true false true β (by simp) (by simp) ζ -- Transported to any ring with `2` nilpotent, at any ring map from `ℤ_(2)`. example {S : Type} [CommRing S] (ψ : GaloisRingArith.Ring 3 [1, 1] →+* S) {n : ℕ} (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) (β : ℕ → ℕ) (ζ : List ℕ) : - ψ (GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 0 1 2 true false true β + ψ (GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 2 0 1 2 true false true β [(0, true)] [(1, true)] ζ)) = packetEntry (p := 2) κ (ψ (GaloisRingArith.val 3 [1, 1] ζ)) 1 0 1 true false true (fun Z => β (Z.1 * 2 + Z.2)) [(0, true)] [(1, true)] := - map_val_packetEntryList ψ hn κ (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) - (by norm_num) 2 true false true β (by simp) (by simp) ζ + map_val_packetEntryList ψ hn κ (by norm_num) (by norm_num) (by simp [mem_cauchyGrid]) + (by norm_num) (by norm_num) 2 true false true β (by simp) (by simp) ζ end ComplexityTests.TensorNetworkPacketEvaluator From 145491542cd6c3efcac34d7ef9645b5f000c44ed Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:54:53 -0400 Subject: [PATCH 22/33] fix(circuit/frontier): mode terms vanish when a packet has no exponents With nb = 0 the exponent k % nb is k itself; guard it so that the exponent of a mode term is always below nb. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Frontier/TensorNetwork/PacketEvaluator.lean | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean index 0d0f3f82e7..8bf1751e32 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean @@ -515,12 +515,14 @@ def modeIndex (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ := rfl /-- The terms of the affine factor of a row port `r = (x, Y_x)`: its local term at `none`, and -the coefficient of the mode `(k / nb, k % nb)` at `some k`. -/ +the coefficient of the mode `(k / nb, k % nb)` at `some k` (zero when there are no exponents, +`nb = 0`, so that the exponent `k % nb` stays below `nb`). -/ def rowTermList (p : ℕ) (fl : List ℕ) (a L i : ℕ) (t : ℤ) (nb : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) (ζ : List ℕ) (r : ℕ × Bool) : Option ℕ → List ℕ | none => if r.2 then localFormList p fl a L t cols r.1 ζ else sub p fl (one fl) (localFormList p fl a L t cols r.1 ζ) - | some k => if r.2 then modeCoeffList p fl a L i t differ r.1 (k / nb) (k % nb) ζ + | some k => if nb = 0 then zero fl else + if r.2 then modeCoeffList p fl a L i t differ r.1 (k / nb) (k % nb) ζ else neg p fl (modeCoeffList p fl a L i t differ r.1 (k / nb) (k % nb) ζ) /-- Row terms are correct, at the dense indices of the modes of the packet. -/ @@ -541,7 +543,8 @@ theorem val_rowTermList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} · rw [val_sub, val_one, val_localFormList hL ht hcols hr ζ] | some Z => simp only [Function.comp_apply, Option.map_some, rowTermList, packetRowTerm, affineSign, - modeIndex_apply, modeIndex_div Z.2.isLt, modeIndex_mod Z.2.isLt] + modeIndex_apply, modeIndex_div Z.2.isLt, modeIndex_mod Z.2.isLt, + Nat.pos_iff_ne_zero.mp (Nat.zero_lt_of_lt Z.2.isLt), ↓reduceIte] have h := val_modeCoeffList (p := p) (fl := fl) hL hia ht hti hti' differ hr Z ζ split_ifs · rw [one_mul]; exact h From fb6f83dfc4a8d276da1ae465a830edc39f4a5fbc Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:05:56 -0400 Subject: [PATCH 23/33] feat(circuit/frontier): the native packet evaluator in time exp(O(L log s)) and polynomial space Row terms of packet entries are polynomial-time: kernel values, cross ratios, local forms, mode coefficients and values, with the counts in unary and the integers in binary sign-magnitude form; the 2-adic valuation needs at most size |n| halvings. A packet context gives coefficient extraction for a packet as polynomial-time CoeffScheme data. packetEntryNative multiplies the stack recursion's value by the source mode powers; val_packetEntryNative_eq proves it is packetEntry in A_p, and entryCertificate_le bounds one native machine by time K (m + 2)^L Z^K and space K Z^K. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/PacketTerms.lean | 1183 +++++++++++++++++ .../Frontier/TensorNetworkPacketTerms.lean | 62 + 3 files changed, 1246 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean diff --git a/Complexity.lean b/Complexity.lean index c980ba15b1..8ee6b74377 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -887,6 +887,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProd public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketTerms public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean new file mode 100644 index 0000000000..f7da6829e8 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean @@ -0,0 +1,1183 @@ +/- +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.CoeffScheme +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.IntBinary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Int.Binary +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Int.BinaryDiv +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.Encodings.Length +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.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.PolyTimeLoops +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeNatFrontends +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeOptionPrimitives +import Mathlib.Data.List.GetD + +/-! +# The native packet evaluator + +The native packet evaluator (`Cslib.Circuits.Frontier.NativePacket`) computes the entries of the +packet vertices of the child networks of the contraction algorithm of [OpenAI2026ETH] on +coefficient lists (§D.4.6, p. 63). This file makes it one native machine with the source's +bounds: time `exp(O(L log s))` and polynomial space. + +* **Row terms in polynomial time.** Kernel values, cross ratios, local forms, mode coefficients + and values, and the row terms are polynomial-time + (`NativePacket.kernelValueList_polyTime`, …, `NativePacket.rowTermList_polyTime`), with the + precision `p`, the bit counts `a` and `i`, the number of slots `L` and the number of exponents + `nb` in unary, and the integers in binary sign-magnitude form (`Turing.MultiTapeTM.encIntBin`). + The `2`-adic valuation needs at most `size |n|` halvings (`NativePacket.twoVal_polyTime`). +* **A packet context** (`NativePacket.PacketCtx`) holds what the row terms and the modes need, + with the number of tiles `2 ^ (i + 1)` in unary. Coefficient extraction for a packet + (`NativePacket.packetTerms`) is an instance of `CoeffScheme.Terms` with polynomial-time data + (`NativePacket.packetTerms_polyTime`). +* **The native packet entry** (`NativePacket.packetEntryNative`) multiplies the value of the stack + recursion by the powers of the source mode values. Its value in `A_p` is `packetEntry` + (`NativePacket.val_packetEntryNative_eq`), and one native machine computes it + (`NativePacket.entryCertificate`) in time `K (m + 2) ^ L Z ^ K` and space `K Z ^ K`, for `m` + target modes, `L` row ports and `Z` polynomial in the input and the reduced coefficient lists + (`NativePacket.entryCertificate_le`). The exponents are read in unary, since the source's are + below the precision. + +## Main definitions + +* `Cslib.Circuits.Frontier.NativePacket.PacketCtx`: the context of a packet entry. +* `Cslib.Circuits.Frontier.NativePacket.packetTerms`: coefficient extraction for a packet. +* `Cslib.Circuits.Frontier.NativePacket.packetEntryNative`: the native packet entry. +* `Cslib.Circuits.Frontier.NativePacket.entryCertificate`: its native machine. + +## Main results + +* `Cslib.Circuits.Frontier.NativePacket.twoVal_polyTime`: the `2`-adic valuation. +* `Cslib.Circuits.Frontier.NativePacket.kernelValueList_polyTime`, + `Cslib.Circuits.Frontier.NativePacket.crossRatioList_polyTime`: kernel values and cross ratios. +* `Cslib.Circuits.Frontier.NativePacket.rowTermList_polyTime`: the row terms. +* `Cslib.Circuits.Frontier.NativePacket.val_packetEntryNative_eq`, + `Cslib.Circuits.Frontier.NativePacket.map_val_packetEntryNative`: the native entry is the + packet entry, in `A_p` and in every ring receiving it. +* `Cslib.Circuits.Frontier.NativePacket.entryCertificate_le`: its time and space. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.6 (p. 63). +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +namespace NativePacket + +open GaloisRingArith TensorNetwork Turing.MultiTapeTM Cslib.GenericRAM + +/-! ### The `2`-adic valuation by halving -/ + +/-- The valuation by halving is `padicValNat 2` whenever the fuel exceeds the valuation. -/ +theorem twoValAux_eq_of_lt : ∀ (k m : ℕ), (m = 0 ∨ padicValNat 2 m < k) → + twoValAux k m = padicValNat 2 m + | 0, m, h => by + rcases h with rfl | h + · rw [twoValAux]; simp + · omega + | k + 1, m, h => by + rw [twoValAux] + split_ifs with hm + · obtain ⟨hm0, hm2⟩ := hm + have hm' : m = 2 * (m / 2) := by omega + have hv : padicValNat 2 m = padicValNat 2 (m / 2) + 1 := by + conv_lhs => rw [hm', padicValNat.mul two_ne_zero (by omega), padicValNat_self, add_comm] + rw [hv, twoValAux_eq_of_lt k (m / 2) (by omega)] + · by_cases hm0 : m = 0 + · rw [hm0]; simp + · exact (padicValNat.eq_zero_of_not_dvd fun h2 => hm ⟨hm0, by omega⟩).symm + +/-- The valuation of a nonzero natural is below its bit length. -/ +theorem padicValNat_two_lt_size {m : ℕ} (hm : m ≠ 0) : padicValNat 2 m < m.size := by + have h1 : 2 ^ padicValNat 2 m ≤ m := Nat.le_of_dvd (Nat.pos_of_ne_zero hm) pow_padicValNat_dvd + have h2 := Nat.lt_size_self m + exact (Nat.pow_lt_pow_iff_right (by norm_num)).mp (h1.trans_lt h2) + +/-- **The valuation needs only `size |n|` halvings.** -/ +theorem twoVal_eq_twoValAux_size (n : ℤ) : twoVal n = twoValAux n.natAbs.size n.natAbs := by + rw [twoVal_eq, twoValAux_eq_of_lt] + · rfl + · by_cases h : n.natAbs = 0 + · exact .inl h + · exact .inr (padicValNat_two_lt_size h) + +/-- One step of the valuation by halving, on the value, the count and whether it has stopped. -/ +def twoValStep (s : ℕ × ℕ × Bool) : ℕ × ℕ × Bool := + if s.2.2 = false ∧ s.1 ≠ 0 ∧ s.1 % 2 = 0 then (s.1 / 2, s.2.1 + 1, false) else (s.1, s.2.1, true) + +private theorem twoValStep_iterate : ∀ (k m v : ℕ), + (twoValStep^[k] (m, v, false)).2.1 = v + twoValAux k m ∧ + ∀ m' v', twoValStep^[k] (m', v', true) = (m', v', true) + | 0, m, v => ⟨by simp [twoValAux], fun _ _ => rfl⟩ + | k + 1, m, v => by + refine ⟨?_, fun m' v' => ?_⟩ + · rw [Function.iterate_succ_apply] + by_cases hm : m ≠ 0 ∧ m % 2 = 0 + · have h1 : twoValStep (m, v, false) = (m / 2, v + 1, false) := by + simp [twoValStep, hm.1, hm.2] + have h2 : twoValAux (k + 1) m = twoValAux k (m / 2) + 1 := by + simp [twoValAux, hm.1, hm.2] + rw [h1, h2, (twoValStep_iterate k (m / 2) (v + 1)).1] + ring + · have h1 : twoValStep (m, v, false) = (m, v, true) := by + simp only [twoValStep] + split_ifs with h + · exact absurd ⟨h.2.1, h.2.2⟩ hm + · rfl + have h2 : twoValAux (k + 1) m = 0 := by + simp only [twoValAux, hm, ↓reduceIte] + rw [h1, h2, (twoValStep_iterate k m v).2] + simp + · have h1 : twoValStep (m', v', true) = (m', v', true) := by simp [twoValStep] + rw [Function.iterate_succ_apply, h1, (twoValStep_iterate k m' v').2] + +private theorem foldl_range_eq_iterate {β : Type} (f : β → β) : ∀ (k : ℕ) (s : β), + (List.range k).foldl (fun s _ => f s) s = f^[k] s + | 0, s => rfl + | k + 1, s => by + rw [List.range_succ, List.foldl_append, foldl_range_eq_iterate f k s, List.foldl_cons, + List.foldl_nil, Function.iterate_succ_apply'] + +/-- The valuation by halving as a fold over `size |n|` steps. -/ +theorem twoVal_eq_foldl (n : ℤ) : + twoVal n = ((List.range n.natAbs.size).foldl (fun s _ => twoValStep s) + (n.natAbs, 0, false)).2.1 := by + rw [foldl_range_eq_iterate, (twoValStep_iterate _ _ 0).1, zero_add, twoVal_eq_twoValAux_size] + +section PolyTime + +variable {α : Type} {encIn : α ↪ List Bool} + +private theorem intBin_natAbs' : + ComputableInPolyTime Int.natAbs encIntBin encNat := + .of_tidy ComputableTidilyInPolyTime.intBin_natAbs + +private theorem intBin_isNeg' : + ComputableInPolyTime (fun z : ℤ => decide (z < 0)) encIntBin encBool := + .of_tidy ComputableTidilyInPolyTime.intBin_isNeg + +/-- **The `2`-adic valuation is polynomial-time**, on binary integers, in unary. -/ +theorem twoVal_polyTime : ComputableInPolyTime twoVal encIntBin encUnary := by + let encS := encPair encNat (encPair encUnary encBool) + have hstep : ComputableInPolyTime (fun q : (ℤ × (ℕ × ℕ × Bool)) × ℕ => twoValStep q.1.2) + (encPair (encPair encIntBin encS) encUnary) encS := by + have hs := (ComputableInPolyTime.snd encIntBin encS).comp + (ComputableInPolyTime.fst (encPair encIntBin encS) encUnary) + have hm := (ComputableInPolyTime.fst encNat (encPair encUnary encBool)).comp hs + have hv := (ComputableInPolyTime.fst encUnary encBool).comp + ((ComputableInPolyTime.snd encNat (encPair encUnary encBool)).comp hs) + have hb := (ComputableInPolyTime.snd encUnary encBool).comp + ((ComputableInPolyTime.snd encNat (encPair encUnary encBool)).comp hs) + have hgo := (ComputableInPolyTime.binary_div.comp + (hm.prod (ComputableInPolyTime.const 2))).prod + ((ComputableInPolyTime.unary_succ.comp hv).prod + (ComputableInPolyTime.const (encOut := encBool) false)) + have hstay := hm.prod (hv.prod (ComputableInPolyTime.const (encOut := encBool) true)) + refine (ComputableInPolyTime.ite hb hstay (ComputableInPolyTime.ite + (ComputableInPolyTime.beq hm (ComputableInPolyTime.const 0)) hstay + (ComputableInPolyTime.ite (ComputableInPolyTime.beq + (ComputableInPolyTime.binary_mod.comp (hm.prod (ComputableInPolyTime.const 2))) + (ComputableInPolyTime.const 0)) hgo hstay))).of_enc_eq fun q => congrArg encS ?_ + obtain ⟨⟨z, m, v, b⟩, j⟩ := q + simp only [twoValStep] + cases b <;> by_cases h0 : m = 0 <;> by_cases h2 : m % 2 = 0 <;> simp [h0, h2] + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun _ s _ => twoValStep s) + (intBin_natAbs'.prod ((ComputableInPolyTime.const 0).prod (ComputableInPolyTime.const false))) + ⟨ComputableInPolyTime.range.comp (ComputableInPolyTime.binary_size.comp intBin_natAbs'), + hstep, inferInstance, inferInstance⟩ + (growth := fun _ => 2) ⟨PolyBounded.const 2, fun z s _ _ => ?_⟩ + · refine ((ComputableInPolyTime.fst encUnary encBool).comp + ((ComputableInPolyTime.snd encNat (encPair encUnary encBool)).comp hfold)).of_enc_eq + fun z => ?_ + simp only [Function.comp_apply, twoVal_eq_foldl] + · obtain ⟨m, v, b⟩ := s + have hdiv : (encNat (m / 2)).length ≤ (encNat m).length := by + change (m / 2).bits.length ≤ m.bits.length + rw [Nat.size_eq_bits_len, Nat.size_eq_bits_len] + exact Nat.size_le_size (Nat.div_le_self _ _) + simp only [twoValStep] + split_ifs <;> cases b <;> + simp only [length_encPair, encUnary_apply, List.length_replicate, encBool_apply, + List.length_cons, List.length_nil] <;> omega + +end PolyTime + + +/-! ### Residues of integers -/ + +/-- The odd part of an integer in sign-magnitude form. -/ +theorem ediv_two_pow_twoVal (n : ℤ) : + n / 2 ^ twoVal n = if n < 0 then -((n.natAbs / 2 ^ twoVal n : ℕ) : ℤ) + else ((n.natAbs / 2 ^ twoVal n : ℕ) : ℤ) := by + have hdvd : 2 ^ twoVal n ∣ n.natAbs := by + rw [twoVal_eq]; exact pow_padicValNat_dvd + have hdvd' : (2 : ℤ) ^ twoVal n ∣ (n.natAbs : ℤ) := by exact_mod_cast hdvd + generalize twoVal n = v at * + split_ifs with h + · have hn : n = -(n.natAbs : ℤ) := by rw [Int.ofNat_natAbs_of_nonpos h.le, neg_neg] + conv_lhs => rw [hn] + rw [Int.neg_ediv_of_dvd hdvd'] + push_cast + rfl + · have hn : n = (n.natAbs : ℤ) := (Int.natAbs_of_nonneg (not_lt.mp h)).symm + conv_lhs => rw [hn] + push_cast + rfl + +/-- The residue of an integer power of two. -/ +theorem toNat_two_pow_emod (k p : ℕ) : ((2 : ℤ) ^ k % 2 ^ p).toNat = 2 ^ k % 2 ^ p := by + rw [show (2 : ℤ) ^ k % 2 ^ p = ((2 ^ k % 2 ^ p : ℕ) : ℤ) by push_cast; rfl, Int.toNat_natCast] + +/-- The residue of a natural number. -/ +theorem toNat_natCast_emod (m p : ℕ) : ((m : ℤ) % 2 ^ p).toNat = m % 2 ^ p := by + rw [show (m : ℤ) % 2 ^ p = ((m % 2 ^ p : ℕ) : ℤ) by push_cast; rfl, Int.toNat_natCast] + +section PolyTime + +variable {α : Type} {encIn : α ↪ List Bool} + +private theorem intBin_natAbs'' : + ComputableInPolyTime Int.natAbs encIntBin encNat := + .of_tidy ComputableTidilyInPolyTime.intBin_natAbs + +private theorem intBin_isNeg'' : + ComputableInPolyTime (fun z : ℤ => decide (z < 0)) encIntBin encBool := + .of_tidy ComputableTidilyInPolyTime.intBin_isNeg + +private theorem intBin_signMagnitude' : + ComputableInPolyTime (fun p : Bool × ℕ => if p.1 then -(p.2 : ℤ) else p.2) + (encPair encBool encNat) encIntBin := + .of_tidy ComputableTidilyInPolyTime.intBin_signMagnitude + +private theorem two_pow' {k : α → ℕ} (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => 2 ^ k x) encIn encNat := + (ComputableInPolyTime.of_tidy ComputableTidilyInPolyTime.binary_two_pow).comp hk + +/-- The odd part of a binary integer. -/ +theorem oddPart_polyTime {z : α → ℤ} (hz : ComputableInPolyTime z encIn encIntBin) : + ComputableInPolyTime (fun x => z x / 2 ^ twoVal (z x)) encIn encIntBin := by + have habs := intBin_natAbs''.comp hz + have hv := twoVal_polyTime.comp hz + refine (intBin_signMagnitude'.comp ((intBin_isNeg''.comp hz).prod + (ComputableInPolyTime.binary_div.comp (habs.prod (two_pow' hv))))).of_enc_eq fun x => ?_ + simp only [Function.comp_apply, decide_eq_true_eq, ediv_two_pow_twoVal (z x)] + +/-- The residue modulo `2 ^ p` of a binary integer, with `p` in unary. -/ +theorem residue_polyTime {n : α → ℤ} {p : α → ℕ} + (hn : ComputableInPolyTime n encIn encIntBin) (hp : ComputableInPolyTime p encIn encUnary) : + ComputableInPolyTime (fun x => (n x % 2 ^ p x).toNat) encIn encNat := + (ComputableInPolyTime.intBin_emodNat' hn (two_pow' hp)).of_enc_eq fun x => by + simp only [Nat.cast_pow, Nat.cast_ofNat] + +/-- An integer as a constant coefficient list, from its residue. -/ +theorem intConst_polyTime {p : α → ℕ} {fl : α → List ℕ} {n : α → ℤ} + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hres : ComputableInPolyTime (fun x => (n x % 2 ^ p x).toNat) encIn encNat) : + ComputableInPolyTime (fun x => intConst (p x) (fl x) (n x)) encIn (encList encNat) := by + have h2 := ComputableInPolyTime.snd encIn encUnary + refine computableInPolyTime_tabulate ((ComputableInPolyTime.length_list encNat).comp hfl) + ((ComputableInPolyTime.ite (ComputableInPolyTime.beq h2 (ComputableInPolyTime.const 0)) + (hres.comp (ComputableInPolyTime.fst encIn encUnary)) + (ComputableInPolyTime.const 0)).of_enc_eq fun q => ?_) + by_cases h : q.2 = 0 <;> simp [h] + +/-- The constant list of a power of two `2 ^ k`, with `k` in unary. -/ +theorem intConst_two_pow_polyTime {p : α → ℕ} {fl : α → List ℕ} {k : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => intConst (p x) (fl x) ((2 : ℤ) ^ k x)) encIn + (encList encNat) := + intConst_polyTime hfl ((ComputableInPolyTime.binary_mod.comp + ((two_pow' hk).prod (two_pow' hp))).of_enc_eq fun x => by + simp only [Function.comp_apply, toNat_two_pow_emod]) + +/-- The constant list of a binary natural number. -/ +theorem intConst_natCast_polyTime {p : α → ℕ} {fl : α → List ℕ} {m : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hm : ComputableInPolyTime m encIn encNat) : + ComputableInPolyTime (fun x => intConst (p x) (fl x) (m x : ℤ)) encIn (encList encNat) := + intConst_polyTime hfl ((ComputableInPolyTime.binary_mod.comp + (hm.prod (two_pow' hp))).of_enc_eq fun x => by + simp only [Function.comp_apply, toNat_natCast_emod]) + +/-- The constant list of the odd part of a binary integer. -/ +theorem intConst_oddPart_polyTime {p : α → ℕ} {fl : α → List ℕ} {z : α → ℤ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hz : ComputableInPolyTime z encIn encIntBin) : + ComputableInPolyTime (fun x => intConst (p x) (fl x) (z x / 2 ^ twoVal (z x))) encIn + (encList encNat) := + intConst_polyTime hfl (residue_polyTime (oddPart_polyTime hz) hp) + +/-! ### Sums and products of lists -/ + +private theorem polyBounded_reduced' {p : α → ℕ} {fl : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) : + PolyBounded (fun x => (encIn x).length) (fun x => (fl x).length * (2 * p x + 1)) := by + have hpl := ComputableInPolyTime.polyBounded_length hp + have hfll := ComputableInPolyTime.polyBounded_length hfl + refine (hfll.mul (((PolyBounded.const 2).mul hpl).add (PolyBounded.const 1))).mono fun x => ?_ + have h1 : p x ≤ (encUnary (p x)).length := by simp + have h2 : (fl x).length ≤ (encList encNat (fl x)).length := by + rw [length_encList] + calc (fl x).length = ((fl x).map fun _ => 1).sum := by simp + _ ≤ ((fl x).map fun a => 2 * (encNat a).length + 1).sum := + List.sum_le_sum fun a _ => by omega + exact Nat.mul_le_mul h2 (Nat.add_le_add_right (Nat.mul_le_mul_left 2 h1) 1) + +/-- **Products of computed lists of coefficient lists are polynomial-time.** -/ +theorem prodList_polyTime {p : α → ℕ} {fl : α → List ℕ} {cs : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hcs : ComputableInPolyTime cs encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => prodList (p x) (fl x) (cs x)) encIn (encList encNat) := by + have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × List ℕ => mul (p q.1.1) (fl q.1.1) + q.1.2 q.2) (encPair (encPair encIn (encList encNat)) (encList encNat)) (encList encNat) := by + have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp + (ComputableInPolyTime.fst (encPair encIn (encList encNat)) (encList encNat)) + exact computableInPolyTime_mul (hp.comp hx) (hfl.comp hx) + ((ComputableInPolyTime.snd encIn (encList encNat)).comp (ComputableInPolyTime.fst _ _)) + (ComputableInPolyTime.snd _ _) + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encList encNat) (encL := encList (encList encNat)) + (step := fun x acc c => mul (p x) (fl x) acc c) (computableInPolyTime_one hfl) + ⟨hcs, hstep, inferInstance, inferInstance⟩ ⟨polyBounded_reduced' hp hfl, fun x acc c _ => by + have := length_encList_le (length_mul (p x) (fl x) acc c) fun _ h => lt_of_mem_mul h + omega⟩ + +/-- **Sums of computed lists of coefficient lists are polynomial-time.** -/ +theorem sumList_polyTime {p : α → ℕ} {fl : α → List ℕ} {cs : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hcs : ComputableInPolyTime cs encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => sumList (p x) (fl x) (cs x)) encIn (encList encNat) := by + have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × List ℕ => add (p q.1.1) (fl q.1.1) + q.1.2 q.2) (encPair (encPair encIn (encList encNat)) (encList encNat)) (encList encNat) := by + have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp + (ComputableInPolyTime.fst (encPair encIn (encList encNat)) (encList encNat)) + exact computableInPolyTime_add (hp.comp hx) (hfl.comp hx) + ((ComputableInPolyTime.snd encIn (encList encNat)).comp (ComputableInPolyTime.fst _ _)) + (ComputableInPolyTime.snd _ _) + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encList encNat) (encL := encList (encList encNat)) + (step := fun x acc c => add (p x) (fl x) acc c) + (computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp hfl) (ComputableInPolyTime.const 0)) + ⟨hcs, hstep, inferInstance, inferInstance⟩ ⟨polyBounded_reduced' hp hfl, fun x acc c _ => by + have := length_encList_le (length_add (p x) (fl x) acc c) fun _ h => lt_of_mem_add h + omega⟩ + +/-! ### Kernel values and cross ratios -/ + +/-- The linear factor `u + 2 ^ k ζ` for the odd part `u` of a binary integer. -/ +theorem linFactor_polyTime {p : α → ℕ} {fl : α → List ℕ} {z : α → ℤ} {k : α → ℕ} + {ζ : α → List ℕ} (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hz : ComputableInPolyTime z encIn encIntBin) (hk : ComputableInPolyTime k encIn encUnary) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) : + ComputableInPolyTime (fun x => linFactor (p x) (fl x) (z x / 2 ^ twoVal (z x)) (k x) (ζ x)) + encIn (encList encNat) := + computableInPolyTime_add hp hfl (intConst_oddPart_polyTime hp hfl hz) + (computableInPolyTime_mul hp hfl (intConst_two_pow_polyTime hp hfl hk) hζ) + +/-- The grid of `L` slots, with `L` in unary, as binary integers. -/ +theorem gridList_polyTime : ComputableInPolyTime gridList encUnary (encList encIntBin) := by + have hj := ComputableInPolyTime.intBin_natCast.comp (ComputableInPolyTime.unary_to_binary.comp + (ComputableInPolyTime.snd encUnary encUnary)) + have hL := ComputableInPolyTime.intBin_natCast.comp (ComputableInPolyTime.unary_to_binary.comp + (ComputableInPolyTime.fst encUnary encUnary)) + exact ComputableInPolyTime.mapWithInput (f := fun (L j : ℕ) => (j : ℤ) - (L : ℤ)) + (ComputableInPolyTime.range.comp (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.const 2) ComputableInPolyTime.id')) + (ComputableInPolyTime.intBin_sub.comp (hj.prod hL)) + +end PolyTime + + +section PolyTime + +variable {α : Type} {encIn : α ↪ List Bool} + +/-- Whether two binary integers differ. -/ +private theorem decide_ne_polyTime {u v : α → ℤ} (hu : ComputableInPolyTime u encIn encIntBin) + (hv : ComputableInPolyTime v encIn encIntBin) : + ComputableInPolyTime (fun x => decide (u x ≠ v x)) encIn encBool := + (ComputableInPolyTime.ite (ComputableInPolyTime.beq hu hv) + (ComputableInPolyTime.const (encOut := encBool) false) + (ComputableInPolyTime.const (encOut := encBool) true)).of_enc_eq fun x => by + by_cases h : u x = v x <;> simp [h] + +/-- **The odd part of `C'(0)` is polynomial-time**, with `L` in unary. -/ +theorem kernelUnitDen_polyTime {p : α → ℕ} {fl : α → List ℕ} {L : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hL : ComputableInPolyTime L encIn encUnary) : + ComputableInPolyTime (fun x => kernelUnitDen (p x) (fl x) (L x)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn encIntBin + have h2 := ComputableInPolyTime.snd encIn encIntBin + have hfilt := ComputableInPolyTime.filterWithInput (test := fun _ q => decide (q ≠ 0)) + (gridList_polyTime.comp hL) (decide_ne_polyTime h2 (ComputableInPolyTime.const 0)) + exact prodList_polyTime hp hfl (ComputableInPolyTime.mapWithInput + (f := fun x q => intConst (p x) (fl x) ((0 - q) / 2 ^ twoVal (0 - q))) hfilt + (intConst_oddPart_polyTime (hp.comp h1) (hfl.comp h1) + (ComputableInPolyTime.intBin_sub.comp ((ComputableInPolyTime.const 0).prod h2)))) + +/-- **The kernel unit is polynomial-time.** -/ +theorem kernelUnitList_polyTime {p : α → ℕ} {fl : α → List ℕ} {L : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hL : ComputableInPolyTime L encIn encUnary) : + ComputableInPolyTime (fun x => kernelUnitList (p x) (fl x) (L x)) encIn (encList encNat) := + computableInPolyTime_inv hp hfl (kernelUnitDen_polyTime hp hfl hL) + +/-- **Kernel values are polynomial-time**, with `a` and `L` in unary and `t`, `d` binary. -/ +theorem kernelValueList_polyTime {p : α → ℕ} {fl : α → List ℕ} {a L : α → ℕ} {t d : α → ℤ} + {ζ : α → List ℕ} (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) (hL : ComputableInPolyTime L encIn encUnary) + (ht : ComputableInPolyTime t encIn encIntBin) (hd : ComputableInPolyTime d encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) : + ComputableInPolyTime (fun x => kernelValueList (p x) (fl x) (a x) (L x) (t x) (d x) (ζ x)) + encIn (encList encNat) := by + have hexp {z : α → ℤ} (hz : ComputableInPolyTime z encIn encIntBin) : + ComputableInPolyTime (fun x => a x + 1 - twoVal (z x)) encIn encUnary := + ComputableInPolyTime.unary_sub' (ComputableInPolyTime.unary_add' ha + (ComputableInPolyTime.const 1)) (twoVal_polyTime.comp hz) + have htd := ComputableInPolyTime.intBin_sub.comp (ht.prod hd) + have hmid : ComputableInPolyTime (fun x => if d x = t x then one (fl x) else + mul (p x) (fl x) (intConst (p x) (fl x) (2 ^ (a x + 1 - twoVal (t x - d x)))) (ζ x)) + encIn (encList encNat) := + (ComputableInPolyTime.ite (ComputableInPolyTime.beq hd ht) (computableInPolyTime_one hfl) + (computableInPolyTime_mul hp hfl (intConst_two_pow_polyTime hp hfl (hexp htd)) hζ)).of_enc_eq + fun x => by by_cases h : d x = t x <;> simp [h] + have h1 := ComputableInPolyTime.fst encIn encIntBin + have h2 := ComputableInPolyTime.snd encIn encIntBin + have htest : ComputableInPolyTime (fun z : α × ℤ => decide (z.2 ≠ d z.1 ∧ z.2 ≠ t z.1)) + (encPair encIn encIntBin) encBool := + (ComputableInPolyTime.ite (ComputableInPolyTime.beq h2 (hd.comp h1)) + (ComputableInPolyTime.const (encOut := encBool) false) (ComputableInPolyTime.ite + (ComputableInPolyTime.beq h2 (ht.comp h1)) + (ComputableInPolyTime.const (encOut := encBool) false) + (ComputableInPolyTime.const (encOut := encBool) true))).of_enc_eq fun z => by + by_cases h : z.2 = d z.1 <;> by_cases h' : z.2 = t z.1 <;> simp [h, h'] + have hfilt := ComputableInPolyTime.filterWithInput + (test := fun x q => decide (q ≠ d x ∧ q ≠ t x)) (gridList_polyTime.comp hL) htest + have htq := ComputableInPolyTime.intBin_sub.comp ((ht.comp h1).prod h2) + have hfac := ComputableInPolyTime.mapWithInput + (f := fun x q => linFactor (p x) (fl x) ((t x - q) / 2 ^ twoVal (t x - q)) + (a x + 1 - twoVal (t x - q)) (ζ x)) hfilt + (linFactor_polyTime (hp.comp h1) (hfl.comp h1) htq + (ComputableInPolyTime.unary_sub' (ComputableInPolyTime.unary_add' (ha.comp h1) + (ComputableInPolyTime.const 1)) (twoVal_polyTime.comp htq)) (hζ.comp h1)) + exact computableInPolyTime_mul hp hfl + (computableInPolyTime_mul hp hfl (kernelUnitList_polyTime hp hfl hL) hmid) + (prodList_polyTime hp hfl hfac) + +/-- **Cross ratios are polynomial-time.** -/ +theorem crossRatioList_polyTime {p : α → ℕ} {fl : α → List ℕ} {a i : α → ℕ} {t d : α → ℤ} + {ζ : α → List ℕ} (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) (hi : ComputableInPolyTime i encIn encUnary) + (ht : ComputableInPolyTime t encIn encIntBin) (hd : ComputableInPolyTime d encIn encIntBin) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) : + ComputableInPolyTime (fun x => crossRatioList (p x) (fl x) (a x) (i x) (t x) (d x) (ζ x)) + encIn (encList encNat) := by + have htd := ComputableInPolyTime.intBin_sub.comp (ht.prod hd) + have hv := twoVal_polyTime.comp htd + exact computableInPolyTime_mul hp hfl + (intConst_two_pow_polyTime hp hfl (ComputableInPolyTime.unary_sub' + (ComputableInPolyTime.unary_add' hi (ComputableInPolyTime.const 1)) hv)) + (computableInPolyTime_inv hp hfl (linFactor_polyTime hp hfl htd + (ComputableInPolyTime.unary_sub' (ComputableInPolyTime.unary_add' ha + (ComputableInPolyTime.const 1)) hv) hζ)) + +end PolyTime + + +section PolyTime + +variable {α : Type} {encIn : α ↪ List Bool} + +/-- **Local forms are polynomial-time**, with the column ports in binary. -/ +theorem localFormList_polyTime {p : α → ℕ} {fl : α → List ℕ} {a L : α → ℕ} {t : α → ℤ} + {cols : α → List (ℕ × Bool)} {x : α → ℕ} {ζ : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) (hL : ComputableInPolyTime L encIn encUnary) + (ht : ComputableInPolyTime t encIn encIntBin) + (hcols : ComputableInPolyTime cols encIn (encList (encPair encNat encBool))) + (hx : ComputableInPolyTime x encIn encNat) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) : + ComputableInPolyTime (fun z => localFormList (p z) (fl z) (a z) (L z) (t z) (cols z) (x z) + (ζ z)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn (encPair encNat encBool) + have h2 := ComputableInPolyTime.snd encIn (encPair encNat encBool) + have hd := ComputableInPolyTime.intBin_sub.comp + ((ComputableInPolyTime.intBin_natCast.comp ((ComputableInPolyTime.fst _ _).comp h2)).prod + (ComputableInPolyTime.intBin_natCast.comp (hx.comp h1))) + have hf : ComputableInPolyTime (fun q : α × (ℕ × Bool) => + if q.2.2 then kernelValueList (p q.1) (fl q.1) (a q.1) (L q.1) (t q.1) ((q.2.1 : ℤ) - x q.1) + (ζ q.1) else zero (fl q.1)) (encPair encIn (encPair encNat encBool)) (encList encNat) := + ComputableInPolyTime.ite ((ComputableInPolyTime.snd _ _).comp h2) + (kernelValueList_polyTime (hp.comp h1) (hfl.comp h1) (ha.comp h1) (hL.comp h1) (ht.comp h1) + hd (hζ.comp h1)) + (computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp (hfl.comp h1)) + (ComputableInPolyTime.const 0)) + exact sumList_polyTime hp hfl (ComputableInPolyTime.mapWithInput + (f := fun z c => if c.2 then kernelValueList (p z) (fl z) (a z) (L z) (t z) ((c.1 : ℤ) - x z) + (ζ z) else zero (fl z)) hcols hf) + +/-- **Mode coefficients are polynomial-time**, with the slots in binary and the exponent `b` in +unary. -/ +theorem modeCoeffList_polyTime {p : α → ℕ} {fl : α → List ℕ} {a L i : α → ℕ} {t : α → ℤ} + {differ : α → Bool} {x y₀ b : α → ℕ} {ζ : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) (hL : ComputableInPolyTime L encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hdiffer : ComputableInPolyTime differ encIn encBool) + (hx : ComputableInPolyTime x encIn encNat) (hy₀ : ComputableInPolyTime y₀ encIn encNat) + (hb : ComputableInPolyTime b encIn encUnary) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) : + ComputableInPolyTime (fun z => modeCoeffList (p z) (fl z) (a z) (L z) (i z) (t z) (differ z) + (x z) (y₀ z) (b z) (ζ z)) encIn (encList encNat) := by + have hd := ComputableInPolyTime.intBin_sub.comp + ((ComputableInPolyTime.intBin_natCast.comp hy₀).prod + (ComputableInPolyTime.intBin_natCast.comp hx)) + have hzero := computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp hfl) (ComputableInPolyTime.const 0) + refine (ComputableInPolyTime.ite hdiffer (ComputableInPolyTime.ite (ComputableInPolyTime.beq + (ComputableInPolyTime.binary_testBit hx hi) (ComputableInPolyTime.binary_testBit hy₀ hi)) + (computableInPolyTime_mul hp hfl (kernelValueList_polyTime hp hfl ha hL ht hd hζ) + (computableInPolyTime_pow hp hfl (crossRatioList_polyTime hp hfl ha hi ht hd hζ) hb)) + hzero) hzero).of_enc_eq fun z => ?_ + simp only [modeCoeffList] + cases hdz : differ z <;> by_cases hbit : (x z).testBit (i z) = (y₀ z).testBit (i z) <;> + simp [hbit, GaloisRingArith.zero] + +/-- **Mode values are polynomial-time**, with the exponent `b` in unary. -/ +theorem modeValueList_polyTime {p : α → ℕ} {fl : α → List ℕ} {i : α → ℕ} {differ : α → Bool} + {cols : α → List (ℕ × Bool)} {y₀ b : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hi : ComputableInPolyTime i encIn encUnary) + (hdiffer : ComputableInPolyTime differ encIn encBool) + (hcols : ComputableInPolyTime cols encIn (encList (encPair encNat encBool))) + (hy₀ : ComputableInPolyTime y₀ encIn encNat) (hb : ComputableInPolyTime b encIn encUnary) : + ComputableInPolyTime (fun z => modeValueList (p z) (fl z) (i z) (differ z) (cols z) (y₀ z) + (b z)) encIn (encList encNat) := by + have h1 := ComputableInPolyTime.fst encIn (encPair encNat encBool) + have h2 := ComputableInPolyTime.snd encIn (encPair encNat encBool) + have hc := (ComputableInPolyTime.fst encNat encBool).comp h2 + have hbit := (ComputableInPolyTime.snd encNat encBool).comp h2 + have hT := two_pow' (ComputableInPolyTime.unary_add' (hi.comp h1) (ComputableInPolyTime.const 1)) + have hzero := computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp (hfl.comp h1)) (ComputableInPolyTime.const 0) + have hpow := computableInPolyTime_pow (hp.comp h1) (hfl.comp h1) + (intConst_natCast_polyTime (hp.comp h1) (hfl.comp h1) + (ComputableInPolyTime.binary_div.comp (hc.prod hT))) (hb.comp h1) + have hf : ComputableInPolyTime (fun q : α × (ℕ × Bool) => + if q.2.2 = true ∧ differ q.1 = true ∧ q.2.1 % 2 ^ (i q.1 + 1) = y₀ q.1 then + pow (p q.1) (fl q.1) (intConst (p q.1) (fl q.1) ((q.2.1 / 2 ^ (i q.1 + 1) : ℕ) : ℤ)) (b q.1) + else zero (fl q.1)) (encPair encIn (encPair encNat encBool)) (encList encNat) := by + refine (ComputableInPolyTime.ite hbit (ComputableInPolyTime.ite (hdiffer.comp h1) + (ComputableInPolyTime.ite (ComputableInPolyTime.beq + (ComputableInPolyTime.binary_mod.comp (hc.prod hT)) (hy₀.comp h1)) hpow hzero) hzero) + hzero).of_enc_eq fun q => ?_ + cases hq : q.2.2 <;> cases hd : differ q.1 <;> + by_cases hm : q.2.1 % 2 ^ (i q.1 + 1) = y₀ q.1 <;> simp [hq, hd, hm, GaloisRingArith.zero] + exact sumList_polyTime hp hfl (ComputableInPolyTime.mapWithInput + (f := fun z c => if c.2 = true ∧ differ z = true ∧ c.1 % 2 ^ (i z + 1) = y₀ z then + pow (p z) (fl z) (intConst (p z) (fl z) ((c.1 / 2 ^ (i z + 1) : ℕ) : ℤ)) (b z) + else zero (fl z)) hcols hf) + +/-- **Row terms are polynomial-time**, with `nb` in unary and the mode index in binary. -/ +theorem rowTermList_polyTime {p : α → ℕ} {fl : α → List ℕ} {a L i nb : α → ℕ} {t : α → ℤ} + {differ : α → Bool} {cols : α → List (ℕ × Bool)} {ζ : α → List ℕ} {r : α → ℕ × Bool} + {o : α → Option ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) (hL : ComputableInPolyTime L encIn encUnary) + (hi : ComputableInPolyTime i encIn encUnary) (ht : ComputableInPolyTime t encIn encIntBin) + (hnb : ComputableInPolyTime nb encIn encUnary) + (hdiffer : ComputableInPolyTime differ encIn encBool) + (hcols : ComputableInPolyTime cols encIn (encList (encPair encNat encBool))) + (hζ : ComputableInPolyTime ζ encIn (encList encNat)) + (hr : ComputableInPolyTime r encIn (encPair encNat encBool)) + (ho : ComputableInPolyTime o encIn (encOption encNat)) : + ComputableInPolyTime (fun z => rowTermList (p z) (fl z) (a z) (L z) (i z) (t z) (nb z) + (differ z) (cols z) (ζ z) (r z) (o z)) encIn (encList encNat) := by + have hx := (ComputableInPolyTime.fst encNat encBool).comp hr + have hbit := (ComputableInPolyTime.snd encNat encBool).comp hr + have hloc := localFormList_polyTime hp hfl ha hL ht hcols hx hζ + have hnone : ComputableInPolyTime (fun z => if (r z).2 then + localFormList (p z) (fl z) (a z) (L z) (t z) (cols z) (r z).1 (ζ z) else + sub (p z) (fl z) (one (fl z)) (localFormList (p z) (fl z) (a z) (L z) (t z) (cols z) + (r z).1 (ζ z))) encIn (encList encNat) := + ComputableInPolyTime.ite hbit hloc + (computableInPolyTime_sub hp hfl (computableInPolyTime_one hfl) hloc) + have h1 := ComputableInPolyTime.fst encIn encNat + have hk := ComputableInPolyTime.snd encIn encNat + have hnb' := ComputableInPolyTime.unary_to_binary.comp (hnb.comp h1) + have hy := ComputableInPolyTime.binary_div.comp (hk.prod hnb') + have hexp := ComputableInPolyTime.binary_min_unary' (hnb.comp h1) + (ComputableInPolyTime.binary_mod.comp (hk.prod hnb')) + have hcoef := modeCoeffList_polyTime (hp.comp h1) (hfl.comp h1) (ha.comp h1) (hL.comp h1) + (hi.comp h1) (ht.comp h1) (hdiffer.comp h1) (hx.comp h1) hy hexp (hζ.comp h1) + have hzero := computableInPolyTime_tabulate (g := fun _ _ => 0) + ((ComputableInPolyTime.length_list encNat).comp (hfl.comp h1)) (ComputableInPolyTime.const 0) + have hsome : ComputableInPolyTime (fun q : α × ℕ => if nb q.1 = 0 then zero (fl q.1) else + if (r q.1).2 then modeCoeffList (p q.1) (fl q.1) (a q.1) (L q.1) (i q.1) (t q.1) + (differ q.1) (r q.1).1 (q.2 / nb q.1) (q.2 % nb q.1) (ζ q.1) + else neg (p q.1) (fl q.1) (modeCoeffList (p q.1) (fl q.1) (a q.1) (L q.1) (i q.1) (t q.1) + (differ q.1) (r q.1).1 (q.2 / nb q.1) (q.2 % nb q.1) (ζ q.1))) + (encPair encIn encNat) (encList encNat) := by + refine (ComputableInPolyTime.ite (ComputableInPolyTime.beq (hnb.comp h1) + (ComputableInPolyTime.const 0)) hzero (ComputableInPolyTime.ite (hbit.comp h1) hcoef + (computableInPolyTime_neg (hp.comp h1) (hfl.comp h1) hcoef))).of_enc_eq fun q => ?_ + by_cases h0 : nb q.1 = 0 + · simp [h0, GaloisRingArith.zero] + · have hmin : min (nb q.1) (q.2 % nb q.1) = q.2 % nb q.1 := + min_eq_right (Nat.mod_lt _ (Nat.pos_of_ne_zero h0)).le + simp only [h0, beq_iff_eq, ↓reduceIte, Function.comp_apply, hmin] + refine (ComputableInPolyTime.optionElimWithInput (body := fun z k => if nb z = 0 then zero (fl z) + else if (r z).2 then modeCoeffList (p z) (fl z) (a z) (L z) (i z) (t z) (differ z) (r z).1 + (k / nb z) (k % nb z) (ζ z) + else neg (p z) (fl z) (modeCoeffList (p z) (fl z) (a z) (L z) (i z) (t z) (differ z) (r z).1 + (k / nb z) (k % nb z) (ζ z))) ho hnone hsome).of_enc_eq fun z => ?_ + cases o z <;> rfl + +end PolyTime + + +/-! ### Coefficient extraction for a packet -/ + +/-- **The context of a packet entry**: the precision `p` and the coefficients `fl` of `f`, the +bit counts `a` and `i`, the number of slots `L = 2 ^ a` and of tiles `tiles = 2 ^ (i + 1)`, the +grid point `t`, the number `nb` of exponents per tile, whether the two symbols of the packet are +the group (`e₁`, `e₂`) and whether they differ, the column ports, and `ζ`. -/ +structure PacketCtx where + /-- The precision. -/ + p : ℕ + /-- The coefficients of `f` below the leading one. -/ + fl : List ℕ + /-- The number of slot bits. -/ + a : ℕ + /-- The number of slots, `2 ^ a`. -/ + L : ℕ + /-- The digit of the tiles. -/ + i : ℕ + /-- The number of tiles, `2 ^ (i + 1)`. -/ + tiles : ℕ + /-- The grid point. -/ + t : ℤ + /-- The number of exponents per tile. -/ + nb : ℕ + /-- Whether the first symbol is the group. -/ + e₁ : Bool + /-- Whether the second symbol is the group. -/ + e₂ : Bool + /-- Whether the two symbols differ. -/ + differ : Bool + /-- The column ports: slots and bits. -/ + cols : List (ℕ × Bool) + /-- The interpolation value. -/ + ζ : List ℕ + +namespace PacketCtx + +/-- A packet context as nested pairs. -/ +def equivProd : PacketCtx ≃ (ℕ × List ℕ) × ((ℕ × ℕ) × ((ℕ × ℕ) × (ℤ × (ℕ × + ((Bool × Bool × Bool) × (List (ℕ × Bool) × List ℕ)))))) where + toFun c := ((c.p, c.fl), ((c.a, c.L), ((c.i, c.tiles), (c.t, (c.nb, + ((c.e₁, c.e₂, c.differ), (c.cols, c.ζ))))))) + invFun q := ⟨q.1.1, q.1.2, q.2.1.1, q.2.1.2, q.2.2.1.1, q.2.2.1.2, q.2.2.2.1, q.2.2.2.2.1, + q.2.2.2.2.2.1.1, q.2.2.2.2.2.1.2.1, q.2.2.2.2.2.1.2.2, q.2.2.2.2.2.2.1, q.2.2.2.2.2.2.2⟩ + left_inv _ := rfl + right_inv _ := rfl + +/-- The encoding of the nested pairs of a packet context. -/ +abbrev encProd : (ℕ × List ℕ) × ((ℕ × ℕ) × ((ℕ × ℕ) × (ℤ × (ℕ × + ((Bool × Bool × Bool) × (List (ℕ × Bool) × List ℕ)))))) ↪ List Bool := + encPair (encPair encUnary (encList encNat)) + (encPair (encPair encUnary encUnary) (encPair (encPair encUnary encUnary) + (encPair encIntBin (encPair encUnary (encPair (encPair encBool (encPair encBool encBool)) + (encPair (encList (encPair encNat encBool)) (encList encNat))))))) + +/-- **The encoding of a packet context**: the counts in unary, `t` in binary sign-magnitude +form, and the lists in binary. -/ +def enc : PacketCtx ↪ List Bool := encOfEquiv equivProd encProd + +private theorem toProd_polyTime : ComputableInPolyTime equivProd enc encProd := + ComputableInPolyTime.of_eq fun _ => rfl + +private theorem rest_polyTime : + ComputableInPolyTime (fun c : PacketCtx => (equivProd c).2.2.2.2.2) enc + (encPair (encPair encBool (encPair encBool encBool)) + (encPair (encList (encPair encNat encBool)) (encList encNat))) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp toProd_polyTime)))) + +/-- The precision, in polynomial time. -/ +theorem p_polyTime : ComputableInPolyTime PacketCtx.p enc encUnary := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.fst _ _).comp toProd_polyTime) +/-- The coefficients of `f`, in polynomial time. -/ +theorem fl_polyTime : ComputableInPolyTime PacketCtx.fl enc (encList encNat) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.fst _ _).comp toProd_polyTime) +/-- The number of slot bits, in polynomial time. -/ +theorem a_polyTime : ComputableInPolyTime PacketCtx.a enc encUnary := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp toProd_polyTime)) +/-- The number of slots, in polynomial time. -/ +theorem L_polyTime : ComputableInPolyTime PacketCtx.L enc encUnary := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp toProd_polyTime)) +/-- The digit of the tiles, in polynomial time. -/ +theorem i_polyTime : ComputableInPolyTime PacketCtx.i enc encUnary := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp toProd_polyTime))) +/-- The number of tiles, in polynomial time. -/ +theorem tiles_polyTime : ComputableInPolyTime PacketCtx.tiles enc encUnary := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.fst _ _).comp + ((ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp toProd_polyTime))) +/-- The grid point, in polynomial time. -/ +theorem t_polyTime : ComputableInPolyTime PacketCtx.t enc encIntBin := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp toProd_polyTime))) +/-- The number of exponents per tile, in polynomial time. -/ +theorem nb_polyTime : ComputableInPolyTime PacketCtx.nb enc encUnary := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.snd _ _).comp toProd_polyTime)))) +/-- Whether the first symbol is the group, in polynomial time. -/ +theorem e₁_polyTime : ComputableInPolyTime PacketCtx.e₁ enc encBool := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.fst _ _).comp rest_polyTime) +/-- Whether the second symbol is the group, in polynomial time. -/ +theorem e₂_polyTime : ComputableInPolyTime PacketCtx.e₂ enc encBool := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.fst _ _).comp rest_polyTime)) +/-- Whether the symbols differ, in polynomial time. -/ +theorem differ_polyTime : ComputableInPolyTime PacketCtx.differ enc encBool := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp + ((ComputableInPolyTime.fst _ _).comp rest_polyTime)) +/-- The column ports, in polynomial time. -/ +theorem cols_polyTime : ComputableInPolyTime PacketCtx.cols enc + (encList (encPair encNat encBool)) := + (ComputableInPolyTime.fst _ _).comp ((ComputableInPolyTime.snd _ _).comp rest_polyTime) +/-- The interpolation value, in polynomial time. -/ +theorem ζ_polyTime : ComputableInPolyTime PacketCtx.ζ enc (encList encNat) := + (ComputableInPolyTime.snd _ _).comp ((ComputableInPolyTime.snd _ _).comp rest_polyTime) + +end PacketCtx + +/-- The dense indices of the modes of a packet whose tiles target the group. -/ +def targetModes (c : PacketCtx) : List ℕ := + (List.range (c.tiles * c.nb)).filter fun k => modeTargets c.i c.e₁ c.e₂ (k / c.nb) + +/-- **Coefficient extraction for a packet**: the precision, the target modes and the row +terms. -/ +def packetTerms : CoeffScheme.Terms PacketCtx (ℕ × Bool) where + prec c := (c.p, c.fl) + modes := targetModes + term c r o := rowTermList c.p c.fl c.a c.L c.i c.t c.nb c.differ c.cols c.ζ r o + +/-- **The data of coefficient extraction for a packet are polynomial-time.** -/ +theorem packetTerms_polyTime : packetTerms.PolyTime PacketCtx.enc (encPair encNat encBool) where + prec := PacketCtx.p_polyTime.prod PacketCtx.fl_polyTime + modes := by + have hrange : ComputableInPolyTime (fun c : PacketCtx => List.range (c.tiles * c.nb)) + PacketCtx.enc (encList encNat) := + (ComputableInPolyTime.mapWithInput (f := fun _ (k : ℕ) => k) + (ComputableInPolyTime.range.comp (ComputableInPolyTime.unary_mul' + PacketCtx.tiles_polyTime PacketCtx.nb_polyTime)) + (ComputableInPolyTime.unary_to_binary.comp + (ComputableInPolyTime.snd PacketCtx.enc encUnary))).of_enc_eq fun c => by + simp + have h1 := ComputableInPolyTime.fst PacketCtx.enc encNat + have h2 := ComputableInPolyTime.snd PacketCtx.enc encNat + have hy := ComputableInPolyTime.binary_div.comp + (h2.prod (ComputableInPolyTime.unary_to_binary.comp (PacketCtx.nb_polyTime.comp h1))) + have htest : ComputableInPolyTime (fun q : PacketCtx × ℕ => + modeTargets q.1.i q.1.e₁ q.1.e₂ (q.2 / q.1.nb)) (encPair PacketCtx.enc encNat) + encBool := + ComputableInPolyTime.ite (ComputableInPolyTime.binary_testBit hy + (PacketCtx.i_polyTime.comp h1)) (PacketCtx.e₂_polyTime.comp h1) + (PacketCtx.e₁_polyTime.comp h1) + exact ComputableInPolyTime.filterWithInput hrange htest + term := by + let E := encPair PacketCtx.enc (encPair (encPair encNat encBool) (encOption encNat)) + have hc := ComputableInPolyTime.fst PacketCtx.enc + (encPair (encPair encNat encBool) (encOption encNat)) + have hr := (ComputableInPolyTime.fst (encPair encNat encBool) (encOption encNat)).comp + (ComputableInPolyTime.snd PacketCtx.enc (encPair (encPair encNat encBool) + (encOption encNat))) + have ho := (ComputableInPolyTime.snd (encPair encNat encBool) (encOption encNat)).comp + (ComputableInPolyTime.snd PacketCtx.enc (encPair (encPair encNat encBool) + (encOption encNat))) + exact rowTermList_polyTime (PacketCtx.p_polyTime.comp hc) (PacketCtx.fl_polyTime.comp hc) + (PacketCtx.a_polyTime.comp hc) (PacketCtx.L_polyTime.comp hc) + (PacketCtx.i_polyTime.comp hc) (PacketCtx.t_polyTime.comp hc) + (PacketCtx.nb_polyTime.comp hc) (PacketCtx.differ_polyTime.comp hc) + (PacketCtx.cols_polyTime.comp hc) (PacketCtx.ζ_polyTime.comp hc) hr ho + +/-- **Coefficient extraction for a packet is correct**: with `tiles = 2 ^ (i + 1)`, the value of +the recursion in `A_p` is that of the coefficient extraction of `packetEntryList`. -/ +theorem val_packetTerms_value (c : PacketCtx) (htiles : c.tiles = 2 ^ (c.i + 1)) + (rows : List (ℕ × Bool)) (γ : List ℕ) : + val c.p c.fl (packetTerms.value c rows γ) = + val c.p c.fl (listCoeffList c.p c.fl ((List.range (2 ^ (c.i + 1) * c.nb)).filter fun k => + modeTargets c.i c.e₁ c.e₂ (k / c.nb)) + (rows.map (rowTermList c.p c.fl c.a c.L c.i c.t c.nb c.differ c.cols c.ζ)) + (fun k => γ.getD k 0)) := by + rw [val_listCoeffList (List.nodup_range.filter _), List.map_map] + have h := packetTerms.val_value c ((List.nodup_range).filter _) rows γ + simp only [packetTerms, targetModes, htiles] at h + exact h + + +/-! ### Packet entries natively -/ + +/-- The dense indices of the modes of a packet whose tiles have the group as source. -/ +def sourceModes (c : PacketCtx) : List ℕ := + (List.range (c.tiles * c.nb)).filter fun k => modeSources c.i c.e₁ c.e₂ (k / c.nb) + +/-- The product of the powers of the values of the source modes, at the exponents of a dense +list. -/ +def sourceProduct (c : PacketCtx) (β : List ℕ) : List ℕ := + prodList c.p c.fl ((sourceModes c).map fun k => + pow c.p c.fl (modeValueList c.p c.fl c.i c.differ c.cols (k / c.nb) (k % c.nb)) (β.getD k 0)) + +/-- **A packet entry, natively**: coefficient extraction by the stack recursion, at the +exponents of the dense list `β`, times the product of the powers of the source mode values. -/ +def packetEntryNative (c : PacketCtx) (rows : List (ℕ × Bool)) (β : List ℕ) : List ℕ := + mul c.p c.fl (packetTerms.value c rows β) (sourceProduct c β) + +/-- **The native packet entry is `packetEntryList`** in `A_p`, when `tiles = 2 ^ (i + 1)`. -/ +theorem val_packetEntryNative (c : PacketCtx) (htiles : c.tiles = 2 ^ (c.i + 1)) + (rows : List (ℕ × Bool)) (β : List ℕ) : + val c.p c.fl (packetEntryNative c rows β) = + val c.p c.fl (packetEntryList c.p c.fl c.a c.L c.i c.t c.nb c.e₁ c.e₂ c.differ + (fun k => β.getD k 0) rows c.cols c.ζ) := by + rw [packetEntryNative, packetEntryList, val_mul, val_mul, val_packetTerms_value c htiles, + sourceProduct, sourceModes, htiles] + +/-- **The native packet entry is the packet entry** (§D.4.6, p. 63): for a grid point `t` of +valuation `i < a`, `L = 2 ^ a`, `tiles = 2 ^ (i + 1)` and ports in slots below `L`, its value in +`A_p` is `packetEntry` at the reduction of `ℤ_(2)`, the value of `ζ`, and the exponents of `β` +at the dense indices of the modes. -/ +theorem val_packetEntryNative_eq (c : PacketCtx) (hL : c.L = 2 ^ c.a) (hia : c.i < c.a) + (htiles : c.tiles = 2 ^ (c.i + 1)) (ht : c.t ∈ cauchyGrid (2 ^ c.a)) + (hti : (2 : ℤ) ^ c.i ∣ c.t) (hti' : ¬ (2 : ℤ) ^ (c.i + 1) ∣ c.t) + {rows : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ c.a) + (hcols : ∀ r ∈ c.cols, r.1 < 2 ^ c.a) (β : List ℕ) : + val c.p c.fl (packetEntryNative c rows β) = + packetEntry (p := c.nb) (scalarHom c.p c.fl) (val c.p c.fl c.ζ) c.a c.i c.t c.e₁ c.e₂ + c.differ (fun Z => β.getD (Z.1 * c.nb + Z.2) 0) rows c.cols := by + rw [val_packetEntryNative c htiles, val_packetEntryList hL hia ht hti hti' c.nb c.e₁ c.e₂ + c.differ _ hrows hcols] + +/-- **The native packet entry in every ring receiving `A_p`.** Along a ring map `ψ : A_p → S` +with `2` nilpotent in `S`, the value of the native entry is `packetEntry` over `S` at any ring +map `κ : ℤ_(2) → S` and at the image of `ζ`; with `child_tensor_inr_cauchyModeSplitting`, it is +the entry of a packet vertex of a child network over `S`. -/ +theorem map_val_packetEntryNative {S : Type*} [CommRing S] (c : PacketCtx) + (ψ : Ring c.p c.fl →+* S) {n : ℕ} (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) + (hL : c.L = 2 ^ c.a) (hia : c.i < c.a) (htiles : c.tiles = 2 ^ (c.i + 1)) + (ht : c.t ∈ cauchyGrid (2 ^ c.a)) (hti : (2 : ℤ) ^ c.i ∣ c.t) + (hti' : ¬ (2 : ℤ) ^ (c.i + 1) ∣ c.t) {rows : List (ℕ × Bool)} + (hrows : ∀ r ∈ rows, r.1 < 2 ^ c.a) (hcols : ∀ r ∈ c.cols, r.1 < 2 ^ c.a) (β : List ℕ) : + ψ (val c.p c.fl (packetEntryNative c rows β)) = + packetEntry (p := c.nb) κ (ψ (val c.p c.fl c.ζ)) c.a c.i c.t c.e₁ c.e₂ c.differ + (fun Z => β.getD (Z.1 * c.nb + Z.2) 0) rows c.cols := by + rw [val_packetEntryNative c htiles, map_val_packetEntryList ψ hn κ hL hia ht hti hti' c.nb + c.e₁ c.e₂ c.differ _ hrows hcols] + +section Certificate + +/-- The input of the native packet evaluator: a context, the row ports and the dense exponents, +the exponents in unary. -/ +abbrev encEntryInput : PacketCtx × (List (ℕ × Bool) × List ℕ) ↪ List Bool := + encPair PacketCtx.enc (encPair (encList (encPair encNat encBool)) (encList encUnary)) + +/-- The root of coefficient extraction, with the exponents re-encoded in binary. -/ +theorem node_polyTime : + ComputableInPolyTime (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => q) + encEntryInput (CoeffScheme.Terms.encNode PacketCtx.enc (encPair encNat encBool)) := by + have hβ := (ComputableInPolyTime.snd (encList (encPair encNat encBool)) (encList encUnary)).comp + (ComputableInPolyTime.snd PacketCtx.enc + (encPair (encList (encPair encNat encBool)) (encList encUnary))) + have hβ' : ComputableInPolyTime (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => q.2.2) + encEntryInput (encList encNat) := + (ComputableInPolyTime.mapWithInput (f := fun _ (k : ℕ) => k) hβ + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _))).of_enc_eq + fun q => by simp + exact (ComputableInPolyTime.fst _ _).prod + (((ComputableInPolyTime.fst (encList (encPair encNat encBool)) (encList encUnary)).comp + (ComputableInPolyTime.snd _ _)).prod hβ') + +/-- The source product is polynomial-time, with the exponents in unary. -/ +theorem sourceProduct_polyTime : + ComputableInPolyTime (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => + sourceProduct q.1 q.2.2) encEntryInput (encList encNat) := by + have hc := ComputableInPolyTime.fst PacketCtx.enc + (encPair (encList (encPair encNat encBool)) (encList encUnary)) + have hβ := (ComputableInPolyTime.snd (encList (encPair encNat encBool)) (encList encUnary)).comp + (ComputableInPolyTime.snd PacketCtx.enc + (encPair (encList (encPair encNat encBool)) (encList encUnary))) + have hp := PacketCtx.p_polyTime.comp hc + have hfl := PacketCtx.fl_polyTime.comp hc + have hrange : ComputableInPolyTime (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => + List.range (q.1.tiles * q.1.nb)) encEntryInput (encList encNat) := + (ComputableInPolyTime.mapWithInput (f := fun _ (k : ℕ) => k) + (ComputableInPolyTime.range.comp (ComputableInPolyTime.unary_mul' + (PacketCtx.tiles_polyTime.comp hc) (PacketCtx.nb_polyTime.comp hc))) + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.snd _ _))).of_enc_eq + fun q => by simp + have h1 := ComputableInPolyTime.fst encEntryInput encNat + have h2 := ComputableInPolyTime.snd encEntryInput encNat + have hnb := ComputableInPolyTime.unary_to_binary.comp (PacketCtx.nb_polyTime.comp (hc.comp h1)) + have hy := ComputableInPolyTime.binary_div.comp (h2.prod hnb) + have htest : ComputableInPolyTime (fun z : (PacketCtx × (List (ℕ × Bool) × List ℕ)) × ℕ => + modeSources z.1.1.i z.1.1.e₁ z.1.1.e₂ (z.2 / z.1.1.nb)) (encPair encEntryInput encNat) + encBool := + ComputableInPolyTime.ite (ComputableInPolyTime.binary_testBit hy + (PacketCtx.i_polyTime.comp (hc.comp h1))) (PacketCtx.e₁_polyTime.comp (hc.comp h1)) + (PacketCtx.e₂_polyTime.comp (hc.comp h1)) + have hmodes := ComputableInPolyTime.filterWithInput + (test := fun q k => modeSources q.1.i q.1.e₁ q.1.e₂ (k / q.1.nb)) hrange htest + have hexp := ComputableInPolyTime.binary_min_unary' + (ComputableInPolyTime.unary_add' (PacketCtx.nb_polyTime.comp (hc.comp h1)) + (ComputableInPolyTime.const 1)) + (ComputableInPolyTime.binary_mod.comp (h2.prod hnb)) + have hval := modeValueList_polyTime (hp.comp h1) (hfl.comp h1) + (PacketCtx.i_polyTime.comp (hc.comp h1)) (PacketCtx.differ_polyTime.comp (hc.comp h1)) + (PacketCtx.cols_polyTime.comp (hc.comp h1)) hy hexp + have hβk : ComputableInPolyTime (fun z : (PacketCtx × (List (ℕ × Bool) × List ℕ)) × ℕ => + z.1.2.2.getD z.2 0) (encPair encEntryInput encNat) encUnary := + ComputableInPolyTime.listGetD_binary (encA := encUnary) 0 (hβ.comp h1) h2 + have hlist := ComputableInPolyTime.mapWithInput + (f := fun q k => pow q.1.p q.1.fl (modeValueList q.1.p q.1.fl q.1.i q.1.differ q.1.cols + (k / q.1.nb) (min (q.1.nb + 1) (k % q.1.nb))) (q.2.2.getD k 0)) hmodes + (computableInPolyTime_pow (hp.comp h1) (hfl.comp h1) hval hβk) + refine (prodList_polyTime hp hfl hlist).of_enc_eq fun q => ?_ + simp only [sourceProduct, sourceModes] + congr 2 + refine List.map_congr_left fun k hk => ?_ + have hk' := List.mem_range.mp (List.mem_filter.mp hk).1 + have hnb0 : 0 < q.1.nb := Nat.pos_of_ne_zero fun h0 => by simp [h0] at hk' + rw [min_eq_right ((Nat.mod_lt _ hnb0).le.trans (Nat.le_succ _))] + +/-- The final product of the native packet evaluator is polynomial-time. -/ +theorem mulFinal_polyTime : + ComputableInPolyTime (fun z : (PacketCtx × (List (ℕ × Bool) × List ℕ)) × (List ℕ × List ℕ) => + mul z.1.1.p z.1.1.fl z.2.1 z.2.2) + (encPair encEntryInput (encPair (encList encNat) (encList encNat))) (encList encNat) := by + have hc := (ComputableInPolyTime.fst PacketCtx.enc + (encPair (encList (encPair encNat encBool)) (encList encUnary))).comp + (ComputableInPolyTime.fst encEntryInput (encPair (encList encNat) (encList encNat))) + have hy := ComputableInPolyTime.snd encEntryInput (encPair (encList encNat) (encList encNat)) + exact computableInPolyTime_mul (PacketCtx.p_polyTime.comp hc) (PacketCtx.fl_polyTime.comp hc) + ((ComputableInPolyTime.fst _ _).comp hy) ((ComputableInPolyTime.snd _ _).comp hy) + +/-- **The native packet evaluator**: one native machine for `packetEntryNative` on every +context, row ports and dense exponents (in unary). Its time is that of the stack recursion of +coefficient extraction (`CoeffScheme.Terms.valueCertificate_le`, `exp(O(L log m))` times a +polynomial), plus polynomial time. -/ +noncomputable def entryCertificate : + NativeCertificate (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => + packetEntryNative q.1 q.2.1 q.2.2) encEntryInput (encList encNat) := + ((NativeCertificate.ofPolyTime mulFinal_polyTime).comp + ((NativeCertificate.ofPolyTime ComputableInPolyTime.id').prod + (((CoeffScheme.Terms.valueCertificate packetTerms_polyTime).comp + (NativeCertificate.ofPolyTime node_polyTime)).prod + (NativeCertificate.ofPolyTime sourceProduct_polyTime)))).congr fun _ => rfl + + +/-- The value of coefficient extraction is reduced. -/ +theorem reduced_value {C Rw : Type} (S : CoeffScheme.Terms C Rw) (x : C) (rows : List Rw) + (γ : List ℕ) : SumOps.galois.Reduced (S.prec x) (S.value x rows γ) := by + cases rows with + | nil => exact S.reduced_leafValue x γ + | cons r rs => + exact SumOps.galois.reduced_foldl (S.prec x) _ (SumOps.galois.reduced_add (S.prec x)) _ _ + (SumOps.galois.reduced_zero (S.prec x)) + +/-- The source product is reduced. -/ +theorem reduced_sourceProduct (c : PacketCtx) (β : List ℕ) : + SumOps.galois.Reduced (c.p, c.fl) (sourceProduct c β) := + SumOps.galois.reduced_foldl (c.p, c.fl) _ (SumOps.galois.reduced_mul (c.p, c.fl)) id _ + (SumOps.galois.reduced_one (c.p, c.fl)) + +/-- **The resources of the native packet evaluator.** There is `K` such that on an input of +length `n` with `m` target modes and `L` row ports, if the reduced coefficient lists of its +precision are at most `Rv` bits long, then with `Z = n + m + Rv + 2` the time is at most +`K (m + 2) ^ L Z ^ K` and the space at most `K Z ^ K`: the `exp(O(L log s))` time and polynomial +space of §D.4.6 (p. 63). -/ +theorem entryCertificate_le : + ∃ K : ℕ, ∀ (q : PacketCtx × (List (ℕ × Bool) × List ℕ)) (Rv : ℕ), + (∀ y, SumOps.galois.Reduced (q.1.p, q.1.fl) y → (encList encNat y).length ≤ Rv) → + entryCertificate.time q ≤ K * ((targetModes q.1).length + 2) ^ q.2.1.length * + ((encEntryInput q).length + (targetModes q.1).length + Rv + 2) ^ K ∧ + entryCertificate.space q ≤ + K * ((encEntryInput q).length + (targetModes q.1).length + Rv + 2) ^ K := by + obtain ⟨K₁, hK₁⟩ := CoeffScheme.Terms.valueCertificate_le packetTerms_polyTime + have hM := NativeCertificate.ofPolyTime_isPolynomial mulFinal_polyTime + have hI := NativeCertificate.ofPolyTime_isPolynomial + (ComputableInPolyTime.id' (encIn := encEntryInput)) + have hN := NativeCertificate.ofPolyTime_isPolynomial node_polyTime + have hP := NativeCertificate.ofPolyTime_isPolynomial sourceProduct_polyTime + obtain ⟨cm, dm, hmt⟩ := hM.1.exists_le + obtain ⟨cm', dm', hms⟩ := hM.2.exists_le + obtain ⟨ci, di, hit⟩ := hI.1.exists_le + obtain ⟨ci', di', his⟩ := hI.2.exists_le + obtain ⟨cn, dn, hnt⟩ := hN.1.exists_le + obtain ⟨cn', dn', hns⟩ := hN.2.exists_le + obtain ⟨cp, dp, hpt⟩ := hP.1.exists_le + obtain ⟨cp', dp', hps⟩ := hP.2.exists_le + obtain ⟨D, hD⟩ : ∃ D, D = K₁ + dm + dm' + di + di' + dn + dn' + dp + dp' := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₁ + cm * 8 ^ dm + cm' * 8 ^ dm' + ci + ci' + cn + cn' + cp + cp' := + ⟨_, rfl⟩ + refine ⟨G + D, fun q Rv hRv => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encEntryInput q).length := ⟨_, rfl⟩ + obtain ⟨m, hm⟩ : ∃ m, m = (targetModes q.1).length := ⟨_, rfl⟩ + obtain ⟨Z, hZ⟩ : ∃ Z, Z = n + m + Rv + 2 := ⟨_, rfl⟩ + rw [← hn, ← hm, ← hZ] + have hZ1 : 1 ≤ Z := by omega + have hpowZ {a b : ℕ} (hab : a ≤ b) : Z ^ a ≤ Z ^ b := Nat.pow_le_pow_right hZ1 hab + have hP1 : 1 ≤ (m + 2) ^ q.2.1.length := Nat.one_le_pow _ _ (by omega) + -- The node is no longer than the input: binary exponents are shorter than unary ones. + have hnode : (CoeffScheme.Terms.encNode PacketCtx.enc (encPair encNat encBool) q).length ≤ n := by + rw [hn] + obtain ⟨c, rows, β⟩ := q + have hβ : (encList encNat β).length ≤ (encList encUnary β).length := by + simp only [length_encList] + refine List.sum_le_sum fun k _ => ?_ + have : (encNat k).length ≤ k := by + change k.bits.length ≤ k + rw [Nat.size_eq_bits_len] + exact (Nat.size_le.mpr (Nat.lt_two_pow_self)).trans le_rfl + simp only [encUnary_apply, List.length_replicate] + omega + simp only [length_encPair] + omega + have hval := hK₁ q (Rv) (by simpa [packetTerms] using hRv) + simp only [packetTerms] at hval + rw [← hm] at hval + obtain ⟨hvt, hvs⟩ := hval + -- The polynomial parts. + have hpoly {c d e : ℕ} (hle : e ≤ n + 1) (hd : d ≤ D) : c * e ^ d ≤ c * Z ^ D := + Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans (hpowZ hd)) + have hZn : (CoeffScheme.Terms.encNode PacketCtx.enc (encPair encNat encBool) q).length + + m + Rv + 2 ≤ Z := by omega + have hvalT : (CoeffScheme.Terms.valueCertificate packetTerms_polyTime).time q ≤ + K₁ * (m + 2) ^ q.2.1.length * Z ^ D := + hvt.trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left hZn _).trans (hpowZ (by omega)))) + have hvalS : (CoeffScheme.Terms.valueCertificate packetTerms_polyTime).space q ≤ K₁ * Z ^ D := + hvs.trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left hZn _).trans (hpowZ (by omega)))) + -- The final multiplication reads the input and two reduced lists. + have hred1 := hRv _ (reduced_value packetTerms q.1 q.2.1 q.2.2) + have hred2 := hRv _ (reduced_sourceProduct q.1 q.2.2) + have hmulIn : (encPair encEntryInput (encPair (encList encNat) (encList encNat)) + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2))).length + 1 ≤ 8 * Z := by + have e1 := length_encPair (enc₁ := encEntryInput) + (enc₂ := encPair (encList encNat) (encList encNat)) + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2)) + have e2 := length_encPair (enc₁ := encList encNat) (enc₂ := encList encNat) + (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2) + simp only [packetTerms] at hred1 e1 e2 ⊢ + rw [e1, e2, ← hn] + omega + have hmulT : (NativeCertificate.ofPolyTime mulFinal_polyTime).time + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2)) ≤ cm * 8 ^ dm * Z ^ D := + (hmt _).trans (by + calc cm * (_ + 1) ^ dm ≤ cm * (8 * Z) ^ dm := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hmulIn _) + _ = cm * 8 ^ dm * Z ^ dm := by rw [Nat.mul_pow]; ring + _ ≤ cm * 8 ^ dm * Z ^ D := Nat.mul_le_mul_left _ (hpowZ (by omega))) + have hmulS : (NativeCertificate.ofPolyTime mulFinal_polyTime).space + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2)) ≤ cm' * 8 ^ dm' * Z ^ D := + (hms _).trans (by + calc cm' * (_ + 1) ^ dm' ≤ cm' * (8 * Z) ^ dm' := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hmulIn _) + _ = cm' * 8 ^ dm' * Z ^ dm' := by rw [Nat.mul_pow]; ring + _ ≤ cm' * 8 ^ dm' * Z ^ D := Nat.mul_le_mul_left _ (hpowZ (by omega))) + have hit' := (hit q).trans (hpoly (c := ci) (by omega) (show di ≤ D by omega)) + have his' := (his q).trans (hpoly (c := ci') (by omega) (show di' ≤ D by omega)) + have hnt' := (hnt q).trans (hpoly (c := cn) (by omega) (show dn ≤ D by omega)) + have hns' := (hns q).trans (hpoly (c := cn') (by omega) (show dn' ≤ D by omega)) + have hpt' := (hpt q).trans (hpoly (c := cp) (by omega) (show dp ≤ D by omega)) + have hps' := (hps q).trans (hpoly (c := cp') (by omega) (show dp' ≤ D by omega)) + have hWK : Z ^ D ≤ Z ^ (G + D) := hpowZ (by omega) + obtain ⟨P, hPdef⟩ : ∃ P, P = (m + 2) ^ q.2.1.length := ⟨_, rfl⟩ + rw [← hPdef] at hP1 hvalT ⊢ + have htime : entryCertificate.time q = + (NativeCertificate.ofPolyTime ComputableInPolyTime.id').time q + + ((NativeCertificate.ofPolyTime node_polyTime).time q + + (CoeffScheme.Terms.valueCertificate packetTerms_polyTime).time q + + (NativeCertificate.ofPolyTime sourceProduct_polyTime).time q) + + (NativeCertificate.ofPolyTime mulFinal_polyTime).time + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2)) := rfl + have hspace : entryCertificate.space q = + (NativeCertificate.ofPolyTime ComputableInPolyTime.id').space q + + ((NativeCertificate.ofPolyTime node_polyTime).space q + + (CoeffScheme.Terms.valueCertificate packetTerms_polyTime).space q + + (NativeCertificate.ofPolyTime sourceProduct_polyTime).space q) + + (NativeCertificate.ofPolyTime mulFinal_polyTime).space + (q, (packetTerms.value q.1 q.2.1 q.2.2, sourceProduct q.1 q.2.2)) := rfl + constructor + · rw [htime] + have e : G * (P * Z ^ D) = P * (K₁ * Z ^ D) + P * ((G - K₁) * Z ^ D) := by + rw [← Nat.mul_add, ← Nat.add_mul, Nat.add_sub_cancel' (by omega)]; ring + have h1 : cm * 8 ^ dm * Z ^ D + ci * Z ^ D + cn * Z ^ D + cp * Z ^ D ≤ + P * ((G - K₁) * Z ^ D) := by + have : cm * 8 ^ dm * Z ^ D + ci * Z ^ D + cn * Z ^ D + cp * Z ^ D = + (cm * 8 ^ dm + ci + cn + cp) * Z ^ D := by ring + rw [this] + exact (Nat.mul_le_mul_right _ (by omega)).trans (Nat.le_mul_of_pos_left _ hP1) + calc _ ≤ K₁ * P * Z ^ D + (cm * 8 ^ dm * Z ^ D + ci * Z ^ D + cn * Z ^ D + cp * Z ^ D) := by + omega + _ ≤ G * (P * Z ^ D) := by + have : K₁ * P * Z ^ D = P * (K₁ * Z ^ D) := by ring + omega + _ ≤ (G + D) * P * Z ^ (G + D) := by + rw [show (G + D) * P * Z ^ (G + D) = (G + D) * (P * Z ^ (G + D)) by ring] + exact Nat.mul_le_mul (by omega) (Nat.mul_le_mul_left _ hWK) + · rw [hspace] + calc _ ≤ K₁ * Z ^ D + cm' * 8 ^ dm' * Z ^ D + ci' * Z ^ D + cn' * Z ^ D + cp' * Z ^ D := by + omega + _ = (K₁ + cm' * 8 ^ dm' + ci' + cn' + cp') * Z ^ D := by ring + _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hWK + +end Certificate + +end NativePacket + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean new file mode 100644 index 0000000000..e0d388e5fb --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean @@ -0,0 +1,62 @@ +/- +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.PacketTerms + +/-! +# Tests for the native packet evaluator + +The tiny packet of the packet-evaluator tests: `2 ^ 1` slots over +`A_3 = (ℤ / 8)[U] / (U ^ 2 + U + 1)`, the grid point `t = 1` of valuation `i = 0`, two exponents +per tile, one row port in slot `0` and one column port in slot `1` set to one, `ζ = U`. The +native entry, through the stack recursion, agrees with `packetEntryList`, and the correctness +theorem and the certificate apply. +-/ + +@[expose] public section + +namespace ComplexityTests.TensorNetworkPacketTerms + +open Cslib Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativePacket + Cslib.Circuits.Frontier.TensorNetwork + +/-- The context of the tiny packet. -/ +def ctx : PacketCtx := + ⟨3, [1, 1], 1, 2, 0, 2, 1, 2, true, false, true, [(1, true)], [0, 1]⟩ + +-- Zero exponents: the entry is the local form, the kernel value `W_1(T) = 5 + 2 U`. +example : packetEntryNative ctx [(0, true)] [0, 0, 0, 0] = [5, 2] := by decide + +-- It agrees with `packetEntryList`, here also as lists. +example : packetEntryNative ctx [(0, true)] [0, 0, 0, 0] = + packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun _ => 0) [(0, true)] [(1, true)] + [0, 1] := by + decide + +-- Exponent `1` at the target mode of dense index `1`: the mode coefficient. +example : packetEntryNative ctx [(0, true)] [0, 1, 0, 0] = + modeCoeffList 3 [1, 1] 1 2 0 1 true 0 0 1 [0, 1] := by + decide + +/-- The correctness theorem applies to the tiny packet. -/ +theorem val_ctx (β : List ℕ) : + GaloisRingArith.val 3 [1, 1] (packetEntryNative ctx [(0, true)] β) = + packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] [0, 1]) 1 0 1 true + false true (fun Z => β.getD (Z.1 * 2 + Z.2) 0) [(0, true)] [(1, true)] := + val_packetEntryNative_eq ctx (by decide) (by decide) (by decide) + (by simp [ctx, mem_cauchyGrid]) (by norm_num [ctx]) (by norm_num [ctx]) (by simp) + (by simp [ctx]) β + +-- One native machine computes the native entry. +noncomputable example : Cslib.GenericRAM.NativeCertificate + (fun q : PacketCtx × (List (ℕ × Bool) × List ℕ) => packetEntryNative q.1 q.2.1 q.2.2) + encEntryInput (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat) := + entryCertificate + +end ComplexityTests.TensorNetworkPacketTerms From e03f0ce8c06c2bc51ec0ec1a69623d3111aac882 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:12:17 -0400 Subject: [PATCH 24/33] feat(circuit/frontier): descriptors of packet vertices and their native evaluator PacketDesc is a packet context, the ports (isRow, slot) in port order and the dense exponents. packetVertexEntry reads the row and column ports from the bits; vertexCertificate is its native machine, the evaluator of the described-network direct summation at coefficient lists, with val_packetVertexEntry and the bounds vertexCertificate_le. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/PacketTerms.lean | 209 +++++++++++++++++- .../Frontier/TensorNetworkPacketTerms.lean | 20 ++ 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean index f7da6829e8..0905c77e84 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean @@ -52,6 +52,12 @@ bounds: time `exp(O(L log s))` and polynomial space. target modes, `L` row ports and `Z` polynomial in the input and the reduced coefficient lists (`NativePacket.entryCertificate_le`). The exponents are read in unary, since the source's are below the precision. +* **Descriptors** (`NativePacket.PacketDesc`): a packet context, the ports `(isRow, slot)` of the + vertex in its port order and the dense exponents, "its packet data, the tuple `β`, the + interpolation value, and the creation precision" (p. 63). `NativePacket.vertexCertificate` + computes the entry at the bits on the ports, the evaluator of + `Cslib.Circuits.Frontier.DescribedNetwork` direct summation at coefficient lists, with the same + bounds (`NativePacket.vertexCertificate_le`). ## Main definitions @@ -59,6 +65,10 @@ bounds: time `exp(O(L log s))` and polynomial space. * `Cslib.Circuits.Frontier.NativePacket.packetTerms`: coefficient extraction for a packet. * `Cslib.Circuits.Frontier.NativePacket.packetEntryNative`: the native packet entry. * `Cslib.Circuits.Frontier.NativePacket.entryCertificate`: its native machine. +* `Cslib.Circuits.Frontier.NativePacket.PacketDesc`, + `Cslib.Circuits.Frontier.NativePacket.packetVertexEntry`, + `Cslib.Circuits.Frontier.NativePacket.vertexCertificate`: descriptors of packet vertices, their + entries at the bits on their ports, and the native machine for them. ## Main results @@ -69,7 +79,10 @@ bounds: time `exp(O(L log s))` and polynomial space. * `Cslib.Circuits.Frontier.NativePacket.val_packetEntryNative_eq`, `Cslib.Circuits.Frontier.NativePacket.map_val_packetEntryNative`: the native entry is the packet entry, in `A_p` and in every ring receiving it. -* `Cslib.Circuits.Frontier.NativePacket.entryCertificate_le`: its time and space. +* `Cslib.Circuits.Frontier.NativePacket.entryCertificate_le`, + `Cslib.Circuits.Frontier.NativePacket.vertexCertificate_le`: their time and space. +* `Cslib.Circuits.Frontier.NativePacket.val_packetVertexEntry`: the entry of a packet vertex is + the packet entry at the ports read from its bits. ## References @@ -1178,6 +1191,200 @@ theorem entryCertificate_le : end Certificate +/-! ### Descriptors of packet vertices -/ + +/-- **The descriptor of a packet vertex**: a packet context, whose column ports are read from +the bits instead, the ports of the vertex in its port order, each `(isRow, slot)`, and the dense +exponents of the modes of the packet. -/ +abbrev PacketDesc := PacketCtx × (List (Bool × ℕ) × List ℕ) + +/-- The ports of one side, with the bits on them: the slots of the ports with `isRow = side`, +paired with their bits, in port order. -/ +def sidePorts (ports : List (Bool × ℕ)) (bs : List Bool) (side : Bool) : List (ℕ × Bool) := + ((List.range ports.length).filter fun j => (ports.getD j (false, 0)).1 == side).map fun j => + ((ports.getD j (false, 0)).2, bs.getD j false) + +/-- **The entry of a packet vertex at the bits on its ports**: the native packet entry, with the +row ports and the column ports read from the bits. -/ +def packetVertexEntry (d : PacketDesc) (bs : List Bool) : List ℕ := + packetEntryNative { d.1 with cols := sidePorts d.2.1 bs false } (sidePorts d.2.1 bs true) d.2.2 + +/-- The encoding of a packet descriptor: the context, the ports in binary, and the exponents in +unary. -/ +abbrev encPacketDesc : PacketDesc ↪ List Bool := + encPair PacketCtx.enc (encPair (encList (encPair encBool encNat)) (encList encUnary)) + +/-- The ports of one side are polynomial-time. -/ +theorem sidePorts_polyTime {α : Type} {encIn : α ↪ List Bool} {ports : α → List (Bool × ℕ)} + {bs : α → List Bool} {side : α → Bool} + (hports : ComputableInPolyTime ports encIn (encList (encPair encBool encNat))) + (hbs : ComputableInPolyTime bs encIn encBits) + (hside : ComputableInPolyTime side encIn encBool) : + ComputableInPolyTime (fun x => sidePorts (ports x) (bs x) (side x)) encIn + (encList (encPair encNat encBool)) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have hport := (ComputableInPolyTime.listGetD (encA := encPair encBool encNat) + (encL := encList (encPair encBool encNat)) (false, 0)).comp ((hports.comp h1).prod h2) + have htest := ComputableInPolyTime.beq ((ComputableInPolyTime.fst _ _).comp hport) + (hside.comp h1) + have hfilt := ComputableInPolyTime.filterWithInput + (test := fun x j => ((ports x).getD j (false, 0)).1 == side x) + (ComputableInPolyTime.range.comp ((ComputableInPolyTime.length_list _).comp hports)) htest + have hbit := (ComputableInPolyTime.listGetD (encA := encBool) (encL := encBits) false).comp + ((hbs.comp h1).prod h2) + exact ComputableInPolyTime.mapWithInput + (f := fun x j => (((ports x).getD j (false, 0)).2, (bs x).getD j false)) hfilt + (((ComputableInPolyTime.snd _ _).comp hport).prod hbit) + +/-- The input of the native packet evaluator from a descriptor and the bits on its ports. -/ +theorem entryInput_polyTime : + ComputableInPolyTime (fun q : PacketDesc × List Bool => + (({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : PacketCtx), + (sidePorts q.1.2.1 q.2 true, q.1.2.2))) + (encPair encPacketDesc encBits) encEntryInput := by + have hd := ComputableInPolyTime.fst encPacketDesc encBits + have hbs := ComputableInPolyTime.snd encPacketDesc encBits + have hc := (ComputableInPolyTime.fst _ _).comp hd + have hports := (ComputableInPolyTime.fst (encList (encPair encBool encNat)) + (encList encUnary)).comp ((ComputableInPolyTime.snd _ _).comp hd) + have hβ := (ComputableInPolyTime.snd (encList (encPair encBool encNat)) + (encList encUnary)).comp ((ComputableInPolyTime.snd _ _).comp hd) + have hcols := sidePorts_polyTime hports hbs (ComputableInPolyTime.const false) + have hrows := sidePorts_polyTime hports hbs (ComputableInPolyTime.const true) + have h01 := (PacketCtx.p_polyTime.comp hc).prod (PacketCtx.fl_polyTime.comp hc) + have h23 := (PacketCtx.a_polyTime.comp hc).prod (PacketCtx.L_polyTime.comp hc) + have h45 := (PacketCtx.i_polyTime.comp hc).prod (PacketCtx.tiles_polyTime.comp hc) + have hsyms := (PacketCtx.e₁_polyTime.comp hc).prod ((PacketCtx.e₂_polyTime.comp hc).prod + (PacketCtx.differ_polyTime.comp hc)) + have hlast := hcols.prod (PacketCtx.ζ_polyTime.comp hc) + have htuple := h01.prod (h23.prod (h45.prod ((PacketCtx.t_polyTime.comp hc).prod + ((PacketCtx.nb_polyTime.comp hc).prod (hsyms.prod hlast))))) + have hctx : ComputableInPolyTime (fun q : PacketDesc × List Bool => + ({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : PacketCtx)) + (encPair encPacketDesc encBits) PacketCtx.enc := + htuple.of_enc_eq fun _ => rfl + exact hctx.prod (hrows.prod hβ) + +/-- **The native packet evaluator on descriptors**: one native machine for the entry of a packet +vertex at the bits on its ports, the `ev` of the direct summation of +`Cslib.Circuits.Frontier.DescribedNetwork` at coefficient lists (`SumOps.galois`). -/ +noncomputable def vertexCertificate : + NativeCertificate (fun q : PacketDesc × List Bool => packetVertexEntry q.1 q.2) + (encPair encPacketDesc encBits) (encList encNat) := + (entryCertificate.comp (NativeCertificate.ofPolyTime entryInput_polyTime)).congr fun _ => rfl + +/-- **The resources of the native packet evaluator on descriptors.** There is `K` such that on a +descriptor and bits of total length `n`, with `m` target modes and `L` row ports, if the reduced +coefficient lists of its precision are at most `Rv` bits long, then with `Z = n + m + Rv + 2` the +time is at most `K (m + 2) ^ L Z ^ K` and the space at most `K Z ^ K`. -/ +theorem vertexCertificate_le : + ∃ K : ℕ, ∀ (q : PacketDesc × List Bool) (Rv : ℕ), + (∀ y, SumOps.galois.Reduced (q.1.1.p, q.1.1.fl) y → (encList encNat y).length ≤ Rv) → + vertexCertificate.time q ≤ K * ((targetModes q.1.1).length + 2) ^ + (sidePorts q.1.2.1 q.2 true).length * + ((encPair encPacketDesc encBits q).length + (targetModes q.1.1).length + Rv + 2) ^ K ∧ + vertexCertificate.space q ≤ K * ((encPair encPacketDesc encBits q).length + + (targetModes q.1.1).length + Rv + 2) ^ K := by + obtain ⟨K₀, hK₀⟩ := entryCertificate_le + have hI := NativeCertificate.ofPolyTime_isPolynomial entryInput_polyTime + obtain ⟨ci, di, hit⟩ := hI.1.exists_le + obtain ⟨ci', di', his⟩ := hI.2.exists_le + obtain ⟨cl, dl, hlen⟩ := (ComputableInPolyTime.polyBounded_length entryInput_polyTime).exists_le + obtain ⟨D, hD⟩ : ∃ D, D = (dl + 1) * K₀ + di + di' := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₀ * (cl + 1) ^ K₀ + ci + ci' := ⟨_, rfl⟩ + refine ⟨G + D, fun q Rv hRv => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encPair encPacketDesc encBits q).length := ⟨_, rfl⟩ + obtain ⟨m, hm⟩ : ∃ m, m = (targetModes q.1.1).length := ⟨_, rfl⟩ + obtain ⟨Z, hZ⟩ : ∃ Z, Z = n + m + Rv + 2 := ⟨_, rfl⟩ + rw [← hn, ← hm, ← hZ] + have hZ1 : 1 ≤ Z := by omega + have hpowZ {a b : ℕ} (hab : a ≤ b) : Z ^ a ≤ Z ^ b := Nat.pow_le_pow_right hZ1 hab + obtain ⟨P, hPdef⟩ : ∃ P, P = (m + 2) ^ (sidePorts q.1.2.1 q.2 true).length := ⟨_, rfl⟩ + rw [← hPdef] + have hP1 : 1 ≤ P := hPdef ▸ Nat.one_le_pow _ _ (by omega) + have hentry := hK₀ ((({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : PacketCtx), + (sidePorts q.1.2.1 q.2 true, q.1.2.2))) Rv hRv + -- The input of the entry is polynomially long. + have hin := hlen q + rw [← hn] at hin + obtain ⟨n', hn'⟩ : ∃ n', n' = (encEntryInput ((({ q.1.1 with + cols := sidePorts q.1.2.1 q.2 false } : PacketCtx), (sidePorts q.1.2.1 q.2 true, + q.1.2.2)))).length := ⟨_, rfl⟩ + rw [← hn'] at hin hentry + have hZ' : n' + m + Rv + 2 ≤ (cl + 1) * Z ^ (dl + 1) := by + have h1 : cl * (n + 1) ^ dl ≤ cl * Z ^ (dl + 1) := + Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans (hpowZ (by omega))) + have h2 : Z ≤ Z ^ (dl + 1) := by + calc Z = Z ^ 1 := (pow_one Z).symm + _ ≤ Z ^ (dl + 1) := hpowZ (by omega) + have e : (cl + 1) * Z ^ (dl + 1) = cl * Z ^ (dl + 1) + Z ^ (dl + 1) := by ring + omega + have hZK : (n' + m + Rv + 2) ^ K₀ ≤ (cl + 1) ^ K₀ * Z ^ D := by + calc (n' + m + Rv + 2) ^ K₀ ≤ ((cl + 1) * Z ^ (dl + 1)) ^ K₀ := Nat.pow_le_pow_left hZ' _ + _ = (cl + 1) ^ K₀ * Z ^ ((dl + 1) * K₀) := by rw [Nat.mul_pow, ← pow_mul] + _ ≤ (cl + 1) ^ K₀ * Z ^ D := Nat.mul_le_mul_left _ (hpowZ (by omega)) + have het : entryCertificate.time ((({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : + PacketCtx), (sidePorts q.1.2.1 q.2 true, q.1.2.2))) ≤ + K₀ * P * (n' + m + Rv + 2) ^ K₀ := by + rw [hPdef, hm]; exact hentry.1 + have hes : entryCertificate.space ((({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : + PacketCtx), (sidePorts q.1.2.1 q.2 true, q.1.2.2))) ≤ K₀ * (n' + m + Rv + 2) ^ K₀ := by + rw [hm]; exact hentry.2 + have hit' : (NativeCertificate.ofPolyTime entryInput_polyTime).time q ≤ ci * Z ^ D := + (hit q).trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans + (hpowZ (by omega)))) + have his' : (NativeCertificate.ofPolyTime entryInput_polyTime).space q ≤ ci' * Z ^ D := + (his q).trans (Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left (by omega) _).trans + (hpowZ (by omega)))) + have hWK : Z ^ D ≤ Z ^ (G + D) := hpowZ (by omega) + have htime : vertexCertificate.time q = + (NativeCertificate.ofPolyTime entryInput_polyTime).time q + + entryCertificate.time ((({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : PacketCtx), + (sidePorts q.1.2.1 q.2 true, q.1.2.2))) := rfl + have hspace : vertexCertificate.space q = + (NativeCertificate.ofPolyTime entryInput_polyTime).space q + + entryCertificate.space ((({ q.1.1 with cols := sidePorts q.1.2.1 q.2 false } : PacketCtx), + (sidePorts q.1.2.1 q.2 true, q.1.2.2))) := rfl + constructor + · rw [htime] + have h1 : K₀ * P * (n' + m + Rv + 2) ^ K₀ ≤ P * (K₀ * (cl + 1) ^ K₀ * Z ^ D) := by + calc K₀ * P * (n' + m + Rv + 2) ^ K₀ ≤ K₀ * P * ((cl + 1) ^ K₀ * Z ^ D) := + Nat.mul_le_mul_left _ hZK + _ = P * (K₀ * (cl + 1) ^ K₀ * Z ^ D) := by ring + have h2 : ci * Z ^ D ≤ P * (ci * Z ^ D) := Nat.le_mul_of_pos_left _ hP1 + have e : P * (K₀ * (cl + 1) ^ K₀ * Z ^ D) + P * (ci * Z ^ D) ≤ P * (G * Z ^ D) := by + rw [← Nat.mul_add, ← Nat.add_mul] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul_right _ (by omega)) + calc _ ≤ P * (G * Z ^ D) := by omega + _ ≤ (G + D) * P * Z ^ (G + D) := by + rw [show (G + D) * P * Z ^ (G + D) = P * ((G + D) * Z ^ (G + D)) by ring] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul (by omega) hWK) + · rw [hspace] + have h1 : K₀ * (n' + m + Rv + 2) ^ K₀ ≤ K₀ * (cl + 1) ^ K₀ * Z ^ D := by + rw [Nat.mul_assoc]; exact Nat.mul_le_mul_left _ hZK + calc _ ≤ (K₀ * (cl + 1) ^ K₀ + ci') * Z ^ D := by rw [Nat.add_mul]; omega + _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hWK + +/-- **The entry of a packet vertex is the packet entry** at the row and column ports read from +its bits. -/ +theorem val_packetVertexEntry (d : PacketDesc) (bs : List Bool) (hL : d.1.L = 2 ^ d.1.a) + (hia : d.1.i < d.1.a) (htiles : d.1.tiles = 2 ^ (d.1.i + 1)) + (ht : d.1.t ∈ cauchyGrid (2 ^ d.1.a)) (hti : (2 : ℤ) ^ d.1.i ∣ d.1.t) + (hti' : ¬ (2 : ℤ) ^ (d.1.i + 1) ∣ d.1.t) (hports : ∀ x ∈ d.2.1, x.2 < 2 ^ d.1.a) : + val d.1.p d.1.fl (packetVertexEntry d bs) = + packetEntry (p := d.1.nb) (scalarHom d.1.p d.1.fl) (val d.1.p d.1.fl d.1.ζ) d.1.a d.1.i + d.1.t d.1.e₁ d.1.e₂ d.1.differ (fun Z => d.2.2.getD (Z.1 * d.1.nb + Z.2) 0) + (sidePorts d.2.1 bs true) (sidePorts d.2.1 bs false) := by + have hside (side : Bool) : ∀ r ∈ sidePorts d.2.1 bs side, r.1 < 2 ^ d.1.a := by + intro r hr + obtain ⟨j, hj, rfl⟩ := List.mem_map.mp hr + have hj' := List.mem_range.mp (List.mem_filter.mp hj).1 + exact hports _ (by rw [List.getD_eq_getElem _ _ hj']; exact List.getElem_mem _) + exact val_packetEntryNative_eq { d.1 with cols := sidePorts d.2.1 bs false } hL hia htiles ht + hti hti' (hside true) (hside false) d.2.2 + + end NativePacket end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean index e0d388e5fb..4d43bf56eb 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean @@ -59,4 +59,24 @@ noncomputable example : Cslib.GenericRAM.NativeCertificate encEntryInput (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat) := entryCertificate +/-- The descriptor of the tiny packet vertex: a row port in slot `0`, then a column port in slot +`1`, and zero exponents. Its column ports are read from the bits. -/ +def desc : PacketDesc := (ctx, ([(true, 0), (false, 1)], [0, 0, 0, 0])) + +-- At the bits `1, 1` on its ports, the entry is the kernel value `5 + 2 U`. +example : packetVertexEntry desc [true, true] = [5, 2] := by decide + +-- With the column bit `0`, the local form vanishes, and the row factor at bit `1` is `0`. +example : packetVertexEntry desc [true, false] = [0, 0] := by decide + +/-- The correctness theorem applies to the descriptor. -/ +theorem val_desc (bs : List Bool) : + GaloisRingArith.val 3 [1, 1] (packetVertexEntry desc bs) = + packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] [0, 1]) 1 0 1 true + false true (fun Z => [0, 0, 0, 0].getD (Z.1 * 2 + Z.2) 0) (sidePorts desc.2.1 bs true) + (sidePorts desc.2.1 bs false) := + val_packetVertexEntry desc bs (by decide) (by decide) (by decide) + (by simp [desc, ctx, mem_cauchyGrid]) (by norm_num [desc, ctx]) (by norm_num [desc, ctx]) + (by decide) + end ComplexityTests.TensorNetworkPacketTerms From 5736072f826686a53f5cc6ff93c24f2ce71bea2b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:13:48 -0400 Subject: [PATCH 25/33] chore: regenerate Complexity.lean after merging main Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 162a20efed..8f445fbc17 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -886,12 +886,10 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DirectCont public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TensorNetwork.KernelProduct -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness From 74a464ed1aeb8bf722b4ad4da3b449d7dbc79179 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:14:36 -0400 Subject: [PATCH 26/33] chore: regenerate Complexity.lean after merging the base Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 87fbb6cc89..3c43ca3ff0 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -879,8 +879,8 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Binarize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CoeffScheme public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildSize +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CoeffScheme public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedContraction From 956e30a0a3aa13ba766ab23bcdfeac890ce9e924 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:49:10 -0400 Subject: [PATCH 27/33] feat(circuit/frontier): vertex descriptors and the entries of native child networks (Theorem D.1, T2.9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VertexDesc (a table or a packet descriptor) with one native evaluator and its resource bound; a described network with port lists has the contraction of the network it describes; retained vertices keep their tensors; the packet vertices of a Cauchy child have the child's entries for ports in any order with tags computed from the same port list; reductionLift maps the Galois ring back into O / 2^n with reductionLift ∘ reduction = mk. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 + .../Frontier/TensorNetwork/ChildEntries.lean | 308 ++++++++++++++++ .../Frontier/TensorNetwork/ReductionLift.lean | 121 ++++++ .../Frontier/TensorNetwork/VertexDesc.lean | 344 ++++++++++++++++++ .../Frontier/TensorNetworkVertexDesc.lean | 95 +++++ 5 files changed, 871 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ReductionLift.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean diff --git a/Complexity.lean b/Complexity.lean index 3054842fdb..c17dc3af9f 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -879,6 +879,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Binarize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildEntries public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildSize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CoeffScheme public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint @@ -893,8 +894,10 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpans public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketTerms +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ReductionLift public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.VertexDesc public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional public import Complexity.Computability.Circuit.Frontier.Witness diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean new file mode 100644 index 0000000000..2eb8415168 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean @@ -0,0 +1,308 @@ +/- +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.VertexDesc +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.DescribedContraction +import Mathlib.Data.List.GetD + +/-! +# The entries of the child networks from their descriptors + +A child network of the contraction recursion of [OpenAI2026ETH] (`ModeSplitting.child`, §D.4.6, +p. 63) is carried natively as a network of vertex descriptors (`VertexDesc`) with port lists. This +file proves that such a network has the contraction of the child, once each descriptor reads its +ports in the right order. + +* **Port lists** (`DescribedNetwork.contraction_eq_of_ports`). Number the vertices and the edges of + a Boolean tensor network `N`, and give each vertex any list of edges as its ports and a + descriptor. If the evaluator at the descriptor of `v`, read at the bits of an assignment on its + ports, is the tensor of `v` at that assignment, the described network has the contraction of + `N`. +* **Retained vertices** (`ModeSplitting.child_tensor_inl_eq_tensor`). A vertex of group `g` keeps + its tensor in the child of group `g`: a row reads its row ports and a column its column ports. + So a retained vertex keeps its descriptor, and its ports are its parent ports, in the same + order, renumbered as child edges. +* **Packet vertices** (`TensorNetwork.map_val_entry_child_inr`). Let the ports of the packet + vertex `q` be the child edges of packet `q` in any order without repetition, and let its + descriptor tag each port with whether it is a row port and with its slot, in the same order. Then + along any ring map from `A_p` to the coefficient ring of the child, the descriptor's entry at the + bits on its ports is the child's entry (`child_tensor_inr_cauchyModeSplitting`), for the + packet's context matching the node `(t, ζ)`, the group `g`, the symbols of `q` and the exponents + `β`. The tags are computed from the port list itself, so tags and bits align by construction + (`NativePacket.sidePorts_map`). + +## Main results + +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction_eq_of_ports`: a described network with + port lists has the contraction of the network it describes. +* `Cslib.Circuits.Frontier.NativePacket.sidePorts_map`: the ports of one side of a tagged list. +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.child_tensor_inl_eq_tensor`: the entries + of the retained vertices of a child. +* `Cslib.Circuits.Frontier.TensorNetwork.map_val_entry_child_inr`: the entries of the packet + vertices of a Cauchy child, from their descriptors. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.6, p. 63. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +open Finset GaloisRingArith NativePacket + +/-! ### Port lists -/ + +namespace DescribedNetwork + +variable {T : Type} {R : Type*} [CommSemiring R] {V E : Type*} [Fintype V] [Fintype E] + [DecidableEq E] + +/-- **A described network with port lists has the contraction of the network it describes.** +Number the vertices of `N` by `ev` and its edges by `ee`, and give each vertex `v` a list of edges +`ports v` and a descriptor `desc v`. If the evaluator at `desc v`, read at the bits of every +assignment on `ports v`, is the tensor of `v` at that assignment, the described network with the +numbered ports and the descriptors has the contraction of `N`. -/ +theorem contraction_eq_of_ports {k l : ℕ} (N : TensorNetwork R Bool V E) (ev : V ≃ Fin k) + (ee : E ≃ Fin l) (ports : V → List E) (desc : V → T) (eval : T → List Bool → R) + (h : ∀ v (x : E → Bool), eval (desc v) ((ports v).map x) = N.tensor v x) : + (⟨l, List.ofFn fun i => ((ports (ev.symm i)).map fun e => (ee e : ℕ), desc (ev.symm i))⟩ : + DescribedNetwork T).contraction eval = N.contraction := by + simp only [contraction, TensorNetwork.contraction, List.map_ofFn, List.prod_ofFn] + rw [← (ee.arrowCongr (Equiv.refl Bool)).symm.sum_comp] + refine sum_congr rfl fun x _ => ?_ + rw [← ev.symm.prod_comp] + refine prod_congr rfl fun v _ => ?_ + simp only [Function.comp_apply] + rw [← h] + congr 1 + simp only [wordBits, List.map_map] + refine List.map_congr_left fun e _ => ?_ + simp only [Function.comp_apply, Equiv.arrowCongr_symm, Equiv.arrowCongr_apply, + Equiv.symm_symm, Equiv.refl_symm, Equiv.coe_refl, id_eq] + rw [List.getD_eq_getElem _ _ (by simp), List.getElem_ofFn] + +end DescribedNetwork + +/-! ### The ports of one side of a tagged list -/ + +namespace NativePacket + +/-- **The ports of one side of a tagged list.** If the ports of a vertex are tagged, in their +order, by whether they are row ports and by their slots, and the bits are read in the same order, +the ports of one side are the ports with that tag, with their slots and bits, in order. -/ +theorem sidePorts_map {α : Type*} (cs : List α) (tag : α → Bool) (sl : α → ℕ) (y : α → Bool) + (side : Bool) : + sidePorts (cs.map fun c => (tag c, sl c)) (cs.map y) side = + (cs.filter fun c => tag c == side).map fun c => (sl c, y c) := by + induction cs with + | nil => rfl + | cons c cs ih => + simp only [sidePorts, List.length_map] at ih + simp only [sidePorts, List.map_cons, List.length_cons, List.length_map, + List.range_succ_eq_map, List.filter_cons, List.filter_map, List.getD_cons_zero, + Function.comp_def, List.getD_cons_succ] + by_cases hc : tag c == side + · rw [ite_eq_left hc, ite_eq_left hc] + simp only [List.map_cons, List.map_map] + rw [← ih] + rfl + · rw [ite_eq_right hc, ite_eq_right hc] + simp only [List.map_map] + rw [← ih] + rfl + +end NativePacket + +namespace TensorNetwork + +/-! ### Retained vertices -/ + +namespace ModeSplitting + +variable {R V E G P M : Type*} [CommRing R] [Fintype M] [DecidableEq V] [Fintype E] + [DecidableEq E] [DecidableEq P] [DecidableEq G] [DecidableEq M] {N : TensorNetwork R Bool V E} + {W : Matrix E E R} (S : ModeSplitting N W G P M) + +/-- **The retained vertices of a child**: a vertex of group `g` keeps its tensor in the child of +group `g`, reading at each of its edges the child edge of that edge: a row its row ports and a +column its column ports. -/ +theorem child_tensor_inl_eq_tensor (β : M → ℕ) (g : G) (v : {v // S.group v = g}) + (y : {e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g} → Bool) : + (S.child β g).tensor (.inl v) y = N.tensor v.1 fun e => + if S.side v.1 then (if h : S.group (N.src e) = g then y (.inl ⟨e, h⟩) else false) + else (if h : S.group (N.tgt e) = g then y (.inr ⟨e, h⟩) else false) := by + simp only [child] + by_cases hv : S.side v.1 + · rw [ite_eq_left hv] + refine N.tensor_congr _ _ _ fun e he => ?_ + simp only [ite_eq_left hv] + by_cases hs : N.src e = v.1 + · rw [dite_eq_left hs, dite_eq_left (hs ▸ v.2)] + · have ht : N.tgt e = v.1 := he.resolve_left hs + have := S.side_tgt e + rw [ht, hv] at this + exact absurd this (by decide) + · rw [ite_eq_right hv] + refine N.tensor_congr _ _ _ fun e he => ?_ + simp only [ite_eq_right hv] + by_cases ht : N.tgt e = v.1 + · rw [dite_eq_left ht, dite_eq_left (ht ▸ v.2)] + · have hs : N.src e = v.1 := he.resolve_right ht + have := S.side_src e + rw [hs] at this + exact absurd this hv + +end ModeSplitting + +/-! ### Packet vertices -/ + +section Ports + +variable {R V E G : Type*} [DecidableEq G] {N : TensorNetwork R Bool V E} {group : V → G} + {slot : E → ℕ} + +/-- The row ports of a list of child edges, with their slots and bits. -/ +private theorem filter_isLeft_map {g : G} + (cs : List ({e // group (N.src e) = g} ⊕ {e // group (N.tgt e) = g})) + (y : {e // group (N.src e) = g} ⊕ {e // group (N.tgt e) = g} → Bool) : + (cs.filter fun c => c.isLeft == true).map + (fun c => (slot (Sum.elim Subtype.val Subtype.val c), y c)) = + ((cs.filterMap Sum.getLeft?).map Subtype.val).map fun e => + (slot e, if h : group (N.src e) = g then y (.inl ⟨e, h⟩) else false) := by + induction cs with + | nil => rfl + | cons c cs ih => + rcases c with c | c + · rw [List.filter_cons_of_pos rfl, List.filterMap_cons_some (f := Sum.getLeft?) rfl, + List.map_cons, List.map_cons, List.map_cons, ih, dite_eq_left c.2] + rfl + · rw [List.filter_cons_of_neg (by simp), List.filterMap_cons_none (f := Sum.getLeft?) rfl, ih] + +/-- The column ports of a list of child edges, with their slots and bits. -/ +private theorem filter_isLeft_false_map {g : G} + (cs : List ({e // group (N.src e) = g} ⊕ {e // group (N.tgt e) = g})) + (y : {e // group (N.src e) = g} ⊕ {e // group (N.tgt e) = g} → Bool) : + (cs.filter fun c => c.isLeft == false).map + (fun c => (slot (Sum.elim Subtype.val Subtype.val c), y c)) = + ((cs.filterMap Sum.getRight?).map Subtype.val).map fun e => + (slot e, if h : group (N.tgt e) = g then y (.inr ⟨e, h⟩) else false) := by + induction cs with + | nil => rfl + | cons c cs ih => + rcases c with c | c + · rw [List.filter_cons_of_neg (by simp), List.filterMap_cons_none (f := Sum.getRight?) rfl, + ih] + · rw [List.filter_cons_of_pos rfl, List.filterMap_cons_some (f := Sum.getRight?) rfl, + List.map_cons, List.map_cons, List.map_cons, ih, dite_eq_left c.2] + rfl + +end Ports + +section Cauchy + +variable {R V E G P : Type*} [CommRing R] [Fintype E] [DecidableEq E] [Fintype P] [DecidableEq G] + [DecidableEq P] {N : TensorNetwork R Bool V E} {group : V → G} {packet : E → P} {slot : E → ℕ} + {sym : P → G × G} {i : ℕ} + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) + (horient : ∀ e, (group (N.src e), group (N.tgt e)) = + if (slot e).testBit i then (sym (packet e)).swap else sym (packet e)) + {a : ℕ} (hslot : ∀ e, slot e < 2 ^ a) (hia : i < a) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hti : (2 : ℤ) ^ i ∣ t) + (hti' : ¬ (2 : ℤ) ^ (i + 1) ∣ t) + +/-- **The packet vertices of a Cauchy child, from their descriptors.** Let the ports of the +packet vertex `q` in the child of group `g` be the child edges of packet `q`, listed without +repetition in any order, and let the descriptor's ports tag each of them, in the same order, with +whether it is a row port and with its slot. Let the packet's context have the node `t` of +valuation `i < a`, `L = 2 ^ a` slots, `2 ^ (i + 1)` tiles and `p` exponents per tile, the symbols +of `q` against `g`, an interpolation value mapping to `ζ`, and the dense exponents of `β` at `q`. +Then along a ring map `ψ` from `A_p`, at the packet's precision, to `R`, the descriptor's entry at +the bits on its ports is the child's entry. -/ +theorem map_val_entry_child_inr [DecidableEq V] (β : P × Fin (2 ^ (i + 1)) × Fin p → ℕ) (g : G) + (q : P) + (cs : List ({e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti + hti').group (N.src e) = g} ⊕ + {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.tgt e) = g})) + (hcs : cs.Nodup) (hcs' : ∀ c, c ∈ cs ↔ packet (Sum.elim Subtype.val Subtype.val c) = q) + (c : PacketCtx) (βd : List ℕ) (ψ : Ring c.p c.fl →+* R) (hζ : ψ (val c.p c.fl c.ζ) = ζ) + (ha : c.a = a) (hL : c.L = 2 ^ a) (hi : c.i = i) (htiles : c.tiles = 2 ^ (i + 1)) + (ht' : c.t = t) (hnb : c.nb = p) (he₁ : c.e₁ = decide ((sym q).1 = g)) + (he₂ : c.e₂ = decide ((sym q).2 = g)) (hdiffer : c.differ = decide ((sym q).1 ≠ (sym q).2)) + (hβ : ∀ Z : Fin (2 ^ (i + 1)) × Fin p, βd.getD (Z.1 * p + Z.2) 0 = β (q, Z)) + (y : {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.src e) = g} ⊕ + {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.tgt e) = g} → Bool) : + ψ (val c.p c.fl (VertexDesc.entry + (.inr (c, cs.map fun e => (e.isLeft, slot (Sum.elim Subtype.val Subtype.val e)), βd)) + (cs.map y))) = + ((cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').child β g).tensor + (.inr q) y := by + subst ha hi ht' hnb + have hports : ∀ x ∈ cs.map (fun e => (e.isLeft, slot (Sum.elim Subtype.val Subtype.val e))), + x.2 < 2 ^ c.a := by + intro x hx + obtain ⟨e, -, rfl⟩ := List.mem_map.mp hx + exact hslot _ + have key := VertexDesc.map_val_entry_inr (c, cs.map fun e => (e.isLeft, + slot (Sum.elim Subtype.val Subtype.val e)), βd) (cs.map y) ψ hp κ hL hia htiles ht hti hti' + hports + have hgoal : ψ (val c.p c.fl (VertexDesc.entry (.inr (c, cs.map fun e => (e.isLeft, + slot (Sum.elim Subtype.val Subtype.val e)), βd)) (cs.map y))) = _ := key + rw [hgoal] + dsimp only + have hrows : ((cs.filterMap Sum.getLeft?).map Subtype.val).Nodup := + ((hcs.filterMap fun _ _ _ h₁ h₂ => by + rw [Option.mem_def, Sum.getLeft?_eq_some_iff] at h₁ h₂; rw [h₁, h₂])).map + Subtype.val_injective + have hcols : ((cs.filterMap Sum.getRight?).map Subtype.val).Nodup := + ((hcs.filterMap fun _ _ _ h₁ h₂ => by + rw [Option.mem_def, Sum.getRight?_eq_some_iff] at h₁ h₂; rw [h₁, h₂])).map + Subtype.val_injective + have hrows' : ∀ e, e ∈ (cs.filterMap Sum.getLeft?).map Subtype.val ↔ + packet e = q ∧ group (N.src e) = g := by + intro e + simp only [List.mem_map, List.mem_filterMap, Sum.getLeft?_eq_some_iff] + constructor + · rintro ⟨e', ⟨c, hc, rfl⟩, rfl⟩ + exact ⟨(hcs' _).1 hc, by simpa using e'.2⟩ + · rintro ⟨hq, hg⟩ + have hg' : (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.src e) = g := by simpa using hg + exact ⟨⟨e, hg'⟩, ⟨_, (hcs' (.inl ⟨e, hg'⟩)).2 hq, rfl⟩, rfl⟩ + have hcols' : ∀ e, e ∈ (cs.filterMap Sum.getRight?).map Subtype.val ↔ + packet e = q ∧ group (N.tgt e) = g := by + intro e + simp only [List.mem_map, List.mem_filterMap, Sum.getRight?_eq_some_iff] + constructor + · rintro ⟨e', ⟨c, hc, rfl⟩, rfl⟩ + exact ⟨(hcs' _).1 hc, by simpa using e'.2⟩ + · rintro ⟨hq, hg⟩ + have hg' : (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group + (N.tgt e) = g := by simpa using hg + exact ⟨⟨e, hg'⟩, ⟨_, (hcs' (.inr ⟨e, hg'⟩)).2 hq, rfl⟩, rfl⟩ + rw [child_tensor_inr_cauchyModeSplitting side hsrc htgt horient hslot hia κ _ hp ht hti hti' β + g q y hrows hrows' hcols hcols', sidePorts_map, sidePorts_map, filter_isLeft_map, + filter_isLeft_false_map, hζ, he₁, he₂, hdiffer] + congr 1 + funext Z + exact hβ Z + +end Cauchy + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ReductionLift.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ReductionLift.lean new file mode 100644 index 0000000000..423643c610 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ReductionLift.lean @@ -0,0 +1,121 @@ +/- +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.PacketEvaluator +public import Complexity.Foundations.NumberTheory.Padics.AdjoinRootReduction +import Mathlib.Data.ZMod.QuotientRing + +/-! +# The Galois ring maps into `O / 2 ^ n` + +The contraction recursion of [OpenAI2026ETH] reasons over `O = ℤ_(2)[U] / (f)` and its quotients +`O / (2 ^ n)` (the children of a call live over `O / (2 ^ {p_t})`, `TensorNetwork.callSplitting`), +while the native algorithm computes in `A_n = (ℤ / 2 ^ n)[U] / (f)`, the Galois ring +`GaloisRing 2 n f`. Reduction `O → A_n` (`AdjoinRoot.reduction`) factors through `O / (2 ^ n)`; +this file gives the map back, `reductionLift : A_n → O / (2 ^ n)`, with +`reductionLift ∘ reduction = mk` (`reductionLift_comp_reduction`). It sends `U` to the +class of `U` and is the unique ring map from `ℤ / 2 ^ n` on scalars. So a value computed natively +in `A_n` and mapped along `reductionLift` is the class of any element of `O` reducing to it: the +canonical lift of a coefficient list (`reductionLift_reduction`). + +## Main definitions + +* `Cslib.Circuits.Frontier.reductionLift`: the map `A_n → O / (2 ^ n)`. + +## Main results + +* `Cslib.Circuits.Frontier.reductionLift_comp_reduction`: `reductionLift ∘ reduction = mk`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1, p. 57. +-/ + +public section + +open Polynomial AdjoinRoot + +namespace Cslib.Circuits.Frontier + +variable {f : ℤ[X]} (hf : f.Monic) (hirr : Irreducible (f.map (Int.castRingHom (ZMod 2)))) + (n : ℕ) + +/-- `2 ^ n = 0` in `O / (2 ^ n)`. -/ +private theorem two_pow_eq_zero_quotient : (2 : ((padicValuation 2 hf hirr).integer ⧸ + Ideal.span {(2 : (padicValuation 2 hf hirr).integer) ^ n})) ^ n = 0 := by + have : Ideal.Quotient.mk (Ideal.span {(2 : (padicValuation 2 hf hirr).integer) ^ n}) + ((2 : (padicValuation 2 hf hirr).integer) ^ n) = 0 := + Ideal.Quotient.eq_zero_iff_mem.mpr (Ideal.mem_span_singleton_self _) + rwa [map_pow, map_ofNat] at this + +/-- The scalars `ℤ / 2 ^ n → O / (2 ^ n)`. -/ +private noncomputable def scalarLift : ZMod (2 ^ n) →+* ((padicValuation 2 hf hirr).integer ⧸ + Ideal.span {(2 : (padicValuation 2 hf hirr).integer) ^ n}) := + (Ideal.Quotient.lift _ (Int.castRingHom _) fun a ha => by + obtain ⟨c, rfl⟩ := Ideal.mem_span_singleton.mp ha + rw [map_mul, eq_intCast, Int.cast_natCast, Nat.cast_pow, Nat.cast_ofNat, + two_pow_eq_zero_quotient hf hirr n, zero_mul]).comp + (Int.quotientSpanNatEquivZMod (2 ^ n)).symm.toRingHom + +/-- The class of `U` in `O / (2 ^ n)`. -/ +private noncomputable def rootClass : ((padicValuation 2 hf hirr).integer ⧸ + Ideal.span {(2 : (padicValuation 2 hf hirr).integer) ^ n}) := + Ideal.Quotient.mk _ (integerEquiv hf hirr (root _)) + +private theorem eval₂_rootClass : + (f.map (Int.castRingHom (ZMod (2 ^ n)))).eval₂ (scalarLift hf hirr n) (rootClass hf hirr n) = + 0 := by + rw [eval₂_map] + have hcomp : (scalarLift hf hirr n).comp (Int.castRingHom (ZMod (2 ^ n))) = + ((Ideal.Quotient.mk _).comp (integerEquiv hf hirr).toRingHom).comp + ((of _).comp (Int.castRingHom (Rat.padicValuation 2).integer)) := + RingHom.ext_int _ _ + rw [hcomp, rootClass, show Ideal.Quotient.mk _ (integerEquiv hf hirr (root _)) = + ((Ideal.Quotient.mk _).comp (integerEquiv hf hirr).toRingHom) (root _) from rfl, + ← Polynomial.hom_eval₂, ← eval₂_map, eval₂_root, map_zero] + +/-- **The Galois ring maps into `O / (2 ^ n)`**: `ℤ / 2 ^ n` by its unique ring map, and `U` to +its class. -/ +noncomputable def reductionLift : GaloisRing 2 n f →+* ((padicValuation 2 hf hirr).integer ⧸ + Ideal.span {(2 : (padicValuation 2 hf hirr).integer) ^ n}) := + lift (scalarLift hf hirr n) (rootClass hf hirr n) (eval₂_rootClass hf hirr n) + +/-- **`reductionLift` undoes reduction modulo `2 ^ n`**: reducing an element of `O` to `A_n` and +mapping back gives its class in `O / (2 ^ n)`. -/ +theorem reductionLift_comp_reduction : + (reductionLift hf hirr n).comp (reduction hf hirr n) = Ideal.Quotient.mk _ := by + -- `U` of `O` is the class of `U` of `ℤ[X] / (f)`. + have hroot : integerEquiv hf hirr (root _) = ⟨map (Int.castRingHom ℚ) f _ dvd_rfl (root f), + (Valuation.mem_integer_iff _ _).mpr (padicValuation_map_le_one hf hirr _)⟩ := by + apply Subtype.ext + change (integerEquiv hf hirr (root _) : AdjoinRoot (f.map (Int.castRingHom ℚ))) = + map (Int.castRingHom ℚ) f _ dvd_rfl (root f) + rw [map_root, coe_integerEquiv] + conv_lhs => rw [← mk_X] + rw [integerMap_mk, Polynomial.map_X, mk_X] + have key : (reductionLift hf hirr n).comp (integerReduction 2 f n) = + (Ideal.Quotient.mk _).comp (integerEquiv hf hirr).toRingHom := by + refine ringHom_ext ?_ ?_ + · exact padicInteger_ringHom_ext + (two_pow_eq_zero_quotient hf hirr n) _ _ + · rw [RingHom.comp_apply, ← reduction_integerEquiv hf hirr, hroot, reduction_toRat hf hirr, + map_root, + reductionLift, lift_root, rootClass] + rfl + ext x + obtain ⟨y, rfl⟩ := (integerEquiv hf hirr).surjective x + rw [RingHom.comp_apply, reduction_integerEquiv, ← RingHom.comp_apply, key] + rfl + +/-- `reductionLift (reduction x)` is the class of `x`. -/ +theorem reductionLift_reduction (x : (padicValuation 2 hf hirr).integer) : + reductionLift hf hirr n (reduction hf hirr n x) = Ideal.Quotient.mk _ x := + RingHom.congr_fun (reductionLift_comp_reduction hf hirr n) x + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean new file mode 100644 index 0000000000..cfb4546d15 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean @@ -0,0 +1,344 @@ +/- +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.PacketTerms +public import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Sum +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.BigO.Sum +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.Degree.Bounds +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.BitString +import Complexity.Computability.Machines.Turing.MultiTape.Internal.DegreeOptionPrimitives +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListBinaryLookup +import Complexity.Computability.Machines.Turing.MultiTape.Internal.PolyTimeListPrimitives +import Complexity.Foundations.Data.BitString.Binary +import Mathlib.Data.List.GetD + +/-! +# Descriptors of the vertices of the recursion's networks + +The networks of the contraction recursion of [OpenAI2026ETH] (§D.4.6–§D.4.7, pp. 63–64) have two +kinds of vertices. The vertices of the input network keep their tables, and they survive into +every child of their group with the same ports. The packet vertices created at a call are +described by "its packet data, the tuple `β`, the interpolation value, and the creation +precision" (p. 63), and from then on they also survive into the children of their group. A +vertex descriptor (`VertexDesc`) is therefore a table or a packet descriptor +(`NativePacket.PacketDesc`), and a retained vertex keeps its descriptor. + +`VertexDesc.entry` evaluates a descriptor at the bits on its ports: the table entry, as a constant +coefficient list, or the native packet entry at the packet's creation precision. A network of the +recursion reads the entries at its own precision `(p, fl)` (`GaloisRingArith.val p fl`), so a +retained packet vertex created at a lower precision is read at the higher one: its coefficients +are those of the same element of `O = ℤ_(2)[U] / (f)`. One native machine evaluates every +descriptor (`VertexDesc.entryCertificate`), with the packet evaluator's bounds: time +`K (m + 2) ^ L Z ^ K` and space `K Z ^ K`, for `m` target modes and `L` row ports of a packet (none +for a table) and `Z` polynomial in the input and the reduced coefficient lists +(`VertexDesc.entryCertificate_le`). It is the evaluator of direct summation over networks of +descriptors at coefficient lists (`DescribedNetwork.contractionCertificate` at `SumOps.galois`). + +## Main definitions + +* `Cslib.Circuits.Frontier.VertexDesc`: a table or a packet descriptor. +* `Cslib.Circuits.Frontier.VertexDesc.entry`: its entry at the bits on its ports. +* `Cslib.Circuits.Frontier.VertexDesc.enc`: its encoding. +* `Cslib.Circuits.Frontier.VertexDesc.entryCertificate`: the native evaluator. + +## Main results + +* `Cslib.Circuits.Frontier.VertexDesc.val_entry_inl`: a table entry is read as a constant. +* `Cslib.Circuits.Frontier.VertexDesc.map_val_entry_inr`: a packet entry is the packet entry in + every ring receiving `A_p` with `2` nilpotent. +* `Cslib.Circuits.Frontier.VertexDesc.entryCertificate_le`: the time and space of the evaluator. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.6–§D.4.7, pp. 63–64. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +open GaloisRingArith NativePacket TensorNetwork Turing.MultiTapeTM Cslib.GenericRAM + +/-- **The descriptor of a vertex** of a network of the contraction recursion: the table of a +vertex of the input network (`inl`), or the descriptor of a packet vertex (`inr`). -/ +abbrev VertexDesc : Type := List ℕ ⊕ PacketDesc + +namespace VertexDesc + +/-- **The entry of a vertex at the bits on its ports**: the table entry, as a constant coefficient +list, or the native packet entry at the packet's creation precision. -/ +@[expose] def entry : VertexDesc → List Bool → List ℕ + | .inl table, bs => [ExplicitTensorNetwork.entry table bs] + | .inr d, bs => packetVertexEntry d bs + +/-- The encoding of a descriptor: a tag, then the table in binary or the packet descriptor. -/ +abbrev enc : VertexDesc ↪ List Bool := encSum (encList encNat) encPacketDesc + +/-- The number of target modes of a descriptor: those of its packet, and none for a table. -/ +@[expose] def modes : VertexDesc → ℕ + | .inl _ => 0 + | .inr d => (targetModes d.1).length + +/-- The number of row ports of a descriptor at the bits on its ports: those of its packet, and +none for a table. -/ +@[expose] def rowPorts : VertexDesc → List Bool → ℕ + | .inl _, _ => 0 + | .inr d, bs => (sidePorts d.2.1 bs true).length + +/-- The reduced coefficient lists of the creation precision of a descriptor are at most `Rv` bits +long; no condition for a table. -/ +@[expose] def ReducedLE (Rv : ℕ) : VertexDesc → Prop + | .inl _ => True + | .inr d => ∀ y, SumOps.galois.Reduced (d.1.p, d.1.fl) y → (encList encNat y).length ≤ Rv + +/-! ### Values -/ + +@[simp] theorem entry_inl (table : List ℕ) (bs : List Bool) : + entry (.inl table) bs = [ExplicitTensorNetwork.entry table bs] := rfl + +@[simp] theorem entry_inr (d : PacketDesc) (bs : List Bool) : + entry (.inr d) bs = packetVertexEntry d bs := rfl + +/-- **A table entry is read as a constant** at every precision. -/ +theorem val_entry_inl (p : ℕ) (fl table : List ℕ) (bs : List Bool) : + val p fl (entry (.inl table) bs) = + ((ExplicitTensorNetwork.entry table bs : ℕ) : Ring p fl) := by + obtain h | ⟨e, he⟩ : fl.length = 0 ∨ ∃ e, fl.length = e + 1 := + (Nat.eq_zero_or_pos _).imp id fun h => ⟨_, (Nat.succ_pred_eq_of_pos h).symm⟩ + · have := subsingleton_of_length_eq_zero (p := p) h + exact Subsingleton.elim _ _ + · rw [entry_inl, val, he, Finset.sum_range_succ', + Finset.sum_eq_zero fun i _ => by rw [List.getD_cons_succ, List.getD_nil]; simp] + simp + +/-- **A packet entry in every ring receiving `A_p`.** Along a ring map `ψ` from `A_p`, at the +precision `p` of the packet, to a ring in which `2` is nilpotent, the entry of a packet descriptor +at the bits on its ports is `packetEntry` at any ring map `κ : ℤ_(2) → S`, the image of the +interpolation value, the dense exponents, and the row and column ports read from the bits. -/ +theorem map_val_entry_inr {S : Type*} [CommRing S] (d : PacketDesc) (bs : List Bool) + (ψ : Ring d.1.p d.1.fl →+* S) {n : ℕ} (hn : (2 : S) ^ n = 0) (κ : ℤ₍₂₎ →+* S) + (hL : d.1.L = 2 ^ d.1.a) (hia : d.1.i < d.1.a) (htiles : d.1.tiles = 2 ^ (d.1.i + 1)) + (ht : d.1.t ∈ cauchyGrid (2 ^ d.1.a)) (hti : (2 : ℤ) ^ d.1.i ∣ d.1.t) + (hti' : ¬ (2 : ℤ) ^ (d.1.i + 1) ∣ d.1.t) (hports : ∀ x ∈ d.2.1, x.2 < 2 ^ d.1.a) : + ψ (val d.1.p d.1.fl (entry (.inr d) bs)) = + packetEntry (p := d.1.nb) κ (ψ (val d.1.p d.1.fl d.1.ζ)) d.1.a d.1.i d.1.t d.1.e₁ d.1.e₂ + d.1.differ (fun Z => d.2.2.getD (Z.1 * d.1.nb + Z.2) 0) (sidePorts d.2.1 bs true) + (sidePorts d.2.1 bs false) := by + have hside (side : Bool) : ∀ r ∈ sidePorts d.2.1 bs side, r.1 < 2 ^ d.1.a := by + intro r hr + obtain ⟨j, hj, rfl⟩ := List.mem_map.mp hr + have hj' := List.mem_range.mp (List.mem_filter.mp hj).1 + exact hports _ (by rw [List.getD_eq_getElem _ _ hj']; exact List.getElem_mem _) + exact map_val_packetEntryNative { d.1 with cols := sidePorts d.2.1 bs false } ψ hn κ hL hia + htiles ht hti hti' (hside true) (hside false) d.2.2 + +/-- A packet entry is a reduced coefficient list at the packet's precision. -/ +theorem reduced_entry_inr (d : PacketDesc) (bs : List Bool) : + SumOps.galois.Reduced (d.1.p, d.1.fl) (entry (.inr d) bs) := + SumOps.galois.reduced_mul (d.1.p, d.1.fl) _ _ + +/-! ### The native evaluator -/ + +/-- The right summand of a descriptor and its bits. -/ +theorem getRight_polyTime : + ComputableInPolyTime (fun q : VertexDesc × List Bool => q.1.getRight?) (encPair enc encBits) + (encOption encPacketDesc) := + (ComputableInO.getRight?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) + +/-- The table entry of a descriptor at its bits, the empty table standing for a packet, as a +singleton list. -/ +theorem tableEntry_polyTime : + ComputableInPolyTime (fun q : VertexDesc × List Bool => + [ExplicitTensorNetwork.entry (q.1.getLeft?.getD []) q.2]) (encPair enc encBits) + (encList encNat) := by + have hleft : ComputableInPolyTime (fun q : VertexDesc × List Bool => q.1.getLeft?) + (encPair enc encBits) (encOption (encList encNat)) := + (ComputableInO.getLeft?_deg _ _).computableInPolyTime.comp (ComputableInPolyTime.fst _ _) + have htable : ComputableInPolyTime (fun q : VertexDesc × List Bool => q.1.getLeft?.getD []) + (encPair enc encBits) (encList encNat) := + (ComputableInDegree.getD (enc₁ := encList encNat) (enc := encOption (encList encNat)) + []).computableInPolyTime.comp hleft + have hindex := ComputableInPolyTime.bitsBinaryValue.comp + (ComputableInPolyTime.bitsReverse.comp (ComputableInPolyTime.snd enc encBits)) + have hentry := ComputableInPolyTime.listGetD_binary (encA := encNat) (encL := encList encNat) 0 + htable hindex + exact (ComputableInPolyTime.cons' hentry (ComputableInPolyTime.const [])).of_enc_eq fun q => by + simp only [ExplicitTensorNetwork.entry, BitString.lsbValue_eq_binaryValue_reverse, + Function.comp_apply] + +/-- The input of the packet evaluator from a descriptor's bits and its packet. -/ +theorem packetInput_polyTime : + ComputableInPolyTime (fun p : (VertexDesc × List Bool) × PacketDesc => (p.2, p.1.2)) + (encPair (encPair enc encBits) encPacketDesc) (encPair encPacketDesc encBits) := + (ComputableInPolyTime.snd _ _).prod + ((ComputableInPolyTime.snd enc encBits).comp (ComputableInPolyTime.fst _ _)) + +/-- **One native machine for the entries of all descriptors**: the table lookup for a table, and +the native packet evaluator (`NativePacket.vertexCertificate`) for a packet. -/ +noncomputable def entryCertificate : + NativeCertificate (fun q : VertexDesc × List Bool => entry q.1 q.2) (encPair enc encBits) + (encList encNat) := + (NativeCertificate.option (value := fun q : VertexDesc × List Bool => q.1.getRight?) + (fallback := fun q => [ExplicitTensorNetwork.entry (q.1.getLeft?.getD []) q.2]) + (body := fun p => packetVertexEntry p.2 p.1.2) + (NativeCertificate.ofPolyTime getRight_polyTime) + (NativeCertificate.ofPolyTime tableEntry_polyTime) + ((vertexCertificate.comp (NativeCertificate.ofPolyTime packetInput_polyTime)).congr + fun _ => rfl)).congr fun q => by + rcases q with ⟨d | d, bs⟩ <;> rfl + +/-- `c (y + 1) ^ d ≤ c Z ^ D` when `y + 1 ≤ Z` and `d ≤ D`. -/ +private theorem mul_pow_le_mul_pow {c y d Z D : ℕ} (hy : y + 1 ≤ Z) (hd : d ≤ D) : + c * (y + 1) ^ d ≤ c * Z ^ D := + Nat.mul_le_mul_left _ ((Nat.pow_le_pow_left hy _).trans + (Nat.pow_le_pow_right (by omega) hd)) + +/-- **The resources of the native evaluator of descriptors.** There is `K` such that on a +descriptor and bits of total length `n`, with `m` target modes and `L` row ports (`0` and `0` for a +table), if the reduced coefficient lists of a packet's precision are at most `Rv` bits long, then +with `Z = n + m + Rv + 2` the time is at most `K (m + 2) ^ L Z ^ K` and the space at most +`K Z ^ K`. -/ +theorem entryCertificate_le : + ∃ K : ℕ, ∀ (q : VertexDesc × List Bool) (Rv : ℕ), q.1.ReducedLE Rv → + entryCertificate.time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * + ((encPair enc encBits q).length + q.1.modes + Rv + 2) ^ K ∧ + entryCertificate.space q ≤ + K * ((encPair enc encBits q).length + q.1.modes + Rv + 2) ^ K := by + obtain ⟨K₀, hK₀⟩ := vertexCertificate_le + have hR := NativeCertificate.ofPolyTime_isPolynomial getRight_polyTime + have hT := NativeCertificate.ofPolyTime_isPolynomial tableEntry_polyTime + have hI := NativeCertificate.ofPolyTime_isPolynomial packetInput_polyTime + obtain ⟨cr, dr, hrt⟩ := hR.1.exists_le + obtain ⟨cr', dr', hrs⟩ := hR.2.exists_le + obtain ⟨ct, dt, htt⟩ := hT.1.exists_le + obtain ⟨ct', dt', hts⟩ := hT.2.exists_le + obtain ⟨ci, di, hit⟩ := hI.1.exists_le + obtain ⟨ci', di', his⟩ := hI.2.exists_le + obtain ⟨D, hD⟩ : ∃ D, D = K₀ + dr + dr' + dt + dt' + di + di' + 1 := ⟨_, rfl⟩ + obtain ⟨G, hG⟩ : ∃ G, G = K₀ + 2 * cr + 2 * cr' + ct + ct' + ci * 3 ^ di + ci' * 3 ^ di' + 1 := + ⟨_, rfl⟩ + refine ⟨G + D, fun q Rv hRv => ?_⟩ + obtain ⟨n, hn⟩ : ∃ n, n = (encPair enc encBits q).length := ⟨_, rfl⟩ + obtain ⟨Z, hZ⟩ : ∃ Z, Z = n + q.1.modes + Rv + 2 := ⟨_, rfl⟩ + obtain ⟨P, hP⟩ : ∃ P, P = (q.1.modes + 2) ^ q.1.rowPorts q.2 := ⟨_, rfl⟩ + rw [← hn, ← hZ, ← hP] + have hP1 : 1 ≤ P := hP ▸ Nat.one_le_pow _ _ (by omega) + have hnZ : n + 1 ≤ Z := by omega + have hZD : Z ^ D ≤ Z ^ (G + D) := Nat.pow_le_pow_right (by omega) (by omega) + have hnD : n ≤ Z ^ D := by + calc n ≤ Z := by omega + _ = Z ^ 1 := (pow_one Z).symm + _ ≤ Z ^ D := Nat.pow_le_pow_right (by omega) (by omega) + have hrt' : (NativeCertificate.ofPolyTime getRight_polyTime).time q ≤ cr * Z ^ D := + (hrt q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + have hrs' : (NativeCertificate.ofPolyTime getRight_polyTime).space q ≤ cr' * Z ^ D := + (hrs q).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + rcases q with ⟨d | d, bs⟩ + · -- A table: the lookup alone. + have htt' : (NativeCertificate.ofPolyTime tableEntry_polyTime).time (.inl d, bs) ≤ + ct * Z ^ D := (htt _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + have hts' : (NativeCertificate.ofPolyTime tableEntry_polyTime).space (.inl d, bs) ≤ + ct' * Z ^ D := (hts _).trans (hn ▸ mul_pow_le_mul_pow hnZ (by omega)) + have htime : entryCertificate.time (.inl d, bs) = + 2 * (NativeCertificate.ofPolyTime getRight_polyTime).time (.inl d, bs) + n + + (NativeCertificate.ofPolyTime tableEntry_polyTime).time (.inl d, bs) := by + rw [hn]; rfl + have hspace : entryCertificate.space (.inl d, bs) = + 2 * (NativeCertificate.ofPolyTime getRight_polyTime).space (.inl d, bs) + n + + (NativeCertificate.ofPolyTime tableEntry_polyTime).space (.inl d, bs) := by + rw [hn]; rfl + constructor + · rw [htime] + calc _ ≤ G * Z ^ D := by + have : (2 * cr + 1 + ct) * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr + 1 + ct) * Z ^ D = 2 * (cr * Z ^ D) + Z ^ D + ct * Z ^ D := by ring + omega + _ ≤ (G + D) * P * Z ^ (G + D) := + Nat.mul_le_mul ((Nat.le_add_right G D).trans (Nat.le_mul_of_pos_right _ hP1)) hZD + · rw [hspace] + calc _ ≤ G * Z ^ D := by + have : (2 * cr' + 1 + ct') * Z ^ D ≤ G * Z ^ D := Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr' + 1 + ct') * Z ^ D = 2 * (cr' * Z ^ D) + Z ^ D + ct' * Z ^ D := by + ring + omega + _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + · -- A packet: the input of the packet evaluator, then the evaluator. + have hn' : n = 2 * ((encPacketDesc d).length + 1) + 1 + (encBits bs).length := by + rw [hn]; exact length_encPair _ + have hlen : (encPair encPacketDesc encBits (d, bs)).length ≤ n := by + have h3 : (encPair encPacketDesc encBits (d, bs)).length = + 2 * (encPacketDesc d).length + 1 + (encBits bs).length := length_encPair _ + omega + have hlenI : (encPair (encPair enc encBits) encPacketDesc ((.inr d, bs), d)).length + 1 ≤ + 3 * Z := by + have h1 : (encPair (encPair enc encBits) encPacketDesc ((.inr d, bs), d)).length = + 2 * (encPair enc encBits (.inr d, bs)).length + 1 + (encPacketDesc d).length := + length_encPair _ + omega + have hpow3 {c e : ℕ} (he : e ≤ D) : c * (3 * Z) ^ e ≤ c * 3 ^ e * Z ^ D := by + rw [Nat.mul_pow, ← Nat.mul_assoc] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_right (by omega) he) + have hit' : (NativeCertificate.ofPolyTime packetInput_polyTime).time ((.inr d, bs), d) ≤ + ci * 3 ^ di * Z ^ D := + (hit _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans + (hpow3 (by omega))) + have his' : (NativeCertificate.ofPolyTime packetInput_polyTime).space ((.inr d, bs), d) ≤ + ci' * 3 ^ di' * Z ^ D := + (his _).trans ((Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hlenI _)).trans + (hpow3 (by omega))) + obtain ⟨hvt, hvs⟩ := hK₀ (d, bs) Rv hRv + have hZ' : (encPair encPacketDesc encBits (d, bs)).length + (targetModes d.1).length + Rv + 2 + ≤ Z := by rw [hZ]; simp only [modes]; omega + have hpowK : ((encPair encPacketDesc encBits (d, bs)).length + (targetModes d.1).length + Rv + + 2) ^ K₀ ≤ Z ^ D := + (Nat.pow_le_pow_left hZ' _).trans (Nat.pow_le_pow_right (by omega) (by omega)) + have hvt' : vertexCertificate.time (d, bs) ≤ K₀ * P * Z ^ D := by + refine hvt.trans ?_ + rw [hP] + exact Nat.mul_le_mul_left _ hpowK + have hvs' : vertexCertificate.space (d, bs) ≤ K₀ * Z ^ D := + hvs.trans (Nat.mul_le_mul_left _ hpowK) + have htime : entryCertificate.time (.inr d, bs) = + 2 * (NativeCertificate.ofPolyTime getRight_polyTime).time (.inr d, bs) + n + + ((NativeCertificate.ofPolyTime packetInput_polyTime).time ((.inr d, bs), d) + + vertexCertificate.time (d, bs)) := by + rw [hn]; rfl + have hspace : entryCertificate.space (.inr d, bs) = + 2 * (NativeCertificate.ofPolyTime getRight_polyTime).space (.inr d, bs) + n + + ((NativeCertificate.ofPolyTime packetInput_polyTime).space ((.inr d, bs), d) + + vertexCertificate.space (d, bs)) := by + rw [hn]; rfl + constructor + · rw [htime] + have h1 : 2 * (cr * Z ^ D) + Z ^ D + ci * 3 ^ di * Z ^ D ≤ + P * ((2 * cr + 1 + ci * 3 ^ di) * Z ^ D) := by + calc _ = (2 * cr + 1 + ci * 3 ^ di) * Z ^ D := by ring + _ ≤ _ := Nat.le_mul_of_pos_left _ hP1 + have h2 : P * ((2 * cr + 1 + ci * 3 ^ di) * Z ^ D) + K₀ * P * Z ^ D ≤ P * (G * Z ^ D) := by + rw [show K₀ * P * Z ^ D = P * (K₀ * Z ^ D) by ring, ← Nat.mul_add, ← Nat.add_mul] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul_right _ (by omega)) + calc _ ≤ P * (G * Z ^ D) := by omega + _ ≤ (G + D) * P * Z ^ (G + D) := by + rw [show (G + D) * P * Z ^ (G + D) = P * ((G + D) * Z ^ (G + D)) by ring] + exact Nat.mul_le_mul_left _ (Nat.mul_le_mul (by omega) hZD) + · rw [hspace] + calc _ ≤ G * Z ^ D := by + have : (2 * cr' + 1 + ci' * 3 ^ di' + K₀) * Z ^ D ≤ G * Z ^ D := + Nat.mul_le_mul_right _ (by omega) + have e : (2 * cr' + 1 + ci' * 3 ^ di' + K₀) * Z ^ D = + 2 * (cr' * Z ^ D) + Z ^ D + ci' * 3 ^ di' * Z ^ D + K₀ * Z ^ D := by ring + omega + _ ≤ (G + D) * Z ^ (G + D) := Nat.mul_le_mul (by omega) hZD + +end VertexDesc + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean new file mode 100644 index 0000000000..e3be365541 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkVertexDesc.lean @@ -0,0 +1,95 @@ +/- +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.ChildEntries +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ReductionLift + +/-! +# Tests for vertex descriptors and the entries of child networks + +A table descriptor reads its table little-endian and is a constant in `A_p`. A packet descriptor +over the tiny packet of the packet-evaluator tests (`2 ^ 1` slots over +`A_3 = (ℤ / 8)[U] / (U ^ 2 + U + 1)`, `t = 1`, `ζ = U`) with one row port in slot `0` and one +column port in slot `1` evaluates to the kernel value `5 + 2 U` at zero exponents, whatever the +column ports of its context, since they are read from the tags. The ports of one side of a tagged +list come out in order. Reducing an element of `O` to `A_n` and mapping back along +`reductionLift` gives its class modulo `2 ^ n`. +-/ + +@[expose] public section + +namespace ComplexityTests.TensorNetworkVertexDesc + +open Cslib Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativePacket + +/-! ### Tables -/ + +-- The bits `[true, false]` index the table at `1`. +example : VertexDesc.entry (.inl [3, 5, 7, 9]) [true, false] = [5] := by decide + +example : VertexDesc.entry (.inl [3, 5, 7, 9]) [false, true] = [7] := by decide + +-- A table has no modes and no row ports. +example : VertexDesc.modes (.inl [3, 5]) = 0 := rfl + +example : VertexDesc.rowPorts (.inl [3, 5]) [true] = 0 := rfl + +-- A table entry is a constant in `A_p`. +example : GaloisRingArith.val 3 [1, 1] (VertexDesc.entry (.inl [3, 5]) [true]) = + ((5 : ℕ) : GaloisRingArith.Ring 3 [1, 1]) := + VertexDesc.val_entry_inl 3 [1, 1] [3, 5] [true] + +/-! ### Packets -/ + +/-- The context of the tiny packet, with no column ports of its own. -/ +def ctx : PacketCtx := + ⟨3, [1, 1], 1, 2, 0, 2, 1, 2, true, false, true, [], [0, 1]⟩ + +/-- The descriptor: a row port in slot `0` and a column port in slot `1`, zero exponents. -/ +def desc : VertexDesc := .inr (ctx, [(true, 0), (false, 1)], [0, 0, 0, 0]) + +-- The column ports are read from the tags and the bits: the entry is `W_1(T) = 5 + 2 U`. +example : VertexDesc.entry desc [true, true] = [5, 2] := by decide + +example : VertexDesc.rowPorts desc [true, true] = 1 := by decide + +/-! ### Tagged ports -/ + +example : sidePorts [(true, 3), (false, 1), (true, 2)] [true, false, false] true = + [(3, true), (2, false)] := by decide + +example : sidePorts [(true, 3), (false, 1), (true, 2)] [true, false, false] false = + [(1, false)] := by decide + +-- The same, through `sidePorts_map` on a list tagged by parity. +example : sidePorts ([4, 1, 2].map fun c => (decide (c % 2 = 0), c)) ([4, 1, 2].map fun c => c < 3) + true = [(4, false), (2, true)] := by + rw [sidePorts_map] + decide + +/-! ### Resources -/ + +example : ∃ K : ℕ, ∀ (q : VertexDesc × List Bool) (Rv : ℕ), q.1.ReducedLE Rv → + VertexDesc.entryCertificate.time q ≤ K * (q.1.modes + 2) ^ q.1.rowPorts q.2 * + ((Turing.MultiTapeTM.encPair VertexDesc.enc Turing.MultiTapeTM.encBits q).length + + q.1.modes + Rv + 2) ^ K ∧ + VertexDesc.entryCertificate.space q ≤ + K * ((Turing.MultiTapeTM.encPair VertexDesc.enc Turing.MultiTapeTM.encBits q).length + + q.1.modes + Rv + 2) ^ K := + VertexDesc.entryCertificate_le + +/-! ### Back from `A_n` to `O / (2 ^ n)` -/ + +example {f : Polynomial ℤ} (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod 2)))) (n : ℕ) + (x : (AdjoinRoot.padicValuation 2 hf hirr).integer) : + reductionLift hf hirr n (AdjoinRoot.reduction hf hirr n x) = Ideal.Quotient.mk _ x := + reductionLift_reduction hf hirr n x + +end ComplexityTests.TensorNetworkVertexDesc From ef0982eab5f5b4afe84ca2cc4a5cc4522b68df7d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 01:59:27 -0400 Subject: [PATCH 28/33] refactor(circuit/frontier): share the port/descriptor record between explicit and described networks `DescribedNetwork T` moves to `TensorNetwork/Explicit.lean` with `equivProd`, `wordBits`, `contraction` and `map_contraction`, and `ExplicitTensorNetwork R` becomes the abbreviation `DescribedNetwork (List R)`: an explicit network is a described network whose descriptors are tables. `DescribedNetwork.ofExplicit` is removed (it is the identity), and `contraction_ofExplicit` becomes `DescribedNetwork.contraction_entry`, for any commutative semiring. `DirectContraction` uses the shared `DescribedNetwork.equivProd` in place of its own copy. Addresses reviewer-codex-edemaine's request on #1928. Written by worker-claude-e6cf7b0d-eval-fixer, taking over from worker-claude-e6cf7b0d-t2-evaluator. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/DescribedContraction.lean | 63 +---------- .../TensorNetwork/DirectContraction.lean | 14 +-- .../Frontier/TensorNetwork/Explicit.lean | 106 +++++++++++++++--- ComplexityTests/DescribedContraction.lean | 4 +- 4 files changed, 96 insertions(+), 91 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean index 9a511b6bca..cbf09baa78 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DescribedContraction.lean @@ -43,7 +43,7 @@ packet vertex has up to `2 L` ports, so its table would be superpolynomial, and computed from a description instead (Appendix D.4.6, p. 63). A *described network* (`DescribedNetwork T`) has, for each vertex, its ports and a descriptor `t : T` of its tensor, whose entry at the bits `bs` on the ports is `eval t bs` for an evaluator `eval`. Explicit -networks are the case of tables (`DescribedNetwork.ofExplicit`). +networks are the case of tables (`ExplicitTensorNetwork`, `DescribedNetwork.contraction_entry`). **Values and their operations** (`SumOps`). The entries are values `Y` combined by ring operations that depend on a precision `q : Q`: a zero, a one, a sum and a product, whose results @@ -73,8 +73,6 @@ contraction algorithm, with implicit packet tensors as descriptors. ## Main definitions -* `Cslib.Circuits.Frontier.DescribedNetwork`: a network with described tensors. -* `Cslib.Circuits.Frontier.DescribedNetwork.contraction`: its contraction. * `Cslib.Circuits.Frontier.SumOps`: ring operations on values, with `SumOps.IsHom` and `SumOps.PolyTime`; the instances `SumOps.mod` and `SumOps.galois`. * `Cslib.Circuits.Frontier.DescribedNetwork.sumScheme`: direct summation as a recursion scheme. @@ -91,7 +89,6 @@ contraction algorithm, with implicit packet tensors as descriptors. `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 contraction; `DescribedNetwork.sumValue_mod` and `DescribedNetwork.val_sumValue_galois`. -* `Cslib.Circuits.Frontier.DescribedNetwork.contraction_ofExplicit`: explicit networks. * `Cslib.Circuits.Frontier.DescribedNetwork.run_sumScheme`: the machine computes `sumValue`. * `Cslib.Circuits.Frontier.DescribedNetwork.contractionCertificate_le`: its time and space. @@ -217,61 +214,10 @@ theorem IsHom.map_foldl_add {q : Q} {R : Type*} [CommSemiring R] {φ : Y → R} end SumOps -/-- A Boolean tensor network with *described* tensors: the number of edges, numbered from zero, -and for each vertex its ordered list of ports and a descriptor of its tensor. -/ -structure DescribedNetwork (T : Type) where - /-- The number of edges; the edges are numbered `0, …, numEdges - 1`. -/ - numEdges : ℕ - /-- The vertices, each given by its ports and the descriptor of its tensor. -/ - vertices : List (List ℕ × T) - namespace DescribedNetwork variable {T : Type} -/-- The bits that a word, read as an assignment of bits to the edges, puts on a list of ports; -a port past the end of the word reads `false`. -/ -def wordBits (word : List Bool) (ports : List ℕ) : List Bool := - ports.map fun i => word.getD i false - -/-- The *contraction*: the sum over all assignments of bits to the edges of the product of the -entries of the vertices at the bits on their ports, for entries in a commutative semiring. -/ -def contraction {R : Type*} [CommSemiring R] (eval : T → List Bool → R) - (d : DescribedNetwork T) : R := - ∑ x : Fin d.numEdges → Bool, - (d.vertices.map fun v => eval v.2 (wordBits (List.ofFn x) v.1)).prod - -/-- An explicit network, with its tables as descriptors. -/ -def ofExplicit (d : ExplicitTensorNetwork ℕ) : DescribedNetwork (List ℕ) := - ⟨d.numEdges, d.vertices⟩ - -private theorem getD_ofFn {n : ℕ} (x : Fin n → Bool) (i : ℕ) : - (List.ofFn x).getD i false = if h : i < n then x ⟨i, h⟩ else false := by - by_cases h : i < n - · rw [dite_eq_left h, List.getD_eq_getElem _ _ (by simpa using h), List.getElem_ofFn] - · rw [dite_eq_right h, List.getD_eq_default _ _ (by simp only [List.length_ofFn]; omega)] - -/-- **Explicit networks**: with table lookup as the evaluator, the contraction is that of the -explicit network. -/ -theorem contraction_ofExplicit (d : ExplicitTensorNetwork ℕ) : - (ofExplicit d).contraction ExplicitTensorNetwork.entry = d.contraction := by - refine sum_congr rfl fun x _ => ?_ - simp only [wordBits, ExplicitTensorNetwork.portBits] - congr 1 - refine List.map_congr_left fun v _ => ?_ - congr 2 - funext i - exact getD_ofFn x i - -/-- The contraction commutes with maps of semirings. -/ -theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) - (eval : T → List Bool → R) (d : DescribedNetwork T) : - ψ (d.contraction eval) = d.contraction fun t bs => ψ (eval t bs) := by - rw [contraction, contraction, map_sum] - refine sum_congr rfl fun x _ => ?_ - rw [map_list_prod, List.map_map] - rfl - /-! ### Direct summation as a recursion scheme -/ /-- A node of direct summation: its kind (`0` the sum, `1` an assignment, `2` an entry), the @@ -538,13 +484,6 @@ section Native variable (encQ : Q ↪ List Bool) (encY : Y ↪ List Bool) (encT : T ↪ List Bool) -/-- A described network is its number of edges and its list of vertices. -/ -def equivProd : DescribedNetwork T ≃ ℕ × List (List ℕ × T) where - toFun d := (d.numEdges, d.vertices) - invFun p := ⟨p.1, p.2⟩ - left_inv _ := rfl - right_inv _ := rfl - /-- **The encoding of a described network**: its number of edges in unary, then its vertices, each the pair of its list of ports in binary and its descriptor. -/ def encDescribed : DescribedNetwork T ↪ List Bool := diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DirectContraction.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DirectContraction.lean index 954c85cb00..0ae9f6e8fa 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/DirectContraction.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/DirectContraction.lean @@ -136,17 +136,11 @@ def directContraction (x : ℕ × ℕ × ExplicitTensorNetwork ℕ) : ℕ := /-! ### Input encoding -/ -/-- An explicit network over `ℕ` is its number of edges and its list of vertices. -/ -def equivProd : ExplicitTensorNetwork ℕ ≃ ℕ × List (List ℕ × List ℕ) where - toFun d := (d.numEdges, d.vertices) - invFun p := ⟨p.1, p.2⟩ - left_inv _ := rfl - right_inv _ := rfl - /-- **The encoding of an explicit network**: its number of edges in unary, then its list of vertices, each the pair of its list of ports and its table, as lists of binary numbers. -/ def encExplicitNetwork : ExplicitTensorNetwork ℕ ↪ List Bool := - encOfEquiv equivProd (encPair encUnary (encList (encPair (encList encNat) (encList encNat)))) + encOfEquiv DescribedNetwork.equivProd + (encPair encUnary (encList (encPair (encList encNat) (encList encNat)))) /-- **The encoding of a contraction input `(P, r, d)`**: the precision `P` in unary, then the number `r` of row vertices in binary and the network (`encExplicitNetwork`). -/ @@ -216,7 +210,7 @@ private theorem vertices_polyTime : ComputableInPolyTime (fun d : ExplicitTensorNetwork ℕ => d.vertices) encExplicitNetwork encVertices := (ComputableInPolyTime.snd encUnary encVertices).comp - (ComputableInPolyTime.of_eq (f := equivProd) fun _ => rfl) + (ComputableInPolyTime.of_eq (f := DescribedNetwork.equivProd) fun _ => rfl) /-- `2 ^ P` in binary from `P` in unary. -/ private theorem twoPow_polyTime : @@ -331,7 +325,7 @@ theorem numEdges_polyTime : ComputableInPolyTime (fun x : ℕ × ℕ × ExplicitTensorNetwork ℕ => x.2.2.numEdges) encContractionInput encUnary := (ComputableInPolyTime.fst encUnary encVertices).comp - ((ComputableInPolyTime.of_eq (f := equivProd) fun _ => rfl).comp + ((ComputableInPolyTime.of_eq (f := DescribedNetwork.equivProd) fun _ => rfl).comp ((ComputableInPolyTime.snd encNat encExplicitNetwork).comp (ComputableInPolyTime.snd encUnary (encPair encNat encExplicitNetwork)))) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean index 42914f7f8f..313760ebfc 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Explicit.lean @@ -16,18 +16,24 @@ import Mathlib.Data.Set.Card /-! # Explicit Boolean tensor networks -An *explicit* Boolean tensor network (`ExplicitTensorNetwork R`) is the data an algorithm reads: -the number of edges, numbered `0, 1, …`, and a list of vertices, each given by - -* its ordered list of *ports*, the numbers of the edges at the vertex, and -* its *table*: the value of its tensor when port `i` carries the bit `bs[i]` is the entry at - position `BitString.lsbValue bs`, the number whose binary digit `i` is `bs[i]` - (`BitString.testBit_lsbValue`). +A Boolean tensor network as an algorithm reads it (`DescribedNetwork T`) is the number of edges, +numbered `0, 1, …`, and a list of vertices, each given by its ordered list of *ports*, the numbers +of the edges at the vertex, and a *descriptor* `t : T` of its tensor. For an evaluator +`eval : T → List Bool → R`, the entry of the vertex when port `i` carries the bit `bs[i]` is +`eval t bs`, and the contraction (`DescribedNetwork.contraction`) sums, over all assignments of +bits to the edges, the product of the entries at the bits on the ports. + +An *explicit* network (`ExplicitTensorNetwork R`) has tables as descriptors: the value of the +tensor of a vertex when port `i` carries the bit `bs[i]` is the entry of its table at position +`BitString.lsbValue bs`, the number whose binary digit `i` is `bs[i]` +(`BitString.testBit_lsbValue`). So a vertex with `k` ports has a table of `2 ^ k` entries. The contraction of an explicit network (`ExplicitTensorNetwork.contraction`) sums, over all assignments of bits to the edges, the product -of the table entries at the bits on the ports. It needs no well-formedness: a port naming no edge -reads `false`, and a position past the end of a table has entry zero. +of the table entries at the bits on the ports, which is the contraction of the described network +with table lookup as the evaluator (`DescribedNetwork.contraction_entry`). It needs no +well-formedness: a port naming no edge reads `false`, and a position past the end of a table has +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 @@ -45,7 +51,12 @@ one end of every edge below `r` and the other at or above it gives a rows-first ## Main definitions -* `Cslib.Circuits.Frontier.ExplicitTensorNetwork`: an explicit Boolean tensor network. +* `Cslib.Circuits.Frontier.DescribedNetwork`: a Boolean tensor network given by the ports and + the descriptors of its vertices. +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction`: its contraction, for an evaluator of + the descriptors. +* `Cslib.Circuits.Frontier.ExplicitTensorNetwork`: an explicit Boolean tensor network, with + tables as descriptors. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.entry`, `Cslib.Circuits.Frontier.ExplicitTensorNetwork.portBits`: table lookup and the bits on a list of ports. @@ -60,6 +71,10 @@ one end of every edge below `r` and the other at or above it gives a rows-first ## Main results +* `Cslib.Circuits.Frontier.DescribedNetwork.map_contraction`: the contraction commutes with maps + of semirings. +* `Cslib.Circuits.Frontier.DescribedNetwork.contraction_entry`: explicit networks are described + networks with table lookup as the evaluator. * `Cslib.Circuits.Frontier.ExplicitTensorNetwork.maxDegree_le_iff`: the degree bound. * `Cslib.Circuits.Frontier.TensorNetwork.entry_explicitTable`: a written-out table evaluates the tensor. @@ -81,15 +96,53 @@ one end of every edge below `r` and the other at or above it gives a rows-first namespace Cslib.Circuits.Frontier -/-- An *explicit Boolean tensor network* over `R`: the number of edges, numbered from zero, and a -list of vertices. A vertex is given by its ordered list of *ports*, the numbers of its edges, and -its *table*, whose entry at position `BitString.lsbValue bs` is the value of its tensor when -port `i` carries the bit `bs[i]`. -/ -structure ExplicitTensorNetwork (R : Type*) where +/-- A Boolean tensor network with *described* tensors: the number of edges, numbered from zero, +and a list of vertices. A vertex is given by its ordered list of *ports*, the numbers of its +edges, and a descriptor `t : T` of its tensor. -/ +structure DescribedNetwork (T : Type*) where /-- The number of edges; the edges are numbered `0, …, numEdges - 1`. -/ numEdges : ℕ - /-- The vertices, each given by its ports and its table. -/ - vertices : List (List ℕ × List R) + /-- The vertices, each given by its ports and the descriptor of its tensor. -/ + vertices : List (List ℕ × T) + +namespace DescribedNetwork + +variable {T : Type*} + +/-- A described network is its number of edges and its list of vertices. -/ +def equivProd : DescribedNetwork T ≃ ℕ × List (List ℕ × T) where + toFun d := (d.numEdges, d.vertices) + invFun p := ⟨p.1, p.2⟩ + left_inv _ := rfl + right_inv _ := rfl + +/-- The bits that a word, read as an assignment of bits to the edges, puts on a list of ports; +a port past the end of the word reads `false`. -/ +def wordBits (word : List Bool) (ports : List ℕ) : List Bool := + ports.map fun i => word.getD i false + +/-- The *contraction*: the sum over all assignments of bits to the edges of the product of the +entries of the vertices at the bits on their ports, for entries in a commutative semiring. -/ +def contraction {R : Type*} [CommSemiring R] (eval : T → List Bool → R) + (d : DescribedNetwork T) : R := + ∑ x : Fin d.numEdges → Bool, + (d.vertices.map fun v => eval v.2 (wordBits (List.ofFn x) v.1)).prod + +/-- The contraction commutes with maps of semirings. -/ +theorem map_contraction {R S : Type*} [CommSemiring R] [CommSemiring S] (ψ : R →+* S) + (eval : T → List Bool → R) (d : DescribedNetwork T) : + ψ (d.contraction eval) = d.contraction fun t bs => ψ (eval t bs) := by + rw [contraction, contraction, map_sum] + refine Finset.sum_congr rfl fun x _ => ?_ + rw [map_list_prod, List.map_map] + rfl + +end DescribedNetwork + +/-- An *explicit Boolean tensor network* over `R`: a network whose descriptors are *tables*. The +entry of the table of a vertex at position `BitString.lsbValue bs` is the value of its tensor +when port `i` carries the bit `bs[i]`. -/ +abbrev ExplicitTensorNetwork (R : Type*) := DescribedNetwork (List R) namespace ExplicitTensorNetwork @@ -151,6 +204,25 @@ instance (r : ℕ) : Decidable (d.IsRowsFirstBipartite r) := by end ExplicitTensorNetwork +private theorem getD_ofFn {n : ℕ} (x : Fin n → Bool) (i : ℕ) : + (List.ofFn x).getD i false = if h : i < n then x ⟨i, h⟩ else false := by + by_cases h : i < n + · rw [dite_eq_left h, List.getD_eq_getElem _ _ (by simpa using h), List.getElem_ofFn] + · rw [dite_eq_right h, List.getD_eq_default _ _ (by simp only [List.length_ofFn]; omega)] + +/-- **Explicit networks**: with table lookup as the evaluator, the contraction of a described +network is that of the explicit network. -/ +theorem DescribedNetwork.contraction_entry {R : Type*} [CommSemiring R] + (d : ExplicitTensorNetwork R) : + DescribedNetwork.contraction ExplicitTensorNetwork.entry d = d.contraction := by + refine Finset.sum_congr rfl fun x _ => ?_ + simp only [wordBits, ExplicitTensorNetwork.portBits] + congr 1 + refine List.map_congr_left fun v _ => ?_ + congr 2 + funext i + exact getD_ofFn x i + /-! ### Writing a tensor network out -/ namespace TensorNetwork diff --git a/ComplexityTests/DescribedContraction.lean b/ComplexityTests/DescribedContraction.lean index ac5647045b..060648f89d 100644 --- a/ComplexityTests/DescribedContraction.lean +++ b/ComplexityTests/DescribedContraction.lean @@ -67,8 +67,8 @@ example {T : Type} (eval : T → List Bool → List ℕ) (q : (ℕ × List ℕ) -- Explicit networks are described networks with their tables. example (d : ExplicitTensorNetwork ℕ) : - (ofExplicit d).contraction ExplicitTensorNetwork.entry = d.contraction := - contraction_ofExplicit d + DescribedNetwork.contraction ExplicitTensorNetwork.entry d = d.contraction := + contraction_entry d /-- Table lookup is polynomial-time. -/ theorem entry_polyTime : ComputableInPolyTime (fun q : List ℕ × List Bool => From d18d15fb22791661fd919cdcd4f6f0f41659225c Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 02:02:00 -0400 Subject: [PATCH 29/33] test(circuit/frontier): name IsRowsFirstBipartite explicitly on an anonymous explicit network MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An anonymous constructor ascribed ExplicitTensorNetwork ℕ elaborates at the shared record DescribedNetwork (List ℕ), so field notation no longer finds the explicit-network namespace. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Computability/Circuit/Frontier/TensorNetwork.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean index f030828558..9206e0547a 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetwork.lean @@ -103,8 +103,8 @@ example : ¬ parallel.IsRowsFirstBipartite 2 := by decide example : ¬ (cycle eqTable).IsRowsFirstBipartite 2 := by decide /-- A port naming no edge is excluded. -/ -example : ¬ (⟨1, [([0], [1, 1]), ([0, 5], [1, 1, 1, 1])]⟩ : - ExplicitTensorNetwork ℕ).IsRowsFirstBipartite 1 := by decide +example : ¬ ExplicitTensorNetwork.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 bipartite with degree `2`, both by the general lemmas and by evaluation. -/ From 88ddd692e8e09af6b8d8acbc1601b58f47f5ffb3 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 02:02:42 -0400 Subject: [PATCH 30/33] refactor(circuit/frontier): number packet modes with Mathlib's finProdFinEquiv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `NativePacket.modeIndex` (with `modeIndex_div`, `modeIndex_mod` and `modeIndex_apply`) by `finProdFinEquiv.toEmbedding.trans Fin.valEmbedding`, which numbers the mode `(y₀, b)` as `b + nb y₀`, the same number. `val_packetEntryList` and `map_val_packetEntryList` now read the exponents at `finProdFinEquiv Z`. Addresses reviewer-codex-edemaine's request on #1932. Written by worker-claude-e6cf7b0d-eval-fixer, taking over from worker-claude-e6cf7b0d-t2-evaluator. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/PacketEvaluator.lean | 62 +++++++------------ .../TensorNetworkPacketEvaluator.lean | 6 +- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean index 8bf1751e32..69da2d1d5f 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketEvaluator.lean @@ -37,7 +37,7 @@ ring that `A_p` maps to. * **Local forms, mode coefficients, mode values and row terms** follow `TensorNetwork.packetLocalForm`, `TensorNetwork.packetModeCoeff`, `TensorNetwork.packetModeValue` and `TensorNetwork.packetRowTerm`, with a mode `(y₀, b)` given - by its dense index `y₀ nb + b` (`NativePacket.modeIndex`). + by its dense index `b + nb y₀`, its number under `finProdFinEquiv`. * **Coefficient extraction** (`NativePacket.listCoeffList`) is the recursion `TensorNetwork.listCoeff` on coefficient lists, along a duplicate-free list of modes. @@ -491,28 +491,15 @@ theorem val_modeValueList (i : ℕ) (differ : Bool) (cols : List (ℕ × Bool)) · rw [val_pow, val_intConst, Int.cast_natCast] · exact val_zero -/-- The tile of a dense mode index `y₀ nb + b`. -/ -theorem modeIndex_div {nb y b : ℕ} (hb : b < nb) : (y * nb + b) / nb = y := by - rw [Nat.add_comm, Nat.add_mul_div_right _ _ (by omega), Nat.div_eq_of_lt hb, Nat.zero_add] - -/-- The exponent of a dense mode index `y₀ nb + b`. -/ -theorem modeIndex_mod {nb y b : ℕ} (hb : b < nb) : (y * nb + b) % nb = b := by - rw [Nat.add_comm, Nat.add_mul_mod_self_right, Nat.mod_eq_of_lt hb] - -/-- The dense index `y₀ nb + b` of the mode `(y₀, b)` of a packet with `nb` exponents per -tile. -/ -def modeIndex (i nb : ℕ) : Fin (2 ^ (i + 1)) × Fin nb ↪ ℕ := - ⟨fun Z => Z.1 * nb + Z.2, fun Z Z' h => by - have h1 := modeIndex_div (y := Z.1) Z.2.isLt - have h2 := modeIndex_mod (y := Z.1) Z.2.isLt - simp only at h - rw [h, modeIndex_div Z'.2.isLt] at h1 - rw [h, modeIndex_mod Z'.2.isLt] at h2 - exact Prod.ext (Fin.ext h1.symm) (Fin.ext h2.symm)⟩ - -@[simp] theorem modeIndex_apply (i nb : ℕ) (Z : Fin (2 ^ (i + 1)) × Fin nb) : - modeIndex i nb Z = (Z.1 : ℕ) * nb + Z.2 := - rfl +/-- The tile of the dense index `b + nb y₀` of a mode `(y₀, b)`. -/ +private theorem finProdFinEquiv_div {m n : ℕ} (Z : Fin m × Fin n) : + (finProdFinEquiv Z : ℕ) / n = Z.1 := + congrArg (fun W => (W.1 : ℕ)) (finProdFinEquiv.symm_apply_apply Z) + +/-- The exponent of the dense index `b + nb y₀` of a mode `(y₀, b)`. -/ +private theorem finProdFinEquiv_mod {m n : ℕ} (Z : Fin m × Fin n) : + (finProdFinEquiv Z : ℕ) % n = Z.2 := + congrArg (fun W => (W.2 : ℕ)) (finProdFinEquiv.symm_apply_apply Z) /-- The terms of the affine factor of a row port `r = (x, Y_x)`: its local term at `none`, and the coefficient of the mode `(k / nb, k % nb)` at `some k` (zero when there are no exponents, @@ -532,7 +519,7 @@ theorem val_rowTermList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} {cols : List (ℕ × Bool)} (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) {r : ℕ × Bool} (hr : r.1 < 2 ^ a) (ζ : List ℕ) : (fun o => val p fl (rowTermList p fl a L i t nb differ cols ζ r o)) ∘ - Option.map (modeIndex i nb) = + Option.map (finProdFinEquiv.toEmbedding.trans Fin.valEmbedding) = packetRowTerm (p := nb) (scalarHom p fl) (val p fl ζ) a i t differ cols r := by funext o cases o with @@ -543,7 +530,8 @@ theorem val_rowTermList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : ℤ} · rw [val_sub, val_one, val_localFormList hL ht hcols hr ζ] | some Z => simp only [Function.comp_apply, Option.map_some, rowTermList, packetRowTerm, affineSign, - modeIndex_apply, modeIndex_div Z.2.isLt, modeIndex_mod Z.2.isLt, + Function.Embedding.trans_apply, Equiv.coe_toEmbedding, Fin.valEmbedding_apply, + finProdFinEquiv_div, finProdFinEquiv_mod, Nat.pos_iff_ne_zero.mp (Nat.zero_lt_of_lt Z.2.isLt), ↓reduceIte] have h := val_modeCoeffList (p := p) (fl := fl) hL hia ht hti hti' differ hr Z ζ split_ifs @@ -588,24 +576,22 @@ theorem val_listCoeffList {M : Type} [DecidableEq M] {modes : List M} (hm : mode modes of the packet. -/ private theorem toFinset_filter_range (i nb : ℕ) (P : ℕ → Bool) : ((List.range (2 ^ (i + 1) * nb)).filter fun k => P (k / nb)).toFinset = - ({Z | P Z.1 = true} : Finset (Fin (2 ^ (i + 1)) × Fin nb)).map (modeIndex i nb) := by + ({Z | P Z.1 = true} : Finset (Fin (2 ^ (i + 1)) × Fin nb)).map + (finProdFinEquiv.toEmbedding.trans Fin.valEmbedding) := by ext k simp only [List.mem_toFinset, List.mem_filter, List.mem_range, mem_map, mem_filter, mem_univ, - true_and, modeIndex_apply] + true_and, Function.Embedding.trans_apply, Equiv.coe_toEmbedding, Fin.valEmbedding_apply] constructor · rintro ⟨hk, h⟩ - have hnb : 0 < nb := Nat.pos_of_ne_zero fun h0 => by simp [h0] at hk - exact ⟨(⟨k / nb, (Nat.div_lt_iff_lt_mul hnb).mpr hk⟩, ⟨k % nb, Nat.mod_lt _ hnb⟩), h, - Nat.div_add_mod' k nb⟩ + exact ⟨finProdFinEquiv.symm ⟨k, hk⟩, h, congrArg Fin.val (finProdFinEquiv.apply_symm_apply _)⟩ · rintro ⟨Z, h, rfl⟩ - refine ⟨?_, by rw [modeIndex_div Z.2.isLt]; exact h⟩ - calc (Z.1 : ℕ) * nb + Z.2 < (Z.1 + 1) * nb := by rw [Nat.add_mul, Nat.one_mul]; omega - _ ≤ 2 ^ (i + 1) * nb := Nat.mul_le_mul_right _ Z.1.isLt + exact ⟨(finProdFinEquiv Z).isLt, by rw [finProdFinEquiv_div]; exact h⟩ /-- **The entry of a packet vertex on coefficient lists** (§D.4.6, p. 63): coefficient extraction along the row terms, at the exponents `β` of the modes targeting the group, times the powers of the values of the modes with the group as source. A mode `(y₀, b)` is its dense index -`y₀ nb + b`, so `β` is read at dense indices, as from a dense list of exponents. -/ +`b + nb y₀` (`finProdFinEquiv`), so `β` is read at dense indices, as from a dense list of +exponents. -/ def packetEntryList (p : ℕ) (fl : List ℕ) (a L i : ℕ) (t : ℤ) (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) (rows cols : List (ℕ × Bool)) (ζ : List ℕ) : List ℕ := mul p fl @@ -626,7 +612,7 @@ theorem val_packetEntryList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : val p fl (packetEntryList p fl a L i t nb e₁ e₂ differ β rows cols ζ) = packetEntry (p := nb) (scalarHom p fl) (val p fl ζ) a i t e₁ e₂ differ - (fun Z => β (Z.1 * nb + Z.2)) rows cols := by + (fun Z => β (finProdFinEquiv Z)) rows cols := by rw [packetEntryList, packetEntry, val_mul] congr 1 · rw [val_listCoeffList (List.nodup_range.filter _), toFinset_filter_range, @@ -636,8 +622,8 @@ theorem val_packetEntryList {a L i : ℕ} (hL : L = 2 ^ a) (hia : i < a) {t : · rw [val_prodList, List.map_map, ← List.prod_toFinset _ (List.nodup_range.filter _), toFinset_filter_range, prod_map] refine prod_congr rfl fun Z _ => ?_ - simp only [Function.comp_apply, modeIndex_apply, modeIndex_div Z.2.isLt, - modeIndex_mod Z.2.isLt] + simp only [Function.comp_apply, Function.Embedding.trans_apply, Equiv.coe_toEmbedding, + Fin.valEmbedding_apply, finProdFinEquiv_div, finProdFinEquiv_mod] rw [val_pow, val_modeValueList] /-- **Packet entries in every ring receiving `A_p`.** Along a ring map `ψ : A_p → S` with `2` @@ -649,7 +635,7 @@ theorem map_val_packetEntryList {S : Type*} [CommRing S] (ψ : Ring p fl →+* S (nb : ℕ) (e₁ e₂ differ : Bool) (β : ℕ → ℕ) {rows cols : List (ℕ × Bool)} (hrows : ∀ r ∈ rows, r.1 < 2 ^ a) (hcols : ∀ c ∈ cols, c.1 < 2 ^ a) (ζ : List ℕ) : ψ (val p fl (packetEntryList p fl a L i t nb e₁ e₂ differ β rows cols ζ)) = - packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (Z.1 * nb + Z.2)) + packetEntry (p := nb) κ (ψ (val p fl ζ)) a i t e₁ e₂ differ (fun Z => β (finProdFinEquiv Z)) rows cols := by rw [val_packetEntryList hL hia ht hti hti' nb e₁ e₂ differ β hrows hcols ζ, map_packetEntry ψ two_pow_eq_zero _ _ hia.le hti hti', diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean index 5115a5d231..6294be96ea 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketEvaluator.lean @@ -58,7 +58,7 @@ example : packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun _ => 0) [(0, f (kernelValueList 3 [1, 1] 1 2 1 1 [0, 1]) := by decide --- Exponent `1` at the mode `(0, 1)`, of dense index `0 * 2 + 1 = 1`, which targets the group +-- Exponent `1` at the mode `(0, 1)`, of dense index `1 + 2 * 0 = 1`, which targets the group -- (digit `0` of `y₀ = 0` selects the first symbol): the single row factor contributes the -- coefficient of that mode, and no source mode has a positive exponent. example : packetEntryList 3 [1, 1] 1 2 0 1 2 true false true (fun k => if k = 1 then 1 else 0) @@ -70,7 +70,7 @@ theorem val_packetEntryList_tiny (β : ℕ → ℕ) (ζ : List ℕ) : GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 2 0 1 2 true false true β [(0, true), (1, false)] [(1, true), (0, true)] ζ) = packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] ζ) 1 0 1 true false - true (fun Z => β (Z.1 * 2 + Z.2)) [(0, true), (1, false)] [(1, true), (0, true)] := + true (fun Z => β (finProdFinEquiv Z)) [(0, true), (1, false)] [(1, true), (0, true)] := val_packetEntryList (by norm_num) (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true false true β (by simp) (by simp) ζ @@ -80,7 +80,7 @@ example {S : Type} [CommRing S] (ψ : GaloisRingArith.Ring 3 [1, 1] →+* S) {n ψ (GaloisRingArith.val 3 [1, 1] (packetEntryList 3 [1, 1] 1 2 0 1 2 true false true β [(0, true)] [(1, true)] ζ)) = packetEntry (p := 2) κ (ψ (GaloisRingArith.val 3 [1, 1] ζ)) 1 0 1 true false true - (fun Z => β (Z.1 * 2 + Z.2)) [(0, true)] [(1, true)] := + (fun Z => β (finProdFinEquiv Z)) [(0, true)] [(1, true)] := map_val_packetEntryList ψ hn κ (by norm_num) (by norm_num) (by simp [mem_cauchyGrid]) (by norm_num) (by norm_num) 2 true false true β (by simp) (by simp) ζ From 30bb409365be3d96e260ab173b5a85e3e5cfd1df Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 02:06:28 -0400 Subject: [PATCH 31/33] refactor(circuit/frontier): read packet exponents at finProdFinEquiv in the native evaluator Follows #1932's switch from `modeIndex` to Mathlib's `finProdFinEquiv`: the statements of `val_packetEntryNative_eq`, `map_val_packetEntryNative` and `val_packetVertexEntry` read the dense exponents at `finProdFinEquiv Z` (the same number as `Z.1 * nb + Z.2`). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Frontier/TensorNetwork/PacketTerms.lean | 6 +++--- .../Circuit/Frontier/TensorNetworkPacketTerms.lean | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean index 0905c77e84..f614aa598e 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/PacketTerms.lean @@ -913,7 +913,7 @@ theorem val_packetEntryNative_eq (c : PacketCtx) (hL : c.L = 2 ^ c.a) (hia : c.i (hcols : ∀ r ∈ c.cols, r.1 < 2 ^ c.a) (β : List ℕ) : val c.p c.fl (packetEntryNative c rows β) = packetEntry (p := c.nb) (scalarHom c.p c.fl) (val c.p c.fl c.ζ) c.a c.i c.t c.e₁ c.e₂ - c.differ (fun Z => β.getD (Z.1 * c.nb + Z.2) 0) rows c.cols := by + c.differ (fun Z => β.getD (finProdFinEquiv Z) 0) rows c.cols := by rw [val_packetEntryNative c htiles, val_packetEntryList hL hia ht hti hti' c.nb c.e₁ c.e₂ c.differ _ hrows hcols] @@ -929,7 +929,7 @@ theorem map_val_packetEntryNative {S : Type*} [CommRing S] (c : PacketCtx) (hrows : ∀ r ∈ rows, r.1 < 2 ^ c.a) (hcols : ∀ r ∈ c.cols, r.1 < 2 ^ c.a) (β : List ℕ) : ψ (val c.p c.fl (packetEntryNative c rows β)) = packetEntry (p := c.nb) κ (ψ (val c.p c.fl c.ζ)) c.a c.i c.t c.e₁ c.e₂ c.differ - (fun Z => β.getD (Z.1 * c.nb + Z.2) 0) rows c.cols := by + (fun Z => β.getD (finProdFinEquiv Z) 0) rows c.cols := by rw [val_packetEntryNative c htiles, map_val_packetEntryList ψ hn κ hL hia ht hti hti' c.nb c.e₁ c.e₂ c.differ _ hrows hcols] @@ -1374,7 +1374,7 @@ theorem val_packetVertexEntry (d : PacketDesc) (bs : List Bool) (hL : d.1.L = 2 (hti' : ¬ (2 : ℤ) ^ (d.1.i + 1) ∣ d.1.t) (hports : ∀ x ∈ d.2.1, x.2 < 2 ^ d.1.a) : val d.1.p d.1.fl (packetVertexEntry d bs) = packetEntry (p := d.1.nb) (scalarHom d.1.p d.1.fl) (val d.1.p d.1.fl d.1.ζ) d.1.a d.1.i - d.1.t d.1.e₁ d.1.e₂ d.1.differ (fun Z => d.2.2.getD (Z.1 * d.1.nb + Z.2) 0) + d.1.t d.1.e₁ d.1.e₂ d.1.differ (fun Z => d.2.2.getD (finProdFinEquiv Z) 0) (sidePorts d.2.1 bs true) (sidePorts d.2.1 bs false) := by have hside (side : Bool) : ∀ r ∈ sidePorts d.2.1 bs side, r.1 < 2 ^ d.1.a := by intro r hr diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean index 4d43bf56eb..e8bd157553 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkPacketTerms.lean @@ -48,7 +48,7 @@ example : packetEntryNative ctx [(0, true)] [0, 1, 0, 0] = theorem val_ctx (β : List ℕ) : GaloisRingArith.val 3 [1, 1] (packetEntryNative ctx [(0, true)] β) = packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] [0, 1]) 1 0 1 true - false true (fun Z => β.getD (Z.1 * 2 + Z.2) 0) [(0, true)] [(1, true)] := + false true (fun Z => β.getD (finProdFinEquiv Z) 0) [(0, true)] [(1, true)] := val_packetEntryNative_eq ctx (by decide) (by decide) (by decide) (by simp [ctx, mem_cauchyGrid]) (by norm_num [ctx]) (by norm_num [ctx]) (by simp) (by simp [ctx]) β @@ -73,7 +73,7 @@ example : packetVertexEntry desc [true, false] = [0, 0] := by decide theorem val_desc (bs : List Bool) : GaloisRingArith.val 3 [1, 1] (packetVertexEntry desc bs) = packetEntry (p := 2) (scalarHom 3 [1, 1]) (GaloisRingArith.val 3 [1, 1] [0, 1]) 1 0 1 true - false true (fun Z => [0, 0, 0, 0].getD (Z.1 * 2 + Z.2) 0) (sidePorts desc.2.1 bs true) + false true (fun Z => [0, 0, 0, 0].getD (finProdFinEquiv Z) 0) (sidePorts desc.2.1 bs true) (sidePorts desc.2.1 bs false) := val_packetVertexEntry desc bs (by decide) (by decide) (by decide) (by simp [desc, ctx, mem_cauchyGrid]) (by norm_num [desc, ctx]) (by norm_num [desc, ctx]) From a74e6d026c3ea4e29a79b9c39a58694561f72f93 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 02:08:20 -0400 Subject: [PATCH 32/33] refactor(circuit/frontier): read packet exponents at finProdFinEquiv for vertex descriptors Follows #1932's switch from `modeIndex` to Mathlib's `finProdFinEquiv`: `map_val_entry_inr` and the dense-exponent hypothesis of `map_val_entry_child_inr` read the exponents at `finProdFinEquiv Z` (the same number as `Z.1 * nb + Z.2`). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Frontier/TensorNetwork/ChildEntries.lean | 2 +- .../Circuit/Frontier/TensorNetwork/VertexDesc.lean | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean index 2eb8415168..bb3b4b2d16 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildEntries.lean @@ -241,7 +241,7 @@ theorem map_val_entry_child_inr [DecidableEq V] (β : P × Fin (2 ^ (i + 1)) × (ha : c.a = a) (hL : c.L = 2 ^ a) (hi : c.i = i) (htiles : c.tiles = 2 ^ (i + 1)) (ht' : c.t = t) (hnb : c.nb = p) (he₁ : c.e₁ = decide ((sym q).1 = g)) (he₂ : c.e₂ = decide ((sym q).2 = g)) (hdiffer : c.differ = decide ((sym q).1 ≠ (sym q).2)) - (hβ : ∀ Z : Fin (2 ^ (i + 1)) × Fin p, βd.getD (Z.1 * p + Z.2) 0 = β (q, Z)) + (hβ : ∀ Z : Fin (2 ^ (i + 1)) × Fin p, βd.getD (finProdFinEquiv Z) 0 = β (q, Z)) (y : {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group (N.src e) = g} ⊕ {e // (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean index cfb4546d15..3fff52005a 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/VertexDesc.lean @@ -131,7 +131,7 @@ theorem map_val_entry_inr {S : Type*} [CommRing S] (d : PacketDesc) (bs : List B (hti' : ¬ (2 : ℤ) ^ (d.1.i + 1) ∣ d.1.t) (hports : ∀ x ∈ d.2.1, x.2 < 2 ^ d.1.a) : ψ (val d.1.p d.1.fl (entry (.inr d) bs)) = packetEntry (p := d.1.nb) κ (ψ (val d.1.p d.1.fl d.1.ζ)) d.1.a d.1.i d.1.t d.1.e₁ d.1.e₂ - d.1.differ (fun Z => d.2.2.getD (Z.1 * d.1.nb + Z.2) 0) (sidePorts d.2.1 bs true) + d.1.differ (fun Z => d.2.2.getD (finProdFinEquiv Z) 0) (sidePorts d.2.1 bs true) (sidePorts d.2.1 bs false) := by have hside (side : Bool) : ∀ r ∈ sidePorts d.2.1 bs side, r.1 < 2 ^ d.1.a := by intro r hr From babef77300239497e034102a8df0a59a9fdeb2f2 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 02:15:35 -0400 Subject: [PATCH 33/33] chore: regenerate Complexity.lean after merging the base Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 88c5763cd3..49730c8bf0 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -903,8 +903,8 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeReco public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEntry public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketEvaluator public import Complexity.Computability.Circuit.Frontier.TensorNetwork.PacketTerms -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ReductionLift public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ReductionLift public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ScheduledRecursion