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/38] 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 3942e2e28..3e53d5172 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 000000000..cf45df7f8 --- /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 000000000..08c80a664 --- /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 000000000..28f5dd3f5 --- /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 000000000..5be31a11e --- /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/38] 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 3e53d5172..8ea2c2b14 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 000000000..c5aa9c025 --- /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 000000000..1f1322b39 --- /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/38] 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 28f5dd3f5..051a95b90 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 d13e8137f..40319f05b 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 5be31a11e..27d867afd 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 fa3aa4aa09b498b2e78b1d7b77c23420be455a66 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 18:26:26 -0400 Subject: [PATCH 04/38] feat(foundations): Galois rings, ring Lagrange interpolation and the p-adic valuation of Q[X]/(f) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T2.2 of #1538 (Theorem D.1 of OpenAI2026ETH, §D.4.1, pp. 57-58). - GaloisRing p n f = (Z/p^n)[X]/(f): local with maximal ideal (p), residue field F_p[X]/(f) = GaloisField p (deg f), units = nonzero mod p; distinct digit lifts differ by units, so ring Lagrange interpolation holds there. - Lagrange.ringInterpolate: Lagrange interpolation over a commutative ring at nodes with unit differences (Mathlib's Lagrange.interpolate needs a field). - AdjoinRoot.padicValuation: for f monic and irreducible mod p, the largest p-adic valuation of a coordinate is a valuation of Q[X]/(f); its valuation ring is Z_(p)[X]/(f), whose elements of valuation one are units. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 + .../Data/Polynomial/RingLagrange.lean | 154 ++++++++ .../Padics/AdjoinRootValuation.lean | 303 +++++++++++++++ .../Foundations/RingTheory/GaloisRing.lean | 351 ++++++++++++++++++ .../Data/Polynomial/RingLagrange.lean | 54 +++ .../Padics/AdjoinRootValuation.lean | 98 +++++ .../Foundations/RingTheory/GaloisRing.lean | 111 ++++++ 7 files changed, 1074 insertions(+) create mode 100644 Complexity/Foundations/Data/Polynomial/RingLagrange.lean create mode 100644 Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean create mode 100644 Complexity/Foundations/RingTheory/GaloisRing.lean create mode 100644 ComplexityTests/Foundations/Data/Polynomial/RingLagrange.lean create mode 100644 ComplexityTests/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean create mode 100644 ComplexityTests/Foundations/RingTheory/GaloisRing.lean diff --git a/Complexity.lean b/Complexity.lean index 1cd97a465..7ae579ac4 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2679,6 +2679,7 @@ public import Complexity.Foundations.Data.Polynomial.CoeffNorm1 public import Complexity.Foundations.Data.Polynomial.HermiteInterpolation public import Complexity.Foundations.Data.Polynomial.ParityAmplifier public import Complexity.Foundations.Data.Polynomial.ParityAmplifierDegree +public import Complexity.Foundations.Data.Polynomial.RingLagrange public import Complexity.Foundations.Data.Polynomial.SignQueries public import Complexity.Foundations.Data.Polynomial.StagedJetArithmetic public import Complexity.Foundations.Data.Rat.Basic @@ -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.AdjoinRootValuation public import Complexity.Foundations.NumberTheory.PrimeCounting public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.NumberTheory.SqrtPrimeWindow @@ -2777,6 +2779,7 @@ public import Complexity.Foundations.Relation.InductiveCounting public import Complexity.Foundations.Relation.RelatesWithinSteps public import Complexity.Foundations.RingTheory.CyclicIntRing public import Complexity.Foundations.RingTheory.CyclicIntRing.Coefficients +public import Complexity.Foundations.RingTheory.GaloisRing public import Complexity.Foundations.Semantics.Stateful.Amortized public import Complexity.Foundations.Semantics.Stateful.Basic public import Complexity.Foundations.Semantics.Stateful.Composition diff --git a/Complexity/Foundations/Data/Polynomial/RingLagrange.lean b/Complexity/Foundations/Data/Polynomial/RingLagrange.lean new file mode 100644 index 000000000..2ee13b010 --- /dev/null +++ b/Complexity/Foundations/Data/Polynomial/RingLagrange.lean @@ -0,0 +1,154 @@ +/- +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/LinearAlgebra/Lagrange.lean`) + +public import Complexity.Init +public import Mathlib.LinearAlgebra.Lagrange + +/-! +# Lagrange interpolation over a commutative ring + +Mathlib's `Lagrange.interpolate` works over a field. Over a commutative ring `R`, interpolation at +nodes `v i`, `i ∈ s`, still works when the nodes have *unit differences*: every `v i - v j` with +`i ≠ j` is a unit. Galois rings such as `(ℤ / 2 ^ p)[U] / (f)` are the motivating example; they +are not domains, but lifts of distinct residue-field elements differ by units. + +The Lagrange basis polynomial at `i` is `∏_{j ∈ s, j ≠ i} (X - v j) / (v i - v j)`, where the +division is multiplication by `Ring.inverse (v i - v j)` (`Lagrange.ringBasis`). It is one at +`v i` and zero at the other nodes, so `Lagrange.ringInterpolate s v r = ∑ i ∈ s, r i • basis i` +takes the value `r i` at `v i`. A polynomial of degree less than `#s` that vanishes at all nodes is +zero, because the factors `X - v i` are pairwise coprime; hence every polynomial of degree less +than `#s` is the interpolant of its values (`Lagrange.eq_ringInterpolate`). Over a field these +are Mathlib's `Lagrange.basis` and `Lagrange.interpolate` +(`Lagrange.ringInterpolate_eq_interpolate`). + +## Main definitions + +* `Lagrange.ringBasis`: the Lagrange basis polynomial, with `Ring.inverse` for division. +* `Lagrange.ringInterpolate`: Lagrange interpolation over a commutative ring, as a linear map. + +## Main results + +* `Lagrange.eval_ringInterpolate_at_node`: the interpolant takes the prescribed values. +* `Lagrange.degree_ringInterpolate_lt`: it has degree less than the number of nodes. +* `Lagrange.eq_zero_of_degree_lt_of_eval_eq_zero`: uniqueness at nodes with unit differences. +* `Lagrange.eq_ringInterpolate`: a polynomial of degree less than `#s` is the interpolant of its + values. +* `Lagrange.ringInterpolate_eq_interpolate`: over a field, this is `Lagrange.interpolate`. +-/ + +public section + +namespace Lagrange + +open Polynomial Finset + +variable {R : Type*} [CommRing R] {ι : Type*} [DecidableEq ι] {s : Finset ι} {v : ι → R} + {i j : ι} + +/-- The Lagrange basis polynomial `∏_{j ∈ s, j ≠ i} (X - v j) / (v i - v j)` over a commutative +ring, where division by `v i - v j` is multiplication by `Ring.inverse (v i - v j)`. When the nodes +have unit differences it is one at `v i` and zero at the other nodes. -/ +@[expose] noncomputable def ringBasis (s : Finset ι) (v : ι → R) (i : ι) : R[X] := + ∏ j ∈ s.erase i, C (Ring.inverse (v i - v j)) * (X - C (v j)) + +/-- **Lagrange interpolation over a commutative ring**: `∑ i ∈ s, r i • ringBasis s v i`. At nodes +with unit differences, it is the unique polynomial of degree less than `#s` taking the value `r i` +at `v i`. -/ +@[expose] noncomputable def ringInterpolate (s : Finset ι) (v : ι → R) : (ι → R) →ₗ[R] R[X] where + toFun r := ∑ i ∈ s, C (r i) * ringBasis s v i + map_add' r r' := by + simp only [Pi.add_apply, C_add, add_mul, sum_add_distrib] + map_smul' c r := by + simp only [Pi.smul_apply, smul_eq_mul, C_mul, RingHom.id_apply, smul_sum, mul_assoc, + smul_eq_C_mul] + +theorem ringInterpolate_apply (s : Finset ι) (v : ι → R) (r : ι → R) : + ringInterpolate s v r = ∑ i ∈ s, C (r i) * ringBasis s v i := + rfl + +/-- The basis polynomial at `i` is one at its own node. -/ +theorem eval_ringBasis_self (hvs : (s : Set ι).Pairwise fun i j => IsUnit (v i - v j)) + (hi : i ∈ s) : (ringBasis s v i).eval (v i) = 1 := by + rw [ringBasis, eval_prod] + refine prod_eq_one fun j hj => ?_ + obtain ⟨hji, hj⟩ := mem_erase.mp hj + rw [eval_mul, eval_C, eval_sub, eval_X, eval_C] + exact Ring.inverse_mul_cancel _ (hvs hi hj (Ne.symm hji)) + +/-- The basis polynomial at `i` vanishes at every other node. -/ +theorem eval_ringBasis_of_ne (hij : i ≠ j) (hj : j ∈ s) : (ringBasis s v i).eval (v j) = 0 := by + rw [ringBasis, eval_prod] + refine prod_eq_zero (mem_erase.mpr ⟨Ne.symm hij, hj⟩) ?_ + simp only [eval_mul, eval_sub, eval_X, eval_C, sub_self, mul_zero] + +theorem natDegree_ringBasis_le (s : Finset ι) (v : ι → R) (i : ι) : + (ringBasis s v i).natDegree ≤ #(s.erase i) := by + refine (natDegree_prod_le _ _).trans + ((sum_le_sum fun j _ => ?_).trans_eq (card_eq_sum_ones _).symm) + exact (natDegree_C_mul_le _ _).trans (natDegree_X_sub_C_le _) + +/-- The basis polynomial at a node has degree less than the number of nodes. -/ +theorem degree_ringBasis_lt (hi : i ∈ s) : (ringBasis s v i).degree < #s := by + refine (degree_le_natDegree).trans_lt ?_ + have h := natDegree_ringBasis_le s v i + rw [card_erase_of_mem hi] at h + have hs : 0 < #s := card_pos.mpr ⟨i, hi⟩ + exact_mod_cast (show (ringBasis s v i).natDegree < #s by omega) + +/-- The interpolant has degree less than the number of nodes. -/ +theorem degree_ringInterpolate_lt (r : ι → R) : (ringInterpolate s v r).degree < #s := by + rw [← mem_degreeLT, ringInterpolate_apply] + refine Submodule.sum_mem _ fun i hi => ?_ + rw [← smul_eq_C_mul] + exact Submodule.smul_mem _ _ (mem_degreeLT.mpr (degree_ringBasis_lt hi)) + +/-- **The interpolant takes the prescribed values** at nodes with unit differences. -/ +theorem eval_ringInterpolate_at_node (hvs : (s : Set ι).Pairwise fun i j => IsUnit (v i - v j)) + (r : ι → R) (hi : i ∈ s) : (ringInterpolate s v r).eval (v i) = r i := by + rw [ringInterpolate_apply, eval_finsetSum, sum_eq_single_of_mem i hi, eval_mul, eval_C, + eval_ringBasis_self hvs hi, mul_one] + intro j _ hji + rw [eval_mul, eval_ringBasis_of_ne hji hi, mul_zero] + +omit [DecidableEq ι] in +/-- **Uniqueness of interpolation over a commutative ring.** A polynomial of degree less than +`#s` that vanishes at nodes with unit differences is zero: the factors `X - v i` are pairwise +coprime, so their product, of degree `#s`, divides it. -/ +theorem eq_zero_of_degree_lt_of_eval_eq_zero + (hvs : (s : Set ι).Pairwise fun i j => IsUnit (v i - v j)) {f : R[X]} (hf : f.degree < #s) + (h0 : ∀ i ∈ s, f.eval (v i) = 0) : f = 0 := by + nontriviality R + by_contra hne + have hdvd : nodal s v ∣ f := by + rw [nodal] + refine prod_dvd_of_coprime (fun i hi j hj hij => ?_) fun i hi => ?_ + · exact isCoprime_X_sub_C_of_isUnit_sub (hvs hi hj hij) + · exact dvd_iff_isRoot.mpr (h0 i hi) + exact nodal_monic.not_dvd_of_degree_lt hne (by rwa [degree_nodal]) hdvd + +/-- **Every polynomial of degree less than `#s` is the interpolant of its values** at nodes with +unit differences. -/ +theorem eq_ringInterpolate (hvs : (s : Set ι).Pairwise fun i j => IsUnit (v i - v j)) + {f : R[X]} (hf : f.degree < #s) : f = ringInterpolate s v (fun i => f.eval (v i)) := by + refine sub_eq_zero.mp (eq_zero_of_degree_lt_of_eval_eq_zero hvs + ((degree_sub_le _ _).trans_lt (max_lt hf (degree_ringInterpolate_lt _))) fun i hi => ?_) + rw [eval_sub, eval_ringInterpolate_at_node hvs _ hi, sub_self] + +/-- Over a field, `ringBasis` is Mathlib's `Lagrange.basis`. -/ +theorem ringBasis_eq_basis {F : Type*} [Field F] (s : Finset ι) (v : ι → F) (i : ι) : + ringBasis s v i = Lagrange.basis s v i := by + simp only [ringBasis, Lagrange.basis, basisDivisor, Ring.inverse_eq_inv'] + +/-- Over a field, `ringInterpolate` is Mathlib's `Lagrange.interpolate`. -/ +theorem ringInterpolate_eq_interpolate {F : Type*} [Field F] (s : Finset ι) (v : ι → F) : + ringInterpolate s v = interpolate s v := by + ext r : 1 + simp only [ringInterpolate_apply, interpolate_apply, ringBasis_eq_basis] + +end Lagrange diff --git a/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean new file mode 100644 index 000000000..707dad117 --- /dev/null +++ b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean @@ -0,0 +1,303 @@ +/- +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/`) + +public import Complexity.Init +public import Mathlib.NumberTheory.Padics.PadicNumbers +public import Mathlib.RingTheory.AdjoinRoot +public import Mathlib.RingTheory.Valuation.Integers +import Complexity.Foundations.RingTheory.GaloisRing +import Mathlib.Algebra.GCDMonoid.IntegrallyClosed +import Mathlib.Algebra.Polynomial.Eval.Irreducible +import Mathlib.RingTheory.Localization.Integral +import Mathlib.RingTheory.Polynomial.Content +import Mathlib.RingTheory.Polynomial.GaussLemma + +/-! +# The `p`-adic valuation of `ℚ[X] / (f)` for `f` irreducible modulo `p` + +Let `p` be a prime and `f` a monic integer polynomial that is irreducible modulo `p`. Then `f` is +irreducible over `ℚ` (`AdjoinRoot.irreducible_map_rat`), so `K = ℚ[X] / (f)` is a field. Give each +element of `K` its coordinates in the basis `1, X, …, X ^ (deg f - 1)`, and let `v x` be the +largest `p`-adic valuation `Rat.padicValuation p` of a coordinate; written additively, `v x` is the +least `p`-adic order of a coordinate. This file proves that `v` is a valuation of `K` +(`AdjoinRoot.padicValuation`): it is multiplicative and ultrametric. + +Multiplicativity is the substantial point. Every element of `K` is a rational multiple +`r • x₀` of the image `x₀` of an element of `ℤ[X] / (f)` that is nonzero modulo `p`, and such +`x₀` have `v x₀ = 1`. Reduction modulo `p` is a ring map into the field `𝔽_p[X] / (f)`, so a +product of two such elements is again nonzero modulo `p`. + +The valuation ring `O = {x | v x ≤ 1}` of `v` consists of the elements whose coordinates have no +`p` in their denominators, that is `ℤ_(p)[X] / (f)` (`AdjoinRoot.mem_integer_padicValuation_iff`), +and an element of `O` with `v x = 1`, one that is nonzero modulo `p`, is a unit of `O` +(`AdjoinRoot.isUnit_of_padicValuation_eq_one`). + +This is the 2-adic valuation `v₂` of the coefficient field `K = ℚ[U] / (f)` of [OpenAI2026ETH] +(§D.4.1, p. 57), with `v₂ x = n` corresponding to `v x = exp (-n)`. + +## Main definitions + +* `AdjoinRoot.padicValuation`: the `p`-adic valuation of `ℚ[X] / (f)`. + +## Main results + +* `AdjoinRoot.irreducible_map_rat`: `f` is irreducible over `ℚ`. +* `AdjoinRoot.padicValuation_apply`: `v x` is the largest valuation of a coordinate of `x`. +* `AdjoinRoot.padicValuation_algebraMap`: `v` extends `Rat.padicValuation p`. +* `AdjoinRoot.padicValuation_map_eq_one_iff`: an element of `ℤ[X] / (f)` has valuation one + exactly when it is nonzero modulo `p`. +* `AdjoinRoot.mem_integer_padicValuation_iff`: the valuation ring is `ℤ_(p)[X] / (f)`. +* `AdjoinRoot.isUnit_of_padicValuation_eq_one`: elements of valuation one are units of it. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1, p. 57. +-/ + +public section + +open Polynomial +open scoped WithZero + +namespace AdjoinRoot + +variable (p : ℕ) [hp : Fact p.Prime] {f : ℤ[X]} + +/-- **Irreducible modulo `p` implies irreducible over `ℚ`**, for monic `f`. -/ +theorem irreducible_map_rat (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) : + Irreducible (f.map (Int.castRingHom ℚ)) := + (hf.irreducible_iff_irreducible_map_fraction_map (K := ℚ)).mp + (Monic.irreducible_of_irreducible_map _ _ hf hirr) + +variable {p} + +/-- The reduced representative has degree below `deg g`. -/ +private theorem degree_modByMonicHom_lt {R : Type*} [CommRing R] [Nontrivial R] {g : R[X]} + (hg : g.Monic) (x : AdjoinRoot g) : (modByMonicHom hg x).degree < g.degree := by + obtain ⟨Q, rfl⟩ := mk_surjective x + rw [modByMonicHom_mk] + exact degree_modByMonic_lt _ hg + +variable (p) in +/-- The largest `p`-adic valuation of a coordinate of `x` in the basis `1, X, …, X ^ (deg f - 1)`. +-/ +private noncomputable def coordVal (hf : f.Monic) (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + ℤᵐ⁰ := + (Finset.range f.natDegree).sup fun i => + Rat.padicValuation p ((modByMonicHom (hf.map (Int.castRingHom ℚ)) x).coeff i) + +private theorem coordVal_smul_le (hf : f.Monic) (r : ℚ) + (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + coordVal p hf (r • x) ≤ Rat.padicValuation p r * coordVal p hf x := by + refine Finset.sup_le fun i hi => ?_ + rw [LinearMap.map_smul_of_tower, coeff_smul, smul_eq_mul, map_mul] + exact mul_le_mul_right (Finset.le_sup (f := fun i => Rat.padicValuation p + ((modByMonicHom (hf.map (Int.castRingHom ℚ)) x).coeff i)) hi) _ + +/-- The coordinates scale with rational scalars. -/ +private theorem coordVal_smul (hf : f.Monic) (r : ℚ) + (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + coordVal p hf (r • x) = Rat.padicValuation p r * coordVal p hf x := by + refine le_antisymm (coordVal_smul_le hf r x) ?_ + rcases eq_or_ne r 0 with rfl | hr + · simp + have h := coordVal_smul_le (p := p) hf r⁻¹ (r • x) + rw [inv_smul_smul₀ hr, map_inv₀] at h + calc + Rat.padicValuation p r * coordVal p hf x + ≤ Rat.padicValuation p r * ((Rat.padicValuation p r)⁻¹ * coordVal p hf (r • x)) := + mul_le_mul_right h _ + _ = coordVal p hf (r • x) := by + rw [← mul_assoc, mul_inv_cancel₀ ((Rat.padicValuation p).ne_zero_iff.mpr hr), one_mul] + +/-- The image of `ℤ[X] / (f)` in `ℚ[X] / (f)`. -/ +private noncomputable abbrev toRat (f : ℤ[X]) : + AdjoinRoot f →+* AdjoinRoot (f.map (Int.castRingHom ℚ)) := + map (Int.castRingHom ℚ) f _ dvd_rfl + +/-- Reduction of `ℤ[X] / (f)` modulo `p`. -/ +private noncomputable abbrev toZMod (p : ℕ) (f : ℤ[X]) : + AdjoinRoot f →+* AdjoinRoot (f.map (Int.castRingHom (ZMod p))) := + map (Int.castRingHom (ZMod p)) f _ dvd_rfl + +/-- The coordinates of the image of `ℤ[X] / (f)` are the integer coordinates. -/ +private theorem modByMonicHom_toRat (hf : f.Monic) (o : AdjoinRoot f) : + modByMonicHom (hf.map (Int.castRingHom ℚ)) (toRat f o) = + (modByMonicHom hf o).map (Int.castRingHom ℚ) := by + obtain ⟨Q, rfl⟩ := mk_surjective o + rw [toRat, map_mk, modByMonicHom_mk, modByMonicHom_mk, map_modByMonic _ hf] + +/-- **Images of `ℤ[X] / (f)` have valuation one exactly when they are nonzero modulo `p`**, and at +most one in any case. -/ +private theorem coordVal_toRat_eq_one_iff (hf : f.Monic) (o : AdjoinRoot f) : + coordVal p hf (toRat f o) ≤ 1 ∧ + (coordVal p hf (toRat f o) = 1 ↔ toZMod p f o ≠ 0) := by + set Q := modByMonicHom hf o + have hQdeg : ∀ i, f.natDegree ≤ i → Q.coeff i = 0 := fun i hi => + coeff_eq_zero_of_degree_lt ((degree_modByMonicHom_lt hf o).trans_le + ((degree_le_natDegree).trans (by exact_mod_cast hi))) + have hval : ∀ i, Rat.padicValuation p ((modByMonicHom (hf.map (Int.castRingHom ℚ)) + (toRat f o)).coeff i) = Int.padicValuation p (Q.coeff i) := fun i => by + rw [modByMonicHom_toRat, coeff_map, eq_intCast, Rat.padicValuation_cast] + have hle : coordVal p hf (toRat f o) ≤ 1 := + Finset.sup_le fun i _ => (hval i).trans_le (Int.padicValuation_le_one p _) + refine ⟨hle, ?_⟩ + rw [Ne, toZMod, map_eq_zero_iff _ hf dvd_rfl rfl] + simp only [eq_intCast, ZMod.intCast_zmod_eq_zero_iff_dvd] + constructor + · intro h1 hdvd + have hlt : coordVal p hf (toRat f o) < 1 := by + refine (Finset.sup_lt_iff (bot_lt_iff_ne_bot.mpr (by simp))).mpr fun i _ => ?_ + rw [hval] + exact Int.padicValuation_lt_one_iff.mpr (hdvd i) + exact hlt.ne h1 + · intro hndvd + obtain ⟨i, hi⟩ := not_forall.mp hndvd + have hie : i < f.natDegree := by + by_contra hie + exact hi (by rw [hQdeg i (not_lt.mp hie)]; exact dvd_zero _) + refine le_antisymm hle ?_ + rw [← Int.padicValuation_eq_one_iff.mpr hi, ← hval] + exact Finset.le_sup (f := fun i => Rat.padicValuation p + ((modByMonicHom (hf.map (Int.castRingHom ℚ)) (toRat f o)).coeff i)) (Finset.mem_range.mpr hie) + +/-- **Every element is a rational multiple of an integral element that is nonzero modulo `p`.** +Clear the denominators of the coordinates and divide by the content. -/ +private theorem exists_eq_smul_toRat (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) + (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + ∃ r : ℚ, ∃ o : AdjoinRoot f, toZMod p f o ≠ 0 ∧ x = r • toRat f o := by + have := Fact.mk hirr + set P := modByMonicHom (hf.map (Int.castRingHom ℚ)) x + have hx : mk _ P = x := mk_leftInverse _ x + rcases eq_or_ne P 0 with hP | hP + · refine ⟨0, 1, by rw [map_one]; exact one_ne_zero, ?_⟩ + rw [zero_smul, ← hx, hP, map_zero] + obtain ⟨b, hb, hQ₀'⟩ := IsLocalization.integerNormalization_spec (nonZeroDivisors ℤ) P + set Q₀ := IsLocalization.integerNormalization (nonZeroDivisors ℤ) P + have hb0 : (b : ℚ) ≠ 0 := by exact_mod_cast nonZeroDivisors.ne_zero hb + have hQ₀ : Q₀.map (Int.castRingHom ℚ) = C (b : ℚ) * P := by + rw [← algebraMap_int_eq, hQ₀', ← Int.cast_smul_eq_zsmul ℚ, smul_eq_C_mul] + have hQ₀ne : Q₀ ≠ 0 := by + rintro h + rw [h, Polynomial.map_zero] at hQ₀ + exact mul_ne_zero (C_ne_zero.mpr hb0) hP hQ₀.symm + set Q₁ := Q₀.primPart + have hdeg : Q₁.degree < (f.map (Int.castRingHom (ZMod p))).degree := by + rw [hf.degree_map, degree_eq_natDegree (primPart_ne_zero Q₀), natDegree_primPart] + have hPdeg : P.degree < f.degree := + (hf.degree_map (Int.castRingHom ℚ)) ▸ degree_modByMonicHom_lt (hf.map _) x + have : Q₀.degree < f.degree := by + refine lt_of_le_of_lt ?_ hPdeg + rw [← degree_map_eq_of_injective (RingHom.injective_int (Int.castRingHom ℚ)) Q₀, hQ₀] + exact degree_mul_le_of_le (degree_C_le) le_rfl |>.trans (by simp) + rwa [degree_eq_natDegree hQ₀ne] at this + have hred : (Q₁.map (Int.castRingHom (ZMod p))) ≠ 0 := by + intro h0 + have hdvd : C (p : ℤ) ∣ Q₁ := (C_dvd_iff_dvd_coeff _ _).mpr fun i => by + rw [← ZMod.intCast_zmod_eq_zero_iff_dvd, ← eq_intCast (Int.castRingHom (ZMod p)), ← coeff_map, + h0, coeff_zero] + have hu := isPrimitive_primPart Q₀ (p : ℤ) hdvd + rw [Int.isUnit_iff_natAbs_eq, Int.natAbs_natCast] at hu + exact hp.out.one_lt.ne' hu + refine ⟨Q₀.content / b, mk f Q₁, ?_, ?_⟩ + · rw [toZMod, map_mk, Ne, mk_eq_zero] + exact (hf.map _).not_dvd_of_degree_lt hred ((degree_map_le).trans_lt hdeg) + · rw [toRat, map_mk, ← hx, Algebra.smul_def, algebraMap_eq, ← mk_C, ← map_mul] + congr 1 + have h := congrArg (Polynomial.map (Int.castRingHom ℚ)) (eq_C_content_mul_primPart Q₀) + rw [Polynomial.map_mul, map_C, hQ₀, eq_intCast] at h + calc + P = C (b : ℚ)⁻¹ * (C (b : ℚ) * P) := by + rw [← mul_assoc, ← C_mul, inv_mul_cancel₀ hb0, C_1, one_mul] + _ = C ((Q₀.content : ℚ) / b) * Q₁.map (Int.castRingHom ℚ) := by + rw [h, ← mul_assoc, ← C_mul, div_eq_inv_mul] + +variable (p) in +/-- **The `p`-adic valuation of `ℚ[X] / (f)`**, for `f` monic and irreducible modulo `p`: the +largest `p`-adic valuation of a coordinate in the basis `1, X, …, X ^ (deg f - 1)`. It extends +`Rat.padicValuation p` (`AdjoinRoot.padicValuation_algebraMap`). -/ +noncomputable def padicValuation (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) : + Valuation (AdjoinRoot (f.map (Int.castRingHom ℚ))) ℤᵐ⁰ where + toFun := coordVal p hf + map_zero' := by + simp only [coordVal, map_zero, coeff_zero] + exact Finset.sup_bot _ + map_one' := by + have := (coordVal_toRat_eq_one_iff (p := p) hf 1).2.mpr (by + have := Fact.mk hirr + rw [map_one]; exact one_ne_zero) + rwa [map_one] at this + map_mul' x y := by + have := Fact.mk hirr + obtain ⟨r, o, ho, rfl⟩ := exists_eq_smul_toRat hf hirr x + obtain ⟨r', o', ho', rfl⟩ := exists_eq_smul_toRat hf hirr y + rw [smul_mul_smul_comm, ← map_mul, coordVal_smul, coordVal_smul, coordVal_smul, map_mul, + (coordVal_toRat_eq_one_iff hf _).2.mpr (by rw [map_mul]; exact mul_ne_zero ho ho'), + (coordVal_toRat_eq_one_iff hf _).2.mpr ho, (coordVal_toRat_eq_one_iff hf _).2.mpr ho', + mul_one, mul_one, mul_one] + map_add_le_max' x y := by + refine Finset.sup_le fun i hi => ?_ + rw [map_add, coeff_add] + refine ((Rat.padicValuation p).map_add _ _).trans (max_le_max ?_ ?_) <;> + exact Finset.le_sup (f := fun i => Rat.padicValuation p _) hi + +variable (hf : f.Monic) (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) + +/-- The valuation is the largest `p`-adic valuation of a coordinate. -/ +theorem padicValuation_apply (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + padicValuation p hf hirr x = (Finset.range f.natDegree).sup fun i => + Rat.padicValuation p ((modByMonicHom (hf.map (Int.castRingHom ℚ)) x).coeff i) := by + simp only [padicValuation] + rfl + +/-- **The valuation extends the `p`-adic valuation of `ℚ`.** -/ +theorem padicValuation_algebraMap (r : ℚ) : + padicValuation p hf hirr (algebraMap ℚ _ r) = Rat.padicValuation p r := by + rw [Algebra.algebraMap_eq_smul_one, ← mul_one (Rat.padicValuation p r), + ← (padicValuation p hf hirr).map_one] + exact coordVal_smul hf r 1 + +/-- **An element of `ℤ[X] / (f)` has valuation one exactly when it is nonzero modulo `p`.** -/ +theorem padicValuation_map_eq_one_iff (o : AdjoinRoot f) : + padicValuation p hf hirr (map (Int.castRingHom ℚ) f _ dvd_rfl o) = 1 ↔ + map (Int.castRingHom (ZMod p)) f _ dvd_rfl o ≠ 0 := + (coordVal_toRat_eq_one_iff hf o).2 + +/-- Elements of `ℤ[X] / (f)` have valuation at most one. -/ +theorem padicValuation_map_le_one (o : AdjoinRoot f) : + padicValuation p hf hirr (map (Int.castRingHom ℚ) f _ dvd_rfl o) ≤ 1 := + (coordVal_toRat_eq_one_iff hf o).1 + +/-- **The valuation ring is `ℤ_(p)[X] / (f)`**: the elements whose coordinates have no `p` in their +denominators. -/ +theorem mem_integer_padicValuation_iff (x : AdjoinRoot (f.map (Int.castRingHom ℚ))) : + x ∈ (padicValuation p hf hirr).integer ↔ + ∀ i, Rat.padicValuation p ((modByMonicHom (hf.map (Int.castRingHom ℚ)) x).coeff i) ≤ 1 := by + rw [Valuation.mem_integer_iff, padicValuation_apply, Finset.sup_le_iff] + refine ⟨fun h i => ?_, fun h i _ => h i⟩ + rcases lt_or_ge i f.natDegree with hi | hi + · exact h i (Finset.mem_range.mpr hi) + · rw [coeff_eq_zero_of_degree_lt, map_zero] + · exact zero_le + refine (degree_modByMonicHom_lt (hf.map _) x).trans_le ?_ + rw [hf.degree_map] + exact (degree_le_natDegree).trans (by exact_mod_cast hi) + +/-- **Elements of the valuation ring that are nonzero modulo `p` are units of it**: an element of +valuation one has an inverse of valuation one. -/ +theorem isUnit_of_padicValuation_eq_one {x : AdjoinRoot (f.map (Int.castRingHom ℚ))} + (hx : x ∈ (padicValuation p hf hirr).integer) (h1 : padicValuation p hf hirr x = 1) : + IsUnit (⟨x, hx⟩ : (padicValuation p hf hirr).integer) := by + have := Fact.mk (irreducible_map_rat p hf hirr) + exact (Valuation.integer.integers _).isUnit_iff_valuation_eq_one.mpr h1 + +end AdjoinRoot diff --git a/Complexity/Foundations/RingTheory/GaloisRing.lean b/Complexity/Foundations/RingTheory/GaloisRing.lean new file mode 100644 index 000000000..da9bc2de1 --- /dev/null +++ b/Complexity/Foundations/RingTheory/GaloisRing.lean @@ -0,0 +1,351 @@ +/- +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, Galois rings) + +public import Complexity.Init +public import Complexity.Foundations.Data.Polynomial.RingLagrange +public import Mathlib.FieldTheory.Finite.GaloisField +public import Mathlib.RingTheory.AdjoinRoot +public import Mathlib.RingTheory.LocalRing.ResidueField.Defs +import Mathlib.RingTheory.LocalRing.ResidueField.Basic +import Mathlib.RingTheory.Polynomial.Nilpotent + +/-! +# Galois rings + +For a prime `p`, an exponent `n ≠ 0` and an integer polynomial `f` that is monic and irreducible +modulo `p`, the *Galois ring* `GaloisRing p n f = (ℤ / p ^ n)[X] / (f)` is a local ring. Its maximal +ideal is `(p)`, and reduction modulo `p` identifies its residue field with `𝔽_p[X] / (f)`, the +finite field with `p ^ deg f` elements. In particular an element is a unit exactly when it is +nonzero modulo `p`. + +The *digit lifts* `∑ i, c i * X ^ i` with digits `c i ∈ {0, …, p - 1}` reduce to the `p ^ deg f` +distinct residue-field elements, so any two of them differ by a unit, and Lagrange interpolation +over the ring holds at any set of them (`GaloisRing.eq_ringInterpolate_digitLift`). + +This is the coefficient ring of the Boolean tensor contraction of [OpenAI2026ETH] (§D.4.1, +pp. 57–58), which takes `p = 2`, a degree `e = 4 ⌈log₂ s⌉` and the precision `n`; the manuscript's +binary-coordinate lifts are the digit lifts for `p = 2`. + +The argument is that of a surjection onto a field with nilpotent kernel +(`RingHom.isUnit_iff_ne_zero_of_isNilpotent`): reduction modulo `p` has kernel `(p)`, and +`p ^ n = 0`. The manuscript states the unit criterion for `ℤ_(2)[U] / (f)` and proves it with the +determinant of the multiplication matrix; at a finite precision the nilpotent kernel suffices. The +statement over `ℤ_(p)` is `AdjoinRoot.isUnit_of_padicValuation_eq_one`. + +## Main definitions + +* `GaloisRing`: the ring `(ℤ / p ^ n)[X] / (f)`. +* `GaloisRing.residue`: reduction modulo `p`, onto `𝔽_p[X] / (f)`. +* `GaloisRing.digitLift`: the lift `∑ i, c i * X ^ i` of a digit vector. + +## Main results + +* `RingHom.isLocalHom_of_isNilpotent`: a surjection with nilpotent kernel is a local + homomorphism. +* `AdjoinRoot.map_eq_zero_iff`: an element of `R[X] / (g)` vanishes in `S[X] / (g)` exactly when + the coefficients of its reduced representative do. +* `GaloisRing.ker_residue`: the kernel of reduction modulo `p` is `(p)`. +* `GaloisRing.isUnit_iff`: an element is a unit exactly when it is nonzero modulo `p`. +* `GaloisRing.instIsLocalRing`, `GaloisRing.maximalIdeal_eq`: the ring is local with maximal ideal + `(p)`. +* `GaloisRing.residueFieldEquiv`, `GaloisRing.algEquivGaloisField`: its residue field is + `𝔽_p[X] / (f) ≃ GaloisField p (deg f)`. +* `GaloisRing.isUnit_digitLift_sub`: distinct digit lifts differ by a unit. +* `GaloisRing.eq_ringInterpolate_digitLift`: Lagrange interpolation at digit lifts. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1, pp. 57–58. +-/ + +public section + +open Polynomial IsLocalRing + +/-! ### Surjections with nilpotent kernel -/ + +namespace RingHom + +variable {R S : Type*} [CommRing R] [CommRing S] + +/-- A surjective ring homomorphism whose kernel consists of nilpotent elements is local: if `ψ x` +is a unit then `x y = 1 + n` for some `y` and nilpotent `n`. -/ +theorem isLocalHom_of_isNilpotent (ψ : R →+* S) (hψ : Function.Surjective ψ) + (hnil : ∀ x, ψ x = 0 → IsNilpotent x) : IsLocalHom ψ where + map_nonunit x hx := by + obtain ⟨u, hu⟩ := hx + obtain ⟨y, hy⟩ := hψ ↑u⁻¹ + have hn : IsNilpotent (x * y - 1) := + hnil _ (by rw [map_sub, map_mul, map_one, hy, ← hu, Units.mul_inv, sub_self]) + have h1 : IsUnit (x * y) := by + simpa only [add_sub_cancel] using hn.isUnit_one_add + exact isUnit_of_mul_isUnit_left h1 + +/-- Over a surjection onto a field with nilpotent kernel, an element is a unit exactly when its +image is nonzero. -/ +theorem isUnit_iff_ne_zero_of_isNilpotent {F : Type*} [Field F] (ψ : R →+* F) + (hψ : Function.Surjective ψ) (hnil : ∀ x, ψ x = 0 → IsNilpotent x) {x : R} : + IsUnit x ↔ ψ x ≠ 0 := by + have := ψ.isLocalHom_of_isNilpotent hψ hnil + exact ⟨fun h => (h.map ψ).ne_zero, fun h => isUnit_of_map_unit ψ x (Ne.isUnit h)⟩ + +end RingHom + +/-! ### Reducing the coefficients of `R[X] / (g)` -/ + +namespace AdjoinRoot + +variable {R S : Type*} [CommRing R] [CommRing S] (φ : R →+* S) {g : R[X]} + +/-- Reducing the coefficients maps the class of `P` to the class of `P.map φ`. -/ +theorem map_mk (q : S[X]) (h : q ∣ g.map φ) (P : R[X]) : + map φ g q h (mk g P) = mk q (P.map φ) := by + have : (map φ g q h).comp (mk g) = (mk q).comp (mapRingHom φ) := by + refine Polynomial.ringHom_ext (fun a => ?_) ?_ + · simp only [RingHom.comp_apply, mk_C, map_of, coe_mapRingHom, Polynomial.map_C] + · simp only [RingHom.comp_apply, mk_X, map_root, coe_mapRingHom, Polynomial.map_X] + exact congrArg (fun F : R[X] →+* AdjoinRoot q => F P) this + +/-- Reducing the coefficients is surjective when `φ` is. -/ +theorem map_surjective_of_surjective (q : S[X]) (h : q ∣ g.map φ) + (hφ : Function.Surjective φ) : Function.Surjective (map φ g q h) := by + intro y + obtain ⟨Q, rfl⟩ := mk_surjective y + obtain ⟨P, rfl⟩ := Polynomial.map_surjective φ hφ Q + exact ⟨mk g P, map_mk φ q h P⟩ + +/-- **An element vanishes after reducing the coefficients** of a monic `g` exactly when the +coefficients of its representative of degree below `deg g` do: that representative reduces to a +polynomial of degree below `deg (g.map φ)`, which `g.map φ` divides only when it is zero. -/ +theorem map_eq_zero_iff [Nontrivial S] (hg : g.Monic) {q : S[X]} (h : q ∣ g.map φ) + (hq : q = g.map φ) (x : AdjoinRoot g) : + map φ g q h x = 0 ↔ ∀ i, φ ((modByMonicHom hg x).coeff i) = 0 := by + subst hq + have := φ.domain_nontrivial + obtain ⟨Q, rfl⟩ := mk_surjective x + rw [modByMonicHom_mk] + set P := Q %ₘ g + have hx : mk g P = mk g Q := by + have := mk_leftInverse hg (mk g Q) + rwa [modByMonicHom_mk] at this + have hdeg : (P.map φ).degree < (g.map φ).degree := by + rw [hg.degree_map] + exact degree_map_le.trans_lt (degree_modByMonic_lt _ hg) + rw [← hx, map_mk, mk_eq_zero] + constructor + · intro hdvd i + by_contra hne + have h0 : P.map φ ≠ 0 := fun h0 => hne (by rw [← coeff_map, h0, coeff_zero]) + exact (hg.map φ).not_dvd_of_degree_lt h0 hdeg hdvd + · intro h + have : P.map φ = 0 := Polynomial.ext fun i => by rw [coeff_map, h, coeff_zero] + rw [this] + exact dvd_zero _ + +end AdjoinRoot + +/-! ### Galois rings -/ + +/-- The ring `(ℤ / p ^ n)[X] / (f)` presented by an integer polynomial `f`. For a prime `p`, +`n ≠ 0` and `f` monic and irreducible modulo `p`, it is the *Galois ring* of characteristic +`p ^ n` and rank `deg f`: a local ring with maximal ideal `(p)` and residue field +`𝔽_{p ^ deg f}`. -/ +abbrev GaloisRing (p n : ℕ) (f : ℤ[X]) : Type := + AdjoinRoot (f.map (Int.castRingHom (ZMod (p ^ n)))) + +namespace GaloisRing + +variable (p n : ℕ) (f : ℤ[X]) + +/-- Reducing integer coefficients modulo `m` and then modulo a divisor `d` of `m` is reducing +modulo `d`. -/ +theorem map_castHom_map {d m : ℕ} (h : d ∣ m) : + (f.map (Int.castRingHom (ZMod m))).map (ZMod.castHom h (ZMod d)) = + f.map (Int.castRingHom (ZMod d)) := by + rw [Polynomial.map_map] + congr 1 + exact RingHom.ext_int _ _ + +/-- The *digit lift* `∑ i, c i * X ^ i` of a digit vector `c`, with each digit `c i ∈ ZMod p` read +as an integer in `{0, …, p - 1}`. For `p = 2` these are the binary-coordinate lifts of +[OpenAI2026ETH] (§D.4.1, p. 58). -/ +@[expose] noncomputable def digitLift (c : Fin f.natDegree → ZMod p) : GaloisRing p n f := + ∑ i, ((c i).val : GaloisRing p n f) * AdjoinRoot.root _ ^ (i : ℕ) + +variable [NeZero n] + +/-- Reduction modulo `p`, from `(ℤ / p ^ n)[X] / (f)` onto `𝔽_p[X] / (f)`. -/ +noncomputable def residue : GaloisRing p n f →+* AdjoinRoot (f.map (Int.castRingHom (ZMod p))) := + AdjoinRoot.map (ZMod.castHom (dvd_pow_self p (NeZero.ne n)) (ZMod p)) _ _ + (map_castHom_map f _).symm.dvd + +@[simp] +theorem residue_mk (P : (ZMod (p ^ n))[X]) : + residue p n f (AdjoinRoot.mk _ P) = + AdjoinRoot.mk _ (P.map (ZMod.castHom (dvd_pow_self p (NeZero.ne n)) (ZMod p))) := + AdjoinRoot.map_mk _ _ _ P + +@[simp] +theorem residue_root : residue p n f (AdjoinRoot.root _) = AdjoinRoot.root _ := + AdjoinRoot.map_root _ _ _ _ + +@[simp] +theorem residue_of (a : ZMod (p ^ n)) : + residue p n f (AdjoinRoot.of _ a) = + AdjoinRoot.of _ (ZMod.castHom (dvd_pow_self p (NeZero.ne n)) (ZMod p) a) := + AdjoinRoot.map_of _ _ _ _ _ + +theorem residue_surjective : Function.Surjective (residue p n f) := + AdjoinRoot.map_surjective_of_surjective _ _ _ (ZMod.castHom_surjective _) + +variable {p n f} [Fact p.Prime] + +variable (p n f) in +/-- A digit lift reduces to `∑ i, c i * X ^ i` modulo `p`. -/ +theorem residue_digitLift (c : Fin f.natDegree → ZMod p) : + residue p n f (digitLift p n f c) = AdjoinRoot.mk _ (∑ i, C (c i) * X ^ (i : ℕ)) := by + simp only [digitLift, map_sum, map_mul, map_pow, map_natCast, residue_root, AdjoinRoot.mk_C, + AdjoinRoot.mk_X] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [← map_natCast (AdjoinRoot.of _), ZMod.natCast_zmod_val] + +/-- Digit lifts reduce to distinct elements modulo `p`: the coordinates `1, X, …, X ^ (deg f - 1)` +are independent in `𝔽_p[X] / (f)` for monic `f`. -/ +theorem residue_digitLift_injective (hf : f.Monic) : + Function.Injective fun c : Fin f.natDegree → ZMod p => residue p n f (digitLift p n f c) := by + intro c c' h + simp only [residue_digitLift] at h + rw [AdjoinRoot.mk_eq_mk, ← Finset.sum_sub_distrib] at h + set P : (ZMod p)[X] := ∑ i, (C (c i) * X ^ (i : ℕ) - C (c' i) * X ^ (i : ℕ)) + have hcoeff : ∀ i : Fin f.natDegree, P.coeff i = c i - c' i := fun i => by + simp only [P, finsetSum_coeff, coeff_sub, coeff_C_mul, coeff_X_pow, mul_ite, mul_one, + mul_zero] + rw [Finset.sum_eq_single i (fun j _ hji => ?_) (by simp), ite_eq_left rfl, ite_eq_left rfl] + rw [ite_eq_right fun h => hji (Fin.ext h.symm), ite_eq_right fun h => hji (Fin.ext h.symm), + sub_zero] + have hP : P = 0 := by + by_contra h0 + refine (hf.map (Int.castRingHom (ZMod p))).not_dvd_of_degree_lt h0 ?_ h + rw [hf.degree_map, degree_eq_natDegree hf.ne_zero] + refine (degree_sum_le _ _).trans_lt + ((Finset.sup_lt_iff (WithBot.bot_lt_coe _)).mpr fun i _ => ?_) + refine (degree_sub_le _ _).trans_lt (max_lt ?_ ?_) <;> + exact (degree_C_mul_X_pow_le _ _).trans_lt (by exact_mod_cast i.isLt) + funext i + have := hcoeff i + rw [hP, coeff_zero] at this + exact (sub_eq_zero.mp this.symm) + + +/-- An element of `ℤ / p ^ n` that vanishes modulo `p` is a multiple of `p`. -/ +private theorem dvd_of_castHom_eq_zero {a : ZMod (p ^ n)} + (ha : ZMod.castHom (dvd_pow_self p (NeZero.ne n)) (ZMod p) a = 0) : (p : ZMod (p ^ n)) ∣ a := by + obtain ⟨k, rfl⟩ := ZMod.natCast_zmod_surjective a + rw [map_natCast, ZMod.natCast_eq_zero_iff] at ha + obtain ⟨m, rfl⟩ := ha + exact ⟨m, by push_cast; ring⟩ + +/-- **The kernel of reduction modulo `p` is `(p)`**, when `f` is monic. -/ +theorem ker_residue (hf : f.Monic) : + RingHom.ker (residue p n f) = Ideal.span {(p : GaloisRing p n f)} := by + ext x + rw [RingHom.mem_ker, Ideal.mem_span_singleton] + constructor + · intro hx + rw [residue, AdjoinRoot.map_eq_zero_iff _ (hf.map _) _ + (map_castHom_map f (dvd_pow_self p (NeZero.ne n))).symm] at hx + obtain ⟨Q, hQ⟩ := (C_dvd_iff_dvd_coeff (p : ZMod (p ^ n)) _).mpr fun i => + dvd_of_castHom_eq_zero (hx i) + refine ⟨AdjoinRoot.mk _ Q, ?_⟩ + rw [← AdjoinRoot.mk_leftInverse (hf.map _) x, hQ, map_mul, AdjoinRoot.mk_C, map_natCast] + · rintro ⟨y, rfl⟩ + rw [map_mul, map_natCast, ← map_natCast (AdjoinRoot.of _), ZMod.natCast_self, map_zero, + zero_mul] + +/-- `p ^ n = 0`, so elements that vanish modulo `p` are nilpotent. -/ +theorem isNilpotent_of_residue_eq_zero (hf : f.Monic) {x : GaloisRing p n f} + (hx : residue p n f x = 0) : IsNilpotent x := by + rw [← RingHom.mem_ker, ker_residue hf, Ideal.mem_span_singleton] at hx + obtain ⟨y, rfl⟩ := hx + refine ⟨n, ?_⟩ + rw [mul_pow, ← Nat.cast_pow, ← map_natCast (AdjoinRoot.of _), ZMod.natCast_self, map_zero, + zero_mul] + +variable (hf : f.Monic) (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) +include hf hirr + +/-- **Units are the elements that are nonzero modulo `p`.** -/ +theorem isUnit_iff {x : GaloisRing p n f} : IsUnit x ↔ residue p n f x ≠ 0 := + have := Fact.mk hirr + (residue p n f).isUnit_iff_ne_zero_of_isNilpotent (residue_surjective p n f) fun _ => + isNilpotent_of_residue_eq_zero hf + +/-- **Distinct digit lifts differ by a unit.** -/ +theorem isUnit_digitLift_sub {c c' : Fin f.natDegree → ZMod p} (h : c ≠ c') : + IsUnit (digitLift p n f c - digitLift p n f c') := by + rw [isUnit_iff hf hirr, map_sub, sub_ne_zero] + exact fun h' => h (residue_digitLift_injective hf h') + +/-- **Lagrange interpolation at digit lifts.** A polynomial over a Galois ring of degree less than +`#s`, for a set `s` of digit vectors, is the interpolant of its values at their lifts; the lifts +have unit differences. -/ +theorem eq_ringInterpolate_digitLift {s : Finset (Fin f.natDegree → ZMod p)} + {P : (GaloisRing p n f)[X]} (hP : P.degree < s.card) : + P = Lagrange.ringInterpolate s (digitLift p n f) (fun c => P.eval (digitLift p n f c)) := + Lagrange.eq_ringInterpolate (fun _ _ _ _ h => isUnit_digitLift_sub hf hirr h) hP + +/-- **A Galois ring is local.** -/ +theorem isLocalRing : IsLocalRing (GaloisRing p n f) := + have := Fact.mk hirr + have := (residue p n f).isLocalHom_of_isNilpotent (residue_surjective p n f) fun _ => + isNilpotent_of_residue_eq_zero hf + (residue p n f).domain_isLocalRing + +omit hirr in +/-- `𝔽_p[X] / (f)` has `p ^ deg f` elements. -/ +theorem natCard_adjoinRoot : + Nat.card (AdjoinRoot (f.map (Int.castRingHom (ZMod p)))) = p ^ f.natDegree := by + let b := (AdjoinRoot.powerBasis' (hf.map (Int.castRingHom (ZMod p)))).basis + rw [Nat.card_congr b.equivFun.toEquiv, Nat.card_fun, Nat.card_zmod, Nat.card_eq_fintype_card, + Fintype.card_fin, AdjoinRoot.powerBasis'_dim, hf.natDegree_map] + +end GaloisRing + +/-! ### The residue field -/ + +namespace GaloisRing + +variable {p n : ℕ} {f : ℤ[X]} [NeZero n] [Fact p.Prime] [hf : Fact f.Monic] + [hirr : Fact (Irreducible (f.map (Int.castRingHom (ZMod p))))] + +instance instIsLocalRing : IsLocalRing (GaloisRing p n f) := + isLocalRing hf.out hirr.out + +/-- **The maximal ideal of a Galois ring is `(p)`.** -/ +theorem maximalIdeal_eq : maximalIdeal (GaloisRing p n f) = Ideal.span {(p : GaloisRing p n f)} := + by rw [← ker_residue hf.out, IsLocalRing.ker_eq_maximalIdeal _ (residue_surjective p n f)] + +/-- **The residue field of a Galois ring** is `𝔽_p[X] / (f)`, by reduction modulo `p`. -/ +noncomputable def residueFieldEquiv : + ResidueField (GaloisRing p n f) ≃+* AdjoinRoot (f.map (Int.castRingHom (ZMod p))) := + (Ideal.quotEquivOfEq (IsLocalRing.ker_eq_maximalIdeal _ (residue_surjective p n f)).symm).trans + (RingHom.quotientKerEquivOfSurjective (residue_surjective p n f)) + +@[simp] +theorem residueFieldEquiv_residue (x : GaloisRing p n f) : + residueFieldEquiv (IsLocalRing.residue _ x) = residue p n f x := by + simp only [residueFieldEquiv] + rfl + +/-- **The residue field is the Galois field** with `p ^ deg f` elements. -/ +noncomputable def algEquivGaloisField : + AdjoinRoot (f.map (Int.castRingHom (ZMod p))) ≃ₐ[ZMod p] GaloisField p f.natDegree := + GaloisField.algEquivGaloisField p f.natDegree (natCard_adjoinRoot hf.out) + +end GaloisRing diff --git a/ComplexityTests/Foundations/Data/Polynomial/RingLagrange.lean b/ComplexityTests/Foundations/Data/Polynomial/RingLagrange.lean new file mode 100644 index 000000000..94333bd0b --- /dev/null +++ b/ComplexityTests/Foundations/Data/Polynomial/RingLagrange.lean @@ -0,0 +1,54 @@ +/- +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.Foundations.Data.Polynomial.RingLagrange +import Mathlib.Data.ZMod.Basic +import Mathlib.Tactic.FinCases + +/-! Tests for Lagrange interpolation over a commutative ring. -/ + +namespace ComplexityTests.RingLagrange + +open Polynomial Lagrange + +/-! ### Interpolation over `ZMod 8`, which is not a domain -/ + +/-- The nodes `0` and `1` of `ZMod 8` differ by a unit. -/ +theorem pairwise_isUnit : + ((Finset.univ : Finset (Fin 2)) : Set (Fin 2)).Pairwise + fun i j => IsUnit ((![0, 1] : Fin 2 → ZMod 8) i - ![0, 1] j) := by + intro i _ j _ hij + fin_cases i <;> fin_cases j + · exact absurd rfl hij + · simp + · simp + · exact absurd rfl hij + +/-- The interpolant of the values `3, 5` at `0, 1` takes those values. -/ +example : (ringInterpolate Finset.univ (![0, 1] : Fin 2 → ZMod 8) ![3, 5]).eval 0 = 3 ∧ + (ringInterpolate Finset.univ (![0, 1] : Fin 2 → ZMod 8) ![3, 5]).eval 1 = 5 := + ⟨eval_ringInterpolate_at_node pairwise_isUnit _ (Finset.mem_univ (0 : Fin 2)), + eval_ringInterpolate_at_node pairwise_isUnit _ (Finset.mem_univ (1 : Fin 2))⟩ + +/-- A linear polynomial over `ZMod 8` is the interpolant of its values at `0` and `1`. -/ +example : (C 3 + C 2 * X : (ZMod 8)[X]) = + ringInterpolate Finset.univ (![0, 1] : Fin 2 → ZMod 8) + (fun i => (C 3 + C 2 * X : (ZMod 8)[X]).eval (![0, 1] i)) := + eq_ringInterpolate pairwise_isUnit ((degree_add_le _ _).trans_lt (max_lt + (degree_C_le.trans_lt (by decide)) ((degree_C_mul_X_le _).trans_lt (by decide)))) + +/-! ### Unit differences are needed -/ + +/-- Over `ZMod 4`, the nodes `0` and `2` differ by the non-unit `2`, and the nonzero polynomial +`2 X` of degree below two vanishes at both: uniqueness fails without unit differences. -/ +example : (C 2 * X : (ZMod 4)[X]).eval 0 = 0 ∧ (C 2 * X : (ZMod 4)[X]).eval 2 = 0 ∧ + (C 2 * X : (ZMod 4)[X]) ≠ 0 := by + refine ⟨by simp, by simp only [eval_mul, eval_C, eval_X]; decide, fun h => ?_⟩ + have := congrArg (coeff · 1) h + simp only [coeff_C_mul, coeff_X_one, mul_one, coeff_zero] at this + exact absurd this (by decide) + +end ComplexityTests.RingLagrange diff --git a/ComplexityTests/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean b/ComplexityTests/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean new file mode 100644 index 000000000..1219f44ba --- /dev/null +++ b/ComplexityTests/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean @@ -0,0 +1,98 @@ +/- +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.Foundations.NumberTheory.Padics.AdjoinRootValuation +import Mathlib.Algebra.Field.ZMod +import Mathlib.Algebra.Polynomial.SpecificDegree +import Mathlib.Tactic.ComputeDegree +import Mathlib.Tactic.FinCases + +/-! Tests for the 2-adic valuation of `K = ℚ[U] / (U ^ 2 + U + 1)`, the case `e = 2` of the +coefficient field of [OpenAI2026ETH], §D.4.1. -/ + +namespace ComplexityTests.AdjoinRootValuation + +open Polynomial +open scoped WithZero + +/-- `U ^ 2 + U + 1`, irreducible modulo 2. -/ +noncomputable def f : ℤ[X] := X ^ 2 + X + 1 + +theorem f_monic : f.Monic := by unfold f; monicity! + +theorem f_irreducible : Irreducible (f.map (Int.castRingHom (ZMod 2))) := by + unfold f + simp only [Polynomial.map_add, Polynomial.map_pow, Polynomial.map_X, Polynomial.map_one] + refine Polynomial.irreducible_of_degree_le_three_of_not_isRoot ?_ ?_ + · have : (X ^ 2 + X + 1 : (ZMod 2)[X]).natDegree = 2 := by compute_degree! + simp [this] + · intro x hx + rw [IsRoot.def, eval_add, eval_add, eval_pow, eval_X, eval_one] at hx + fin_cases x <;> revert hx <;> decide + +/-- The field `K = ℚ[U] / (U ^ 2 + U + 1)`. -/ +abbrev K : Type := AdjoinRoot (f.map (Int.castRingHom ℚ)) + +/-- The 2-adic valuation of `K`. -/ +noncomputable abbrev v : Valuation K ℤᵐ⁰ := AdjoinRoot.padicValuation 2 f_monic f_irreducible + +/-- The map `ℤ[U] / (f) → K`. -/ +noncomputable abbrev ι : AdjoinRoot f →+* K := AdjoinRoot.map (Int.castRingHom ℚ) f _ dvd_rfl + +instance : Fact (Irreducible (f.map (Int.castRingHom (ZMod 2)))) := ⟨f_irreducible⟩ + +/-- `U ^ 2 + U + 1 = 0` modulo `2`. -/ +theorem root_sq_add_root_add_one : + (AdjoinRoot.root (f.map (Int.castRingHom (ZMod 2)))) ^ 2 + AdjoinRoot.root _ + 1 = 0 := by + rw [← AdjoinRoot.mk_X, ← map_pow, ← map_add, ← map_one (AdjoinRoot.mk _), ← map_add, + AdjoinRoot.mk_eq_zero] + exact dvd_of_eq (by simp [f]) + +/-- `U` is nonzero modulo `2`: otherwise `f(0) = 1` would vanish. -/ +theorem root_ne_zero_mod_two : + AdjoinRoot.map (Int.castRingHom (ZMod 2)) f _ dvd_rfl (AdjoinRoot.root f) ≠ 0 := by + rw [AdjoinRoot.map_root] + intro h + have := root_sq_add_root_add_one + rw [h] at this + simp at this + +/-- `1 + U` is nonzero modulo `2`: it is `U²`, the square of a nonzero element of a field. -/ +theorem one_add_root_ne_zero_mod_two : + AdjoinRoot.map (Int.castRingHom (ZMod 2)) f _ dvd_rfl (1 + AdjoinRoot.root f) ≠ 0 := by + have hroot := root_ne_zero_mod_two + rw [AdjoinRoot.map_root] at hroot + rw [map_add, map_one, AdjoinRoot.map_root] + intro h1 + have h := root_sq_add_root_add_one + rw [add_assoc, add_comm (AdjoinRoot.root _), h1, add_zero] at h + exact pow_ne_zero 2 hroot h + +/-! ### The valuation on concrete elements -/ + +theorem padicValRat_two : padicValRat 2 (2 : ℚ) = 1 := by + exact_mod_cast padicValRat.self (p := 2) one_lt_two + +/-- `v (1 / 2) = exp 1`: the valuation extends that of `ℚ`. -/ +example : v (algebraMap ℚ K (1 / 2)) = WithZero.exp 1 := by + rw [AdjoinRoot.padicValuation_algebraMap] + simp [Rat.padicValuation, padicValRat_two] + +/-- `v (2 U) = v 2 * v U = exp (-1)`, by multiplicativity: `U` is nonzero modulo `2`. -/ +example : v (algebraMap ℚ K 2 * ι (AdjoinRoot.root f)) = WithZero.exp (-1) := by + rw [map_mul, AdjoinRoot.padicValuation_algebraMap, + (AdjoinRoot.padicValuation_map_eq_one_iff f_monic f_irreducible _).mpr root_ne_zero_mod_two, + mul_one] + simp [Rat.padicValuation, padicValRat_two] + +/-- `1 + U` is integral and nonzero modulo `2`, hence a unit of the valuation ring. -/ +example : ∃ h : ι (1 + AdjoinRoot.root f) ∈ v.integer, IsUnit (⟨_, h⟩ : v.integer) := + have h := AdjoinRoot.padicValuation_map_le_one f_monic f_irreducible (1 + AdjoinRoot.root f) + ⟨h, AdjoinRoot.isUnit_of_padicValuation_eq_one f_monic f_irreducible h + ((AdjoinRoot.padicValuation_map_eq_one_iff f_monic f_irreducible _).mpr + one_add_root_ne_zero_mod_two)⟩ + +end ComplexityTests.AdjoinRootValuation diff --git a/ComplexityTests/Foundations/RingTheory/GaloisRing.lean b/ComplexityTests/Foundations/RingTheory/GaloisRing.lean new file mode 100644 index 000000000..c11fcc94d --- /dev/null +++ b/ComplexityTests/Foundations/RingTheory/GaloisRing.lean @@ -0,0 +1,111 @@ +/- +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.Foundations.RingTheory.GaloisRing +import Mathlib.Algebra.Polynomial.SpecificDegree +import Mathlib.Tactic.ComputeDegree +import Mathlib.Tactic.FinCases + +/-! Tests for Galois rings, on `(ℤ / 8)[U] / (U ^ 2 + U + 1)`: the case `p = 2`, `e = 2` and +precision `3` of the coefficient ring of [OpenAI2026ETH], §D.4.1. -/ + +namespace ComplexityTests.GaloisRing + +open Polynomial IsLocalRing + +/-- `U ^ 2 + U + 1`, irreducible modulo 2. -/ +noncomputable def f : ℤ[X] := X ^ 2 + X + 1 + +theorem f_monic : f.Monic := by unfold f; monicity! + +theorem f_natDegree : f.natDegree = 2 := by unfold f; compute_degree! + +theorem f_irreducible : Irreducible (f.map (Int.castRingHom (ZMod 2))) := by + unfold f + simp only [Polynomial.map_add, Polynomial.map_pow, Polynomial.map_X, Polynomial.map_one] + refine Polynomial.irreducible_of_degree_le_three_of_not_isRoot ?_ ?_ + · have : (X ^ 2 + X + 1 : (ZMod 2)[X]).natDegree = 2 := by compute_degree! + simp [this] + · intro x hx + rw [IsRoot.def, eval_add, eval_add, eval_pow, eval_X, eval_one] at hx + fin_cases x <;> revert hx <;> decide + +instance : Fact f.Monic := ⟨f_monic⟩ + +instance : Fact (Irreducible (f.map (Int.castRingHom (ZMod 2)))) := ⟨f_irreducible⟩ + +/-- The ring `(ℤ / 8)[U] / (U ^ 2 + U + 1)`. -/ +abbrev A : Type := GaloisRing 2 3 f + +/-- The root `U`. -/ +noncomputable abbrev U : A := AdjoinRoot.root _ + +/-! ### Locality and units -/ + +/-- The residue field has characteristic two. -/ +theorem two_eq_zero : (2 : AdjoinRoot (f.map (Int.castRingHom (ZMod 2)))) = 0 := by + rw [← map_ofNat (AdjoinRoot.of _), show (OfNat.ofNat 2 : ZMod 2) = 0 by decide, map_zero] + +/-- The ring is local. -/ +example : IsLocalRing A := inferInstance + +/-- `2` is not a unit: it vanishes modulo `2`. -/ +example : ¬IsUnit (2 : A) := by + rw [GaloisRing.isUnit_iff f_monic f_irreducible, map_ofNat, not_not] + exact two_eq_zero + +/-- `1 + 2 U` is a unit: it is one modulo `2`. -/ +example : IsUnit (1 + 2 * U) := by + rw [GaloisRing.isUnit_iff f_monic f_irreducible] + simp only [map_add, map_mul, map_one, map_ofNat, two_eq_zero, zero_mul, add_zero] + exact one_ne_zero + +/-- `2` generates the maximal ideal. -/ +example : (2 : A) ∈ maximalIdeal A := by + rw [GaloisRing.maximalIdeal_eq] + exact_mod_cast Ideal.mem_span_singleton_self (2 : A) + +/-- The residue field has `2 ^ 2 = 4` elements. -/ +example : Nat.card (AdjoinRoot (f.map (Int.castRingHom (ZMod 2)))) = 4 := by + rw [GaloisRing.natCard_adjoinRoot f_monic, f_natDegree] + norm_num + +/-! ### Digit lifts and interpolation -/ + +/-- The digit vector `(0, 1)`. -/ +def c₀₁ : Fin f.natDegree → ZMod 2 := fun i => if (i : ℕ) = 1 then 1 else 0 + +/-- The digit vector `(1, 0)`. -/ +def c₁₀ : Fin f.natDegree → ZMod 2 := fun i => if (i : ℕ) = 0 then 1 else 0 + +/-- The lift of `(0, 1)` is `U`, and the lift of `(1, 0)` is `1`. -/ +theorem digitLift_c₀₁ : + GaloisRing.digitLift 2 3 f c₀₁ = U ∧ GaloisRing.digitLift 2 3 f c₁₀ = 1 := by + simp only [GaloisRing.digitLift, Fin.sum_univ_eq_sum_range + (fun i => (((if i = 1 then 1 else 0 : ZMod 2)).val : A) * AdjoinRoot.root _ ^ i), + Fin.sum_univ_eq_sum_range + (fun i => (((if i = 0 then 1 else 0 : ZMod 2)).val : A) * AdjoinRoot.root _ ^ i), + c₀₁, c₁₀, f_natDegree, Finset.sum_range_succ, Finset.sum_range_zero] + exact ⟨by simp [ZMod.val_one]; rfl, by simp [ZMod.val_one]⟩ + +/-- `U - 1` is a unit, as the difference of two distinct digit lifts. -/ +example : IsUnit (U - 1) := by + rw [← digitLift_c₀₁.1, ← digitLift_c₀₁.2] + refine GaloisRing.isUnit_digitLift_sub f_monic f_irreducible fun h => ?_ + have := congrFun h ⟨1, by rw [f_natDegree]; norm_num⟩ + simp [c₀₁, c₁₀] at this + +/-- There are `4` digit vectors, so a cubic over the ring is the interpolant of its values at the +four digit lifts. -/ +example (P : A[X]) (hP : P.natDegree ≤ 3) : + P = Lagrange.ringInterpolate Finset.univ (GaloisRing.digitLift 2 3 f) + (fun c => P.eval (GaloisRing.digitLift 2 3 f c)) := by + refine GaloisRing.eq_ringInterpolate_digitLift f_monic f_irreducible + ((degree_le_natDegree).trans_lt ?_) + rw [Finset.card_univ, Fintype.card_fun, ZMod.card, Fintype.card_fin, f_natDegree] + exact_mod_cast (show P.natDegree < 2 ^ 2 by omega) + +end ComplexityTests.GaloisRing From 31ff8a6ae3d937e6263f8f29045eb1005a0cf90d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 18:40:37 -0400 Subject: [PATCH 05/38] 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 8ea2c2b14..ffc52e5b2 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 000000000..d44097932 --- /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 000000000..044fad57b --- /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 000000000..998c69fca --- /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 06/38] 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 ffc52e5b2..09b1a7b72 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 d44097932..be7ad962d 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 044fad57b..000000000 --- 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 000000000..3c249ac98 --- /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 998c69fca..65ad05b7a 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 07/38] 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 8ea2c2b14..72282f4bf 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 000000000..422460a8e --- /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 000000000..227e628e6 --- /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 08/38] 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 b11988bdb..db420cdf2 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 000000000..77494386c --- /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 000000000..eb5731330 --- /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 f6826a6c5efd2a0383251a4fe5caa9a985d3ae7c Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:07:13 -0400 Subject: [PATCH 09/38] feat(circuit/frontier): local deformation and finite-precision tapered recovery (Lemma D.5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The evaluation bridge for the deformed contraction F(t + αz) and the precision half of Lemma D.5 of OpenAI2026ETH (§D.4.4, pp. 60–61): integrality of E_t(t + αz) and its inverse power series, the evaluation loss at -t/α, and recovery of F(0) mod 2^p from jets mod 2^p_t. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 5 +- .../TensorNetwork/LocalDeformation.lean | 223 ++++++++ .../TensorNetwork/TaperedRecovery.lean | 475 ++++++++++++++++++ .../TensorNetworkLocalDeformation.lean | 68 +++ .../TensorNetworkTaperedRecovery.lean | 50 ++ 5 files changed, 818 insertions(+), 3 deletions(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/LocalDeformation.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkLocalDeformation.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean diff --git a/Complexity.lean b/Complexity.lean index 8bb600f47..baecded44 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -869,15 +869,14 @@ 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.Deformation -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit 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.IntegralCoordinates +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery 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/LocalDeformation.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/LocalDeformation.lean new file mode 100644 index 000000000..5f497f546 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/LocalDeformation.lean @@ -0,0 +1,223 @@ +/- +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 + +/-! +# The deformed contraction in local coordinates + +This file connects the polynomial `F(T)` of (D.8) in [OpenAI2026ETH] (§D.4.3, p. 59) with its +values at the interpolation points of §D.4.4–§D.4.5 (pp. 61–62). + +Let `B` be a commutative ring receiving `ℤ_(2)` through `κ`, and let `N` be a Boolean tensor network +with entries in `B`. For a packing of the edges into packets of `2 ^ a` slots and a grid point `t`, +the *local deformation* (`TensorNetwork.localDeformation`) is the deformed contraction of `N` along +the matrix of local kernels `W_{y - x}(t + α z)` over `B[z]` (`cauchyLocalMatrix`), built from +`Cslib.Circuits.Frontier.localKernel`. It is the manuscript's `F(t + α z) ∈ A[z]`: + +* in a `ℚ`-algebra `R` receiving `B` compatibly with `ℚ`, its image is `F(t + α z)`, where `F(T)` + is the deformed contraction along the Cauchy matrix + (`TensorNetwork.map_localDeformation`), whose value at `0` is the contraction + (`TensorNetwork.eval_zero_deformedContraction_cauchyMatrix`); +* under any ring homomorphism `π : B → R'`, its value at `z = ζ` is the deformed contraction of the + reduced network along the Cauchy matrix at `T = t + α ζ` + (`TensorNetwork.eval₂_localDeformation`), which the child identity + `TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum` expands when `2 ^ p = 0` in `R'`. + +So the values of the children of §D.4.6 at `ζ` are values of one polynomial over `B`, and +interpolation recovers its reduction. + +## Main definitions + +* `Cslib.Circuits.Frontier.cauchyLocalMatrix`: the Cauchy matrix of a packing in local + coordinates, over `B[z]`. +* `Cslib.Circuits.Frontier.TensorNetwork.localDeformation`: `F(t + α z)` over `B`. + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.map_localDeformation`: its image in a `ℚ`-algebra is + `F(t + α z)`. +* `Cslib.Circuits.Frontier.TensorNetwork.eval₂_localDeformation`: its values are deformed + contractions along `cauchyValueMatrix`. +* `Cslib.Circuits.Frontier.TensorNetwork.natDegree_deformedContraction_le`, + `Cslib.Circuits.Frontier.TensorNetwork.natDegree_localDeformation_le`, + `Cslib.Circuits.Frontier.TensorNetwork.natDegree_deformedContraction_cauchyMatrix_le`: both have + degree at most `M (2 L - 1)` for `M` edges and `L` slots. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.3–§D.4.5, + pp. 59–62. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Polynomial + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +variable {B V E P : Type*} [CommRing B] + +/-- The Cauchy matrix of a packing into packets of `2 ^ a` slots in local coordinates +`z ↦ W(t + α z)`, `α = 2 ^ (a + 1)`, over `B[z]` for a ring `B` receiving `ℤ_(2)` through `κ`: +between two edges of the same packet, the local kernel at the difference of their slots, and zero +between packets. -/ +@[expose] noncomputable def cauchyLocalMatrix [DecidableEq P] (κ : ℤ₍₂₎ →+* B) (a : ℕ) (t : ℤ) + (packet : E → P) (slot : E → ℕ) : Matrix E E B[X] := + fun e e' => + if packet e = packet e' then (localKernel a t ((slot e' : ℤ) - slot e)).map κ else 0 + +namespace TensorNetwork + +/-- Mapping the tensors twice is mapping them by the composition. -/ +theorem map_map {R S T U : Type*} [CommSemiring R] [CommSemiring S] [CommSemiring T] + (f : R →+* S) (g : S →+* T) (N : TensorNetwork R U V E) : (N.map f).map g = N.map (g.comp f) := + rfl + +variable [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] + +/-- The deformed contraction along the Cauchy matrix of a packing in local coordinates at the grid +point `t`, as a polynomial in `z` over `B`: the manuscript's `F(t + α z) ∈ A[z]`. -/ +@[expose] noncomputable def localDeformation (N : TensorNetwork B Bool V E) (side : V → Bool) + (κ : ℤ₍₂₎ →+* B) (a : ℕ) (t : ℤ) (packet : E → P) (slot : E → ℕ) : B[X] := + (N.map C).deformedContraction side (cauchyLocalMatrix κ a t packet slot) + +/-- **The local deformation is `F(t + α z)`.** In a `ℚ`-algebra `R` receiving `B` through `ι` +compatibly with `ℚ`, the image of the local deformation at a grid point `t` is the deformed +contraction `F(T)` along the Cauchy matrix, composed with `T = t + α z`. -/ +theorem map_localDeformation {R : Type*} [CommRing R] [Algebra ℚ R] (N : TensorNetwork B Bool V E) + (side : V → Bool) (κ : ℤ₍₂₎ →+* B) (ι : B →+* R) + (hκ : ι.comp κ = (algebraMap ℚ R).comp (ℤ₍₂₎).subtype) {a : ℕ} {packet : E → P} + {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) : + (N.localDeformation side κ a t packet slot).map ι = + (((N.map ι).map C).deformedContraction side + ((cauchyMatrix (2 ^ a) packet slot).map (mapRingHom (algebraMap ℚ R)))).comp + (C (t : R) + C (2 ^ (a + 1)) * X) := by + rw [← coe_mapRingHom, localDeformation, ← deformedContraction_map, ← coe_compRingHom_apply, + ← deformedContraction_map, map_map, map_map, map_map] + have hlin : (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).map (algebraMap ℚ R) = + C (t : R) + C (2 ^ (a + 1)) * X := by + rw [Polynomial.map_add, Polynomial.map_mul, Polynomial.map_C, Polynomial.map_C, + Polynomial.map_X, map_intCast, map_pow, map_ofNat] + congr 1 + · congr 1 + ext x : 1 + simp only [RingHom.coe_comp, Function.comp_apply, coe_mapRingHom, Polynomial.map_C, + coe_compRingHom_apply, C_comp] + · ext e e' + simp only [Matrix.map_apply, cauchyLocalMatrix, cauchyMatrix, coe_mapRingHom, + coe_compRingHom_apply] + split_ifs with hp + · rw [Polynomial.map_map, hκ, ← Polynomial.map_map, + map_localKernel ht (sub_mem_cauchyGrid (hslot e) (hslot e')), Polynomial.map_comp, hlin] + · rw [Polynomial.map_zero, Polynomial.map_zero, zero_comp] + +/-- **Values of the local deformation.** Under a ring homomorphism `π : B → R'`, the value of the +local deformation at `z = ζ` is the deformed contraction of the reduced network along the Cauchy +matrix at `T = t + α ζ`. -/ +theorem eval₂_localDeformation {R' : Type*} [CommRing R'] (N : TensorNetwork B Bool V E) + (side : V → Bool) (κ : ℤ₍₂₎ →+* B) (π : B →+* R') (ζ : R') (a : ℕ) (t : ℤ) (packet : E → P) + (slot : E → ℕ) : + (N.localDeformation side κ a t packet slot).eval₂ π ζ = + (N.map π).deformedContraction side (cauchyValueMatrix (π.comp κ) ζ a t packet slot) := by + rw [← coe_eval₂RingHom, localDeformation, ← deformedContraction_map, map_map] + congr 1 + · congr 1 + ext x : 1 + simp only [RingHom.coe_comp, Function.comp_apply, coe_eval₂RingHom, eval₂_C] + · ext e e' + simp only [Matrix.map_apply, cauchyLocalMatrix, cauchyValueMatrix, coe_eval₂RingHom] + split_ifs + · rw [eval₂_map] + rfl + · exact eval₂_zero _ _ + +/-! ### Degrees -/ + +/-- **Degree of a deformed contraction.** For tensors that are constants and a matrix whose entries +have degree at most `d`, the deformed contraction has degree at most `d` times the number of +edges: it is a sum of products of one affine factor per edge. -/ +theorem natDegree_deformedContraction_le {R : Type*} [CommRing R] (N : TensorNetwork R Bool V E) + (side : V → Bool) (W : Matrix E E R[X]) {d : ℕ} (hW : ∀ e e', (W e e').natDegree ≤ d) : + ((N.map C).deformedContraction side W).natDegree ≤ Fintype.card E * d := by + unfold deformedContraction + refine natDegree_sum_le_of_forall_le _ _ fun X _ => natDegree_sum_le_of_forall_le _ _ + fun Y _ => ?_ + have hc : ∀ s : Finset V, ∀ x : E → Bool, (∏ v ∈ s, (N.map C).tensor v x).natDegree = 0 := + fun s x => by simp only [map_tensor, ← map_prod, natDegree_C] + have hv : ∀ e, (W.mulVec (fun e' => if X e' then (1 : R[X]) else 0) e).natDegree ≤ d := by + intro e + refine natDegree_sum_le_of_forall_le _ _ fun e' _ => ?_ + dsimp only + split_ifs + · rw [mul_one]; exact hW e e' + · rw [mul_zero, natDegree_zero]; exact Nat.zero_le _ + have hprod : (∏ e, affineFactor (Y e) + (W.mulVec (fun e' => if X e' then (1 : R[X]) else 0) e)).natDegree ≤ Fintype.card E * d := by + refine (natDegree_prod_le _ _).trans ?_ + rw [← smul_eq_mul, ← Finset.card_univ, ← Finset.sum_const] + refine Finset.sum_le_sum fun e _ => ?_ + unfold affineFactor + split_ifs + · exact hv e + · exact (natDegree_sub_le _ _).trans (max_le (by rw [natDegree_one]; exact Nat.zero_le _) + (hv e)) + refine (natDegree_mul_le).trans ?_ + have h2 := natDegree_mul_le (p := ∏ v ∈ Finset.univ.filter (side · = true), (N.map C).tensor v Y) + (q := ∏ v ∈ Finset.univ.filter (side · = false), (N.map C).tensor v X) + rw [hc, hc] at h2 + omega + +/-- On the grid, the local kernel has degree at most `2 L - 1`, `L = 2 ^ a`. -/ +theorem natDegree_localKernel_le {a : ℕ} {t d : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hd : d ∈ cauchyGrid (2 ^ a)) : (localKernel a t d).natDegree ≤ 2 * 2 ^ a - 1 := by + rw [← natDegree_map_eq_of_injective (f := (ℤ₍₂₎).subtype) Subtype.val_injective, + map_localKernel ht hd] + refine natDegree_comp_le.trans ?_ + rw [natDegree_cauchyKernel hd] + have : (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).natDegree ≤ 1 := + (natDegree_add_le _ _).trans (max_le (by rw [natDegree_C]; exact Nat.zero_le _) + ((natDegree_C_mul_le _ _).trans natDegree_X_le)) + calc (2 * 2 ^ a - 1) * (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).natDegree + ≤ (2 * 2 ^ a - 1) * 1 := Nat.mul_le_mul_left _ this + _ = 2 * 2 ^ a - 1 := mul_one _ + +/-- The local deformation has degree at most `M (2 L - 1)` for `M` edges. -/ +theorem natDegree_localDeformation_le (N : TensorNetwork B Bool V E) (side : V → Bool) + (κ : ℤ₍₂₎ →+* B) {a : ℕ} {packet : E → P} {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) + {t : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) : + (N.localDeformation side κ a t packet slot).natDegree ≤ Fintype.card E * (2 * 2 ^ a - 1) := by + refine natDegree_deformedContraction_le N side _ fun e e' => ?_ + unfold cauchyLocalMatrix + split_ifs + · exact natDegree_map_le.trans + (natDegree_localKernel_le ht (sub_mem_cauchyGrid (hslot e) (hslot e'))) + · rw [natDegree_zero]; exact Nat.zero_le _ + +/-- The deformed contraction `F(T)` along the Cauchy matrix of a packing into packets of `L` slots +has degree at most `M (2 L - 1)` for `M` edges (§D.4.3 of [OpenAI2026ETH], p. 59). -/ +theorem natDegree_deformedContraction_cauchyMatrix_le {R : Type*} [CommRing R] [Algebra ℚ R] + (N : TensorNetwork R Bool V E) (side : V → Bool) {L : ℕ} {packet : E → P} {slot : E → ℕ} + (hslot : ∀ e, slot e < L) : + ((N.map C).deformedContraction side + ((cauchyMatrix L packet slot).map (mapRingHom (algebraMap ℚ R)))).natDegree ≤ + Fintype.card E * (2 * L - 1) := by + refine natDegree_deformedContraction_le N side _ fun e e' => ?_ + simp only [Matrix.map_apply, cauchyMatrix, coe_mapRingHom] + split_ifs + · exact natDegree_map_le.trans + (natDegree_cauchyKernel (sub_mem_cauchyGrid (hslot e) (hslot e'))).le + · rw [Polynomial.map_zero, natDegree_zero]; exact Nat.zero_le _ + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean new file mode 100644 index 000000000..4d914e464 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean @@ -0,0 +1,475 @@ +/- +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.Foundations.Data.Polynomial.TaperedHermite +import Mathlib.Algebra.Polynomial.Eval.Degree +import Mathlib.RingTheory.PowerSeries.Trunc + +/-! +# Tapered Hermite recovery with finite precision + +This file finishes Lemma D.5 of [OpenAI2026ETH] (§D.4.4, pp. 60–61): the value `F(0)` modulo +`2 ^ p` is recovered from the local jets of `F(t + α z)` modulo `2 ^ p_t`. + +**Setting.** Let `K` be a `ℚ`-algebra and `B` a subring of `K` containing `ℤ_(2)`, the rationals +with odd denominator (in the manuscript, `K = ℚ[U] / (f)` and `B = O`). The nodes are the nonzero +points `t` of the grid `S = [-L, L)` of `L = 2 ^ a` slots (`recoveryNodes`), with the tapered +multiplicities `q_t = h max(0, u - v₂ t)` (`taperMult`), and `α = 2 ^ (a + 1)`. From polynomials +`G t ∈ K[z]`, the recovered value (`taperedRecovery`) is the right side of (D.9), + + `∑_t E_t(0) (G t / E_t(t + α z) mod z ^ q_t)(-t / α)`, + +with the Hermite basis `E_t` and quotient of +`Polynomial.eval_eq_sum_hermiteBasis_mul_hermiteQuotient`. For `G t = F(t + α z)` and +`deg F < ∑_t q_t` it is `F(0)` (`eval_zero_eq_taperedRecovery`), the manuscript's (D.9). + +**Precision.** Each `E_t(t + α z)` is a product of powers of `1 + α z / (t - q)`, so it has +coefficients in `ℤ_(2)` and constant term one, and so has the inverse power series +(`hermiteBasis_comp_eq_map`, `coeff_invOfUnit_hermiteBasis_comp_mem`). The quotient of a jet that +is divisible by `2 ^ p_t` is therefore divisible by `2 ^ p_t`. Evaluating a polynomial of degree +at most `q_t` at `-t / α` multiplies by rationals of valuation at least `-q_t (a + 1)`, and `E_t(0)` +has valuation at least `-6 h D_t` with `D_t = 2 ^ (a - v₂ t)` ((D.10), +`Polynomial.neg_le_padicValRat_eval_zero_hermiteBasis_taper`). So if the jets `G t` agree with +`F(t + α z)` modulo `2 ^ p_t` below `z ^ q_t`, where `p + 6 h D_t + q_t (a + 1) ≤ p_t`, the +recovered value agrees with `F(0)` modulo `2 ^ p` (`exists_taperedRecovery_eq_add`). The +manuscript's choice `p_t = p + 8 h D_t + 8 M (a + 1)` satisfies this when `q_t ≤ 4 M` +(`taperMult_le`); the manuscript's multiplicities have enough total weight +(`le_sum_taperMult`). + +Congruences are stated in `K` as `x = y + 2 ^ p * b` with `b ∈ B`. + +## Main definitions + +* `Cslib.Circuits.Frontier.recoveryNodes`: the nonzero points of the grid. +* `Cslib.Circuits.Frontier.taperMult`: the tapered multiplicities `q_t`. +* `Cslib.Circuits.Frontier.taperedRecovery`: the right side of (D.9) for given jets. + +## Main results + +* `Cslib.Circuits.Frontier.le_sum_taperMult`: the total multiplicity is at least `2 ^ a h u`. +* `Cslib.Circuits.Frontier.taperMult_le`: `q_t ≤ 4 M` for `h = ⌈3 M / u⌉` and `u ≤ M`. +* `Cslib.Circuits.Frontier.eval_zero_eq_taperedRecovery`: (D.9). +* `Cslib.Circuits.Frontier.hermiteBasis_comp_eq_map`: `E_t(t + α z)` is integral with constant + term one. +* `Cslib.Circuits.Frontier.exists_taperedRecovery_eq_add`: recovery modulo `2 ^ p` from jets + modulo `2 ^ p_t`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.4, Lemma D.5, + (D.9)–(D.10), pp. 60–61. +-/ + +public section + +namespace Cslib.Circuits.Frontier + +open Polynomial Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### Nodes and multiplicities -/ + +/-- The nodes of tapered Hermite recovery: the nonzero points of the grid of `2 ^ a` slots. -/ +@[expose] def recoveryNodes (a : ℕ) : Finset ℤ := + (cauchyGrid (2 ^ a)).erase 0 + +/-- The tapered multiplicity `q_t = h max(0, u - v₂ t)` of Lemma D.5, with natural +subtraction. -/ +@[expose] def taperMult (u h : ℕ) (t : ℤ) : ℕ := + h * (u - padicValInt 2 t) + +/-- The grid of `2 ^ a` slots is `[-2 ^ a, 2 ^ a)`. -/ +theorem cauchyGrid_two_pow (a : ℕ) : cauchyGrid (2 ^ a) = Ico (-(2 : ℤ) ^ a) (2 ^ a) := by + simp only [cauchyGrid, Nat.cast_pow, Nat.cast_ofNat] + +theorem recoveryNodes_eq (a : ℕ) : recoveryNodes a = (Ico (-(2 : ℤ) ^ a) (2 ^ a)).erase 0 := by + rw [recoveryNodes, cauchyGrid_two_pow] + +/-- **There is enough multiplicity.** The `2 ^ a` odd nodes have multiplicity `h u` each. -/ +theorem le_sum_taperMult (a u h : ℕ) : + 2 ^ a * (h * u) ≤ ∑ t ∈ recoveryNodes a, taperMult u h t := by + have key := sum_Ico_erase_zero_padicValInt_two a fun j => ((h * (u - j) : ℕ) : ℤ) + rw [← recoveryNodes_eq] at key + have hsum : ((∑ t ∈ recoveryNodes a, taperMult u h t : ℕ) : ℤ) = + ∑ j ∈ range (a + 1), 2 ^ (a - j) * ((h * (u - j) : ℕ) : ℤ) := by + rw [← key] + push_cast [taperMult] + rfl + have hle : ((2 ^ a * (h * u) : ℕ) : ℤ) ≤ + ∑ j ∈ range (a + 1), 2 ^ (a - j) * ((h * (u - j) : ℕ) : ℤ) := by + rw [sum_range_succ'] + simp only [Nat.sub_zero, Nat.cast_mul, Nat.cast_pow, Nat.cast_ofNat] + have : 0 ≤ ∑ j ∈ range a, (2 : ℤ) ^ (a - (j + 1)) * ((h : ℤ) * ((u - (j + 1) : ℕ) : ℤ)) := + sum_nonneg fun j _ => by positivity + linarith + exact_mod_cast hle.trans hsum.symm.le + +/-- **Multiplicities are at most `4 M`** when `h = ⌈3 M / u⌉` (any `h ≤ 3 M / u + 1`) and +`u ≤ M`. -/ +theorem taperMult_le {u h M : ℕ} (hh : h * u ≤ 3 * M + u) (huM : u ≤ M) (t : ℤ) : + taperMult u h t ≤ 4 * M := by + unfold taperMult + calc h * (u - padicValInt 2 t) ≤ h * u := Nat.mul_le_mul_left _ (Nat.sub_le _ _) + _ ≤ 4 * M := by omega + +/-! ### The recovered value -/ + +variable {K : Type*} [CommRing K] [Algebra ℚ K] + +/-- The value recovered by tapered Hermite interpolation from the polynomials `G t`: the right +side of (D.9) of [OpenAI2026ETH], `∑_t E_t(0) (G t / E_t(t + α z) mod z ^ q_t)(-t / α)` over the +nonzero grid points `t`, with `α = 2 ^ (a + 1)`. Only the first `q_t` coefficients of `G t` +matter (`Polynomial.hermiteQuotient_trunc`). -/ +noncomputable def taperedRecovery (a u h : ℕ) (G : ℤ → K[X]) : K := + ∑ t ∈ recoveryNodes a, + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) t).eval 0 * + (hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) (2 ^ (a + 1)) t + (G t)).eval (algebraMap ℚ K (-(t : ℚ) / 2 ^ (a + 1))) + +/-- `2 ^ n` as a unit of a `ℚ`-algebra. -/ +private noncomputable def twoPowUnit (K : Type*) [CommRing K] [Algebra ℚ K] (n : ℕ) : Kˣ := + Units.map (algebraMap ℚ K : ℚ →* K) (Units.mk0 ((2 : ℚ) ^ n) (by positivity)) + +private theorem coe_twoPowUnit (n : ℕ) : ((twoPowUnit K n : Kˣ) : K) = 2 ^ n := by + simp [twoPowUnit, map_pow, map_ofNat] + +private theorem coe_twoPowUnit_inv (n : ℕ) : + ((twoPowUnit K n)⁻¹ : Kˣ) = algebraMap ℚ K ((2 : ℚ) ^ n)⁻¹ := by + simp [twoPowUnit, Units.val_inv_eq_inv_val] + +/-- Distinct integers are units of a `ℚ`-algebra apart. -/ +private theorem pairwise_isUnit_sub (s : Finset ℤ) : + (s : Set ℤ).Pairwise fun i j => IsUnit ((i : K) - (j : K)) := by + intro i _ j _ hij + have h : ((i : ℚ) - j) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast hij) + have := (isUnit_iff_ne_zero.mpr h).map (algebraMap ℚ K) + simpa using this + +/-- **Hermite reconstruction (D.9)** of [OpenAI2026ETH]: for `deg F < ∑_t q_t`, the recovered value +from the local polynomials `F(t + α z)` is `F(0)`. -/ +theorem eval_zero_eq_taperedRecovery {a u h : ℕ} {F : K[X]} + (hF : F.degree < ↑(∑ t ∈ recoveryNodes a, taperMult u h t)) : + F.eval 0 = taperedRecovery a u h fun t => F.comp (C (t : K) + C (2 ^ (a + 1)) * X) := by + rw [eval_eq_sum_hermiteBasis_mul_hermiteQuotient (pairwise_isUnit_sub _) (twoPowUnit K (a + 1)) + hF 0, taperedRecovery] + refine sum_congr rfl fun t _ => ?_ + rw [coe_twoPowUnit, coe_twoPowUnit_inv, zero_sub, ← map_intCast (algebraMap ℚ K) t, ← map_neg, + ← map_mul] + congr 3 + ring + +/-! ### Integrality of the Hermite basis in local coordinates -/ + +/-- The rationals `α / (t - q)` with `t ≠ q` on the grid, `α = 2 ^ (a + 1)`, lie in `ℤ_(2)`. -/ +private theorem padicValuation_two_pow_div_le_one {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : + Rat.padicValuation 2 ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) ≤ 1 := by + have hle := padicValuation_two_pow_le_sub ht hq hne + have hne' : ((t : ℚ) - q) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast hne.symm) + rw [map_div₀, div_le_one₀ ((Rat.padicValuation 2).pos_iff.mpr hne')] + refine le_trans ?_ hle + rw [pow_succ, map_mul] + exact mul_le_of_le_one_right' (by + rw [show (2 : ℚ) = ((2 : ℕ) : ℚ) by norm_num, Rat.padicValuation_natCast] + exact Int.padicValuation_le_one 2 2) + +/-- The Hermite basis over `ℚ` in local coordinates is a product of powers of +`1 + α / (t - q) z`. -/ +private theorem hermiteBasis_comp_rat (a u h : ℕ) (t : ℤ) : + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).comp + (C (t : ℚ) + C (2 ^ (a + 1)) * X) = + ∏ q ∈ (recoveryNodes a).erase t, + (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X) ^ taperMult u h q := by + rw [hermiteBasis, Polynomial.prod_comp] + refine prod_congr rfl fun q hq => ?_ + have hqt : ((t : ℚ) - q) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast (ne_of_mem_erase hq).symm) + rw [pow_comp, mul_comp, C_comp, sub_comp, X_comp, C_comp, Ring.inverse_eq_inv'] + congr 1 + rw [show C (t : ℚ) + C (2 ^ (a + 1)) * X - C (q : ℚ) = + C ((t : ℚ) - q) * (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X) by + rw [mul_add, mul_one, ← mul_assoc, ← C_mul, mul_div_cancel₀ _ hqt, C_sub]; ring, + ← mul_assoc, ← C_mul, inv_mul_cancel₀ hqt, C_1, one_mul] + +/-- **Lemma D.5, integrality.** For a nonzero grid point `t`, the Hermite basis polynomial in +local coordinates, `E_t(t + α z)`, is the image of a polynomial over `ℤ_(2)` with constant term +one: each factor is a power of `1 + α z / (t - q)` with `α / (t - q)` in `ℤ_(2)`. -/ +theorem hermiteBasis_comp_eq_map (a u h : ℕ) {t : ℤ} (ht : t ∈ recoveryNodes a) : + ∃ E : ℤ₍₂₎[X], E.coeff 0 = 1 ∧ + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) t).comp + (C (t : K) + C (2 ^ (a + 1)) * X) = + E.map ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype) := by + have htS : t ∈ cauchyGrid (2 ^ a) := mem_of_mem_erase ht + -- each factor is integral + have hfac : ∀ q ∈ (recoveryNodes a).erase t, + (1 + C ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) * X) ∈ lifts (ℤ₍₂₎).subtype := fun q hq => + add_mem (one_mem _) (mul_mem (C_mem_lifts _ (⟨_, padicValuation_two_pow_div_le_one htS + (mem_of_mem_erase (mem_of_mem_erase hq)) (ne_of_mem_erase hq)⟩ : ℤ₍₂₎)) (X_mem_lifts _)) + obtain ⟨E, hE⟩ := (mem_lifts _).mp (Subsemiring.prod_mem _ fun q hq => + Subsemiring.pow_mem _ (hfac q hq) (taperMult u h q)) + have hEq : E.map (ℤ₍₂₎).subtype = (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) + (taperMult u h) t).comp (C (t : ℚ) + C (2 ^ (a + 1)) * X) := by + rw [hE, hermiteBasis_comp_rat] + refine ⟨E, ?_, ?_⟩ + · apply Subtype.val_injective + have h0 := congrArg (fun P => Polynomial.coeff P 0) hEq + simp only [coeff_map, Subring.coe_subtype] at h0 + rw [h0, coeff_zero_eq_eval_zero, eval_comp, eval_add, eval_C, eval_mul, eval_C, eval_X, + mul_zero, add_zero, eval_hermiteBasis_self (pairwise_isUnit_sub _) ht] + rfl + · have hv : (fun q : ℤ => algebraMap ℚ K (q : ℚ)) = fun q : ℤ => (q : K) := + funext fun q => map_intCast _ q + have hlin : (C (t : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).map (algebraMap ℚ K) = + C (t : K) + C (2 ^ (a + 1)) * X := by + rw [Polynomial.map_add, Polynomial.map_mul, Polynomial.map_C, Polynomial.map_C, + Polynomial.map_X, map_intCast, map_pow, map_ofNat] + rw [← Polynomial.map_map, hEq, Polynomial.map_comp, + map_hermiteBasis _ (pairwise_isUnit_sub _) ht, hv, hlin] + +/-- The inverse power series of `E_t(t + α z)` is the image of a power series over `ℤ_(2)`. -/ +private theorem invOfUnit_hermiteBasis_comp_eq_map (a u h : ℕ) {t : ℤ} (ht : t ∈ recoveryNodes a) : + ∃ ψ : PowerSeries ℤ₍₂₎, + PowerSeries.invOfUnit (((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) + (taperMult u h) t).comp (C (t : K) + C (2 ^ (a + 1)) * X) : K[X]) : PowerSeries K) 1 = + PowerSeries.map ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype) ψ := by + obtain ⟨E, hE0, hE⟩ := hermiteBasis_comp_eq_map (K := K) a u h ht + refine ⟨PowerSeries.invOfUnit (E : PowerSeries ℤ₍₂₎) 1, ?_⟩ + set κ := (algebraMap ℚ K).comp (ℤ₍₂₎).subtype + set φ : PowerSeries K := ((E.map κ : K[X]) : PowerSeries K) + have hφ : φ = PowerSeries.map κ (E : PowerSeries ℤ₍₂₎) := Polynomial.polynomial_map_coe + have h1 : φ * PowerSeries.invOfUnit φ 1 = 1 := + PowerSeries.mul_invOfUnit _ _ (by + rw [Polynomial.constantCoeff_coe, coeff_map, hE0, map_one, Units.val_one]) + have h2 : φ * PowerSeries.map κ (PowerSeries.invOfUnit (E : PowerSeries ℤ₍₂₎) 1) = 1 := by + rw [hφ, ← map_mul, PowerSeries.mul_invOfUnit _ _ (by + rw [Polynomial.constantCoeff_coe, hE0, Units.val_one]), map_one] + rw [hE] + calc PowerSeries.invOfUnit φ 1 + = PowerSeries.invOfUnit φ 1 * (φ * PowerSeries.map κ + (PowerSeries.invOfUnit (E : PowerSeries ℤ₍₂₎) 1)) := by rw [h2, mul_one] + _ = PowerSeries.map κ (PowerSeries.invOfUnit (E : PowerSeries ℤ₍₂₎) 1) := by + rw [← mul_assoc, mul_comm _ φ, h1, one_mul] + +/-! ### Congruences modulo powers of two -/ + +private theorem padicValRat_two : padicValRat 2 (2 : ℚ) = 1 := by + exact_mod_cast padicValRat.self (p := 2) one_lt_two + +omit [Algebra ℚ K] in +/-- `x` is `2 ^ n` times an element of `B`. -/ +private def InTwoPow (B : Subring K) (n : ℕ) (x : K) : Prop := + ∃ b ∈ B, x = 2 ^ n * b + +omit [Algebra ℚ K] in +private theorem inTwoPow_zero (B : Subring K) (n : ℕ) : InTwoPow B n (0 : K) := + ⟨0, B.zero_mem, (mul_zero _).symm⟩ + +omit [Algebra ℚ K] in +private theorem InTwoPow.add {B : Subring K} {n : ℕ} {x y : K} (hx : InTwoPow B n x) + (hy : InTwoPow B n y) : InTwoPow B n (x + y) := by + obtain ⟨b, hb, rfl⟩ := hx + obtain ⟨c, hc, rfl⟩ := hy + exact ⟨b + c, B.add_mem hb hc, by ring⟩ + +omit [Algebra ℚ K] in +private theorem InTwoPow.sub {B : Subring K} {n : ℕ} {x y : K} (hx : InTwoPow B n x) + (hy : InTwoPow B n y) : InTwoPow B n (x - y) := by + obtain ⟨b, hb, rfl⟩ := hx + obtain ⟨c, hc, rfl⟩ := hy + exact ⟨b - c, B.sub_mem hb hc, by ring⟩ + +omit [Algebra ℚ K] in +private theorem inTwoPow_sum {B : Subring K} {n : ℕ} {ι : Type*} (s : Finset ι) {f : ι → K} + (hf : ∀ i ∈ s, InTwoPow B n (f i)) : InTwoPow B n (∑ i ∈ s, f i) := by + classical + induction s using Finset.induction_on with + | empty => exact inTwoPow_zero B n + | insert j s hj ih => + rw [sum_insert hj] + exact (hf j (mem_insert_self j s)).add (ih fun i hi => hf i (mem_insert_of_mem hi)) + +omit [Algebra ℚ K] in +private theorem InTwoPow.mul_left {B : Subring K} {n : ℕ} {x y : K} (hx : InTwoPow B n x) + (hy : y ∈ B) : InTwoPow B n (x * y) := by + obtain ⟨b, hb, rfl⟩ := hx + exact ⟨b * y, B.mul_mem hb hy, by ring⟩ + +/-- A rational of valuation at least `-k` maps `2 ^ (n + k) B` into `2 ^ n B`. -/ +private theorem InTwoPow.algebraMap_mul {B : Subring K} + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {n k : ℕ} {x : K} (hx : InTwoPow B (n + k) x) + {r : ℚ} (hr : r ≠ 0) (hv : -(k : ℤ) ≤ padicValRat 2 r) : + InTwoPow B n (algebraMap ℚ K r * x) := by + obtain ⟨b, hb, rfl⟩ := hx + have hr2 : r * 2 ^ k ∈ ℤ₍₂₎ := by + have hne : r * 2 ^ k ≠ 0 := mul_ne_zero hr (by positivity) + have hval : 0 ≤ padicValRat 2 (r * 2 ^ k) := by + rw [padicValRat.mul hr (by positivity), padicValRat.pow, padicValRat_two] + omega + change Rat.padicValuation 2 (r * 2 ^ k) ≤ 1 + simp only [Rat.padicValuation, Valuation.coe_mk, MonoidWithZeroHom.coe_mk, ZeroHom.coe_mk, + hne, ↓reduceIte] + rw [← WithZero.exp_zero, WithZero.exp_le_exp] + omega + refine ⟨algebraMap ℚ K (r * 2 ^ k) * b, B.mul_mem (hB _ hr2) hb, ?_⟩ + rw [map_mul, map_pow, map_ofNat, pow_add] + ring + +/-- **The quotient of a jet divisible by `2 ^ n` is divisible by `2 ^ n`**: the inverse power +series of `E_t(t + α z)` is integral. -/ +private theorem inTwoPow_coeff_hermiteQuotient {B : Subring K} + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {a u h n : ℕ} {t : ℤ} (ht : t ∈ recoveryNodes a) + {P : K[X]} (hP : ∀ j < taperMult u h t, InTwoPow B n (P.coeff j)) (j : ℕ) : + InTwoPow B n ((hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) + (2 ^ (a + 1)) t P).coeff j) := by + obtain ⟨ψ, hψ⟩ := invOfUnit_hermiteBasis_comp_eq_map (K := K) a u h ht + rw [hermiteQuotient, hψ, PowerSeries.coeff_trunc] + split_ifs with hj + · rw [PowerSeries.coeff_mul] + refine inTwoPow_sum _ fun kl hkl => ?_ + have hk : kl.1 < taperMult u h t := by + have := mem_antidiagonal.mp hkl + omega + rw [Polynomial.coeff_coe, PowerSeries.coeff_map] + exact (hP _ hk).mul_left (hB _ (PowerSeries.coeff kl.2 ψ).2) + · exact inTwoPow_zero B n + +/-- The value of `E_t(0)` in `K` is the image of the rational one. -/ +private theorem eval_zero_hermiteBasis_eq (a u h : ℕ) {t : ℤ} (ht : t ∈ recoveryNodes a) : + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) t).eval 0 = + algebraMap ℚ K + ((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).eval 0) := by + have hv : (fun q : ℤ => algebraMap ℚ K (q : ℚ)) = fun q : ℤ => (q : K) := + funext fun q => map_intCast _ q + rw [← hv, ← map_hermiteBasis _ (pairwise_isUnit_sub _) ht, eval_map, + ← map_zero (algebraMap ℚ K), eval₂_at_apply] + +/-- `E_t(0) ≠ 0`: its factors are `-q / (t - q)` with `q ≠ 0`. -/ +private theorem eval_zero_hermiteBasis_ne_zero (a u h : ℕ) {t : ℤ} : + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).eval 0 ≠ 0 := by + rw [eval_hermiteBasis, prod_ne_zero_iff] + intro q hq + have hq0 : (q : ℚ) ≠ 0 := by exact_mod_cast ne_of_mem_erase (mem_of_mem_erase hq) + have hqt : ((t : ℚ) - q) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast (ne_of_mem_erase hq).symm) + rw [Ring.inverse_eq_inv'] + exact pow_ne_zero _ (mul_ne_zero (inv_ne_zero hqt) (by rwa [zero_sub, neg_ne_zero])) + +/-- **Lemma D.5, precision** ([OpenAI2026ETH], §D.4.4, pp. 60–61). Let `B` be a subring of the +`ℚ`-algebra `K` containing `ℤ_(2)`, and let `F ∈ K[T]` have degree below the total multiplicity +`∑_t q_t`. Suppose that at every nonzero grid point `t` the polynomial `G t` agrees with +`F(t + α z)` modulo `2 ^ p_t` below `z ^ q_t`, where `p + 6 h D_t + q_t (a + 1) ≤ p_t` and +`D_t = 2 ^ (a - v₂ t)`. Then the recovered value agrees with `F(0)` modulo `2 ^ p`. -/ +theorem exists_taperedRecovery_eq_add (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) + {a u h p : ℕ} (pt : ℤ → ℕ) + (hpt : ∀ t ∈ recoveryNodes a, + p + 6 * h * 2 ^ (a - padicValInt 2 t) + taperMult u h t * (a + 1) ≤ pt t) + {F : K[X]} (hF : F.degree < ↑(∑ t ∈ recoveryNodes a, taperMult u h t)) (G : ℤ → K[X]) + (hG : ∀ t ∈ recoveryNodes a, ∀ j < taperMult u h t, ∃ b ∈ B, + (G t).coeff j = (F.comp (C (t : K) + C (2 ^ (a + 1)) * X)).coeff j + 2 ^ pt t * b) : + ∃ b ∈ B, taperedRecovery a u h G = F.eval 0 + 2 ^ p * b := by + suffices hsub : InTwoPow B p (taperedRecovery a u h G - F.eval 0) by + obtain ⟨b, hb, hb'⟩ := hsub + exact ⟨b, hb, by rw [← hb', add_sub_cancel]⟩ + rw [eval_zero_eq_taperedRecovery hF, taperedRecovery, taperedRecovery, ← sum_sub_distrib] + refine inTwoPow_sum _ fun t ht => ?_ + set F' := F.comp (C (t : K) + C (2 ^ (a + 1)) * X) + set J := taperMult u h t + set Q := hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) + (2 ^ (a + 1)) t ((G t - F' : K[X]) : PowerSeries K) + have hQ : ∀ j, InTwoPow B (pt t) (Q.coeff j) := by + refine inTwoPow_coeff_hermiteQuotient hB ht (P := G t - F') (fun j hj => ?_) + obtain ⟨b, hb, hb'⟩ := hG t ht j hj + exact ⟨b, hb, by rw [coeff_sub, hb', add_sub_cancel_left]⟩ + have hQsub : hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) + (2 ^ (a + 1)) t (G t) - hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) + (taperMult u h) (2 ^ (a + 1)) t F' = Q := by + simp only [Q, hermiteQuotient, Polynomial.coe_sub, sub_mul, map_sub] + have hdeg : Q.natDegree < J + 1 := by + have := degree_hermiteQuotient_lt (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) + (2 ^ (a + 1)) t ((G t - F' : K[X]) : PowerSeries K) + by_cases hQ0 : Q = 0 + · rw [hQ0, natDegree_zero]; omega + · have := (natDegree_lt_iff_degree_lt hQ0).mpr this + omega + rw [← mul_sub, ← eval_sub, hQsub, eval_eq_sum_range' hdeg, mul_sum, + eval_zero_hermiteBasis_eq a u h ht] + refine inTwoPow_sum _ fun j hj => ?_ + have hjJ : j ≤ J := Nat.lt_succ_iff.mp (mem_range.mp hj) + set E0 := (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).eval 0 + have ht0 : (t : ℚ) ≠ 0 := by exact_mod_cast ne_of_mem_erase ht + have hx0 : (-(t : ℚ) / 2 ^ (a + 1)) ≠ 0 := div_ne_zero (neg_ne_zero.mpr ht0) (by positivity) + have hr : E0 * (-(t : ℚ) / 2 ^ (a + 1)) ^ j ≠ 0 := + mul_ne_zero (eval_zero_hermiteBasis_ne_zero a u h) (pow_ne_zero _ hx0) + have hE0 : -(6 * (h : ℤ) * 2 ^ (a - padicValInt 2 t)) ≤ padicValRat 2 E0 := by + have := neg_le_padicValRat_eval_zero_hermiteBasis_taper (a := a) u h + (mem_of_mem_erase (by rw [← recoveryNodes_eq]; exact ht)) (ne_of_mem_erase ht) + rw [← recoveryNodes_eq] at this + exact this + have hxv : -((a : ℤ) + 1) ≤ padicValRat 2 (-(t : ℚ) / 2 ^ (a + 1)) := by + rw [padicValRat.div (neg_ne_zero.mpr ht0) (by positivity), padicValRat.neg, + padicValRat.of_int, padicValRat.pow, padicValRat_two] + push_cast + omega + have hval : -((pt t - p : ℕ) : ℤ) ≤ padicValRat 2 (E0 * (-(t : ℚ) / 2 ^ (a + 1)) ^ j) := by + rw [padicValRat.mul (eval_zero_hermiteBasis_ne_zero a u h) (pow_ne_zero _ hx0), + padicValRat.pow] + have hpt' := hpt t ht + have hJ : (j : ℤ) * padicValRat 2 (-(t : ℚ) / 2 ^ (a + 1)) ≥ -((J : ℤ) * ((a : ℤ) + 1)) := by + have : (j : ℤ) ≤ J := by exact_mod_cast hjJ + nlinarith + have : ((pt t - p : ℕ) : ℤ) = (pt t : ℤ) - p := by omega + push_cast [this] + have hpt'' : (p : ℤ) + 6 * h * 2 ^ (a - padicValInt 2 t) + (J : ℤ) * (a + 1) ≤ pt t := by + exact_mod_cast hpt' + linarith + have hk : p + (pt t - p) = pt t := by have := hpt t ht; omega + have hQj := hQ j + rw [← hk] at hQj + have := hQj.algebraMap_mul hB hr hval + rw [map_mul, map_pow] at this + convert this using 1 + ring + +/-- **Lemma D.5 with the manuscript's parameters.** Let `1 ≤ u ≤ M`, let `h` satisfy +`3 M ≤ h u ≤ 3 M + u` (as `h = ⌈3 M / u⌉` does), and let `F` have degree at most `M (2 L - 1)`, +`L = 2 ^ a`. If the jets `G t` agree with `F(t + α z)` modulo `2 ^ p_t` below `z ^ q_t`, with +`p_t = p + 8 h D_t + 8 M (a + 1)`, then the recovered value agrees with `F(0)` modulo `2 ^ p`. -/ +theorem exists_taperedRecovery_eq_add_of_params (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {a u h p M : ℕ} (hu : 1 ≤ u) (huM : u ≤ M) + (hh : 3 * M ≤ h * u) (hh' : h * u ≤ 3 * M + u) {F : K[X]} + (hF : F.natDegree ≤ M * (2 * 2 ^ a - 1)) (G : ℤ → K[X]) + (hG : ∀ t ∈ recoveryNodes a, ∀ j < taperMult u h t, ∃ b ∈ B, + (G t).coeff j = (F.comp (C (t : K) + C (2 ^ (a + 1)) * X)).coeff j + + 2 ^ (p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * M * (a + 1)) * b) : + ∃ b ∈ B, taperedRecovery a u h G = F.eval 0 + 2 ^ p * b := by + refine exists_taperedRecovery_eq_add B hB + (fun t => p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * M * (a + 1)) (fun t _ => ?_) ?_ G hG + · have hJ := taperMult_le hh' huM t + have hD : 6 * h * 2 ^ (a - padicValInt 2 t) ≤ 8 * h * 2 ^ (a - padicValInt 2 t) := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by norm_num)) + have hJa : taperMult u h t * (a + 1) ≤ 8 * M * (a + 1) := + Nat.mul_le_mul_right _ (by omega) + omega + · refine (degree_le_natDegree).trans_lt ?_ + have hsum := le_sum_taperMult a u h + have hM : 1 ≤ M := hu.trans huM + have h2a : 1 ≤ 2 ^ a := Nat.one_le_two_pow + have : M * (2 * 2 ^ a - 1) < 2 ^ a * (h * u) := by + have h3 : 2 ^ a * (3 * M) ≤ 2 ^ a * (h * u) := Nat.mul_le_mul_left _ hh + have : M * (2 * 2 ^ a - 1) < 2 ^ a * (3 * M) := by + rw [Nat.mul_sub, mul_one] + have : M * (2 * 2 ^ a) = 2 * (2 ^ a * M) := by ring + have : 2 ^ a * (3 * M) = 3 * (2 ^ a * M) := by ring + have : 1 ≤ 2 ^ a * M := Nat.one_le_iff_ne_zero.mpr (by positivity) + omega + omega + exact_mod_cast (hF.trans_lt (this.trans_le hsum)) + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkLocalDeformation.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkLocalDeformation.lean new file mode 100644 index 000000000..eea99015e --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkLocalDeformation.lean @@ -0,0 +1,68 @@ +/- +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.LocalDeformation +public import Complexity.Foundations.NumberTheory.Padics.RatValuation + +/-! +# Tests for the deformed contraction in local coordinates + +The two-edge network of the Cauchy-mode tests, with tensors over `ℤ_(2)`: one packet of two +slots (`a = 1`), at the grid point `t = 1`. Its local deformation is a polynomial over `ℤ_(2)` of +degree at most `2 (2 * 2 - 1) = 6`; in `ℚ` it is `F(1 + 4 z)`, and modulo `4` its value at `ζ` is +the deformed contraction along the Cauchy matrix at `T = 1 + 4 ζ`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation + +open Cslib.Circuits.Frontier TensorNetwork Polynomial + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-- The two-edge network over `ℤ_(2)`, with tensor `(1, 2)` on the bit of the edge at each +vertex. -/ +def net : TensorNetwork ℤ₍₂₎ 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 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) + +/-- Edge `e` sits in slot `e` of the single packet. -/ +def slot (e : Fin 2) : ℕ := e.val + +theorem hslot (e : Fin 2) : slot e < 2 ^ 1 := by fin_cases e <;> decide + +/-- The degree bound `M (2 L - 1)`. -/ +example : (net.localDeformation side (RingHom.id _) 1 1 (fun _ => ()) slot).natDegree ≤ 6 := + natDegree_localDeformation_le net side (RingHom.id _) hslot (by decide) + +/-- In `ℚ`, the local deformation is `F(1 + 4 z)` for the deformed contraction `F(T)` along the +Cauchy matrix. -/ +example : (net.localDeformation side (RingHom.id _) 1 1 (fun _ => ()) slot).map (ℤ₍₂₎).subtype = + (((net.map (ℤ₍₂₎).subtype).map C).deformedContraction side + ((cauchyMatrix (2 ^ 1) (fun _ => ()) slot).map (mapRingHom (algebraMap ℚ ℚ)))).comp + (C ((1 : ℤ) : ℚ) + C (2 ^ (1 + 1)) * X) := + map_localDeformation net side (RingHom.id _) (ℤ₍₂₎).subtype rfl hslot (by decide) + +/-- Modulo `4`, its value at `ζ` is the deformed contraction along the Cauchy matrix at +`T = 1 + 4 ζ`. -/ +example (ζ : ZMod (2 ^ 2)) : + (net.localDeformation side (RingHom.id _) 1 1 (fun _ => ()) slot).eval₂ + (Rat.padicIntegerToZModPow 2 2) ζ = + (net.map (Rat.padicIntegerToZModPow 2 2)).deformedContraction side + (cauchyValueMatrix (Rat.padicIntegerToZModPow 2 2) ζ 1 1 (fun _ => ()) slot) := + eval₂_localDeformation net side (RingHom.id _) _ ζ 1 1 _ slot + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean new file mode 100644 index 000000000..af21199d2 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean @@ -0,0 +1,50 @@ +/- +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.TaperedRecovery + +/-! +# Tests for tapered Hermite recovery with finite precision + +The grid of two slots (`a = 1`) has the nodes `-2, -1, 1`. With `u = 2` and `h = 3`, the odd nodes +have multiplicity `6` and the node `-2`, of valuation one, has multiplicity `3`. The precision +theorem applies over `K = ℚ` with `B = ℤ_(2)`: its hypotheses are met by exact jets. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery + +open Cslib.Circuits.Frontier Polynomial + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +example : recoveryNodes 1 = {-2, -1, 1} := by decide + +example : taperMult 2 3 1 = 6 ∧ taperMult 2 3 (-1) = 6 := by + simp [taperMult, padicValInt] + +example : taperMult 2 3 (-2) = 3 := by + simp [taperMult, padicValInt] + +/-- The total multiplicity of the two odd nodes alone is `2 * 6`. -/ +example : 2 ^ 1 * (3 * 2) ≤ ∑ t ∈ recoveryNodes 1, taperMult 2 3 t := + le_sum_taperMult 1 2 3 + +/-- **Non-vacuity.** For `F = X` (degree `1 = M (2 L - 1)` with `M = 1`, `L = 1`), `u = 1` and +`h = 3`, exact jets recover `F(0)` modulo every `2 ^ p`. -/ +example (p : ℕ) : + ∃ b ∈ ℤ₍₂₎, taperedRecovery 0 1 3 (fun t => (X : ℚ[X]).comp (C (t : ℚ) + C (2 ^ (0 + 1)) * X)) = + (X : ℚ[X]).eval 0 + 2 ^ p * b := + exists_taperedRecovery_eq_add_of_params (K := ℚ) (M := 1) ℤ₍₂₎ (fun c hc => hc) le_rfl le_rfl + (by norm_num) (by norm_num) (by rw [natDegree_X]; norm_num) _ + (fun t _ j _ => ⟨0, zero_mem _, by rw [mul_zero, add_zero]⟩) + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery From f9349461854369452a5b74f4698f350ab194f7a5 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:11:30 -0400 Subject: [PATCH 10/38] refactor(foundations): move AdjoinRoot coefficient reduction to its own module `AdjoinRoot.map_mk`, `AdjoinRoot.map_surjective_of_surjective` and `AdjoinRoot.map_eq_zero_iff` move unchanged from `GaloisRing.lean` to the new `Complexity/Foundations/RingTheory/AdjoinRoot.lean`. Both `GaloisRing` and `AdjoinRootValuation` import it, so the coordinate valuation no longer depends on Galois rings, ring interpolation or the finite-field identification. Names are unchanged; `GaloisRing` re-exports the module publicly. Addresses the placement finding of reviewer-codex-edemaine on #1859. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Padics/AdjoinRootValuation.lean | 3 +- .../Foundations/RingTheory/AdjoinRoot.lean | 85 +++++++++++++++++++ .../Foundations/RingTheory/GaloisRing.lean | 57 +------------ 4 files changed, 89 insertions(+), 57 deletions(-) create mode 100644 Complexity/Foundations/RingTheory/AdjoinRoot.lean diff --git a/Complexity.lean b/Complexity.lean index 7ae579ac4..6e4816571 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2777,6 +2777,7 @@ public import Complexity.Foundations.Probability.TrueSampleMeans public import Complexity.Foundations.Quantum.Measurement public import Complexity.Foundations.Relation.InductiveCounting public import Complexity.Foundations.Relation.RelatesWithinSteps +public import Complexity.Foundations.RingTheory.AdjoinRoot public import Complexity.Foundations.RingTheory.CyclicIntRing public import Complexity.Foundations.RingTheory.CyclicIntRing.Coefficients public import Complexity.Foundations.RingTheory.GaloisRing diff --git a/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean index 707dad117..edb4016e1 100644 --- a/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean +++ b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootValuation.lean @@ -11,7 +11,8 @@ public import Complexity.Init public import Mathlib.NumberTheory.Padics.PadicNumbers public import Mathlib.RingTheory.AdjoinRoot public import Mathlib.RingTheory.Valuation.Integers -import Complexity.Foundations.RingTheory.GaloisRing +import Complexity.Foundations.RingTheory.AdjoinRoot +import Mathlib.Algebra.Field.ZMod import Mathlib.Algebra.GCDMonoid.IntegrallyClosed import Mathlib.Algebra.Polynomial.Eval.Irreducible import Mathlib.RingTheory.Localization.Integral diff --git a/Complexity/Foundations/RingTheory/AdjoinRoot.lean b/Complexity/Foundations/RingTheory/AdjoinRoot.lean new file mode 100644 index 000000000..39bfed75e --- /dev/null +++ b/Complexity/Foundations/RingTheory/AdjoinRoot.lean @@ -0,0 +1,85 @@ +/- +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/RingTheory/AdjoinRoot.lean`) + +public import Complexity.Init +public import Mathlib.RingTheory.AdjoinRoot + +/-! +# Reducing the coefficients of `R[X] / (g)` + +For a ring homomorphism `φ : R →+* S` and `q ∣ g.map φ`, Mathlib's `AdjoinRoot.map φ g q` sends +`R[X] / (g)` to `S[X] / (q)`. This file computes it on classes of polynomials, shows that it is +surjective when `φ` is, and characterizes its kernel when `g` is monic and `q = g.map φ`: an element +vanishes exactly when the coefficients of its reduced representative do. + +These facts are used both for Galois rings (`GaloisRing.residue`, reduction modulo `p`) and for the +`p`-adic valuation of `ℚ[X] / (f)` (`AdjoinRoot.padicValuation`). + +## Main results + +* `AdjoinRoot.map_mk`: the class of `P` maps to the class of `P.map φ`. +* `AdjoinRoot.map_surjective_of_surjective`: reducing the coefficients is surjective when `φ` is. +* `AdjoinRoot.map_eq_zero_iff`: an element of `R[X] / (g)` vanishes in `S[X] / (g.map φ)` exactly + when the coefficients of its reduced representative do. +-/ + +public section + +open Polynomial + +namespace AdjoinRoot + +variable {R S : Type*} [CommRing R] [CommRing S] (φ : R →+* S) {g : R[X]} + +/-- Reducing the coefficients maps the class of `P` to the class of `P.map φ`. -/ +theorem map_mk (q : S[X]) (h : q ∣ g.map φ) (P : R[X]) : + map φ g q h (mk g P) = mk q (P.map φ) := by + have : (map φ g q h).comp (mk g) = (mk q).comp (mapRingHom φ) := by + refine Polynomial.ringHom_ext (fun a => ?_) ?_ + · simp only [RingHom.comp_apply, mk_C, map_of, coe_mapRingHom, Polynomial.map_C] + · simp only [RingHom.comp_apply, mk_X, map_root, coe_mapRingHom, Polynomial.map_X] + exact congrArg (fun F : R[X] →+* AdjoinRoot q => F P) this + +/-- Reducing the coefficients is surjective when `φ` is. -/ +theorem map_surjective_of_surjective (q : S[X]) (h : q ∣ g.map φ) + (hφ : Function.Surjective φ) : Function.Surjective (map φ g q h) := by + intro y + obtain ⟨Q, rfl⟩ := mk_surjective y + obtain ⟨P, rfl⟩ := Polynomial.map_surjective φ hφ Q + exact ⟨mk g P, map_mk φ q h P⟩ + +/-- **An element vanishes after reducing the coefficients** of a monic `g` exactly when the +coefficients of its representative of degree below `deg g` do: that representative reduces to a +polynomial of degree below `deg (g.map φ)`, which `g.map φ` divides only when it is zero. -/ +theorem map_eq_zero_iff [Nontrivial S] (hg : g.Monic) {q : S[X]} (h : q ∣ g.map φ) + (hq : q = g.map φ) (x : AdjoinRoot g) : + map φ g q h x = 0 ↔ ∀ i, φ ((modByMonicHom hg x).coeff i) = 0 := by + subst hq + have := φ.domain_nontrivial + obtain ⟨Q, rfl⟩ := mk_surjective x + rw [modByMonicHom_mk] + set P := Q %ₘ g + have hx : mk g P = mk g Q := by + have := mk_leftInverse hg (mk g Q) + rwa [modByMonicHom_mk] at this + have hdeg : (P.map φ).degree < (g.map φ).degree := by + rw [hg.degree_map] + exact degree_map_le.trans_lt (degree_modByMonic_lt _ hg) + rw [← hx, map_mk, mk_eq_zero] + constructor + · intro hdvd i + by_contra hne + have h0 : P.map φ ≠ 0 := fun h0 => hne (by rw [← coeff_map, h0, coeff_zero]) + exact (hg.map φ).not_dvd_of_degree_lt h0 hdeg hdvd + · intro h + have : P.map φ = 0 := Polynomial.ext fun i => by rw [coeff_map, h, coeff_zero] + rw [this] + exact dvd_zero _ + +end AdjoinRoot diff --git a/Complexity/Foundations/RingTheory/GaloisRing.lean b/Complexity/Foundations/RingTheory/GaloisRing.lean index da9bc2de1..79a1b2965 100644 --- a/Complexity/Foundations/RingTheory/GaloisRing.lean +++ b/Complexity/Foundations/RingTheory/GaloisRing.lean @@ -9,8 +9,8 @@ module public import Complexity.Init public import Complexity.Foundations.Data.Polynomial.RingLagrange +public import Complexity.Foundations.RingTheory.AdjoinRoot public import Mathlib.FieldTheory.Finite.GaloisField -public import Mathlib.RingTheory.AdjoinRoot public import Mathlib.RingTheory.LocalRing.ResidueField.Defs import Mathlib.RingTheory.LocalRing.ResidueField.Basic import Mathlib.RingTheory.Polynomial.Nilpotent @@ -48,8 +48,6 @@ statement over `ℤ_(p)` is `AdjoinRoot.isUnit_of_padicValuation_eq_one`. * `RingHom.isLocalHom_of_isNilpotent`: a surjection with nilpotent kernel is a local homomorphism. -* `AdjoinRoot.map_eq_zero_iff`: an element of `R[X] / (g)` vanishes in `S[X] / (g)` exactly when - the coefficients of its reduced representative do. * `GaloisRing.ker_residue`: the kernel of reduction modulo `p` is `(p)`. * `GaloisRing.isUnit_iff`: an element is a unit exactly when it is nonzero modulo `p`. * `GaloisRing.instIsLocalRing`, `GaloisRing.maximalIdeal_eq`: the ring is local with maximal ideal @@ -97,59 +95,6 @@ theorem isUnit_iff_ne_zero_of_isNilpotent {F : Type*} [Field F] (ψ : R →+* F) end RingHom -/-! ### Reducing the coefficients of `R[X] / (g)` -/ - -namespace AdjoinRoot - -variable {R S : Type*} [CommRing R] [CommRing S] (φ : R →+* S) {g : R[X]} - -/-- Reducing the coefficients maps the class of `P` to the class of `P.map φ`. -/ -theorem map_mk (q : S[X]) (h : q ∣ g.map φ) (P : R[X]) : - map φ g q h (mk g P) = mk q (P.map φ) := by - have : (map φ g q h).comp (mk g) = (mk q).comp (mapRingHom φ) := by - refine Polynomial.ringHom_ext (fun a => ?_) ?_ - · simp only [RingHom.comp_apply, mk_C, map_of, coe_mapRingHom, Polynomial.map_C] - · simp only [RingHom.comp_apply, mk_X, map_root, coe_mapRingHom, Polynomial.map_X] - exact congrArg (fun F : R[X] →+* AdjoinRoot q => F P) this - -/-- Reducing the coefficients is surjective when `φ` is. -/ -theorem map_surjective_of_surjective (q : S[X]) (h : q ∣ g.map φ) - (hφ : Function.Surjective φ) : Function.Surjective (map φ g q h) := by - intro y - obtain ⟨Q, rfl⟩ := mk_surjective y - obtain ⟨P, rfl⟩ := Polynomial.map_surjective φ hφ Q - exact ⟨mk g P, map_mk φ q h P⟩ - -/-- **An element vanishes after reducing the coefficients** of a monic `g` exactly when the -coefficients of its representative of degree below `deg g` do: that representative reduces to a -polynomial of degree below `deg (g.map φ)`, which `g.map φ` divides only when it is zero. -/ -theorem map_eq_zero_iff [Nontrivial S] (hg : g.Monic) {q : S[X]} (h : q ∣ g.map φ) - (hq : q = g.map φ) (x : AdjoinRoot g) : - map φ g q h x = 0 ↔ ∀ i, φ ((modByMonicHom hg x).coeff i) = 0 := by - subst hq - have := φ.domain_nontrivial - obtain ⟨Q, rfl⟩ := mk_surjective x - rw [modByMonicHom_mk] - set P := Q %ₘ g - have hx : mk g P = mk g Q := by - have := mk_leftInverse hg (mk g Q) - rwa [modByMonicHom_mk] at this - have hdeg : (P.map φ).degree < (g.map φ).degree := by - rw [hg.degree_map] - exact degree_map_le.trans_lt (degree_modByMonic_lt _ hg) - rw [← hx, map_mk, mk_eq_zero] - constructor - · intro hdvd i - by_contra hne - have h0 : P.map φ ≠ 0 := fun h0 => hne (by rw [← coeff_map, h0, coeff_zero]) - exact (hg.map φ).not_dvd_of_degree_lt h0 hdeg hdvd - · intro h - have : P.map φ = 0 := Polynomial.ext fun i => by rw [coeff_map, h, coeff_zero] - rw [this] - exact dvd_zero _ - -end AdjoinRoot - /-! ### Galois rings -/ /-- The ring `(ℤ / p ^ n)[X] / (f)` presented by an integer polynomial `f`. For a prime `p`, From 5189108242a32270693556cd43044d989591fe1d Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:11:30 -0400 Subject: [PATCH 11/38] doc(foundations): scope GaloisRing to the manuscript's base ring O/(2^n) On p. 57 of the pinned manuscript, O = Z_(2)[U]/(f), A = O[V]/(V^l) and A_p = A/(2^p). `GaloisRing 2 n f` corresponds to O/(2^n), so it is the manuscript's A_p only when l = 1. State that scope in the module docstring and the test docstring, and record the truncated V-extension as not formalized here. Addresses the source-scope finding of reviewer-codex-edemaine on #1859. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity/Foundations/RingTheory/GaloisRing.lean | 10 +++++++--- ComplexityTests/Foundations/RingTheory/GaloisRing.lean | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Complexity/Foundations/RingTheory/GaloisRing.lean b/Complexity/Foundations/RingTheory/GaloisRing.lean index 79a1b2965..b9f21502c 100644 --- a/Complexity/Foundations/RingTheory/GaloisRing.lean +++ b/Complexity/Foundations/RingTheory/GaloisRing.lean @@ -28,9 +28,13 @@ The *digit lifts* `∑ i, c i * X ^ i` with digits `c i ∈ {0, …, p - 1}` red distinct residue-field elements, so any two of them differ by a unit, and Lagrange interpolation over the ring holds at any set of them (`GaloisRing.eq_ringInterpolate_digitLift`). -This is the coefficient ring of the Boolean tensor contraction of [OpenAI2026ETH] (§D.4.1, -pp. 57–58), which takes `p = 2`, a degree `e = 4 ⌈log₂ s⌉` and the precision `n`; the manuscript's -binary-coordinate lifts are the digit lifts for `p = 2`. +This is the base coefficient ring of the Boolean tensor contraction of [OpenAI2026ETH] (§D.4.1, +pp. 57–58), with `p = 2`, a degree `e = 4 ⌈log₂ s⌉` and the precision `n`. The manuscript puts +`O = ℤ_(2)[U] / (f)` and `A = O[V] / (V ^ ℓ)`, and computes in `A / (2 ^ n)` (written `A_p` there, +with `p` the precision). `GaloisRing 2 n f` corresponds to `O / (2 ^ n)`, since +`ℤ_(2) / (2 ^ n) ≅ ℤ / 2 ^ n` (that identification is not formalized here). It is therefore the +manuscript's `A / (2 ^ n)` only when `ℓ = 1`; the truncated `V`-extension for general `ℓ` is not +formalized here. The manuscript's binary-coordinate lifts are the digit lifts for `p = 2`. The argument is that of a surjection onto a field with nilpotent kernel (`RingHom.isUnit_iff_ne_zero_of_isNilpotent`): reduction modulo `p` has kernel `(p)`, and diff --git a/ComplexityTests/Foundations/RingTheory/GaloisRing.lean b/ComplexityTests/Foundations/RingTheory/GaloisRing.lean index c11fcc94d..9a4438a77 100644 --- a/ComplexityTests/Foundations/RingTheory/GaloisRing.lean +++ b/ComplexityTests/Foundations/RingTheory/GaloisRing.lean @@ -10,7 +10,8 @@ import Mathlib.Tactic.ComputeDegree import Mathlib.Tactic.FinCases /-! Tests for Galois rings, on `(ℤ / 8)[U] / (U ^ 2 + U + 1)`: the case `p = 2`, `e = 2` and -precision `3` of the coefficient ring of [OpenAI2026ETH], §D.4.1. -/ +precision `3` of the base coefficient ring `O / (2 ^ 3)` of [OpenAI2026ETH], §D.4.1, which is the +manuscript's `A_3` when `ℓ = 1`. -/ namespace ComplexityTests.GaloisRing From 1e12be93f698fbdbe699e102b5e54cd73d2898e0 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:18:04 -0400 Subject: [PATCH 12/38] feat(circuit/frontier): correctness of one active call, packing bridge and child sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One active call of the contraction recursion of OpenAI2026ETH (§D.4.4–§D.4.7, pp. 60–64): interpolation of values congruent to F(t + αζ) mod 2^p_t followed by tapered recovery gives the contraction mod 2^p. Also the Lemma D.3 (2) orientation bridge for PacketPacking.Packing, the child degree and edge bounds of §D.4.6, and field lemmas for cauchyModeSplitting. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 4 +- .../Frontier/TensorNetwork/ActiveCall.lean | 284 ++++++++++++++++++ .../Frontier/TensorNetwork/CauchyModes.lean | 27 ++ .../Frontier/TensorNetwork/ChildSize.lean | 131 ++++++++ .../Frontier/TensorNetworkActiveCall.lean | 109 +++++++ 5 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildSize.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean diff --git a/Complexity.lean b/Complexity.lean index b6d298b92..8cbfae21c 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -869,7 +869,9 @@ 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.ActiveCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildSize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit @@ -2822,8 +2824,8 @@ 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.RatValuation public import Complexity.Foundations.NumberTheory.Padics.AdjoinRootValuation +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/ActiveCall.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean new file mode 100644 index 000000000..cf9a248fe --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean @@ -0,0 +1,284 @@ +/- +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.LocalDeformation +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery +public import Complexity.Foundations.Data.Polynomial.RingLagrange +public import Complexity.Foundations.Combinatorics.PacketPacking +public import Mathlib.RingTheory.Ideal.Quotient.Defs +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ChildSize + +/-! +# One active call of the contraction recursion + +This file proves the correctness of one active call of the recursive contraction algorithm of +[OpenAI2026ETH] (§D.4.4–§D.4.7, pp. 61–64): the contraction modulo `2 ^ p` is recovered from the +contractions of the children modulo the higher precisions `2 ^ p_t`. + +**Setting.** Let `K` be a `ℚ`-algebra and `B` a subring containing `ℤ_(2)` (in the manuscript, +`K = ℚ[U] / (f)` and `B = O`), and let `N` be a Boolean tensor network with entries in `B` whose +edges run from rows to columns. Pack its `M` edges into packets of `L = 2 ^ a` slots, and choose +`n > M (2 L - 1)` interpolation points `ζ k ∈ B` whose differences are units (in the manuscript, +binary-coordinate lifts of residue-field elements). For each nonzero grid point `t` of positive +multiplicity `q_t` and each point `ζ k`, a value `v t k ∈ B` is supplied. + +**Recovery** (`recoverFromValues`). Interpolate the values `v t ·` at the points `ζ` over `B` +(`Lagrange.ringInterpolate`), and apply the tapered Hermite recovery (D.9) to the interpolants +(`Cslib.Circuits.Frontier.taperedRecovery`). + +**Correctness.** If every `v t k` agrees with the local deformation `F(t + α ζ k)` +(`TensorNetwork.localDeformation`) modulo `2 ^ p_t`, `p_t = p + 8 h D_t + 8 M (a + 1)`, then the +interpolant agrees with `F(t + α z)` modulo `2 ^ p_t` (`Lagrange.exists_ringInterpolate_eq_add`), +and Lemma D.5 (`Cslib.Circuits.Frontier.exists_taperedRecovery_eq_add_of_params`) recovers +`F(0) = Z(N)` modulo `2 ^ p` (`TensorNetwork.exists_recoverFromValues_eq_add`). Modulo `2 ^ p_t`, +`F(t + α ζ)` is the deformed contraction of the reduced network along the Cauchy matrix at +`T = t + α ζ` (`TensorNetwork.mk_eval_localDeformation`), which the child identity (D.16), +`TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum`, writes as the sum over the mode tuples +of the products of the contractions of the children. So the values may be computed from the +children's contractions modulo `2 ^ p_t` (`TensorNetwork.exists_recoverFromValues_eq_add_of_mk`). + +**The packing bridge.** The orientation that the child identity needs is property (2) of +Lemma D.3 for `Cslib.PacketPacking.Packing` (`TensorNetwork.packingGroup_src_tgt`), and the port +counts of Lemma D.3 (3) are the group sizes that bound the children +(`TensorNetwork.card_packingGroup_src`, `TensorNetwork.card_packingGroup_tgt`, with +`TensorNetwork.ModeSplitting.mul_card_edges_child_le`). + +## Main definitions + +* `Cslib.Circuits.Frontier.integerHom`: the inclusion of `ℤ_(2)` into `B`. +* `Cslib.Circuits.Frontier.recoverFromValues`: interpolation followed by tapered recovery. +* `Cslib.Circuits.Frontier.TensorNetwork.packingGroup`: the group of a vertex in coordinate `i` + of a packing. + +## Main results + +* `Lagrange.exists_ringInterpolate_eq_add`: interpolating values that agree with a polynomial + modulo `π` gives a polynomial that agrees with it modulo `π`. +* `Cslib.Circuits.Frontier.TensorNetwork.exists_recoverFromValues_eq_add`: recovery modulo + `2 ^ p` from values modulo `2 ^ p_t`. +* `Cslib.Circuits.Frontier.TensorNetwork.mk_eval_localDeformation`: the values of the local + deformation modulo `2 ^ p_t` are deformed contractions along `cauchyValueMatrix`. +* `Cslib.Circuits.Frontier.TensorNetwork.exists_recoverFromValues_eq_add_of_mk`: correctness of + one active call. +* `Cslib.Circuits.Frontier.TensorNetwork.packingGroup_src_tgt`: the packing bridge, with + `packing_slot_lt`, `packing_slot_injective`, `card_packingGroup_src` and + `card_packingGroup_tgt`. +* `Cslib.Circuits.Frontier.two_pow_padicValInt_dvd_and_not_dvd`: a node of valuation `i` meets + the divisibility hypotheses of the child identity. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.3 (p. 58), + §D.4.4–§D.4.7, pp. 60–64. +-/ + +public section + +open Polynomial Finset + +/-! ### Interpolation of congruent values -/ + +namespace Lagrange + +variable {R ι : Type*} [CommRing R] [DecidableEq ι] {s : Finset ι} {v : ι → R} + +/-- **Interpolating congruent values.** At nodes with unit differences, if the values `r i` agree +with a polynomial `f` of degree below `#s` modulo `π`, then so does every coefficient of their +interpolant. -/ +theorem exists_ringInterpolate_eq_add (hvs : (s : Set ι).Pairwise fun i j => IsUnit (v i - v j)) + {f : R[X]} (hf : f.degree < #s) {π : R} {r : ι → R} + (hr : ∀ i ∈ s, ∃ c, r i = f.eval (v i) + π * c) : + ∃ g : R[X], ringInterpolate s v r = f + C π * g := by + choose! c hc using hr + refine ⟨∑ i ∈ s, C (c i) * ringBasis s v i, ?_⟩ + rw [← sub_eq_iff_eq_add'] + calc ringInterpolate s v r - f + = ringInterpolate s v r - ringInterpolate s v (fun i => f.eval (v i)) := by + rw [sub_right_inj]; exact eq_ringInterpolate hvs hf + _ = ringInterpolate s v (r - fun i => f.eval (v i)) := (map_sub _ _ _).symm + _ = ∑ i ∈ s, C (π * c i) * ringBasis s v i := by + rw [ringInterpolate_apply] + refine sum_congr rfl fun i hi => ?_ + rw [Pi.sub_apply, hc i hi, add_sub_cancel_left] + _ = C π * ∑ i ∈ s, C (c i) * ringBasis s v i := by + rw [mul_sum] + exact sum_congr rfl fun i _ => by rw [C_mul, mul_assoc] + +end Lagrange + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +variable {K : Type*} [CommRing K] [Algebra ℚ K] + +/-- The inclusion of `ℤ_(2)` into a subring `B` of a `ℚ`-algebra containing it. -/ +@[expose] noncomputable def integerHom (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) : + ℤ₍₂₎ →+* B := + ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype).codRestrict B fun c => hB c c.2 + +theorem subtype_comp_integerHom (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) : + B.subtype.comp (integerHom B hB) = (algebraMap ℚ K).comp (ℤ₍₂₎).subtype := + rfl + +/-- The value recovered at an active call from the values `v t k` at the interpolation points +`ζ k`: interpolate over `B` at each node `t`, and apply tapered Hermite recovery (D.9). -/ +noncomputable def recoverFromValues (B : Subring K) (a u h : ℕ) {n : ℕ} (ζ : Fin n → B) + (v : ℤ → Fin n → B) : K := + taperedRecovery a u h fun t => (Lagrange.ringInterpolate univ ζ (v t)).map B.subtype + +/-- A nonzero integer `t` of valuation `i` is divisible by `2 ^ i` but not by `2 ^ (i + 1)`. -/ +theorem two_pow_padicValInt_dvd_and_not_dvd {t : ℤ} (ht : t ≠ 0) : + (2 : ℤ) ^ padicValInt 2 t ∣ t ∧ ¬ (2 : ℤ) ^ (padicValInt 2 t + 1) ∣ t := by + refine ⟨by exact_mod_cast padicValInt_dvd (p := 2) t, fun h => ?_⟩ + have := (padicValInt_dvd_iff (p := 2) (padicValInt 2 t + 1) t).mp (by exact_mod_cast h) + omega + +namespace TensorNetwork + +variable {V E P : Type*} [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] + +/-- **Correctness of recovery from values.** Let `N` have entries in `B`, edges from rows to +columns packed into packets of `2 ^ a` slots, and let `1 ≤ u ≤ M` and `3 M ≤ h u ≤ 3 M + u` for +`M` edges. Let the `n > M (2 L - 1)` points `ζ k` have unit differences. If at every node `t` of +positive multiplicity the values `v t k` agree with `F(t + α ζ k)` modulo `2 ^ p_t`, +`p_t = p + 8 h D_t + 8 M (a + 1)`, then the recovered value agrees with the contraction modulo +`2 ^ p`. -/ +theorem exists_recoverFromValues_eq_add (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) (N : TensorNetwork B Bool V E) (side : V → Bool) + {a : ℕ} {packet : E → P} {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) + (hinj : ∀ e e', packet e = packet e' → slot e = slot e' → e = e') {u h p : ℕ} (hu : 1 ≤ u) + (huM : u ≤ Fintype.card E) (hh : 3 * Fintype.card E ≤ h * u) + (hh' : h * u ≤ 3 * Fintype.card E + u) {n : ℕ} (ζ : Fin n → B) + (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) (hn : Fintype.card E * (2 * 2 ^ a - 1) < n) + (v : ℤ → Fin n → B) + (hv : ∀ t ∈ recoveryNodes a, 0 < taperMult u h t → ∀ k, ∃ c : B, + v t k = (N.localDeformation side (integerHom B hB) a t packet slot).eval (ζ k) + + 2 ^ (p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * Fintype.card E * (a + 1)) * c) : + ∃ b ∈ B, recoverFromValues B a u h ζ v = (N.contraction : K) + 2 ^ p * b := by + have hF0 : (((N.map B.subtype).map C).deformedContraction side + ((cauchyMatrix (2 ^ a) packet slot).map (mapRingHom (algebraMap ℚ K)))).eval 0 = + (N.contraction : K) := by + rw [eval_zero_deformedContraction_cauchyMatrix _ side hslot hinj, contraction_map] + rfl + rw [← hF0] + refine exists_taperedRecovery_eq_add_of_params B hB hu huM hh hh' + (natDegree_deformedContraction_cauchyMatrix_le _ side hslot) _ fun t ht j hj => ?_ + have htS : t ∈ cauchyGrid (2 ^ a) := mem_of_mem_erase ht + have hnat : (N.localDeformation side (integerHom B hB) a t packet slot).natDegree < n := + (natDegree_localDeformation_le N side (integerHom B hB) hslot htS).trans_lt hn + have hdeg : (N.localDeformation side (integerHom B hB) a t packet slot).degree < + ↑(#(univ : Finset (Fin n))) := by + rw [card_univ, Fintype.card_fin] + exact (degree_le_natDegree).trans_lt (Nat.cast_lt.mpr hnat) + obtain ⟨g, hg⟩ := Lagrange.exists_ringInterpolate_eq_add (s := univ) (v := ζ) + (fun k _ l _ hkl => hζ hkl) hdeg + (π := 2 ^ (p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * Fintype.card E * (a + 1))) + (fun k _ => hv t ht (by omega) k) + refine ⟨(g.coeff j : K), (g.coeff j).2, ?_⟩ + rw [← map_localDeformation N side (integerHom B hB) B.subtype (subtype_comp_integerHom B hB) + hslot htS, coeff_map, coeff_map, hg, coeff_add, coeff_C_mul, map_add, map_mul, map_pow] + rfl + +/-! ### The packing bridge -/ + +/-- The group of a vertex in coordinate `i` of a packing: its row label if it is a row and its +column label if it is a column. -/ +@[expose] def packingGroup {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) (side : V → Bool) + (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (v : V) : Fin r := + if side v then Q.rowLabel v i else Q.colLabel v i + +omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in +/-- **The packing bridge.** For a packing of the edges of a network whose edges run from rows to +columns (Lemma D.3 of [OpenAI2026ETH]), the groups in coordinate `i` are oriented by digit `i` of +the slots (Lemma D.3 (2)): the hypothesis of `TensorNetwork.cauchyModeSplitting`. -/ +theorem packingGroup_src_tgt {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (e : E) : + (packingGroup N side Q i (N.src e), packingGroup N side Q i (N.tgt e)) = + if (Q.slot e : ℕ).testBit i then (Q.symbols (Q.packet e) i).swap + else Q.symbols (Q.packet e) i := by + simp only [packingGroup, hsrc, htgt, ↓reduceIte, Bool.false_eq_true] + exact Q.labels_eq e i + +omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in +/-- The slots of a packing lie below `2 ^ a`. -/ +theorem packing_slot_lt {R : Type*} {a r : ℕ} {N : TensorNetwork R Bool V E} + (Q : PacketPacking.Packing N.src N.tgt a r) (e : E) : (Q.slot e : ℕ) < 2 ^ a := + (Q.slot e).isLt + +omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in +/-- No two edges share a slot of a packet. -/ +theorem packing_slot_injective {R : Type*} {a r : ℕ} {N : TensorNetwork R Bool V E} + (Q : PacketPacking.Packing N.src N.tgt a r) (e e' : E) (hp : Q.packet e = Q.packet e') + (hs : (Q.slot e : ℕ) = Q.slot e') : e = e' := + Q.injective (Prod.ext hp (Fin.ext hs)) + +omit [Fintype V] [DecidableEq E] [DecidableEq P] in +/-- The rows of group `σ` in coordinate `i` of a packing are the rows with symbol `σ`. -/ +theorem card_packingGroup_src {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (σ : Fin r) : + #{e | packingGroup N side Q i (N.src e) = σ} = #{e | Q.rowLabel (N.src e) i = σ} := by + simp only [packingGroup, hsrc, ↓reduceIte] + +omit [Fintype V] [DecidableEq E] [DecidableEq P] in +/-- The columns of group `σ` in coordinate `i` of a packing are the columns with symbol `σ`. -/ +theorem card_packingGroup_tgt {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) + (side : V → Bool) (htgt : ∀ e, side (N.tgt e) = false) + (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (σ : Fin r) : + #{e | packingGroup N side Q i (N.tgt e) = σ} = #{e | Q.colLabel (N.tgt e) i = σ} := by + simp only [packingGroup, htgt, Bool.false_eq_true, ↓reduceIte] + +/-- **The values of the local deformation modulo `2 ^ p_t`** are deformed contractions of the +reduced network along the Cauchy matrix at `T = t + α ζ` (which the child identity (D.16), +`TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum`, expands). -/ +theorem mk_eval_localDeformation {B : Type*} [CommRing B] (N : TensorNetwork B Bool V E) + (side : V → Bool) (κ : ℤ₍₂₎ →+* B) (a : ℕ) (t : ℤ) (packet : E → P) (slot : E → ℕ) + (I : Ideal B) (ζ : B) : + Ideal.Quotient.mk I ((N.localDeformation side κ a t packet slot).eval ζ) = + (N.map (Ideal.Quotient.mk I)).deformedContraction side + (cauchyValueMatrix ((Ideal.Quotient.mk I).comp κ) (Ideal.Quotient.mk I ζ) a t packet + slot) := by + rw [← eval₂_hom, eval₂_localDeformation] + +/-- **Correctness of one active call** ([OpenAI2026ETH], §D.4.4–§D.4.7). Under the hypotheses of +`exists_recoverFromValues_eq_add`, suppose that at every node `t` of positive multiplicity, each +value `v t k` reduces modulo `2 ^ p_t` to the deformed contraction of the reduced network along the +Cauchy matrix at `T = t + α ζ k`, `p_t = p + 8 h D_t + 8 M (a + 1)`: by the child identity (D.16), +the sum over the mode tuples of the products of the contractions of the children. Then the +recovered value agrees with the contraction modulo `2 ^ p`. -/ +theorem exists_recoverFromValues_eq_add_of_mk (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) (N : TensorNetwork B Bool V E) (side : V → Bool) + {a : ℕ} {packet : E → P} {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) + (hinj : ∀ e e', packet e = packet e' → slot e = slot e' → e = e') {u h p : ℕ} (hu : 1 ≤ u) + (huM : u ≤ Fintype.card E) (hh : 3 * Fintype.card E ≤ h * u) + (hh' : h * u ≤ 3 * Fintype.card E + u) {n : ℕ} (ζ : Fin n → B) + (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) (hn : Fintype.card E * (2 * 2 ^ a - 1) < n) + (v : ℤ → Fin n → B) + (hv : ∀ t ∈ recoveryNodes a, 0 < taperMult u h t → ∀ k, + let I := Ideal.span {(2 : B) ^ (p + 8 * h * 2 ^ (a - padicValInt 2 t) + + 8 * Fintype.card E * (a + 1))} + Ideal.Quotient.mk I (v t k) = + (N.map (Ideal.Quotient.mk I)).deformedContraction side + (cauchyValueMatrix ((Ideal.Quotient.mk I).comp (integerHom B hB)) + (Ideal.Quotient.mk I (ζ k)) a t packet slot)) : + ∃ b ∈ B, recoverFromValues B a u h ζ v = (N.contraction : K) + 2 ^ p * b := by + refine exists_recoverFromValues_eq_add B hB N side hslot hinj hu huM hh hh' ζ hζ hn v + fun t ht hq k => ?_ + have h1 := hv t ht hq k + simp only at h1 + rw [← mk_eval_localDeformation, Ideal.Quotient.eq, Ideal.mem_span_singleton'] at h1 + obtain ⟨c, hc⟩ := h1 + exact ⟨c, by rw [mul_comm, hc, add_sub_cancel]⟩ + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean index 77494386c..556464fe4 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/CauchyModes.lean @@ -585,6 +585,33 @@ noncomputable def cauchyModeSplitting (side : V → Bool) (hsrc : ∀ e, side (N simp rw [group_tgt_eq horient e, hb, hq] +section Fields + +variable (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) + +omit [Fintype E] in +@[simp] theorem cauchyModeSplitting_side : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').side = side := by + unfold cauchyModeSplitting; rfl + +omit [Fintype E] in +@[simp] theorem cauchyModeSplitting_group : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').group = group := by + unfold cauchyModeSplitting; rfl + +omit [Fintype E] in +@[simp] theorem cauchyModeSplitting_packet : + (cauchyModeSplitting side hsrc htgt horient hslot hia κ ζ hp ht hti hti').packet = packet := by + unfold cauchyModeSplitting; rfl + +end Fields + 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. -/ diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildSize.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildSize.lean new file mode 100644 index 000000000..390e7d894 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ChildSize.lean @@ -0,0 +1,131 @@ +/- +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.ModeExpansion +import Mathlib.Data.Fintype.Sum + +/-! +# Sizes of the child networks + +The child networks of a mode splitting (`TensorNetwork.ModeSplitting.child`, §D.4.6 of +[OpenAI2026ETH], p. 63) keep the invariants of the recursion: + +* an old vertex keeps its degree (`ModeSplitting.card_child_src_inl`); +* a packet vertex has one port for each port of its group in its packet + (`ModeSplitting.card_child_tgt_inr`), so at most `2 L` ports when no two edges share a slot of a + packet of `L` slots (`ModeSplitting.card_child_tgt_inr_le`): the manuscript's + "a new packet vertex has at most `2L ⩽ Δ` ports"; +* the child has one edge per port of its group (`ModeSplitting.card_edges_child`), so with at most + `c M / r` ports per group on each side (Lemma D.3 (3)) it has at most `2 c M / r` edges + (`ModeSplitting.mul_card_edges_child_le`): the manuscript's "at most `6M / r < M / 8` edges". + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.card_child_src_inl`: old degrees are + unchanged. +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.card_child_tgt_inr_le`: packet degrees are + at most `2 L`. +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.mul_card_edges_child_le`: the edge bound. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.3 (pp. 58–59) and + §D.4.6 (p. 63). +-/ + +public section + +namespace Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting + +open Finset + +variable {R V E G P M : Type*} [CommRing R] [Fintype M] {N : TensorNetwork R Bool V E} + {W : Matrix E E R} (S : N.ModeSplitting W G P M) [Fintype E] [DecidableEq E] [DecidableEq P] + [DecidableEq G] [DecidableEq M] [DecidableEq V] + +/-- Counting the elements of a sum type with a property. -/ +private theorem card_filter_sum {A B : Type*} [Fintype A] [Fintype B] (p : A ⊕ B → Prop) + [DecidablePred p] : + #{x | p x} = #{a | p (.inl a)} + #{b | p (.inr b)} := by + rw [← Fintype.card_subtype, Fintype.card_congr Equiv.subtypeSum, Fintype.card_sum, + Fintype.card_subtype, Fintype.card_subtype] + +/-- Counting the elements of a subtype with a property. -/ +private theorem card_filter_subtype {A : Type*} [Fintype A] (Q : A → Prop) [DecidablePred Q] + (f : A → Prop) [DecidablePred f] : #{e : {e // Q e} | f e.1} = #{e | Q e ∧ f e} := by + rw [← Fintype.card_subtype, Fintype.card_congr (Equiv.subtypeSubtypeEquivSubtypeInter Q f), + Fintype.card_subtype] + +/-- **Old degrees are unchanged.** In a child, an old vertex has one port for each edge of `N` +at it. -/ +theorem card_child_src_inl (β : M → ℕ) (g : G) (v : {v // S.group v = g}) : + #{x | (S.child β g).src x = .inl v} = #{e | N.src e = v.1} + #{e | N.tgt e = v.1} := by + rw [card_filter_sum] + congr 1 + · simp only [child_src_inl, Sum.inl.injEq, Subtype.ext_iff] + rw [card_filter_subtype (fun e => S.group (N.src e) = g) (fun e => N.src e = v.1)] + exact congrArg card (filter_congr fun e _ => ⟨fun h => h.2, fun h => ⟨h ▸ v.2, h⟩⟩) + · simp only [child_src_inr, Sum.inl.injEq, Subtype.ext_iff] + rw [card_filter_subtype (fun e => S.group (N.tgt e) = g) (fun e => N.tgt e = v.1)] + exact congrArg card (filter_congr fun e _ => ⟨fun h => h.2, fun h => ⟨h ▸ v.2, h⟩⟩) + +/-- Every edge of a child ends at a packet vertex. -/ +theorem card_child_tgt_inl (β : M → ℕ) (g : G) (v : {v // S.group v = g}) : + #{x | (S.child β g).tgt x = .inl v} = 0 := by + rw [card_filter_sum] + simp + +/-- Every edge of a child starts at an old vertex. -/ +theorem card_child_src_inr (β : M → ℕ) (g : G) (q : P) : + #{x | (S.child β g).src x = .inr q} = 0 := by + rw [card_filter_sum] + simp + +/-- A packet vertex of a child has one port for each port of its group in its packet. -/ +theorem card_child_tgt_inr (β : M → ℕ) (g : G) (q : P) : + #{x | (S.child β g).tgt x = .inr q} = + #{e | S.group (N.src e) = g ∧ S.packet e = q} + + #{e | S.group (N.tgt e) = g ∧ S.packet e = q} := by + rw [card_filter_sum] + simp only [child_tgt_inl, child_tgt_inr, Sum.inr.injEq] + rw [card_filter_subtype (fun e => S.group (N.src e) = g) (fun e => S.packet e = q), + card_filter_subtype (fun e => S.group (N.tgt e) = g) (fun e => S.packet e = q)] + +omit [DecidableEq E] [DecidableEq G] [DecidableEq M] [DecidableEq V] in +/-- At most `L` edges lie in a packet of `L` slots when no two share a slot. -/ +private theorem card_packet_le {a : ℕ} {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) + (hinj : ∀ e e', S.packet e = S.packet e' → slot e = slot e' → e = e') (q : P) + (f : E → Prop) [DecidablePred f] : #{e | f e ∧ S.packet e = q} ≤ 2 ^ a := by + calc #{e | f e ∧ S.packet e = q} ≤ #(range (2 ^ a)) := + card_le_card_of_injOn slot (fun e _ => by simpa using hslot e) fun e he e' he' h => by + simp only [coe_filter, Set.mem_ofPred_eq, mem_univ, true_and] at he he' + exact hinj e e' (he.2.trans he'.2.symm) h + _ = 2 ^ a := card_range _ + +/-- **Packet degrees are at most `2 L`** in a child, for packets of `L = 2 ^ a` slots with no two +edges in the same slot of a packet. -/ +theorem card_child_tgt_inr_le {a : ℕ} {slot : E → ℕ} (hslot : ∀ e, slot e < 2 ^ a) + (hinj : ∀ e e', S.packet e = S.packet e' → slot e = slot e' → e = e') (β : M → ℕ) (g : G) + (q : P) : #{x | (S.child β g).tgt x = .inr q} ≤ 2 * 2 ^ a := by + rw [card_child_tgt_inr, two_mul] + exact add_le_add (S.card_packet_le hslot hinj q _) (S.card_packet_le hslot hinj q _) + +omit [DecidableEq E] [DecidableEq P] [DecidableEq M] [DecidableEq V] in +/-- **The edge bound.** If, in the group `g`, the rows have at most `c M / r` ports and so do the +columns, then the child of `g` has at most `2 c M / r` edges. With Lemma D.3 (3), `c = 3` and +`r = 64`, this is the manuscript's `6M / r < M / 8`. -/ +theorem mul_card_edges_child_le {r c : ℕ} (g : G) + (hrow : r * #{e | S.group (N.src e) = g} ≤ c * Fintype.card E) + (hcol : r * #{e | S.group (N.tgt e) = g} ≤ c * Fintype.card E) : + r * Fintype.card ({e // S.group (N.src e) = g} ⊕ {e // S.group (N.tgt e) = g}) ≤ + 2 * c * Fintype.card E := by + rw [card_edges_child, mul_add] + linarith + +end Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean new file mode 100644 index 000000000..dd0a261b1 --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean @@ -0,0 +1,109 @@ +/- +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.ActiveCall + +/-! +# Tests for one active call of the contraction recursion + +* **The packing bridge.** The two-edge network of the Cauchy-mode tests with a hand-made packing + into one packet of two slots (`a = 1`, `r = 2`) satisfies the orientation hypothesis of the + child identity, its groups have one port per side, and the children of a mode splitting along + its packets have packet degree at most `2 L`. +* **Non-vacuity of the active call.** A one-edge network over `ℤ_(2)` (`M = 1`, `a = 0`, so + `M (2 L - 1) = 1`), with the two interpolation points `0` and `1`, `u = 1` and `h = 3`: values + equal to `F(t + α ζ)` recover the contraction modulo every `2 ^ p`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.ActiveCall + +open Cslib.Circuits.Frontier TensorNetwork + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### The packing bridge -/ + +/-- Edge `e` runs from row `e` to column `e + 2`, with the all-one tensors. -/ +def net : TensorNetwork ℤ Bool (Fin 4) (Fin 2) where + src e := ⟨e.val, by omega⟩ + tgt e := ⟨e.val + 2, by omega⟩ + tensor _ _ := 1 + tensor_congr _ _ _ _ := rfl + +/-- The rows `0, 1`. -/ +def side (v : Fin 4) : Bool := decide (v.val < 2) + +/-- One packet of two slots; edge `e` in slot `e`. Row `1` and column `2` have symbol `1`, so +edge `0` (slot `0`) runs from symbol `0` to `1` and edge `1` (slot `1`) the other way. -/ +def packing : Cslib.PacketPacking.Packing net.src net.tgt 1 2 where + rowLabel v _ := if v = 1 then 1 else 0 + colLabel v _ := if v = 2 then 1 else 0 + numPackets := 1 + packet _ := 0 + slot e := ⟨e.val, by have := e.isLt; simp; omega⟩ + symbols _ _ := (0, 1) + injective e e' h := by fin_cases e <;> fin_cases e' <;> simp_all + labels_eq e i := by fin_cases e <;> fin_cases i <;> decide + +example (e : Fin 2) : + (packingGroup net side packing 0 (net.src e), packingGroup net side packing 0 (net.tgt e)) = + if (packing.slot e : ℕ).testBit 0 then (packing.symbols (packing.packet e) 0).swap + else packing.symbols (packing.packet e) 0 := + packingGroup_src_tgt net side (fun e => by fin_cases e <;> decide) + (fun e => by fin_cases e <;> decide) packing 0 e + +example : Finset.card {e | packingGroup net side packing 0 (net.src e) = 1} = 1 := by + rw [card_packingGroup_src net side (fun e => by fin_cases e <;> decide)] + decide + +/-- **Child invariants.** For any mode splitting of `net` along the packets of `packing`, a packet +vertex of a child has at most `2 L = 4` ports. -/ +example {M : Type} [Fintype M] [DecidableEq M] {W : Matrix (Fin 2) (Fin 2) ℤ} + (S : net.ModeSplitting W (Fin 2) (Fin 1) M) (hS : S.packet = packing.packet) (β : M → ℕ) + (g : Fin 2) (q : Fin 1) : + Finset.card {x | (S.child β g).tgt x = .inr q} ≤ 2 * 2 ^ 1 := + S.card_child_tgt_inr_le (packing_slot_lt packing) + (by rw [hS]; exact packing_slot_injective packing) β g q + +/-! ### Non-vacuity of the active call -/ + +/-- One edge from row `0` to column `1`, with tensor `(1, 2)` at the row and `(1, 1)` at the +column. -/ +def net₁ : TensorNetwork ℤ₍₂₎ Bool (Fin 2) (Fin 1) where + src _ := 0 + tgt _ := 1 + tensor v x := if v = 0 ∧ x 0 then 2 else 1 + tensor_congr v x y h := by rw [h 0 (by fin_cases v <;> simp)] + +/-- The interpolation points `0, 1`, whose difference is a unit. -/ +def ζ (k : Fin 2) : ℤ₍₂₎ := k.val + +theorem hζ : Pairwise fun k l => IsUnit (ζ k - ζ l) := by + intro k l hkl + fin_cases k <;> fin_cases l + · exact absurd rfl hkl + · simp [ζ] + · simp [ζ] + · exact absurd rfl hkl + +/-- Exact values of `F(t + α ζ)` recover the contraction modulo every `2 ^ p`. -/ +example (p : ℕ) : ∃ b ∈ ℤ₍₂₎, + recoverFromValues ℤ₍₂₎ 0 1 3 ζ (fun t k => + (net₁.localDeformation (fun v => decide (v = 0)) (integerHom ℤ₍₂₎ fun _ hc => hc) 0 t + (fun _ => ()) (fun _ => 0)).eval (ζ k)) = + ((net₁.contraction : ℤ₍₂₎) : ℚ) + 2 ^ p * b := + exists_recoverFromValues_eq_add_of_mk (K := ℚ) ℤ₍₂₎ (fun _ hc => hc) net₁ _ + (fun _ => by decide) (fun e e' _ _ => Subsingleton.elim e e') le_rfl le_rfl (by simp) + (by simp) ζ hζ (by simp) _ fun t _ _ k => + mk_eval_localDeformation net₁ _ _ 0 t _ _ _ (ζ k) + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.ActiveCall From 8e242417d972de64f33df2c494274633b213a50a Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:19:14 -0400 Subject: [PATCH 13/38] test: make the tapered recovery test module public Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Frontier/TensorNetworkTaperedRecovery.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean index af21199d2..58cea84af 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkTaperedRecovery.lean @@ -40,7 +40,7 @@ example : 2 ^ 1 * (3 * 2) ≤ ∑ t ∈ recoveryNodes 1, taperMult 2 3 t := /-- **Non-vacuity.** For `F = X` (degree `1 = M (2 L - 1)` with `M = 1`, `L = 1`), `u = 1` and `h = 3`, exact jets recover `F(0)` modulo every `2 ^ p`. -/ -example (p : ℕ) : +theorem exists_taperedRecovery_X (p : ℕ) : ∃ b ∈ ℤ₍₂₎, taperedRecovery 0 1 3 (fun t => (X : ℚ[X]).comp (C (t : ℚ) + C (2 ^ (0 + 1)) * X)) = (X : ℚ[X]).eval 0 + 2 ^ p * b := exists_taperedRecovery_eq_add_of_params (K := ℚ) (M := 1) ℤ₍₂₎ (fun c hc => hc) le_rfl le_rfl From d9722cc3898cd3fa92c7828961b82c768914bc1c Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:21:07 -0400 Subject: [PATCH 14/38] chore: merge main and regenerate Complexity.lean Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 0c8c217b2..735fba8eb 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -869,6 +869,7 @@ 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.Binarize public import Complexity.Computability.Circuit.Frontier.TensorNetwork.CauchyModes public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Constraint public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Deformation @@ -877,11 +878,6 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCo public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery -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.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 From 99d9e0d69372a439fe89c955398c74361686e888 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:28:05 -0400 Subject: [PATCH 15/38] 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 fdd3b220c..566b83dcb 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 000000000..b46df6766 --- /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 000000000..566fca586 --- /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 b9caee50441cd14c22ce4d3f0285c1f20bed8e32 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:36:29 -0400 Subject: [PATCH 16/38] 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 b46df6766..8a9bfcd2a 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 566fca586..f0e7710ba 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 17/38] 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 8a9bfcd2a..123a3cfdb 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 f0e7710ba..c3f7705e2 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 5312093045d6432d93242e34e2a13a13954fdef4 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:53:27 -0400 Subject: [PATCH 18/38] refactor(circuit/frontier): packingGroup takes the edge ends directly packingGroup and its lemmas now take a packing of arbitrary edge ends src/tgt instead of a network, so the orientation hypothesis of cauchyModeSplitting can be supplied for N.map f. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/ActiveCall.lean | 75 ++++++++++--------- .../Frontier/TensorNetworkActiveCall.lean | 8 +- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean index cf9a248fe..afa5522d4 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean @@ -44,17 +44,16 @@ of the products of the contractions of the children. So the values may be comput children's contractions modulo `2 ^ p_t` (`TensorNetwork.exists_recoverFromValues_eq_add_of_mk`). **The packing bridge.** The orientation that the child identity needs is property (2) of -Lemma D.3 for `Cslib.PacketPacking.Packing` (`TensorNetwork.packingGroup_src_tgt`), and the port +Lemma D.3 for `Cslib.PacketPacking.Packing` (`packingGroup_src_tgt`), and the port counts of Lemma D.3 (3) are the group sizes that bound the children -(`TensorNetwork.card_packingGroup_src`, `TensorNetwork.card_packingGroup_tgt`, with +(`card_packingGroup_src`, `card_packingGroup_tgt`, with `TensorNetwork.ModeSplitting.mul_card_edges_child_le`). ## Main definitions * `Cslib.Circuits.Frontier.integerHom`: the inclusion of `ℤ_(2)` into `B`. * `Cslib.Circuits.Frontier.recoverFromValues`: interpolation followed by tapered recovery. -* `Cslib.Circuits.Frontier.TensorNetwork.packingGroup`: the group of a vertex in coordinate `i` - of a packing. +* `Cslib.Circuits.Frontier.packingGroup`: the group of a vertex in coordinate `i` of a packing. ## Main results @@ -66,7 +65,7 @@ counts of Lemma D.3 (3) are the group sizes that bound the children deformation modulo `2 ^ p_t` are deformed contractions along `cauchyValueMatrix`. * `Cslib.Circuits.Frontier.TensorNetwork.exists_recoverFromValues_eq_add_of_mk`: correctness of one active call. -* `Cslib.Circuits.Frontier.TensorNetwork.packingGroup_src_tgt`: the packing bridge, with +* `Cslib.Circuits.Frontier.packingGroup_src_tgt`: the packing bridge, with `packing_slot_lt`, `packing_slot_injective`, `card_packingGroup_src` and `card_packingGroup_tgt`. * `Cslib.Circuits.Frontier.two_pow_padicValInt_dvd_and_not_dvd`: a node of valuation `i` meets @@ -187,56 +186,64 @@ theorem exists_recoverFromValues_eq_add (B : Subring K) hslot htS, coeff_map, coeff_map, hg, coeff_add, coeff_C_mul, map_add, map_mul, map_pow] rfl +end TensorNetwork + /-! ### The packing bridge -/ -/-- The group of a vertex in coordinate `i` of a packing: its row label if it is a row and its -column label if it is a column. -/ -@[expose] def packingGroup {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) (side : V → Bool) - (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (v : V) : Fin r := +section PackingBridge + +variable {V E : Type*} {src tgt : E → V} {a r : ℕ} + +/-- The group of a vertex in coordinate `i` of a packing of the edges `src e → tgt e`: its row +label if it is a row and its column label if it is a column. -/ +@[expose] def packingGroup (side : V → Bool) (Q : PacketPacking.Packing src tgt a r) (i : Fin a) + (v : V) : Fin r := if side v then Q.rowLabel v i else Q.colLabel v i -omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in -/-- **The packing bridge.** For a packing of the edges of a network whose edges run from rows to -columns (Lemma D.3 of [OpenAI2026ETH]), the groups in coordinate `i` are oriented by digit `i` of -the slots (Lemma D.3 (2)): the hypothesis of `TensorNetwork.cauchyModeSplitting`. -/ -theorem packingGroup_src_tgt {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) - (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) - (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (e : E) : - (packingGroup N side Q i (N.src e), packingGroup N side Q i (N.tgt e)) = +/-- **The packing bridge.** For a packing of edges that run from rows to columns (Lemma D.3 of +[OpenAI2026ETH]), the groups in coordinate `i` are oriented by digit `i` of the slots +(Lemma D.3 (2)): the hypothesis of `TensorNetwork.cauchyModeSplitting`. The edge ends `src` and +`tgt` are arbitrary functions, so the bridge applies to `N.src` and `N.tgt` of any network with +these ends, such as `N.map f`. -/ +theorem packingGroup_src_tgt (side : V → Bool) (hsrc : ∀ e, side (src e) = true) + (htgt : ∀ e, side (tgt e) = false) (Q : PacketPacking.Packing src tgt a r) (i : Fin a) + (e : E) : + (packingGroup side Q i (src e), packingGroup side Q i (tgt e)) = if (Q.slot e : ℕ).testBit i then (Q.symbols (Q.packet e) i).swap else Q.symbols (Q.packet e) i := by simp only [packingGroup, hsrc, htgt, ↓reduceIte, Bool.false_eq_true] exact Q.labels_eq e i -omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in /-- The slots of a packing lie below `2 ^ a`. -/ -theorem packing_slot_lt {R : Type*} {a r : ℕ} {N : TensorNetwork R Bool V E} - (Q : PacketPacking.Packing N.src N.tgt a r) (e : E) : (Q.slot e : ℕ) < 2 ^ a := +theorem packing_slot_lt (Q : PacketPacking.Packing src tgt a r) (e : E) : + (Q.slot e : ℕ) < 2 ^ a := (Q.slot e).isLt -omit [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] in /-- No two edges share a slot of a packet. -/ -theorem packing_slot_injective {R : Type*} {a r : ℕ} {N : TensorNetwork R Bool V E} - (Q : PacketPacking.Packing N.src N.tgt a r) (e e' : E) (hp : Q.packet e = Q.packet e') - (hs : (Q.slot e : ℕ) = Q.slot e') : e = e' := +theorem packing_slot_injective (Q : PacketPacking.Packing src tgt a r) (e e' : E) + (hp : Q.packet e = Q.packet e') (hs : (Q.slot e : ℕ) = Q.slot e') : e = e' := Q.injective (Prod.ext hp (Fin.ext hs)) -omit [Fintype V] [DecidableEq E] [DecidableEq P] in +variable [Fintype E] + /-- The rows of group `σ` in coordinate `i` of a packing are the rows with symbol `σ`. -/ -theorem card_packingGroup_src {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) - (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) - (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (σ : Fin r) : - #{e | packingGroup N side Q i (N.src e) = σ} = #{e | Q.rowLabel (N.src e) i = σ} := by +theorem card_packingGroup_src (side : V → Bool) (hsrc : ∀ e, side (src e) = true) + (Q : PacketPacking.Packing src tgt a r) (i : Fin a) (σ : Fin r) : + #{e | packingGroup side Q i (src e) = σ} = #{e | Q.rowLabel (src e) i = σ} := by simp only [packingGroup, hsrc, ↓reduceIte] -omit [Fintype V] [DecidableEq E] [DecidableEq P] in /-- The columns of group `σ` in coordinate `i` of a packing are the columns with symbol `σ`. -/ -theorem card_packingGroup_tgt {R : Type*} {a r : ℕ} (N : TensorNetwork R Bool V E) - (side : V → Bool) (htgt : ∀ e, side (N.tgt e) = false) - (Q : PacketPacking.Packing N.src N.tgt a r) (i : Fin a) (σ : Fin r) : - #{e | packingGroup N side Q i (N.tgt e) = σ} = #{e | Q.colLabel (N.tgt e) i = σ} := by +theorem card_packingGroup_tgt (side : V → Bool) (htgt : ∀ e, side (tgt e) = false) + (Q : PacketPacking.Packing src tgt a r) (i : Fin a) (σ : Fin r) : + #{e | packingGroup side Q i (tgt e) = σ} = #{e | Q.colLabel (tgt e) i = σ} := by simp only [packingGroup, htgt, Bool.false_eq_true, ↓reduceIte] +end PackingBridge + +namespace TensorNetwork + +variable {V E P : Type*} [Fintype V] [Fintype E] [DecidableEq E] [DecidableEq P] + /-- **The values of the local deformation modulo `2 ^ p_t`** are deformed contractions of the reduced network along the Cauchy matrix at `T = t + α ζ` (which the child identity (D.16), `TensorNetwork.deformedContraction_cauchyValueMatrix_eq_sum`, expands). -/ diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean index dd0a261b1..d75f5cbbe 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkActiveCall.lean @@ -55,14 +55,14 @@ def packing : Cslib.PacketPacking.Packing net.src net.tgt 1 2 where labels_eq e i := by fin_cases e <;> fin_cases i <;> decide example (e : Fin 2) : - (packingGroup net side packing 0 (net.src e), packingGroup net side packing 0 (net.tgt e)) = + (packingGroup side packing 0 (net.src e), packingGroup side packing 0 (net.tgt e)) = if (packing.slot e : ℕ).testBit 0 then (packing.symbols (packing.packet e) 0).swap else packing.symbols (packing.packet e) 0 := - packingGroup_src_tgt net side (fun e => by fin_cases e <;> decide) + packingGroup_src_tgt side (fun e => by fin_cases e <;> decide) (fun e => by fin_cases e <;> decide) packing 0 e -example : Finset.card {e | packingGroup net side packing 0 (net.src e) = 1} = 1 := by - rw [card_packingGroup_src net side (fun e => by fin_cases e <;> decide)] +example : Finset.card {e | packingGroup side packing 0 (net.src e) = 1} = 1 := by + rw [card_packingGroup_src side (fun e => by fin_cases e <;> decide)] decide /-- **Child invariants.** For any mode splitting of `net` along the packets of `packing`, a packet From c64e111a74ca71665df6828fbb6ab58f4e7a88e3 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:53:27 -0400 Subject: [PATCH 19/38] feat(circuit/frontier): renaming and lifting tensor networks TensorNetwork.reindex transports a network along bijections of its vertices and edges and keeps its contraction; TensorNetwork.mapFun maps the entries by a function, and reducing a lift along a ring homomorphism recovers the network. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Reindex.lean | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean new file mode 100644 index 000000000..306a069d3 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean @@ -0,0 +1,128 @@ +/- +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 + +/-! +# Renaming and lifting tensor networks + +Two operations that keep the contraction of a tensor network under control while its types or its +coefficient ring change. + +* **Renaming** (`TensorNetwork.reindex`). Transport a network along bijections of its vertices + and of its edges. The contraction is unchanged (`TensorNetwork.contraction_reindex`). The + contraction recursion of [OpenAI2026ETH] (§D.4.6–§D.4.7, pp. 63–64) uses it to number the + vertices and edges of a child network by `Fin`. +* **Lifting the entries** (`TensorNetwork.mapFun`). Apply an arbitrary function to every tensor + entry. When the function is a section of a ring homomorphism `f`, reducing the lifted network + along `f` recovers the original (`TensorNetwork.map_mapFun`), so its contraction reduces to the + original contraction (`TensorNetwork.contraction_map_mapFun`). This is the manuscript's + canonical lift of entries specified modulo `2 ^ p` (§D.4.1, p. 58); any section works. + +## Main definitions + +* `Cslib.Circuits.Frontier.TensorNetwork.reindex`: renaming the vertices and edges. +* `Cslib.Circuits.Frontier.TensorNetwork.mapFun`: mapping every entry by a function. + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_reindex`: renaming preserves the contraction. +* `Cslib.Circuits.Frontier.TensorNetwork.map_mapFun`: reducing a lift along `f` recovers the + network. +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_map_mapFun`: the contraction of a lift + reduces to the contraction. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1 (p. 58) and + §D.4.6 (p. 63). +-/ + +public section + +namespace Cslib.Circuits.Frontier.TensorNetwork + +variable {R S U V E V' E' : Type*} + +/-! ### Renaming the vertices and edges -/ + +section Reindex + +variable (eV : V ≃ V') (eE : E ≃ E') (N : TensorNetwork R U V E) + +/-- The network transported along bijections `eV` of the vertices and `eE` of the edges: edge +`eE e` runs from `eV (N.src e)` to `eV (N.tgt e)`, and vertex `eV v` carries the tensor of `v`. -/ +@[expose] def reindex : TensorNetwork R U V' E' where + src e := eV (N.src (eE.symm e)) + tgt e := eV (N.tgt (eE.symm e)) + tensor v x := N.tensor (eV.symm v) (x ∘ eE) + tensor_congr v x y h := N.tensor_congr _ _ _ fun e he => h (eE e) (by + simp only [Equiv.symm_apply_apply] + rcases he with he | he + · exact Or.inl (by rw [he, Equiv.apply_symm_apply]) + · exact Or.inr (by rw [he, Equiv.apply_symm_apply])) + +@[simp] theorem reindex_src (e : E') : (N.reindex eV eE).src e = eV (N.src (eE.symm e)) := + rfl + +@[simp] theorem reindex_tgt (e : E') : (N.reindex eV eE).tgt e = eV (N.tgt (eE.symm e)) := + rfl + +@[simp] theorem reindex_tensor (v : V') (x : E' → U) : + (N.reindex eV eE).tensor v x = N.tensor (eV.symm v) (x ∘ eE) := + rfl + +/-- **Renaming preserves the contraction.** -/ +theorem contraction_reindex [CommSemiring R] [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] + [Fintype V'] [Fintype E'] [DecidableEq E'] : + (N.reindex eV eE).contraction = N.contraction := + Fintype.sum_equiv (eE.symm.arrowCongr (Equiv.refl U)) _ _ fun _ => + Fintype.prod_equiv eV.symm _ _ fun _ => rfl + +end Reindex + +/-! ### Lifting the entries -/ + +section MapFun + +/-- The tensor network with every tensor entry mapped by the function `f`. Unlike +`TensorNetwork.map`, `f` need not be a ring homomorphism; a section of a reduction map lifts the +entries. -/ +@[expose] def mapFun (f : R → S) (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 mapFun_tensor (f : R → S) (N : TensorNetwork R U V E) (v : V) (x : E → U) : + (N.mapFun f).tensor v x = f (N.tensor v x) := + rfl + +@[simp] theorem toMultigraph_mapFun (f : R → S) (N : TensorNetwork R U V E) : + (N.mapFun f).toMultigraph = N.toMultigraph := + rfl + +variable [CommSemiring R] [CommSemiring S] (f : R →+* S) {σ : S → R} + +/-- **Reducing a lift recovers the network**: for a section `σ` of `f`, mapping the entries by +`σ` and then by `f` changes nothing. -/ +theorem map_mapFun (hσ : ∀ x, f (σ x) = x) (N : TensorNetwork S U V E) : + (N.mapFun σ).map f = N := by + obtain ⟨G, T, hT⟩ := N + simp only [map, mapFun, hσ] + +/-- **The contraction of a lift reduces to the contraction**: for a section `σ` of `f`, `f` maps +the contraction of the lifted network to the contraction of the network. -/ +theorem contraction_map_mapFun [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] + (hσ : ∀ x, f (σ x) = x) (N : TensorNetwork S U V E) : + f (N.mapFun σ).contraction = N.contraction := by + rw [← contraction_map, map_mapFun f hσ] + +end MapFun + +end Cslib.Circuits.Frontier.TensorNetwork From 55c7a8fc3faab8ee9e8cce97f5347236d7423578 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:53:27 -0400 Subject: [PATCH 20/38] feat(foundations): digit lifts in Z_(p)[X]/(f) have unit differences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The O-side counterpart of GaloisRing.isUnit_digitLift_sub: the binary-coordinate lifts of [OpenAI2026ETH] (§D.4.1, p. 58) in the valuation ring of AdjoinRoot.padicValuation differ by units. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Padics/AdjoinRootDigitLift.lean | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Complexity/Foundations/NumberTheory/Padics/AdjoinRootDigitLift.lean diff --git a/Complexity/Foundations/NumberTheory/Padics/AdjoinRootDigitLift.lean b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootDigitLift.lean new file mode 100644 index 000000000..9d807df9a --- /dev/null +++ b/Complexity/Foundations/NumberTheory/Padics/AdjoinRootDigitLift.lean @@ -0,0 +1,117 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +module +-- upstream: candidate (Mathlib, `Mathlib/NumberTheory/Padics/`) + +public import Complexity.Foundations.NumberTheory.Padics.AdjoinRootValuation +import Complexity.Foundations.RingTheory.AdjoinRoot +import Complexity.Foundations.RingTheory.GaloisRing + +/-! +# Digit lifts in `ℤ_(p)[X] / (f)` + +Let `p` be a prime and `f` a monic integer polynomial that is irreducible modulo `p`, and let +`O = ℤ_(p)[X] / (f)` be the valuation ring of `AdjoinRoot.padicValuation` in `K = ℚ[X] / (f)`. +The *digit lift* of a digit vector `c : Fin (deg f) → ZMod p` is `∑ i, c i * X ^ i` in `O`, with +each digit read as an integer in `{0, …, p - 1}` (`AdjoinRoot.integerDigitLift`). For `p = 2` +these are the binary-coordinate lifts of the residue-field elements of [OpenAI2026ETH] (§D.4.1, +p. 58). + +**Distinct digit lifts differ by a unit of `O`** (`AdjoinRoot.isUnit_integerDigitLift_sub`). The +difference of two lifts is the image of `∑ i, (c i - c' i) * X ^ i ∈ ℤ[X] / (f)`, which is nonzero +modulo `p` because the digit lifts reduce to distinct elements of `𝔽_p[X] / (f)` +(`GaloisRing.residue_digitLift_injective`). So it has valuation one +(`AdjoinRoot.padicValuation_map_eq_one_iff`), and is a unit of the valuation ring +(`AdjoinRoot.isUnit_of_padicValuation_eq_one`). This is the counterpart in `O` of +`GaloisRing.isUnit_digitLift_sub`, and it supplies the interpolation points of the contraction +recursion (`Cslib.Circuits.Frontier.TensorNetwork.exists_contractRec_eq_add`) over `B = O`. + +## Main definitions + +* `AdjoinRoot.integerDigitLift`: the digit lift of a digit vector, in `O`. + +## Main results + +* `AdjoinRoot.coe_integerDigitLift`: the digit lift is `∑ i, c i * X ^ i`. +* `AdjoinRoot.isUnit_integerDigitLift_sub`: distinct digit lifts differ by a unit of `O`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.1, p. 58. +-/ + +public section + +open Polynomial + +namespace AdjoinRoot + +variable {p : ℕ} [Fact p.Prime] {f : ℤ[X]} (hf : f.Monic) + (hirr : Irreducible (f.map (Int.castRingHom (ZMod p)))) + +variable (p f) in +/-- The integer polynomial `∑ i, c i * X ^ i` of a digit vector, with each digit read as an +integer in `{0, …, p - 1}`. -/ +private noncomputable def digitPoly (c : Fin f.natDegree → ZMod p) : ℤ[X] := + ∑ i, C ((c i).val : ℤ) * X ^ (i : ℕ) + +/-- Modulo `p`, the digit polynomial is `∑ i, c i * X ^ i`. -/ +private theorem map_digitPoly (c : Fin f.natDegree → ZMod p) : + (digitPoly p f c).map (Int.castRingHom (ZMod p)) = ∑ i, C (c i) * X ^ (i : ℕ) := by + simp only [digitPoly, Polynomial.map_sum, Polynomial.map_mul, Polynomial.map_C, + Polynomial.map_pow, Polynomial.map_X] + simp only [eq_intCast, Int.cast_natCast, ZMod.natCast_zmod_val] + +include hf hirr + +/-- The image in `ℚ[X] / (f)` of the class of the digit polynomial lies in the valuation ring. -/ +private theorem map_mk_digitPoly_mem (c : Fin f.natDegree → ZMod p) : + map (Int.castRingHom ℚ) f _ dvd_rfl (mk f (digitPoly p f c)) ∈ + (padicValuation p hf hirr).integer := + padicValuation_map_le_one hf hirr _ + +/-- **The digit lift** of a digit vector `c`: the element `∑ i, c i * X ^ i` of the valuation ring +`O = ℤ_(p)[X] / (f)`, with each digit `c i ∈ ZMod p` read as an integer in `{0, …, p - 1}`. For +`p = 2` these are the binary-coordinate lifts of [OpenAI2026ETH] (§D.4.1, p. 58). -/ +noncomputable def integerDigitLift (c : Fin f.natDegree → ZMod p) : + (padicValuation p hf hirr).integer := + ⟨_, map_mk_digitPoly_mem hf hirr c⟩ + +/-- The digit lift is `∑ i, c i * X ^ i` in `ℚ[X] / (f)`. -/ +theorem coe_integerDigitLift (c : Fin f.natDegree → ZMod p) : + (integerDigitLift hf hirr c : AdjoinRoot (f.map (Int.castRingHom ℚ))) = + ∑ i, ((c i).val : AdjoinRoot (f.map (Int.castRingHom ℚ))) * root _ ^ (i : ℕ) := by + simp only [integerDigitLift, digitPoly, map_sum, map_mul, map_pow, mk_X, map_natCast, map_root] + +/-- **Distinct digit lifts differ by a unit of `O`**: the difference reduces modulo `p` to a +nonzero element of `𝔽_p[X] / (f)`, so it has valuation one. -/ +theorem isUnit_integerDigitLift_sub {c c' : Fin f.natDegree → ZMod p} (h : c ≠ c') : + IsUnit (integerDigitLift hf hirr c - integerDigitLift hf hirr c') := by + have hx : ((integerDigitLift hf hirr c - integerDigitLift hf hirr c' : + (padicValuation p hf hirr).integer) : AdjoinRoot (f.map (Int.castRingHom ℚ))) = + map (Int.castRingHom ℚ) f _ dvd_rfl (mk f (digitPoly p f c - digitPoly p f c')) := by + rw [map_sub, map_sub] + rfl + have h1 : padicValuation p hf hirr + ((integerDigitLift hf hirr c - integerDigitLift hf hirr c' : + (padicValuation p hf hirr).integer) : AdjoinRoot (f.map (Int.castRingHom ℚ))) = 1 := by + rw [hx, padicValuation_map_eq_one_iff hf hirr, map_mk, Polynomial.map_sub, map_digitPoly, + map_digitPoly, map_sub, sub_ne_zero] + intro heq + refine h (GaloisRing.residue_digitLift_injective (n := 1) hf ?_) + simp only [GaloisRing.residue_digitLift] + exact heq + exact isUnit_of_padicValuation_eq_one hf hirr _ h1 + +/-- **Digit lifts are interpolation points**: distinct digit vectors have lifts with unit +differences. -/ +theorem pairwise_isUnit_integerDigitLift_sub : + Pairwise fun c c' : Fin f.natDegree → ZMod p => + IsUnit (integerDigitLift hf hirr c - integerDigitLift hf hirr c') := + fun _ _ h => isUnit_integerDigitLift_sub hf hirr h + +end AdjoinRoot From 9d8168fd914484488dd263f2d7118a0c324ad995 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:53:27 -0400 Subject: [PATCH 21/38] feat(circuit/frontier): the contraction recursion and its correctness (T2.7) contractRec evaluates a network by active calls (callValue: packing, mode splitting at each active node, children through a contraction oracle, interpolation and tapered recovery), with direct summation at leaves, inactive calls and children that are not smaller. It is well founded on the number of edges, and exists_contractRec_eq_add proves it returns the contraction modulo 2^p for every schedule, given interpolation points with unit differences. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 + .../Frontier/TensorNetwork/Recursion.lean | 439 ++++++++++++++++++ .../Frontier/TensorNetworkRecursion.lean | 146 ++++++ 3 files changed, 588 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean diff --git a/Complexity.lean b/Complexity.lean index 84b80a176..cb21ca7e9 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -879,6 +879,8 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery public import Complexity.Computability.Circuit.Frontier.TwoSidedWitness public import Complexity.Computability.Circuit.Frontier.Unconditional @@ -2829,6 +2831,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.AdjoinRootDigitLift public import Complexity.Foundations.NumberTheory.Padics.AdjoinRootValuation public import Complexity.Foundations.NumberTheory.Padics.RatValuation public import Complexity.Foundations.NumberTheory.PrimeCounting diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean new file mode 100644 index 000000000..3c9753220 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -0,0 +1,439 @@ +/- +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.ActiveCall +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex + +/-! +# The contraction recursion and its correctness + +This file assembles the recursive contraction algorithm of [OpenAI2026ETH] (§D.4.7, p. 64) from +its active calls, and proves it correct by induction from the leaves. + +**One node** (`TensorNetwork.nodeSplitting`). For a network whose edges run from rows to columns, +a packing `Q` of its edges into packets of `2 ^ a` slots, and a nonzero grid point `t` of +valuation `i < a`, group the vertices by their symbol in coordinate `i` of `Q` (`packingGroup`). +The packing bridge `packingGroup_src_tgt` orients the groups, so the Cauchy matrix at +`T = t + α ζ` has the mode splitting `TensorNetwork.cauchyModeSplitting` (§D.4.5, p. 62). At a call +with input precision `p`, `TensorNetwork.callSplitting` is this splitting for the network reduced +modulo `2 ^ p_t`, `p_t = p + 8 h D_t + 8 M (a + 1)` (`nodePrecision`). + +**One call from its children.** If every value `v t k` reduces modulo `2 ^ p_t` to the sum over +the mode tuples of the products of the contractions of the children (the right side of (D.16)), +then interpolation and tapered recovery return the contraction modulo `2 ^ p` +(`TensorNetwork.exists_recoverFromValues_eq_add_of_children`). The children's contractions may +come from any *contraction oracle* (`TensorNetwork.ContractionOracle`) that is correct modulo the +precision it is given: a child over `B ⧸ (2 ^ p_t)` is lifted to `B` along a section of the +reduction and renumbered by `Fin` (`TensorNetwork.ContractionOracle.evalLift`), and the oracle's +value then reduces to its contraction (`TensorNetwork.ContractionOracle.mk_evalLift`). The value of +an active node is `TensorNetwork.nodeValue`, the values of a call are `TensorNetwork.callValues`, +and the recovered value is `TensorNetwork.callValue` +(`TensorNetwork.exists_callValue_eq_add`). + +**The recursion** (`TensorNetwork.contractRec`). A call receives a network on `Fin n` vertices +and `Fin m` edges, its sides, its depth `d` and its precision `p`. A *schedule* gives the +parameters `a`, `u`, `h` of a call from its depth and its number of edges (`CallParams`), or none +at a leaf. The call is active when the parameters meet the hypotheses of Lemma D.5 +(`CallParams.Admissible`, with `u ≤ a` and enough interpolation points) and the edges run from +rows to columns. It packs the edges with `PacketPacking.packing` (Lemma D.3, `r = 64`), takes the +first `M (2 L - 1) + 1` of a fixed family of interpolation points, and evaluates each child by a +recursive call at depth `d + 1` and precision `p_t`. A leaf, an inactive call, and a child that +does not have fewer edges than its parent are evaluated by the contraction itself, the direct +summation of the manuscript's leaves. The recursion is well founded on the number of edges. + +**Correctness** (`TensorNetwork.exists_contractRec_eq_add`). For interpolation points with unit +differences, every call returns the contraction modulo `2 ^ p`, for every schedule: the manuscript's +"induction from the leaves" (p. 64) becomes strong induction on the number of edges. So the +parameter schedule (D.4)–(D.6) matters only for the running time, which this file does not treat; +under it, Lemma D.3 (3) and `ModeSplitting.mul_card_edges_child_le` make every child smaller, so the +fallback for children is not taken. In the manuscript's ring `B = O = ℤ_(2)[U] / (f)`, the +binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub`). + +## Main definitions + +* `Cslib.Circuits.Frontier.nodePrecision`: the child precision `p_t`. +* `Cslib.Circuits.Frontier.TensorNetwork.nodeSplitting`, + `Cslib.Circuits.Frontier.TensorNetwork.callSplitting`: the mode splitting at an active node. +* `Cslib.Circuits.Frontier.TensorNetwork.ContractionOracle`: values for child calls, with + `ContractionOracle.Correct` and `ContractionOracle.evalLift`. +* `Cslib.Circuits.Frontier.TensorNetwork.nodeValue`, + `Cslib.Circuits.Frontier.TensorNetwork.callValues`, + `Cslib.Circuits.Frontier.TensorNetwork.callValue`: one active call from an oracle. +* `Cslib.Circuits.Frontier.TensorNetwork.CallParams`: the parameters of a call. +* `Cslib.Circuits.Frontier.TensorNetwork.contractRec`: the recursion. + +## Main results + +* `Cslib.Circuits.Frontier.TensorNetwork.exists_recoverFromValues_eq_add_of_children`: one call is + correct when its values are the sums of the products of the children's contractions. +* `Cslib.Circuits.Frontier.TensorNetwork.exists_callValue_eq_add`: one call is correct for a + correct oracle. +* `Cslib.Circuits.Frontier.TensorNetwork.exists_contractRec_eq_add`, + `Cslib.Circuits.Frontier.TensorNetwork.mk_contractRec`: the recursion returns the contraction + modulo `2 ^ p`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Lemma D.3 (pp. 58–59), + §D.4.4–§D.4.7, pp. 60–64. +-/ + +public section + +open Polynomial Finset + +namespace Cslib.Circuits.Frontier + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### Node precisions -/ + +/-- The precision `p_t = p + 8 h D_t + 8 M (a + 1)`, `D_t = 2 ^ (a - v₂ t)`, that a call of input +precision `p` with `M` edges passes to the children of the node `t` (Lemma D.5). -/ +@[expose] def nodePrecision (M a h p : ℕ) (t : ℤ) : ℕ := + p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * M * (a + 1) + +/-- A node of positive multiplicity has valuation below `u`, so below `a` when `u ≤ a`. -/ +theorem padicValInt_lt_of_taperMult_pos {u h a : ℕ} {t : ℤ} (hq : 0 < taperMult u h t) + (hua : u ≤ a) : padicValInt 2 t < a := by + unfold taperMult at hq + have : padicValInt 2 t < u := by + by_contra hc + rw [Nat.sub_eq_zero_of_le (not_lt.mp hc), mul_zero] at hq + exact lt_irrefl 0 hq + omega + +/-- The ideal `(2 ^ n)` of a commutative ring. -/ +abbrev twoPowIdeal (B : Type*) [CommRing B] (n : ℕ) : Ideal B := + Ideal.span {(2 : B) ^ n} + +/-- `2 ^ n = 0` modulo `2 ^ n`. -/ +theorem two_pow_eq_zero_twoPowIdeal (B : Type*) [CommRing B] (n : ℕ) : + (2 : B ⧸ twoPowIdeal B n) ^ n = 0 := by + have : Ideal.Quotient.mk (twoPowIdeal B n) ((2 : B) ^ n) = 0 := + Ideal.Quotient.eq_zero_iff_mem.mpr (Ideal.mem_span_singleton_self _) + rwa [map_pow, map_ofNat] at this + +namespace TensorNetwork + +/-! ### The modes of an active node -/ + +section Node + +variable {R V E : Type*} [CommRing R] [Fintype E] {a r : ℕ} + +/-- **The modes of an active node** (§D.4.5). For a network whose edges run from rows to columns, +a packing `Q` of its edges, and a nonzero grid point `t` of valuation `i < a`, the mode splitting +of the Cauchy matrix at `T = t + α ζ` with the vertices grouped by their symbol in coordinate `i` +of `Q` (`cauchyModeSplitting`, oriented by `packingGroup_src_tgt`). -/ +noncomputable def nodeSplitting (N : TensorNetwork R Bool V E) (side : V → Bool) + (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + (Q : PacketPacking.Packing N.src N.tgt a r) (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} + (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ recoveryNodes a) (hia : padicValInt 2 t < a) : + N.ModeSplitting (cauchyValueMatrix κ ζ a t Q.packet fun e => Q.slot e) (Fin r) + (Fin Q.numPackets) (Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × Fin p) := + cauchyModeSplitting (group := packingGroup side Q ⟨_, hia⟩) (packet := Q.packet) + (slot := fun e => Q.slot e) (sym := fun q => Q.symbols q ⟨_, hia⟩) (i := padicValInt 2 t) + side hsrc htgt (packingGroup_src_tgt side hsrc htgt Q ⟨_, hia⟩) (packing_slot_lt Q) hia κ ζ hp + (mem_of_mem_erase ht) (two_pow_padicValInt_dvd_and_not_dvd (ne_of_mem_erase ht)).1 + (two_pow_padicValInt_dvd_and_not_dvd (ne_of_mem_erase ht)).2 + +/-- **The child identity at an active node** ((D.16)): the deformed contraction along the Cauchy +matrix at `T = t + α ζ` is the sum, over the mode tuples of weight below `p`, of the products over +the groups of the contractions of the children of `nodeSplitting`. -/ +theorem deformedContraction_eq_sum_nodeSplitting [Fintype V] [DecidableEq V] [DecidableEq E] + (N : TensorNetwork R Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) (Q : PacketPacking.Packing N.src N.tgt a r) + (κ : ℤ₍₂₎ →+* R) (ζ : R) {p : ℕ} (hp : (2 : R) ^ p = 0) {t : ℤ} (ht : t ∈ recoveryNodes a) + (hia : padicValInt 2 t < a) : + N.deformedContraction side (cauchyValueMatrix κ ζ a t Q.packet fun e => Q.slot e) = + ∑ β ∈ modeBudget + (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × Fin p => (Z.2.2 : ℕ) + 1) p, + ∏ g, ((nodeSplitting N side hsrc htgt Q κ ζ hp ht hia).child β g).contraction := + deformedContraction_cauchyValueMatrix_eq_sum (group := packingGroup side Q ⟨_, hia⟩) + (packet := Q.packet) (slot := fun e => Q.slot e) (sym := fun q => Q.symbols q ⟨_, hia⟩) + (i := padicValInt 2 t) side hsrc htgt (packingGroup_src_tgt side hsrc htgt Q ⟨_, hia⟩) + (packing_slot_lt Q) hia κ ζ hp (mem_of_mem_erase ht) + (two_pow_padicValInt_dvd_and_not_dvd (ne_of_mem_erase ht)).1 + (two_pow_padicValInt_dvd_and_not_dvd (ne_of_mem_erase ht)).2 + +end Node + +/-! ### One call from its children -/ + +section Children + +variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {V E : Type*} [Fintype V] [DecidableEq V] [Fintype E] + [DecidableEq E] + +/-- The mode splitting at the active node `t` and the point `z` of a call with input precision +`p`: `nodeSplitting` for the network reduced modulo `2 ^ p_t`, at the reduction of `z`. -/ +noncomputable def callSplitting (N : TensorNetwork B Bool V E) (side : V → Bool) + (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} + (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : + ModeSplitting + (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) + (cauchyValueMatrix + ((Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t))).comp + (integerHom B hB)) + (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) z) a t + Q.packet fun e => Q.slot e) + (Fin r) (Fin Q.numPackets) + (Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision (Fintype.card E) a h p t)) := + nodeSplitting (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) + side hsrc htgt Q _ _ (two_pow_eq_zero_twoPowIdeal _ _) ht + (padicValInt_lt_of_taperMult_pos hq hua) + +/-- **Correctness of one active call from its children** ([OpenAI2026ETH], §D.4.4–§D.4.7). Let +`N` have entries in `B`, edges from rows to columns, and a packing `Q` into packets of `2 ^ a` +slots. Let `1 ≤ u ≤ M`, `u ≤ a` and `3 M ≤ h u ≤ 3 M + u` for `M` edges, and let the +`n > M (2 L - 1)` points `ζ k` have unit differences. If at every node `t` of positive +multiplicity each value `v t k` reduces modulo `2 ^ p_t` to the sum, over the mode tuples of +weight below `p_t`, of the products over the groups of the contractions of the children of +`callSplitting` at `ζ k` (the right side of (D.16)), then the recovered value agrees with the +contraction modulo `2 ^ p`. -/ +theorem exists_recoverFromValues_eq_add_of_children (N : TensorNetwork B Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) {u h p : ℕ} (hu : 1 ≤ u) + (huM : u ≤ Fintype.card E) (hua : u ≤ a) (hh : 3 * Fintype.card E ≤ h * u) + (hh' : h * u ≤ 3 * Fintype.card E + u) {n : ℕ} (ζ : Fin n → B) + (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) (hn : Fintype.card E * (2 * 2 ^ a - 1) < n) + (v : ℤ → Fin n → B) + (hv : ∀ t (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) k, + Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) (v t k) = + ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) + (nodePrecision (Fintype.card E) a h p t), + ∏ g, ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq (ζ k)).child β + g).contraction) : + ∃ b ∈ B, recoverFromValues B a u h ζ v = (N.contraction : K) + 2 ^ p * b := + exists_recoverFromValues_eq_add_of_mk B hB N side (packing_slot_lt Q) + (packing_slot_injective Q) hu huM hh hh' ζ hζ hn v fun t ht hq k => + (hv t ht hq k).trans (deformedContraction_eq_sum_nodeSplitting + (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) side + hsrc htgt Q _ _ (two_pow_eq_zero_twoPowIdeal _ _) ht + (padicValInt_lt_of_taperMult_pos hq hua)).symm + +end Children + +/-! ### Contraction oracles -/ + +/-- A *contraction oracle*: a value for every Boolean tensor network on the vertices `Fin n` and the +edges `Fin m`, with its sides, at every precision. A call of the recursion evaluates its children +through an oracle. -/ +abbrev ContractionOracle (B : Type*) : Type _ := + (n m : ℕ) → TensorNetwork B Bool (Fin n) (Fin m) → (Fin n → Bool) → ℕ → B + +namespace ContractionOracle + +variable {B : Type*} [CommRing B] + +/-- An oracle is *correct* when each of its values agrees with the contraction modulo the +precision `2 ^ p` it is given. -/ +def Correct (rec : ContractionOracle B) : Prop := + ∀ n m (N : TensorNetwork B Bool (Fin n) (Fin m)) side p, + ∃ c, rec n m N side p = N.contraction + 2 ^ p * c + +/-- The value of an oracle on a network over `B ⧸ (2 ^ p)` on finite types, at precision `p`: lift +the entries to `B` along a section of the reduction (`TensorNetwork.mapFun`), number the vertices +and edges (`TensorNetwork.reindex`), and query. -/ +noncomputable def evalLift (rec : ContractionOracle B) {V E : Type*} [Fintype V] [Fintype E] + {p : ℕ} (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) (side : V → Bool) : B := + rec _ _ ((N.mapFun (Function.surjInv Ideal.Quotient.mk_surjective)).reindex + (Fintype.equivFin V) (Fintype.equivFin E)) (side ∘ (Fintype.equivFin V).symm) p + +/-- **A correct oracle evaluates a lifted network**: its value reduces modulo `2 ^ p` to the +contraction. -/ +theorem mk_evalLift {rec : ContractionOracle B} (hrec : rec.Correct) {V E : Type*} [Fintype V] + [Fintype E] [DecidableEq E] {p : ℕ} (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) + (side : V → Bool) : + Ideal.Quotient.mk (twoPowIdeal B p) (rec.evalLift N side) = N.contraction := by + obtain ⟨c, hc⟩ := hrec _ _ ((N.mapFun (Function.surjInv Ideal.Quotient.mk_surjective)).reindex + (Fintype.equivFin V) (Fintype.equivFin E)) (side ∘ (Fintype.equivFin V).symm) p + have h2 : Ideal.Quotient.mk (twoPowIdeal B p) ((2 : B) ^ p) = 0 := + Ideal.Quotient.eq_zero_iff_mem.mpr (Ideal.mem_span_singleton_self _) + rw [evalLift, hc, map_add, map_mul, h2, zero_mul, add_zero, contraction_reindex, + contraction_map_mapFun _ (Function.surjInv_eq Ideal.Quotient.mk_surjective)] + +end ContractionOracle + +/-! ### One call from an oracle -/ + +section Call + +variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {V E : Type*} [Fintype V] [DecidableEq V] [Fintype E] + [DecidableEq E] + +/-- **The value at an active node** ((D.16)): the sum, over the mode tuples of weight below `p_t`, +of the products over the groups of the oracle's values on the children of `callSplitting` at `z`. +A child has its old vertices as rows and its packets as columns. -/ +noncomputable def nodeValue (rec : ContractionOracle B) (N : TensorNetwork B Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : B := + ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) + (nodePrecision (Fintype.card E) a h p t), + ∏ g, rec.evalLift ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).child β g) + Sum.isLeft + +/-- For a correct oracle, the value at an active node reduces modulo `2 ^ p_t` to the right side of +(D.16). -/ +theorem mk_nodeValue {rec : ContractionOracle B} (hrec : rec.Correct) + (N : TensorNetwork B Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) + (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) + (z : B) : + Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) + (nodeValue B hB rec N side hsrc htgt Q p u h hua ht hq z) = + ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) + (nodePrecision (Fintype.card E) a h p t), + ∏ g, ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).child β g).contraction := by + simp only [nodeValue, map_sum, map_prod, ContractionOracle.mk_evalLift hrec] + +/-- The values of an active call at the nodes and interpolation points: `nodeValue` at the nodes +of positive multiplicity, and zero elsewhere. -/ +noncomputable def callValues (rec : ContractionOracle B) (N : TensorNetwork B Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {n : ℕ} + (ζ : Fin n → B) (t : ℤ) (k : Fin n) : B := + if hq : t ∈ recoveryNodes a ∧ 0 < taperMult u h t then + nodeValue B hB rec N side hsrc htgt Q p u h hua hq.1 hq.2 (ζ k) + else 0 + +open Classical in +/-- **One active call** (§D.4.7): interpolate and recover (`recoverFromValues`) the values +`callValues` computed from the oracle. The recovered value lies in `B` when the oracle is correct +(`exists_callValue_eq_add`); otherwise the call returns zero. -/ +noncomputable def callValue (rec : ContractionOracle B) (N : TensorNetwork B Bool V E) + (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) + {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {n : ℕ} + (ζ : Fin n → B) : B := + if hx : recoverFromValues B a u h ζ (callValues B hB rec N side hsrc htgt Q p u h hua ζ) ∈ B then + ⟨_, hx⟩ + else 0 + +/-- **Correctness of one active call** for a correct oracle: under the hypotheses of +`exists_recoverFromValues_eq_add_of_children`, the call returns the contraction modulo `2 ^ p`. -/ +theorem exists_callValue_eq_add {rec : ContractionOracle B} (hrec : rec.Correct) + (N : TensorNetwork B Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) + {u h p : ℕ} (hu : 1 ≤ u) (huM : u ≤ Fintype.card E) (hua : u ≤ a) + (hh : 3 * Fintype.card E ≤ h * u) (hh' : h * u ≤ 3 * Fintype.card E + u) {n : ℕ} + (ζ : Fin n → B) (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) + (hn : Fintype.card E * (2 * 2 ^ a - 1) < n) : + ∃ c : B, callValue B hB rec N side hsrc htgt Q p u h hua ζ = N.contraction + 2 ^ p * c := by + obtain ⟨b, hb, hx⟩ := exists_recoverFromValues_eq_add_of_children B hB N side hsrc htgt Q hu + huM hua hh hh' ζ hζ hn (callValues B hB rec N side hsrc htgt Q p u h hua ζ) + fun t ht hq k => by + rw [callValues, dite_eq_left ⟨ht, hq⟩] + exact mk_nodeValue B hB hrec N side hsrc htgt Q p u h hua ht hq (ζ k) + have hx' : recoverFromValues B a u h ζ (callValues B hB rec N side hsrc htgt Q p u h hua ζ) = + ((N.contraction + 2 ^ p * ⟨b, hb⟩ : B) : K) := by + rw [hx] + push_cast + rfl + refine ⟨⟨b, hb⟩, ?_⟩ + rw [callValue, dite_eq_left (by rw [hx']; exact (N.contraction + 2 ^ p * ⟨b, hb⟩).2)] + exact Subtype.ext hx' + +end Call + +/-! ### The recursion -/ + +/-- The parameters of an active call: packets of `L = 2 ^ a` slots, and the tapered multiplicities +`q_t = h max(0, u - v₂ t)` of Lemma D.5. -/ +structure CallParams where + /-- The packets have `2 ^ a` slots. -/ + a : ℕ + /-- The number of levels of positive multiplicity. -/ + u : ℕ + /-- The step of the multiplicities between levels. -/ + h : ℕ + +/-- The parameters of a call with `M` edges and `n` interpolation points are *admissible* when +they meet the hypotheses of Lemma D.5, `1 ≤ u ≤ M`, `u ≤ a` and `3 M ≤ h u ≤ 3 M + u`, and there +are more than `M (2 L - 1)` points. -/ +@[expose] def CallParams.Admissible (θ : CallParams) (M n : ℕ) : Prop := + 1 ≤ θ.u ∧ θ.u ≤ M ∧ θ.u ≤ θ.a ∧ 3 * M ≤ θ.h * θ.u ∧ θ.h * θ.u ≤ 3 * M + θ.u ∧ + M * (2 * 2 ^ θ.a - 1) < n + +instance (θ : CallParams) (M n : ℕ) : Decidable (θ.Admissible M n) := by + unfold CallParams.Admissible + infer_instance + +section Recursion + +variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) (sched : ℕ → ℕ → Option CallParams) {N₀ : ℕ} + (pts : Fin N₀ → B) + +/-- **The contraction recursion** ([OpenAI2026ETH], §D.4.7). A call at depth `d` on a network `N` +with `m` edges, sides `side` and precision `p` asks the schedule for its parameters. When there +are none, the parameters are not admissible, or an edge does not run from a row to a column, it +returns the contraction (direct summation). Otherwise it is an active call (`callValue`) with the +packing of Lemma D.3 (`r = 64`) and the first `M (2 L - 1) + 1` points of `pts`, whose children +are recursive calls at depth `d + 1`; a child that does not have fewer edges returns its +contraction. -/ +noncomputable def contractRec (d n m : ℕ) (N : TensorNetwork B Bool (Fin n) (Fin m)) + (side : Fin n → Bool) (p : ℕ) : B := + match sched d m with + | none => N.contraction + | some θ => + if hθ : θ.Admissible m N₀ ∧ (∀ e, side (N.src e) = true) ∧ ∀ e, side (N.tgt e) = false then + callValue B hB + (fun n' m' N' side' p' => + if hm : m' < m then contractRec (d + 1) n' m' N' side' p' else N'.contraction) + N side hθ.2.1 hθ.2.2 (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h hθ.1.2.2.1 + fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.1.2.2.2.2.2 k) + else N.contraction +termination_by m + +/-- **Correctness of the recursion** ([OpenAI2026ETH], §D.4.7, p. 64: correctness "by induction +from the leaves"). For interpolation points with unit differences, every call returns the +contraction modulo `2 ^ p`, for every schedule and depth. -/ +theorem exists_contractRec_eq_add (hpts : Pairwise fun k l => IsUnit (pts k - pts l)) (d n m : ℕ) + (N : TensorNetwork B Bool (Fin n) (Fin m)) (side : Fin n → Bool) (p : ℕ) : + ∃ c : B, contractRec B hB sched pts d n m N side p = N.contraction + 2 ^ p * c := by + induction m using Nat.strong_induction_on generalizing d n p with + | _ m ih => + rw [contractRec] + split + · exact ⟨0, by simp⟩ + · rename_i θ _ + split_ifs with hθ + · obtain ⟨hu, huM, hua, hh, hh', hn⟩ := hθ.1 + refine exists_callValue_eq_add B hB (fun n' m' N' side' p' => ?_) N side hθ.2.1 hθ.2.2 _ + hu (by simpa using huM) hua (by simpa using hh) (by simpa using hh') _ + (fun k l hkl => hpts fun h => hkl (Fin.castLE_injective _ h)) (by simp) + split_ifs with hm + · exact ih m' hm (d + 1) n' N' side' p' + · exact ⟨0, by simp⟩ + · exact ⟨0, by simp⟩ + +/-- **The recursion computes the contraction modulo `2 ^ p`.** -/ +theorem mk_contractRec (hpts : Pairwise fun k l => IsUnit (pts k - pts l)) (d n m : ℕ) + (N : TensorNetwork B Bool (Fin n) (Fin m)) (side : Fin n → Bool) (p : ℕ) : + Ideal.Quotient.mk (twoPowIdeal B p) (contractRec B hB sched pts d n m N side p) = + Ideal.Quotient.mk (twoPowIdeal B p) N.contraction := by + obtain ⟨c, hc⟩ := exists_contractRec_eq_add B hB sched pts hpts d n m N side p + rw [hc, Ideal.Quotient.eq, add_sub_cancel_left] + exact Ideal.mul_mem_right _ _ (Ideal.mem_span_singleton_self _) + +end Recursion + +end TensorNetwork + +end Cslib.Circuits.Frontier diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean new file mode 100644 index 000000000..c3b115f8a --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean @@ -0,0 +1,146 @@ +/- +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.Recursion +import Complexity.Foundations.NumberTheory.Padics.AdjoinRootDigitLift +import Mathlib.Algebra.Field.ZMod +import Mathlib.Algebra.Polynomial.SpecificDegree +import Mathlib.Tactic.ComputeDegree +import Mathlib.Tactic.FinCases + +/-! +# Tests for the contraction recursion + +* **Renaming and lifting.** Renaming a two-edge network along a rotation of its vertices and a + swap of its edges keeps its contraction, and the canonical lift of a network over `ℤ / 4` to `ℤ` + (integer representatives in `{0, 1, 2, 3}`) reduces back to it. +* **Interpolation points in `O`.** For `f = U ^ 2 + U + 1`, the four binary-coordinate lifts in + `O = ℤ_(2)[U] / (f)` have unit differences, and `ℤ_(2)` lies in `O`. +* **Correctness of the recursion over `O`**, with those four points and a constant schedule + `a = u = 1`, `h = 3`. On a one-edge network these parameters are admissible (`3 M ≤ h u ≤ 3 M + u` + and `M (2 L - 1) = 3 < 4`), so the root call is active; its children are not smaller and are + evaluated directly. +-/ + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Recursion + +open Polynomial Cslib.Circuits.Frontier TensorNetwork + +/-! ### Renaming and lifting -/ + +/-- Edge `e` runs from row `e` to column `e + 2`; the tensor at `v` is `v + 1` when its edge +carries `true`, and `1` otherwise. -/ +def net : TensorNetwork ℤ 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, by omega⟩ then v.val + 1 else 1 + tensor_congr v x y h := by + rw [h ⟨v.val % 2, by omega⟩ (by fin_cases v <;> decide)] + +/-- Renaming the vertices by a rotation and the edges by a swap keeps the contraction. -/ +example : (net.reindex (finRotate 4) (Equiv.swap 0 1)).contraction = net.contraction := + contraction_reindex _ _ _ + +/-- The same network over `ℤ / 4`. -/ +def net₄ : TensorNetwork (ZMod 4) Bool (Fin 4) (Fin 2) := + net.map (Int.castRingHom (ZMod 4)) + +/-- The canonical lift of `net₄` to `ℤ`, by the integer representatives in `{0, …, 3}`, reduces +to `net₄`, so its contraction reduces to the contraction of `net₄`. -/ +example : (net₄.mapFun fun x => (x.val : ℤ)).map (Int.castRingHom (ZMod 4)) = net₄ := + map_mapFun _ (fun x => by simp) _ + +/-! ### Interpolation points in `O = ℤ_(2)[U] / (U ^ 2 + U + 1)` -/ + +/-- `U ^ 2 + U + 1`, irreducible modulo 2. -/ +noncomputable def f : ℤ[X] := X ^ 2 + X + 1 + +theorem f_monic : f.Monic := by unfold f; monicity! + +theorem f_natDegree : f.natDegree = 2 := by unfold f; compute_degree! + +theorem f_irreducible : Irreducible (f.map (Int.castRingHom (ZMod 2))) := by + unfold f + simp only [Polynomial.map_add, Polynomial.map_pow, Polynomial.map_X, Polynomial.map_one] + refine Polynomial.irreducible_of_degree_le_three_of_not_isRoot ?_ ?_ + · have : (X ^ 2 + X + 1 : (ZMod 2)[X]).natDegree = 2 := by compute_degree! + simp [this] + · intro x hx + rw [IsRoot.def, eval_add, eval_add, eval_pow, eval_X, eval_one] at hx + fin_cases x <;> revert hx <;> decide + +/-- The field `K = ℚ[U] / (U ^ 2 + U + 1)`. -/ +abbrev K : Type := AdjoinRoot (f.map (Int.castRingHom ℚ)) + +/-- The ring `O = ℤ_(2)[U] / (U ^ 2 + U + 1)`, the valuation ring of `K`. -/ +noncomputable abbrev O : Subring K := (AdjoinRoot.padicValuation 2 f_monic f_irreducible).integer + +/-- `ℤ_(2)` lies in `O`: the valuation of `K` extends that of `ℚ`. -/ +theorem hO : ∀ c ∈ Valuation.integer (Rat.padicValuation 2), algebraMap ℚ K c ∈ O := fun c hc => by + rw [Valuation.mem_integer_iff, AdjoinRoot.padicValuation_algebraMap] + exact hc + +/-- There are `2 ^ 2 = 4` digit vectors. -/ +theorem card_digits : Fintype.card (Fin f.natDegree → ZMod 2) = 4 := by + rw [Fintype.card_fun, ZMod.card, Fintype.card_fin, f_natDegree] + norm_num + +/-- The four binary-coordinate lifts `0, 1, U, 1 + U`, in some order. -/ +noncomputable def pts (k : Fin 4) : O := + AdjoinRoot.integerDigitLift f_monic f_irreducible + ((Fintype.equivFin (Fin f.natDegree → ZMod 2)).symm (Fin.cast card_digits.symm k)) + +/-- **The binary-coordinate lifts are interpolation points**: their differences are units of `O`. -/ +theorem pts_pairwise : Pairwise fun k l => IsUnit (pts k - pts l) := fun _ _ hkl => + AdjoinRoot.pairwise_isUnit_integerDigitLift_sub f_monic f_irreducible fun h => + hkl (Fin.cast_injective _ ((Fintype.equivFin _).symm.injective h)) + +/-! ### The recursion over `O` -/ + +/-- The constant schedule `a = u = 1`, `h = 3`. -/ +def sched (_ _ : ℕ) : Option TensorNetwork.CallParams := some ⟨1, 1, 3⟩ + +/-- On one edge with four points the schedule is admissible. -/ +example : (TensorNetwork.CallParams.mk 1 1 3).Admissible 1 4 := by decide + +/-- One edge from row `0` to column `1`, with tensor `(1, 2)` at the row and `(1, 1)` at the +column. -/ +noncomputable def net₁ : TensorNetwork O Bool (Fin 2) (Fin 1) where + src _ := 0 + tgt _ := 1 + tensor v x := if v = 0 ∧ x 0 then 2 else 1 + tensor_congr v x y h := by rw [h 0 (by fin_cases v <;> simp)] + +/-- **The root call is active**: it is the active call of `callValue` with the packing of +Lemma D.3, the four lifts as interpolation points, and recursive calls at depth one for the +children with fewer edges. -/ +example (p : ℕ) : + contractRec O hO sched pts 0 2 1 net₁ (fun v => decide (v = 0)) p = + callValue O hO + (fun n' m' N' side' p' => + if m' < 1 then contractRec O hO sched pts 1 n' m' N' side' p' else N'.contraction) + net₁ (fun v => decide (v = 0)) (fun _ => rfl) (fun _ => rfl) + (Cslib.PacketPacking.packing net₁.src net₁.tgt 1 64) p 1 3 le_rfl pts := by + rw [contractRec.eq_def] + simp only [sched] + rw [dite_eq_left ⟨by decide, fun _ => rfl, fun _ => rfl⟩] + rfl + +/-- **The recursion is correct**: at every precision, the root call returns the contraction of +`net₁` modulo `2 ^ p`. -/ +example (p : ℕ) : + Ideal.Quotient.mk (twoPowIdeal O p) + (contractRec O hO sched pts 0 2 1 net₁ (fun v => decide (v = 0)) p) = + Ideal.Quotient.mk (twoPowIdeal O p) net₁.contraction := + mk_contractRec O hO sched pts pts_pairwise 0 2 1 net₁ _ p + +/-- The recursion is correct for every schedule and every network. -/ +example (sched' : ℕ → ℕ → Option TensorNetwork.CallParams) (n m p : ℕ) + (N : TensorNetwork O Bool (Fin n) (Fin m)) (side : Fin n → Bool) : + ∃ c : O, contractRec O hO sched' pts 0 n m N side p = N.contraction + 2 ^ p * c := + exists_contractRec_eq_add O hO sched' pts pts_pairwise 0 n m N side p + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Recursion From bffa3cfe4e5d12e668c7cbd21458063402759c10 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:53:49 -0400 Subject: [PATCH 22/38] feat(foundations): count tuples of bounded weight by the partition generating function The number of tuples beta : iota -> N of total weight sum w_i beta_i < p, with at most m coordinates of each positive weight, is at most exp(theta p + m pi^2/(6 theta)) for every theta > 0, hence at most 2^(4 floor(sqrt(m p)) + 4). This is the mode count (D.15) of the Boolean tensor contraction theorem of [OpenAI2026ETH] (#1538). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Combinatorics/WeightBudget.lean | 245 ++++++++++++++++++ .../Combinatorics/WeightBudget.lean | 59 +++++ 3 files changed, 305 insertions(+) create mode 100644 Complexity/Foundations/Combinatorics/WeightBudget.lean create mode 100644 ComplexityTests/Foundations/Combinatorics/WeightBudget.lean diff --git a/Complexity.lean b/Complexity.lean index fdd3b220c..68fc1497c 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2594,6 +2594,7 @@ public import Complexity.Foundations.Combinatorics.Sunflower public import Complexity.Foundations.Combinatorics.Sunflower.LowerBound public import Complexity.Foundations.Combinatorics.Sunflower.Pluck public import Complexity.Foundations.Combinatorics.UniformSchedules +public import Complexity.Foundations.Combinatorics.WeightBudget public import Complexity.Foundations.Control.Monad.IsMonadHom.StateT public import Complexity.Foundations.Control.Monad.Memoize public import Complexity.Foundations.Data.BiTape diff --git a/Complexity/Foundations/Combinatorics/WeightBudget.lean b/Complexity/Foundations/Combinatorics/WeightBudget.lean new file mode 100644 index 000000000..7d742fb6f --- /dev/null +++ b/Complexity/Foundations/Combinatorics/WeightBudget.lean @@ -0,0 +1,245 @@ +/- +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 Mathlib.Analysis.SpecialFunctions.Log.Basic +public import Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic +import Mathlib.Algebra.BigOperators.Ring.Finset +import Mathlib.Analysis.Complex.ExponentialBounds +import Mathlib.Analysis.Real.Pi.Bounds +import Mathlib.Analysis.SpecialFunctions.Log.Deriv +import Mathlib.Analysis.SpecialFunctions.Pow.Real +import Mathlib.Analysis.SpecificLimits.Basic +import Mathlib.Data.Fintype.Pi +import Mathlib.NumberTheory.ZetaValues +import Mathlib.Tactic.FieldSimp +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Positivity + +/-! +# Counting tuples of bounded weight + +Give each coordinate `i` of a tuple `β : ι → ℕ` a positive weight `w i`, with at most `m` +coordinates of each weight. This file bounds the number of tuples of total weight +`∑ i, w i * β i < p` by the partition generating function: + +* for every `θ > 0` there are at most `exp (θ p + m π² / (6 θ))` of them + (`Cslib.card_le_exp_of_sum_mul_lt`); +* taking `θ = √(m / p)`, there are at most `2 ^ (4 ⌊√(m p)⌋ + 4)` + (`Cslib.card_le_two_pow_of_sum_mul_lt`). + +This is the count (D.15) of the exponent tuples of the modes in the proof of the Boolean tensor +contraction theorem of [OpenAI2026ETH] (§D.4.5, pp. 62–63), where the modes play the coordinates +and their weights are their divisibility exponents. + +**The proof.** Put `x = e^{-θ}`. Every tuple of weight below `p` has `x ^ (weight) ≥ x ^ p`, and the +sum of `x ^ (weight)` over all tuples is at most the generating product `∏ i, (1 - x ^ w i)⁻¹`. +Its logarithm is `∑ i, -log (1 - x ^ w i)`, which is at most `m` times the sum of +`-log (1 - e^{-θ j})` over the distinct weights `j`. Expanding the logarithm and summing the +geometric series bounds that sum (`Cslib.sum_neg_log_one_sub_exp_le`): + + `∑_{j ≥ 1} -log (1 - e^{-θ j}) = ∑_{h ≥ 1} 1 / (h (e^{θ h} - 1)) ≤ (1 / θ) ∑_{h ≥ 1} 1 / h² + = π² / (6 θ)`, + +using `e^y - 1 ≥ y` and `∑ 1 / h² = π² / 6` (`hasSum_zeta_two`). + +## Main results + +* `Cslib.sum_neg_log_one_sub_exp_le`: `∑_{j ∈ s} -log (1 - e^{-θ j}) ≤ π² / (6 θ)` for a finite + set `s` of positive integers. +* `Cslib.card_le_exp_of_sum_mul_lt`: the generating-function bound on the number of tuples. +* `Cslib.card_le_two_pow_of_sum_mul_lt`: its natural-number form `2 ^ (4 ⌊√(m p)⌋ + 4)`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.5, the count (D.15) + and the estimate following it (pp. 62–63). +-/ + +public section + +namespace Cslib + +open Finset Real + +/-- **The logarithm of the partition generating function.** For `θ > 0` and every finite set +`s` of positive integers, `∑_{j ∈ s} -log (1 - e^{-θ j}) ≤ π² / (6 θ)`. -/ +theorem sum_neg_log_one_sub_exp_le {θ : ℝ} (hθ : 0 < θ) (s : Finset ℕ) (hs : 0 ∉ s) : + ∑ j ∈ s, -log (1 - exp (-(θ * j))) ≤ π ^ 2 / (6 * θ) := by + have hterm : ∀ j ∈ s, HasSum (fun h : ℕ => exp (-(θ * j)) ^ (h + 1) / (h + 1)) + (-log (1 - exp (-(θ * j)))) := by + intro j hj + have hj0 : (0 : ℝ) < j := Nat.cast_pos.2 (Nat.pos_of_ne_zero fun h => hs (h ▸ hj)) + apply Real.hasSum_pow_div_log_of_abs_lt_one + rw [abs_of_pos (exp_pos _), exp_lt_one_iff] + have := mul_pos hθ hj0 + linarith + have hzeta : HasSum (fun h : ℕ => 1 / θ * (1 / ((h : ℝ) + 1) ^ 2)) (1 / θ * (π ^ 2 / 6)) := by + have h := (hasSum_nat_add_iff' 1).mpr hasSum_zeta_two + simp only [range_one, sum_singleton, Nat.cast_zero, ne_eq, OfNat.ofNat_ne_zero, + not_false_eq_true, zero_pow, div_zero, sub_zero, Nat.cast_add, Nat.cast_one] at h + exact h.mul_left _ + have hle := hasSum_le (fun h => ?_) (hasSum_sum hterm) hzeta + · calc _ ≤ 1 / θ * (π ^ 2 / 6) := hle + _ = π ^ 2 / (6 * θ) := by field_simp + -- For fixed `h`, the terms form a geometric series in `q = e^{-θ (h + 1)}`. + set y : ℝ := θ * (h + 1) with hy + have hy0 : 0 < y := by positivity + set q : ℝ := exp (-y) with hq + have hq0 : 0 ≤ q := (exp_pos _).le + have hq1 : q < 1 := exp_lt_one_iff.2 (neg_lt_zero.2 hy0) + have hpow : ∀ j : ℕ, exp (-(θ * j)) ^ (h + 1) = q ^ j := fun j => by + rw [hq, ← exp_nat_mul, ← exp_nat_mul, hy] + push_cast + ring_nf + have hgeo : ∑ j ∈ s, q ^ j ≤ (1 - q)⁻¹ - 1 := by + have := sum_le_hasSum (insert 0 s) (fun j _ => pow_nonneg hq0 j) + (hasSum_geometric_of_lt_one hq0 hq1) + rw [sum_insert hs, pow_zero] at this + linarith + have hkey : q * (1 + y) ≤ 1 := by + calc q * (1 + y) ≤ q * exp y := mul_le_mul_of_nonneg_left (by linarith [add_one_le_exp y]) hq0 + _ = 1 := by rw [hq, ← exp_add, neg_add_cancel, exp_zero] + have hq' : (1 - q)⁻¹ - 1 ≤ 1 / y := by + have h1q : 0 < 1 - q := by linarith + rw [show (1 - q)⁻¹ - 1 = q / (1 - q) by field_simp; ring, div_le_div_iff₀ h1q hy0] + linarith + have hh : (0 : ℝ) < h + 1 := by positivity + calc ∑ j ∈ s, exp (-(θ * j)) ^ (h + 1) / (h + 1) = (∑ j ∈ s, q ^ j) / (h + 1) := by + rw [sum_div] + exact sum_congr rfl fun j _ => by rw [hpow] + _ ≤ (1 / y) / (h + 1) := div_le_div_of_nonneg_right (hgeo.trans hq') hh.le + _ = 1 / θ * (1 / ((h : ℝ) + 1) ^ 2) := by rw [hy]; field_simp + +/-- **Counting tuples of bounded weight by the generating function.** Let every coordinate have a +positive weight, with at most `m` coordinates of each weight. Then for every `θ > 0` there are at +most `exp (θ p + m π² / (6 θ))` tuples of total weight below `p`. -/ +theorem card_le_exp_of_sum_mul_lt {ι : Type*} [Fintype ι] {w : ι → ℕ} (hw : ∀ i, 0 < w i) + {m : ℕ} (hm : ∀ j, #{i | w i = j} ≤ m) {p : ℕ} {S : Finset (ι → ℕ)} + (hS : ∀ β ∈ S, ∑ i, w i * β i < p) {θ : ℝ} (hθ : 0 < θ) : + (#S : ℝ) ≤ exp (θ * p + m * (π ^ 2 / (6 * θ))) := by + classical + set x : ℝ := exp (-θ) with hx + have hx0 : 0 < x := exp_pos _ + have hx1 : x < 1 := exp_lt_one_iff.2 (neg_lt_zero.2 hθ) + -- Every tuple contributes at least `x ^ p` to the generating sum. + have h1 : (#S : ℝ) * x ^ p ≤ ∑ β ∈ S, x ^ (∑ i, w i * β i) := by + calc (#S : ℝ) * x ^ p = ∑ _β ∈ S, x ^ p := by rw [sum_const, nsmul_eq_mul] + _ ≤ _ := sum_le_sum fun β hβ => pow_le_pow_of_le_one hx0.le hx1.le (hS β hβ).le + -- The tuples lie in a box, over which the generating sum factors. + have hsub : S ⊆ Fintype.piFinset fun _ => range p := by + intro β hβ + rw [Fintype.mem_piFinset] + intro i + rw [mem_range] + calc β i ≤ w i * β i := Nat.le_mul_of_pos_left _ (hw i) + _ ≤ ∑ i, w i * β i := single_le_sum (fun i _ => Nat.zero_le (w i * β i)) (mem_univ i) + _ < p := hS β hβ + have h2 : ∑ β ∈ S, x ^ (∑ i, w i * β i) ≤ ∏ i, ∑ b ∈ range p, (x ^ w i) ^ b := by + rw [prod_univ_sum] + calc ∑ β ∈ S, x ^ (∑ i, w i * β i) = ∑ β ∈ S, ∏ i, (x ^ w i) ^ β i := by + refine sum_congr rfl fun β _ => ?_ + rw [← prod_pow_eq_pow_sum] + exact prod_congr rfl fun i _ => pow_mul x (w i) (β i) + _ ≤ ∑ β ∈ Fintype.piFinset fun _ => range p, ∏ i, (x ^ w i) ^ β i := + sum_le_sum_of_subset_of_nonneg hsub fun _ _ _ => prod_nonneg fun _ _ => by positivity + -- Each factor is a truncated geometric series. + have h3 : ∏ i, ∑ b ∈ range p, (x ^ w i) ^ b ≤ exp (∑ i, -log (1 - x ^ w i)) := by + rw [exp_sum] + refine prod_le_prod₀ (fun i _ => sum_nonneg fun _ _ => by positivity) fun i _ => ?_ + have hy0 : 0 ≤ x ^ w i := by positivity + have hy1 : x ^ w i < 1 := pow_lt_one₀ hx0.le hx1 (hw i).ne' + rw [exp_neg, exp_log (by linarith)] + exact sum_le_hasSum _ (fun _ _ => by positivity) (hasSum_geometric_of_lt_one hy0 hy1) + -- Group the coordinates by weight. + have h4 : ∑ i, -log (1 - x ^ w i) ≤ m * (π ^ 2 / (6 * θ)) := by + have hxj : ∀ j : ℕ, x ^ j = exp (-(θ * j)) := fun j => by + rw [hx, ← exp_nat_mul] + ring_nf + have hnn : ∀ j ∈ univ.image w, 0 ≤ -log (1 - x ^ j) := by + intro j hj + obtain ⟨i, -, rfl⟩ := mem_image.1 hj + have : 0 < 1 - x ^ w i := by linarith [pow_lt_one₀ hx0.le hx1 (hw i).ne'] + have : 1 - x ^ w i ≤ 1 := by linarith [pow_pos hx0 (w i)] + linarith [log_nonpos (by linarith) this] + have h0 : 0 ∉ univ.image w := by + simp only [mem_image, mem_univ, true_and, not_exists] + exact fun i h => (hw i).ne' h + rw [sum_comp (fun j => -log (1 - x ^ j)) w] + calc ∑ j ∈ univ.image w, #{i ∈ univ | w i = j} • -log (1 - x ^ j) + ≤ ∑ j ∈ univ.image w, m • -log (1 - x ^ j) := + sum_le_sum fun j hj => nsmul_le_nsmul_left (hnn j hj) (hm j) + _ = m * ∑ j ∈ univ.image w, -log (1 - exp (-(θ * j))) := by + rw [← smul_sum, nsmul_eq_mul] + simp only [hxj] + _ ≤ m * (π ^ 2 / (6 * θ)) := + mul_le_mul_of_nonneg_left (sum_neg_log_one_sub_exp_le hθ _ h0) (Nat.cast_nonneg m) + have hxp : x ^ p * exp (θ * p) = 1 := by + rw [hx, ← exp_nat_mul, ← exp_add] + ring_nf + exact exp_zero + calc (#S : ℝ) = #S * x ^ p * exp (θ * p) := by rw [mul_assoc, hxp, mul_one] + _ ≤ exp (m * (π ^ 2 / (6 * θ))) * exp (θ * p) := + mul_le_mul_of_nonneg_right ((h1.trans h2).trans (h3.trans (exp_le_exp.2 h4))) + (exp_pos _).le + _ = exp (θ * p + m * (π ^ 2 / (6 * θ))) := by rw [← exp_add, add_comm] + +/-- `1 + π² / 6 ≤ 4 log 2`, so `exp ((1 + π² / 6) y) ≤ 16 ^ y`. -/ +private theorem one_add_pi_sq_div_six_le : 1 + π ^ 2 / 6 ≤ 4 * log 2 := by + nlinarith [pi_lt_d2, pi_pos, log_two_gt_d9] + +/-- **Counting tuples of bounded weight, natural-number form.** Let every coordinate have a +positive weight, with at most `m` coordinates of each weight. Then there are at most +`2 ^ (4 ⌊√(m p)⌋ + 4)` tuples of total weight below `p`: the generating-function bound at +`θ = √(m / p)`. -/ +theorem card_le_two_pow_of_sum_mul_lt {ι : Type*} [Fintype ι] {w : ι → ℕ} (hw : ∀ i, 0 < w i) + {m : ℕ} (hm : ∀ j, #{i | w i = j} ≤ m) {p : ℕ} {S : Finset (ι → ℕ)} + (hS : ∀ β ∈ S, ∑ i, w i * β i < p) : #S ≤ 2 ^ (4 * Nat.sqrt (m * p) + 4) := by + classical + rcases Nat.eq_zero_or_pos p with rfl | hp + · have : S = ∅ := eq_empty_of_forall_notMem fun β hβ => Nat.not_lt_zero _ (hS β hβ) + simp [this] + rcases Nat.eq_zero_or_pos m with rfl | hm0 + · have : IsEmpty ι := ⟨fun i => by + have hpos : 0 < #{i' | w i' = w i} := card_pos.2 ⟨i, by simp⟩ + have := hm (w i) + omega⟩ + exact (card_le_one_of_subsingleton S).trans Nat.one_le_two_pow + set n := Nat.sqrt (m * p) + set a : ℝ := √(m : ℝ) with ha + set b : ℝ := √(p : ℝ) with hb + have ha0 : 0 < a := Real.sqrt_pos.2 (Nat.cast_pos.2 hm0) + have hb0 : 0 < b := Real.sqrt_pos.2 (Nat.cast_pos.2 hp) + have ha2 : (m : ℝ) = a ^ 2 := (Real.sq_sqrt (Nat.cast_nonneg m)).symm + have hb2 : (p : ℝ) = b ^ 2 := (Real.sq_sqrt (Nat.cast_nonneg p)).symm + have hexp : a / b * p + m * (π ^ 2 / (6 * (a / b))) = a * b * (1 + π ^ 2 / 6) := by + rw [ha2, hb2] + field_simp + have hab : a * b ≤ n + 1 := by + have hlt : ((m * p : ℕ) : ℝ) < ((n + 1 : ℕ) : ℝ) ^ 2 := by + exact_mod_cast Nat.lt_succ_sqrt' (m * p) + have hsq : (a * b) ^ 2 < ((n : ℝ) + 1) ^ 2 := by + rw [mul_pow, ← ha2, ← hb2] + push_cast at hlt + exact hlt + exact (lt_of_pow_lt_pow_left₀ 2 (by positivity) hsq).le + have hreal : (#S : ℝ) ≤ 2 ^ (4 * n + 4) := by + calc (#S : ℝ) ≤ exp (a / b * p + m * (π ^ 2 / (6 * (a / b)))) := + card_le_exp_of_sum_mul_lt hw hm hS (div_pos ha0 hb0) + _ = exp (a * b * (1 + π ^ 2 / 6)) := by rw [hexp] + _ ≤ exp (((4 * n + 4 : ℕ) : ℝ) * log 2) := by + refine exp_le_exp.2 ?_ + have h1 : (0 : ℝ) ≤ 1 + π ^ 2 / 6 := by positivity + calc a * b * (1 + π ^ 2 / 6) ≤ (n + 1) * (4 * log 2) := + mul_le_mul hab one_add_pi_sq_div_six_le h1 (by positivity) + _ = ((4 * n + 4 : ℕ) : ℝ) * log 2 := by push_cast; ring + _ = 2 ^ (4 * n + 4) := by rw [exp_nat_mul, exp_log two_pos] + exact_mod_cast hreal + +end Cslib diff --git a/ComplexityTests/Foundations/Combinatorics/WeightBudget.lean b/ComplexityTests/Foundations/Combinatorics/WeightBudget.lean new file mode 100644 index 000000000..294c9e724 --- /dev/null +++ b/ComplexityTests/Foundations/Combinatorics/WeightBudget.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 +-/ +module + +public import Complexity.Foundations.Combinatorics.WeightBudget + +public section + +open Finset Real + +namespace Cslib.Tests.WeightBudget + +/-! ### A concrete budget + +Two coordinates of weights `1` and `2` and budget `p = 5`: the tuples with `β₀ + 2 β₁ < 5` are +the five with `β₁ = 0`, the three with `β₁ = 1` and the one with `β₁ = 2`. -/ + +/-- The weights `1, 2`. -/ +def w (i : Fin 2) : ℕ := i.val + 1 + +/-- The tuples of weight below `5`. -/ +def budget : Finset (Fin 2 → ℕ) := + {β ∈ Fintype.piFinset fun _ => range 5 | ∑ i, w i * β i < 5} + +example : #budget = 9 := by decide + +/-- Each weight occurs once, so `m = 1`: the bound is `2 ^ (4 ⌊√5⌋ + 4) = 2 ^ 12`. -/ +example : #budget ≤ 2 ^ 12 := by + have h := card_le_two_pow_of_sum_mul_lt (w := w) (m := 1) (p := 5) (S := budget) + (fun i => Nat.succ_pos _) (fun j => by + rw [card_le_one] + intro a ha b hb + rw [mem_filter] at ha hb + exact Fin.ext (by simp only [w] at ha hb; omega)) + (fun β hβ => (mem_filter.1 hβ).2) + simpa [(Nat.eq_sqrt.2 ⟨by norm_num, by norm_num⟩ : 2 = Nat.sqrt 5).symm] using h + +/-- The real form at `θ = 1`. -/ +example : (#budget : ℝ) ≤ exp (5 + π ^ 2 / 6) := by + have h := card_le_exp_of_sum_mul_lt (w := w) (m := 1) (p := 5) (S := budget) + (fun i => Nat.succ_pos _) (fun j => by + rw [card_le_one] + intro a ha b hb + rw [mem_filter] at ha hb + exact Fin.ext (by simp only [w] at ha hb; omega)) + (fun β hβ => (mem_filter.1 hβ).2) one_pos + simpa using h + +/-! ### The logarithmic sum -/ + +example : -log (1 - exp (-(1 * ((1 : ℕ) : ℝ)))) + -log (1 - exp (-(1 * ((2 : ℕ) : ℝ)))) ≤ + π ^ 2 / (6 * 1) := by + have h := sum_neg_log_one_sub_exp_le one_pos {1, 2} (by decide) + rwa [sum_pair (by decide)] at h + +end Cslib.Tests.WeightBudget From bbfbba0b54fa647fdf327b2ec6455559155b0797 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:55:43 -0400 Subject: [PATCH 23/38] feat(circuit/frontier): children of an active call have at most M/16 edges under (D.6) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Recursion.lean | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean index 3c9753220..92f4e7460 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -50,9 +50,10 @@ summation of the manuscript's leaves. The recursion is well founded on the numbe **Correctness** (`TensorNetwork.exists_contractRec_eq_add`). For interpolation points with unit differences, every call returns the contraction modulo `2 ^ p`, for every schedule: the manuscript's "induction from the leaves" (p. 64) becomes strong induction on the number of edges. So the -parameter schedule (D.4)–(D.6) matters only for the running time, which this file does not treat; -under it, Lemma D.3 (3) and `ModeSplitting.mul_card_edges_child_le` make every child smaller, so the -fallback for children is not taken. In the manuscript's ring `B = O = ℤ_(2)[U] / (f)`, the +parameter schedule (D.4)–(D.6) matters only for the running time, which this file does not treat. +Under the first inequality of (D.6), every child has at most `M / 16` edges +(`TensorNetwork.mul_card_edges_child_callSplitting_le`, from Lemma D.3 (3)), so an active call with +an edge never falls back for its children. In the manuscript's ring `B = O = ℤ_(2)[U] / (f)`, the binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub`). ## Main definitions @@ -74,6 +75,8 @@ binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub correct when its values are the sums of the products of the children's contractions. * `Cslib.Circuits.Frontier.TensorNetwork.exists_callValue_eq_add`: one call is correct for a correct oracle. +* `Cslib.Circuits.Frontier.TensorNetwork.mul_card_edges_child_callSplitting_le`: under (D.6), the + children have at most `M / 16` edges. * `Cslib.Circuits.Frontier.TensorNetwork.exists_contractRec_eq_add`, `Cslib.Circuits.Frontier.TensorNetwork.mk_contractRec`: the recursion returns the contraction modulo `2 ^ p`. @@ -351,6 +354,50 @@ theorem exists_callValue_eq_add {rec : ContractionOracle B} (hrec : rec.Correct) end Call +/-! ### Children are smaller under (D.6) -/ + +section ChildSize + +variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {V E : Type*} [Fintype V] [DecidableEq V] [FinEnum V] + [Fintype E] [DecidableEq E] [LinearOrder E] + +omit [Fintype V] [DecidableEq E] in +/-- **Children have at most `M / 16` edges** under the size hypothesis `2 Δ D² ≤ M` of (D.6), +`D = 64 ^ (2 a)`, for the packing of Lemma D.3 with `r = 64` and maximum degree `Δ`: each group has +at most `2 M / 64` ports on each side (`PacketPacking.row_ports_packing_le`), and a child has one +edge per port of its group. The source (§D.4.6, p. 63) bounds a child by `6M/r = 3M/32 < M/8` +edges from the `3M/r` port bound of Lemma D.3 (3); `PacketPacking.packing` has the sharper port +bound `2M/r`. -/ +theorem mul_card_edges_child_callSplitting_le (N : TensorNetwork B Bool V E) (side : V → Bool) + (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) {a Δ : ℕ} + (hrow : ∀ v, #{e | N.src e = v} ≤ Δ) (hcol : ∀ w, #{e | N.tgt e = w} ≤ Δ) + (hM : 2 * Δ * (64 ^ (2 * a)) ^ 2 ≤ Fintype.card E) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) (g : Fin 64) : + 64 * Fintype.card + ({e // (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h + hua ht hq z).group (N.src e) = g} ⊕ + {e // (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h + hua ht hq z).group (N.tgt e) = g}) ≤ + 4 * Fintype.card E := by + have hg : (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h + hua ht hq z).group = + packingGroup side (PacketPacking.packing N.src N.tgt a 64) + ⟨_, padicValInt_lt_of_taperMult_pos hq hua⟩ := by + exact cauchyModeSplitting_group .. + have := ModeSplitting.mul_card_edges_child_le (c := 2) (r := 64) + (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h hua ht hq z) + g (by + rw [hg] + exact (congrArg (64 * ·) (card_packingGroup_src side hsrc _ _ g)).trans_le + (PacketPacking.row_ports_packing_le N.src N.tgt a 64 hrow hcol hM _ g)) (by + rw [hg] + exact (congrArg (64 * ·) (card_packingGroup_tgt side htgt _ _ g)).trans_le + (PacketPacking.col_ports_packing_le N.src N.tgt a 64 hrow hcol hM _ g)) + exact this.trans_eq (by ring) + +end ChildSize + /-! ### The recursion -/ /-- The parameters of an active call: packets of `L = 2 ^ a` slots, and the tapered multiplicities From 5bea2c18e98885ca91912b0cb285c04d22e7b250 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:59:31 -0400 Subject: [PATCH 24/38] feat(PolyTime): polynomials over (Z/2^p)[U]/(f) and Lagrange interpolation Addition, scaling, multiplication by z - v and Horner evaluation of coefficient-list polynomials, and Lagrange interpolation with Newton inverses of the denominators, proved to take the given values and certified polynomial-time with the precision in unary. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../PolyTime/GaloisRingInterpolation.lean | 594 ++++++++++++++++++ ComplexityTests/GaloisRingInterpolation.lean | 55 ++ 3 files changed, 650 insertions(+) create mode 100644 Complexity/Computability/PolyTime/GaloisRingInterpolation.lean create mode 100644 ComplexityTests/GaloisRingInterpolation.lean diff --git a/Complexity.lean b/Complexity.lean index 566b83dcb..a79124c54 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1986,6 +1986,7 @@ 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.GaloisRingInterpolation 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/GaloisRingInterpolation.lean b/Complexity/Computability/PolyTime/GaloisRingInterpolation.lean new file mode 100644 index 000000000..d0d12173f --- /dev/null +++ b/Complexity/Computability/PolyTime/GaloisRingInterpolation.lean @@ -0,0 +1,594 @@ +/- +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.PolyTime.GaloisRingArithmetic +public import Mathlib.Algebra.Polynomial.BigOperators +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.List +import Complexity.Computability.Machines.Turing.MultiTape.Complexity.PolyTime.Nat +import Complexity.Computability.Machines.Turing.MultiTape.Encodings.Length +import Complexity.Computability.Machines.Turing.MultiTape.Internal.EqualityPrimitives +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 + +/-! +# Polynomials over `(ℤ / 2 ^ p)[U] / (f)` and Lagrange interpolation, on coefficient lists + +Polynomials in `z` over the coefficient ring `A_p = (ℤ / 2 ^ p)[U] / (f)` of +`Complexity/Computability/PolyTime/GaloisRingArithmetic.lean` are lists of coefficients, +constant first, each a coefficient list of `A_p` (`GaloisRingArith.pval`). + +* **Addition** (`GaloisRingArith.padd`), **scaling** (`GaloisRingArith.psmul`) and + **multiplication by `z - v`** (`GaloisRingArith.pmulLinear`). +* **Evaluation** (`GaloisRingArith.peval`) by Horner's rule. +* **Lagrange interpolation** (`GaloisRingArith.interp`) at nodes `v₀, …, v_{n-1}`: the sum of + `y_i (∏_{j ≠ i} (v_i - v_j))⁻¹ ∏_{j ≠ i} (z - v_j)`, with the inverses of + `GaloisRingArith.inv`. When those inverses are correct, for instance at nodes with unit + differences in a Galois ring, the result takes the value `y_i` at `v_i` + (`GaloisRingArith.eval_pval_interp`). It has at most `n + 1` coefficients and degree below `n` + (`GaloisRingArith.pval_interp`); at nodes with unit differences, a polynomial of degree below + `n` is determined by its values there. + +Every operation is polynomial-time, with the precision in unary +(`GaloisRingArith.computableInPolyTime_interp` and the rules before it). + +This is the interpolation step of the contraction algorithm of [OpenAI2026ETH] +(Appendix D.4.4, p. 61), which recovers `F (t + α z)` modulo `2 ^ p_t` from its values at +`M (2 L - 1) + 1` binary-coordinate lifts. + +## Main definitions + +* `Cslib.GaloisRingArith.pval`: the polynomial of a list of coefficient lists. +* `Cslib.GaloisRingArith.padd`, `Cslib.GaloisRingArith.psmul`, `Cslib.GaloisRingArith.pmulLinear`, + `Cslib.GaloisRingArith.peval`: polynomial arithmetic. +* `Cslib.GaloisRingArith.interp`: Lagrange interpolation. + +## Main results + +* `Cslib.GaloisRingArith.pval_padd`, `Cslib.GaloisRingArith.pval_psmul`, + `Cslib.GaloisRingArith.pval_pmulLinear`, `Cslib.GaloisRingArith.peval_eq`: correctness. +* `Cslib.GaloisRingArith.eval_pval_interp`: interpolation takes the given values. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.4 (p. 61). +-/ + +@[expose] public section + +namespace Cslib.GaloisRingArith + +open Polynomial Finset + +variable {p : ℕ} {fl : List ℕ} + +/-- The polynomial `∑ k, P_k z ^ k` over `A_p` of a list of coefficient lists. -/ +noncomputable def pval (p : ℕ) (fl : List ℕ) (P : List (List ℕ)) : (Ring p fl)[X] := + ∑ k ∈ range P.length, C (val p fl (P.getD k [])) * X ^ k + +/-- The empty coefficient list has value zero. -/ +@[simp] theorem val_nil : val p fl [] = 0 := by simp [val] + +/-- Coefficientwise sum, of length the longer length. -/ +def padd (p : ℕ) (fl : List ℕ) (P Q : List (List ℕ)) : List (List ℕ) := + (List.range (max P.length Q.length)).map fun k => add p fl (P.getD k []) (Q.getD k []) + +/-- Scaling by an element of `A_p`. -/ +def psmul (p : ℕ) (fl c : List ℕ) (P : List (List ℕ)) : List (List ℕ) := + P.map fun a => mul p fl c a + +/-- Multiplication by `z - v`. -/ +def pmulLinear (p : ℕ) (fl v : List ℕ) (P : List (List ℕ)) : List (List ℕ) := + (List.range (P.length + 1)).map fun k => + sub p fl (([] :: P).getD k []) (mul p fl v (P.getD k [])) + +/-- Evaluation at `z` by Horner's rule. -/ +def peval (p : ℕ) (fl : List ℕ) (P : List (List ℕ)) (z : List ℕ) : List ℕ := + P.foldr (fun a acc => add p fl a (mul p fl z acc)) (zero fl) + +/-- Extending a polynomial by zero coefficients does not change it. -/ +theorem pval_eq_sum_of_le (P : List (List ℕ)) {n : ℕ} (h : P.length ≤ n) : + pval p fl P = ∑ k ∈ range n, C (val p fl (P.getD k [])) * X ^ k := by + rw [pval, ← sum_range_add_sum_Ico _ h] + rw [sum_eq_zero (s := Ico _ _) fun k hk => by + rw [List.getD_eq_default _ _ (by simp at hk; omega), val_nil, C_0, zero_mul], add_zero] + +/-- **Addition is correct.** -/ +theorem pval_padd (P Q : List (List ℕ)) : + pval p fl (padd p fl P Q) = pval p fl P + pval p fl Q := by + rw [pval_eq_sum_of_le P (le_max_left P.length Q.length), + pval_eq_sum_of_le Q (le_max_right P.length Q.length), ← sum_add_distrib, pval] + simp only [padd, List.length_map, List.length_range] + refine sum_congr rfl fun k hk => ?_ + rw [List.getD_eq_getElem _ _ (by simpa using hk)] + simp [val_add, add_mul] + +/-- **Scaling is correct.** -/ +theorem pval_psmul (c : List ℕ) (P : List (List ℕ)) : + pval p fl (psmul p fl c P) = C (val p fl c) * pval p fl P := by + rw [pval, pval, mul_sum] + simp only [psmul, List.length_map] + refine sum_congr rfl fun k hk => ?_ + rw [List.getD_eq_getElem _ _ (by simpa using hk), List.getD_eq_getElem _ _ (by simpa using hk)] + simp [val_mul, mul_assoc] + +/-- **Multiplication by `z - v` is correct.** -/ +theorem pval_pmulLinear (v : List ℕ) (P : List (List ℕ)) : + pval p fl (pmulLinear p fl v P) = (X - C (val p fl v)) * pval p fl P := by + have hcoeff (k : ℕ) (hk : k < P.length + 1) : + val p fl ((pmulLinear p fl v P).getD k []) = + val p fl (([] :: P).getD k []) - val p fl v * val p fl (P.getD k []) := by + rw [pmulLinear, List.getD_eq_getElem _ _ (by simpa using hk)] + simp [val_sub, val_mul] + rw [pval, show (pmulLinear p fl v P).length = P.length + 1 by simp [pmulLinear], + sum_congr rfl fun k hk => by rw [hcoeff k (mem_range.mp hk)]] + rw [sub_mul, pval, mul_sum, mul_sum] + simp only [map_sub, map_mul, sub_mul, sum_sub_distrib] + congr 1 + · rw [sum_range_succ'] + simp only [List.getD_cons_zero, val_nil, C_0, zero_mul, add_zero, List.getD_cons_succ] + refine sum_congr rfl fun k _ => by ring + · rw [sum_range_succ, List.getD_eq_default _ _ le_rfl, val_nil, C_0, mul_zero, zero_mul, + add_zero] + refine sum_congr rfl fun k _ => by ring + +/-- **Evaluation is correct.** -/ +theorem peval_eq (P : List (List ℕ)) (z : List ℕ) : + val p fl (peval p fl P z) = (pval p fl P).eval (val p fl z) := by + induction P with + | nil => simp [peval, pval] + | cons a P ih => + rw [peval, List.foldr_cons, ← peval, val_add, val_mul, ih] + simp only [pval, List.length_cons, sum_range_succ', List.getD_cons_succ, List.getD_cons_zero, + eval_add, eval_finsetSum, eval_mul, eval_C, eval_pow, eval_X, pow_zero, mul_one, pow_succ, + mul_sum] + rw [add_comm] + congr 1 + refine sum_congr rfl fun k _ => by ring + +/-! ### Lagrange interpolation -/ + +/-- The numerator `∏_{j < n, j ≠ i} (z - v_j)` of the `i`-th Lagrange basis polynomial. -/ +def basisNum (p : ℕ) (fl : List ℕ) (v : List (List ℕ)) (i : ℕ) : List (List ℕ) := + (List.range v.length).foldl + (fun P j => if j = i then P else pmulLinear p fl (v.getD j []) P) [one fl] + +/-- The denominator `∏_{j < n, j ≠ i} (v_i - v_j)` of the `i`-th Lagrange basis polynomial. -/ +def basisDen (p : ℕ) (fl : List ℕ) (v : List (List ℕ)) (i : ℕ) : List ℕ := + (List.range v.length).foldl + (fun d j => if j = i then d else mul p fl d (sub p fl (v.getD i []) (v.getD j []))) (one fl) + +/-- **Lagrange interpolation** of the values `y` at the nodes `v`. -/ +def interp (p : ℕ) (fl : List ℕ) (v : List (List ℕ)) (y : List (List ℕ)) : List (List ℕ) := + (List.range v.length).foldl (fun P i => + padd p fl P (psmul p fl (mul p fl (y.getD i []) (inv p fl (basisDen p fl v i))) + (basisNum p fl v i))) [] + +theorem pval_basisNum (v : List (List ℕ)) (i : ℕ) : + pval p fl (basisNum p fl v i) = + ∏ j ∈ (range v.length).erase i, (X - C (val p fl (v.getD j []))) := by + have h (m : ℕ) (hm : m ≤ v.length) : pval p fl ((List.range m).foldl + (fun P j => if j = i then P else pmulLinear p fl (v.getD j []) P) [one fl]) = + ∏ j ∈ (range m).erase i, (X - C (val p fl (v.getD j []))) := by + induction m with + | zero => simp [pval] + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, range_add_one] + by_cases hmi : m = i + · subst hmi + simp only [↓reduceIte] + rw [ih (by omega), erase_insert (by simp), Finset.erase_eq_of_notMem (by simp)] + · simp only [hmi, ↓reduceIte] + rw [pval_pmulLinear, ih (by omega), erase_insert_of_ne hmi, prod_insert (by simp)] + exact h _ le_rfl + +theorem val_basisDen (v : List (List ℕ)) (i : ℕ) : + val p fl (basisDen p fl v i) = + ∏ j ∈ (range v.length).erase i, (val p fl (v.getD i []) - val p fl (v.getD j [])) := by + have h (m : ℕ) : val p fl ((List.range m).foldl + (fun d j => if j = i then d else mul p fl d (sub p fl (v.getD i []) (v.getD j []))) + (one fl)) = ∏ j ∈ (range m).erase i, (val p fl (v.getD i []) - val p fl (v.getD j [])) := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, range_add_one] + by_cases hmi : m = i + · subst hmi + simp only [↓reduceIte] + rw [ih, erase_insert (by simp), Finset.erase_eq_of_notMem (by simp)] + · simp only [hmi, ↓reduceIte] + rw [val_mul, val_sub, ih, erase_insert_of_ne hmi, prod_insert (by simp), mul_comm] + exact h _ + +/-- **Interpolation is the Lagrange formula**, with the computed inverses of the denominators. -/ +theorem pval_interp (v y : List (List ℕ)) : + pval p fl (interp p fl v y) = ∑ i ∈ range v.length, + C (val p fl (y.getD i []) * val p fl (inv p fl (basisDen p fl v i))) * + ∏ j ∈ (range v.length).erase i, (X - C (val p fl (v.getD j []))) := by + have h (m : ℕ) (hm : m ≤ v.length) : pval p fl ((List.range m).foldl (fun P i => + padd p fl P (psmul p fl (mul p fl (y.getD i []) (inv p fl (basisDen p fl v i))) + (basisNum p fl v i))) []) = ∑ i ∈ range m, + C (val p fl (y.getD i []) * val p fl (inv p fl (basisDen p fl v i))) * + ∏ j ∈ (range v.length).erase i, (X - C (val p fl (v.getD j []))) := by + induction m with + | zero => simp [pval] + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, sum_range_succ, + pval_padd, ih (by omega), pval_psmul, val_mul, pval_basisNum] + exact h _ le_rfl + +/-- **Interpolation takes the given values** at the nodes, given correct inverses of the +denominators. -/ +theorem eval_pval_interp (v y : List (List ℕ)) + (hinv : ∀ i < v.length, val p fl (basisDen p fl v i) * + val p fl (inv p fl (basisDen p fl v i)) = 1) {k : ℕ} (hk : k < v.length) : + (pval p fl (interp p fl v y)).eval (val p fl (v.getD k [])) = val p fl (y.getD k []) := by + rw [pval_interp v y, eval_finsetSum, sum_eq_single k] + · rw [eval_mul, eval_C, eval_prod] + simp only [eval_sub, eval_X, eval_C] + rw [← val_basisDen, mul_assoc, mul_comm (val p fl (inv p fl _)), hinv k hk, mul_one] + · intro i hi hik + rw [eval_mul, eval_prod, prod_eq_zero (i := k) (mem_erase.mpr ⟨Ne.symm hik, mem_range.mpr hk⟩) + (by simp), mul_zero] + · intro hk' + exact absurd (mem_range.mpr hk) hk' + +/-! ### Sizes -/ + +/-- A coefficient list is *small* when it has `e` coordinates below `2 ^ (p + 1)`: reduced +coordinates and the unit both are. -/ +def Small (p : ℕ) (fl c : List ℕ) : Prop := c.length = fl.length ∧ ∀ a ∈ c, a < 2 ^ (p + 1) + +theorem small_of_lt {c : List ℕ} (hlen : c.length = fl.length) (hlt : ∀ a ∈ c, a < 2 ^ p) : + Small p fl c := + ⟨hlen, fun a ha => (hlt a ha).trans (Nat.pow_lt_pow_right (by norm_num) (by omega))⟩ + +theorem small_add (c d : List ℕ) : Small p fl (add p fl c d) := + small_of_lt (length_add p fl c d) fun _ h => lt_of_mem_add h + +theorem small_mul (c d : List ℕ) : Small p fl (mul p fl c d) := + small_of_lt (length_mul p fl c d) fun _ h => lt_of_mem_mul h + +theorem small_sub (c d : List ℕ) : Small p fl (sub p fl c d) := small_add _ _ + +theorem small_one : Small p fl (one fl) := by + refine ⟨by simp [one], fun a ha => ?_⟩ + simp only [one, List.mem_map, List.mem_range] at ha + obtain ⟨i, -, rfl⟩ := ha + have : 1 < 2 ^ (p + 1) := Nat.one_lt_two_pow (by omega) + split <;> omega + +theorem length_encList_le_of_small {c : List ℕ} (h : Small p fl c) : + (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat c).length ≤ + fl.length * (2 * (p + 1) + 1) := + length_encList_le h.1 h.2 + +/-- A list of small coefficient lists of length at most `n` is at most +`n (2 e (2 p + 3) + 1)` long. -/ +theorem length_encPoly_le {P : List (List ℕ)} {n : ℕ} (hlen : P.length ≤ n) + (h : ∀ c ∈ P, Small p fl c) : + (Turing.MultiTapeTM.encList (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat) P).length ≤ + n * (2 * (fl.length * (2 * (p + 1) + 1)) + 1) := by + rw [Turing.MultiTapeTM.length_encList] + calc (P.map fun c => 2 * (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat c).length + 1).sum + ≤ (P.map fun c => 2 * (Turing.MultiTapeTM.encList Turing.MultiTapeTM.encNat c).length + + 1).length • (2 * (fl.length * (2 * (p + 1) + 1)) + 1) := + List.sum_le_length_nsmul _ _ fun m hm => by + obtain ⟨c, hc, rfl⟩ := List.mem_map.mp hm + have := length_encList_le_of_small (h c hc) + omega + _ ≤ n * (2 * (fl.length * (2 * (p + 1) + 1)) + 1) := by + rw [List.length_map, smul_eq_mul] + exact Nat.mul_le_mul_right _ hlen + +@[simp] theorem length_padd (P Q : List (List ℕ)) : + (padd p fl P Q).length = max P.length Q.length := by simp [padd] + +@[simp] theorem length_psmul (c : List ℕ) (P : List (List ℕ)) : + (psmul p fl c P).length = P.length := by simp [psmul] + +@[simp] theorem length_pmulLinear (v : List ℕ) (P : List (List ℕ)) : + (pmulLinear p fl v P).length = P.length + 1 := by simp [pmulLinear] + +theorem small_of_mem_padd {P Q : List (List ℕ)} {c : List ℕ} (h : c ∈ padd p fl P Q) : + Small p fl c := by + obtain ⟨k, -, rfl⟩ := List.mem_map.mp h + exact small_add _ _ + +theorem small_of_mem_psmul {c' : List ℕ} {P : List (List ℕ)} {c : List ℕ} + (h : c ∈ psmul p fl c' P) : Small p fl c := by + obtain ⟨a, -, rfl⟩ := List.mem_map.mp h + exact small_mul _ _ + +theorem small_of_mem_pmulLinear {v : List ℕ} {P : List (List ℕ)} {c : List ℕ} + (h : c ∈ pmulLinear p fl v P) : Small p fl c := by + obtain ⟨k, -, rfl⟩ := List.mem_map.mp h + exact small_sub _ _ + +theorem length_basisNum_le (v : List (List ℕ)) (i : ℕ) : + (basisNum p fl v i).length ≤ v.length + 1 := by + have h (m : ℕ) : ((List.range m).foldl + (fun P j => if j = i then P else pmulLinear p fl (v.getD j []) P) [one fl]).length ≤ + m + 1 := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil] + split + · omega + · rw [length_pmulLinear]; omega + exact h _ + +theorem small_of_mem_basisNum {v : List (List ℕ)} {i : ℕ} {c : List ℕ} + (h : c ∈ basisNum p fl v i) : Small p fl c := by + have key (m : ℕ) : ∀ c ∈ (List.range m).foldl + (fun P j => if j = i then P else pmulLinear p fl (v.getD j []) P) [one fl], Small p fl c := by + induction m with + | zero => simp [small_one] + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil] + split + · exact ih + · exact fun c hc => small_of_mem_pmulLinear hc + exact key _ c h + +/-! ### Polynomial time -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {α : Type} {encIn : α ↪ List Bool} + +private theorem lengthPolyTime {β : Type} {encB : β ↪ List Bool} {l : α → List β} + (hl : ComputableInPolyTime l encIn (encList encB)) : + ComputableInPolyTime (fun x => (l x).length) encIn encUnary := + (ComputableInPolyTime.length_list encB).comp hl + +private theorem getDPolyTime {P : α → List (List ℕ)} {k : α → ℕ} + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => (P x).getD (k x) []) encIn (encList encNat) := + (ComputableInPolyTime.listGetD (encA := encList encNat) []).comp (hP.prod hk) + +/-- **Addition of polynomials is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_padd {p : α → ℕ} {fl : α → List ℕ} {P Q : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hQ : ComputableInPolyTime Q encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => padd (p x) (fl x) (P x) (Q x)) encIn + (encList (encList encNat)) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have hmax : ComputableInPolyTime (fun x => max (P x).length (Q x).length) encIn encUnary := + (ComputableInPolyTime.unary_add' (lengthPolyTime hP) + (ComputableInPolyTime.unary_sub' (lengthPolyTime hQ) (lengthPolyTime hP))).of_enc_eq + fun x => by congr 1; omega + exact ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp hmax) + (computableInPolyTime_add (hp.comp h1) (hfl.comp h1) (getDPolyTime (hP.comp h1) h2) + (getDPolyTime (hQ.comp h1) h2)) + +/-- **Scaling a polynomial is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_psmul {p : α → ℕ} {fl c : α → List ℕ} {P : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => psmul (p x) (fl x) (c x) (P x)) encIn + (encList (encList encNat)) := by + have h1 := ComputableInPolyTime.fst encIn (encList encNat) + have h2 := ComputableInPolyTime.snd encIn (encList encNat) + exact ComputableInPolyTime.mapWithInput hP + (computableInPolyTime_mul (hp.comp h1) (hfl.comp h1) (hc.comp h1) h2) + +/-- **Multiplication by `z - v` is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_pmulLinear {p : α → ℕ} {fl v : α → List ℕ} + {P : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hv : ComputableInPolyTime v encIn (encList encNat)) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => pmulLinear (p x) (fl x) (v x) (P x)) encIn + (encList (encList encNat)) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp + (ComputableInPolyTime.unary_add' (lengthPolyTime hP) (ComputableInPolyTime.const 1))) + (computableInPolyTime_sub (hp.comp h1) (hfl.comp h1) + (getDPolyTime (ComputableInPolyTime.cons' (ComputableInPolyTime.const []) + (hP.comp h1)) h2) + (computableInPolyTime_mul (hp.comp h1) (hfl.comp h1) (hv.comp h1) + (getDPolyTime (hP.comp h1) h2))) + +private theorem polyBounded_small {p : α → ℕ} {fl : α → List ℕ} {n : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hn : PolyBounded (fun x => (encIn x).length) n) : + PolyBounded (fun x => (encIn x).length) + (fun x => n x * (2 * ((fl x).length * (2 * (p x + 1) + 1)) + 1)) := by + have hpl : PolyBounded (fun x => (encIn x).length) p := by + simpa using (ComputableInPolyTime.polyBounded_length hp) + have hfll : PolyBounded (fun x => (encIn x).length) fun x => (fl x).length := + (lengthPolyTime hfl).polyBounded_length.mono fun x => by simp + exact hn.mul (((PolyBounded.const 2).mul (hfll.mul (((PolyBounded.const 2).mul + (hpl.add (PolyBounded.const 1))).add (PolyBounded.const 1)))).add (PolyBounded.const 1)) + +/-- **Evaluation is polynomial-time**, with the precision in unary. -/ +@[fun_prop] +theorem computableInPolyTime_peval {p : α → ℕ} {fl z : α → List ℕ} {P : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hz : ComputableInPolyTime z encIn (encList encNat)) : + ComputableInPolyTime (fun x => peval (p x) (fl x) (P x) (z x)) encIn (encList encNat) := by + simp only [peval, List.foldr_eq_foldl_reverse] + have hstep : ComputableInPolyTime + (fun q : (α × List ℕ) × List ℕ => add (p q.1.1) (fl q.1.1) q.2 (mul (p q.1.1) (fl q.1.1) + (z q.1.1) q.1.2)) (encPair (encPair encIn (encList encNat)) (encList encNat)) + (encList encNat) := by + have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) (encList encNat) + have ha := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq + have hacc := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + have hc := ComputableInPolyTime.snd (encPair encIn (encList encNat)) (encList encNat) + exact computableInPolyTime_add (hp.comp ha) (hfl.comp ha) hc + (computableInPolyTime_mul (hp.comp ha) (hfl.comp ha) (hz.comp ha) hacc) + have hzero : ComputableInPolyTime (fun x => zero (fl x)) encIn (encList encNat) := + computableInPolyTime_tabulate (g := fun _ _ => 0) (lengthPolyTime hfl) + (ComputableInPolyTime.const 0) + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encList encNat) (encL := encList (encList encNat)) + (step := fun x acc a => add (p x) (fl x) a (mul (p x) (fl x) (z x) acc)) hzero + ⟨ComputableInPolyTime.listReverseWithInput hP, hstep, inferInstance, inferInstance⟩ + ⟨(polyBounded_small hp hfl (PolyBounded.const 1)), fun x acc a _ => by + have := length_encList_le_of_small (small_add (p := p x) (fl := fl x) a + (mul (p x) (fl x) (z x) acc)) + omega⟩ + +/-- **Lagrange numerators are polynomial-time**, with the precision and the index in unary. -/ +@[fun_prop] +theorem computableInPolyTime_basisNum {p : α → ℕ} {fl : α → List ℕ} {v : α → List (List ℕ)} + {i : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hv : ComputableInPolyTime v encIn (encList (encList encNat))) + (hi : ComputableInPolyTime i encIn encUnary) : + ComputableInPolyTime (fun x => basisNum (p x) (fl x) (v x) (i x)) encIn + (encList (encList encNat)) := by + let encS := encList (encList encNat) + have hstep : ComputableInPolyTime + (fun q : (α × List (List ℕ)) × ℕ => if q.2 = i q.1.1 then q.1.2 + else pmulLinear (p q.1.1) (fl q.1.1) ((v q.1.1).getD q.2 []) 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 hP := (ComputableInPolyTime.snd encIn encS).comp hq + have hj := ComputableInPolyTime.snd (encPair encIn encS) encUnary + refine (ComputableInPolyTime.ite (ComputableInPolyTime.beq hj (hi.comp ha)) hP + (computableInPolyTime_pmulLinear (hp.comp ha) (hfl.comp ha) (getDPolyTime (hv.comp ha) hj) + hP)).of_enc_eq fun q => ?_ + by_cases h : q.2 = i q.1.1 <;> simp [h] + have hfold := ComputableInPolyTime.foldlWith_spec (encA := encUnary) + (encL := encList encUnary) (par := fun x => x) + (step := fun x P j => if j = i x then P else pmulLinear (p x) (fl x) ((v x).getD j []) P) + ComputableInPolyTime.id' (ComputableInPolyTime.range.comp (lengthPolyTime hv)) + (ComputableInPolyTime.cons' (computableInPolyTime_one hfl) (ComputableInPolyTime.const [])) + hstep (fun x consumed P => P.length ≤ consumed.length + 1 ∧ ∀ c ∈ P, Small (p x) (fl x) c) + (fun x => ⟨by simp, by simp [small_one]⟩) + (fun x consumed P j _ hP => by + refine ⟨?_, ?_⟩ + · simp only [List.length_append, List.length_singleton] + split + · omega + · rw [length_pmulLinear]; omega + · split + · exact hP.2 + · exact fun c hc => small_of_mem_pmulLinear hc) + (polyBounded_small hp hfl ((lengthPolyTime hv).polyBounded_length.add (PolyBounded.const 1))) + (fun x consumed P hpre hP => by + have hlen : consumed.length ≤ (v x).length := by + simpa using hpre.length_le + refine (length_encPoly_le (n := (v x).length + 1) (by omega) hP.2).trans ?_ + simp [encUnary_apply]) + exact hfold.1 + +/-- **Lagrange denominators are polynomial-time**, with the precision and the index in unary. -/ +@[fun_prop] +theorem computableInPolyTime_basisDen {p : α → ℕ} {fl : α → List ℕ} {v : α → List (List ℕ)} + {i : α → ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hv : ComputableInPolyTime v encIn (encList (encList encNat))) + (hi : ComputableInPolyTime i encIn encUnary) : + ComputableInPolyTime (fun x => basisDen (p x) (fl x) (v x) (i x)) encIn (encList encNat) := by + have hstep : ComputableInPolyTime + (fun q : (α × List ℕ) × ℕ => if q.2 = i q.1.1 then q.1.2 + else mul (p q.1.1) (fl q.1.1) q.1.2 (sub (p q.1.1) (fl q.1.1) ((v q.1.1).getD (i q.1.1) []) + ((v q.1.1).getD q.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 hd := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + have hj := ComputableInPolyTime.snd (encPair encIn (encList encNat)) encUnary + refine (ComputableInPolyTime.ite (ComputableInPolyTime.beq hj (hi.comp ha)) hd + (computableInPolyTime_mul (hp.comp ha) (hfl.comp ha) hd + (computableInPolyTime_sub (hp.comp ha) (hfl.comp ha) + (getDPolyTime (hv.comp ha) (hi.comp ha)) (getDPolyTime (hv.comp ha) hj)))).of_enc_eq + fun q => ?_ + by_cases h : q.2 = i q.1.1 <;> simp [h] + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x d j => if j = i x then d + else mul (p x) (fl x) d (sub (p x) (fl x) ((v x).getD (i x) []) ((v x).getD j []))) + (computableInPolyTime_one hfl) + ⟨ComputableInPolyTime.range.comp (lengthPolyTime hv), hstep, inferInstance, inferInstance⟩ + ⟨polyBounded_small hp hfl (PolyBounded.const 1), fun x d j _ => by + split + · omega + · have := length_encList_le_of_small (small_mul (p := p x) (fl := fl x) d + (sub (p x) (fl x) ((v x).getD (i x) []) ((v x).getD j []))) + omega⟩ + +/-- **Lagrange interpolation is polynomial-time**, with the precision in unary and the nodes and +values as lists of coefficient lists. -/ +@[fun_prop] +theorem computableInPolyTime_interp {p : α → ℕ} {fl : α → List ℕ} {v y : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hv : ComputableInPolyTime v encIn (encList (encList encNat))) + (hy : ComputableInPolyTime y encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => interp (p x) (fl x) (v x) (y x)) encIn + (encList (encList encNat)) := by + let encS := encList (encList encNat) + have hstep : ComputableInPolyTime + (fun q : (α × List (List ℕ)) × ℕ => padd (p q.1.1) (fl q.1.1) q.1.2 + (psmul (p q.1.1) (fl q.1.1) (mul (p q.1.1) (fl q.1.1) ((y q.1.1).getD q.2 []) + (inv (p q.1.1) (fl q.1.1) (basisDen (p q.1.1) (fl q.1.1) (v q.1.1) q.2))) + (basisNum (p q.1.1) (fl q.1.1) (v q.1.1) q.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 hP := (ComputableInPolyTime.snd encIn encS).comp hq + have hj := ComputableInPolyTime.snd (encPair encIn encS) encUnary + have hp' := hp.comp ha + have hfl' := hfl.comp ha + exact computableInPolyTime_padd hp' hfl' hP (computableInPolyTime_psmul hp' hfl' + (computableInPolyTime_mul hp' hfl' (getDPolyTime (hy.comp ha) hj) + (computableInPolyTime_inv hp' hfl' + (computableInPolyTime_basisDen hp' hfl' (hv.comp ha) hj))) + (computableInPolyTime_basisNum hp' hfl' (hv.comp ha) hj)) + have hfold := ComputableInPolyTime.foldlWith_spec (encA := encUnary) + (encL := encList encUnary) (par := fun x => x) + (step := fun x P i => padd (p x) (fl x) P + (psmul (p x) (fl x) (mul (p x) (fl x) ((y x).getD i []) (inv (p x) (fl x) + (basisDen (p x) (fl x) (v x) i))) (basisNum (p x) (fl x) (v x) i))) + ComputableInPolyTime.id' (ComputableInPolyTime.range.comp (lengthPolyTime hv)) + (ComputableInPolyTime.const []) hstep + (fun x _ P => P.length ≤ (v x).length + 1 ∧ ∀ c ∈ P, Small (p x) (fl x) c) + (fun x => ⟨by simp, by simp⟩) + (fun x consumed P j _ hP => by + refine ⟨?_, fun c hc => small_of_mem_padd hc⟩ + rw [length_padd, length_psmul] + exact max_le hP.1 (length_basisNum_le _ _)) + (polyBounded_small hp hfl ((lengthPolyTime hv).polyBounded_length.add (PolyBounded.const 1))) + (fun x consumed P _ hP => by + refine (length_encPoly_le hP.1 hP.2).trans ?_ + simp [encUnary_apply]) + exact hfold.1 + +end PolyTime + +end Cslib.GaloisRingArith diff --git a/ComplexityTests/GaloisRingInterpolation.lean b/ComplexityTests/GaloisRingInterpolation.lean new file mode 100644 index 000000000..00e715206 --- /dev/null +++ b/ComplexityTests/GaloisRingInterpolation.lean @@ -0,0 +1,55 @@ +/- +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.GaloisRingInterpolation + +/-! # Polynomials over `(ℤ / 2 ^ p)[U] / (f)` and Lagrange interpolation + +Concrete evaluation and interpolation in the Galois ring `(ℤ / 8)[U] / (U ^ 2 + U + 1)`, at the +nodes `0, 1, U`, whose differences `1, U, U - 1` are units; the correctness statements; and the +polynomial-time certificate of interpolation in closed form. -/ + +namespace ComplexityTests.GaloisRingInterpolation + +open Cslib.GaloisRingArith Turing.MultiTapeTM + +/-- The nodes `0, 1, U`. -/ +def nodes : List (List ℕ) := [[0, 0], [1, 0], [0, 1]] + +/-- The values `1, 2, 3 + U`. -/ +def values : List (List ℕ) := [[1, 0], [2, 0], [3, 1]] + +-- Horner evaluation of `1 + U z + z ^ 2` at `z = U`: `1 + U ^ 2 + U ^ 2 = 1 + 2 U ^ 2 = 7 + 6 U`. +example : peval 3 [1, 1] [[1, 0], [0, 1], [1, 0]] [0, 1] = [7, 6] := by decide + +-- `(z - U) (z - 1) = U - (1 + U) z + z ^ 2`, that is `[U, -1 - U, 1]`. +example : pmulLinear 3 [1, 1] [0, 1] [[7, 0], [1, 0]] = [[0, 1], [7, 7], [1, 0]] := by decide + +-- The interpolating polynomial takes the values `1, 2, 3 + U` at `0, 1, U`. +example : (nodes.map fun z => peval 3 [1, 1] (interp 3 [1, 1] nodes values) z) = values := by + decide + +-- It has three coefficients. +example : (interp 3 [1, 1] nodes values).length = 3 := by decide + +/-- Interpolation takes the given values, given correct inverses of the denominators. -/ +example (v y : List (List ℕ)) + (hinv : ∀ i < v.length, val 3 [1, 1] (basisDen 3 [1, 1] v i) * + val 3 [1, 1] (inv 3 [1, 1] (basisDen 3 [1, 1] v i)) = 1) {k : ℕ} (hk : k < v.length) : + (pval 3 [1, 1] (interp 3 [1, 1] v y)).eval (val 3 [1, 1] (v.getD k [])) = + val 3 [1, 1] (y.getD k []) := + eval_pval_interp v y hinv hk + +/-- **Interpolation in polynomial time**, on the ring description `(p, fl)` with `p` in unary, +the nodes and the values. -/ +example : ComputableInPolyTime + (fun q : (ℕ × List ℕ) × (List (List ℕ) × List (List ℕ)) => interp q.1.1 q.1.2 q.2.1 q.2.2) + (encPair (encPair encUnary (encList encNat)) + (encPair (encList (encList encNat)) (encList (encList encNat)))) + (encList (encList encNat)) := by + fun_prop + +end ComplexityTests.GaloisRingInterpolation From c2e5c59cfd9c88e57605d77505457d18b1b85499 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 22:59:37 -0400 Subject: [PATCH 25/38] feat(circuit/frontier): unfolding lemmas for an active call and a leaf of the recursion Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Recursion.lean | 28 +++++++++++++++++++ .../Frontier/TensorNetworkRecursion.lean | 17 ++++++----- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean index 92f4e7460..26e9824a2 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -77,6 +77,8 @@ binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub correct oracle. * `Cslib.Circuits.Frontier.TensorNetwork.mul_card_edges_child_callSplitting_le`: under (D.6), the children have at most `M / 16` edges. +* `Cslib.Circuits.Frontier.TensorNetwork.contractRec_eq_callValue`, + `Cslib.Circuits.Frontier.TensorNetwork.contractRec_of_sched_eq_none`: an active call and a leaf. * `Cslib.Circuits.Frontier.TensorNetwork.exists_contractRec_eq_add`, `Cslib.Circuits.Frontier.TensorNetwork.mk_contractRec`: the recursion returns the contraction modulo `2 ^ p`. @@ -448,6 +450,32 @@ noncomputable def contractRec (d n m : ℕ) (N : TensorNetwork B Bool (Fin n) (F else N.contraction termination_by m +variable {B hB sched pts} in +/-- **An active call** of the recursion: when the schedule gives admissible parameters and the edges +run from rows to columns, the call is `callValue` with the packing of Lemma D.3, the first +`M (2 L - 1) + 1` points, and recursive calls at depth `d + 1` for the children with fewer edges. -/ +theorem contractRec_eq_callValue {d n m : ℕ} {N : TensorNetwork B Bool (Fin n) (Fin m)} + {side : Fin n → Bool} {θ : CallParams} (hs : sched d m = some θ) (hθ : θ.Admissible m N₀) + (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) (p : ℕ) : + contractRec B hB sched pts d n m N side p = + callValue B hB + (fun n' m' N' side' p' => + if m' < m then contractRec B hB sched pts (d + 1) n' m' N' side' p' else N'.contraction) + N side hsrc htgt (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h hθ.2.2.1 + fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.2.2.2.2.2 k) := by + rw [contractRec.eq_def] + simp only [hs] + rw [dite_eq_left ⟨hθ, hsrc, htgt⟩] + rfl + +variable {B hB sched pts} in +/-- **A leaf** of the recursion, where the schedule gives no parameters, is evaluated directly. -/ +theorem contractRec_of_sched_eq_none {d n m : ℕ} {N : TensorNetwork B Bool (Fin n) (Fin m)} + {side : Fin n → Bool} (hs : sched d m = none) (p : ℕ) : + contractRec B hB sched pts d n m N side p = N.contraction := by + rw [contractRec.eq_def] + simp only [hs] + /-- **Correctness of the recursion** ([OpenAI2026ETH], §D.4.7, p. 64: correctness "by induction from the leaves"). For interpolation points with unit differences, every call returns the contraction modulo `2 ^ p`, for every schedule and depth. -/ diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean index c3b115f8a..69be86417 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean @@ -21,8 +21,8 @@ import Mathlib.Tactic.FinCases `O = ℤ_(2)[U] / (f)` have unit differences, and `ℤ_(2)` lies in `O`. * **Correctness of the recursion over `O`**, with those four points and a constant schedule `a = u = 1`, `h = 3`. On a one-edge network these parameters are admissible (`3 M ≤ h u ≤ 3 M + u` - and `M (2 L - 1) = 3 < 4`), so the root call is active; its children are not smaller and are - evaluated directly. + and `M (2 L - 1) = 3 < 4`), so the root call is active (`contractRec_eq_callValue`); its children + are not smaller and are evaluated directly. Without parameters the call is a leaf. -/ namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Recursion @@ -123,11 +123,14 @@ example (p : ℕ) : (fun n' m' N' side' p' => if m' < 1 then contractRec O hO sched pts 1 n' m' N' side' p' else N'.contraction) net₁ (fun v => decide (v = 0)) (fun _ => rfl) (fun _ => rfl) - (Cslib.PacketPacking.packing net₁.src net₁.tgt 1 64) p 1 3 le_rfl pts := by - rw [contractRec.eq_def] - simp only [sched] - rw [dite_eq_left ⟨by decide, fun _ => rfl, fun _ => rfl⟩] - rfl + (Cslib.PacketPacking.packing net₁.src net₁.tgt 1 64) p 1 3 le_rfl pts := + contractRec_eq_callValue (θ := ⟨1, 1, 3⟩) rfl (by decide) (fun _ => rfl) (fun _ => rfl) p + +/-- With no parameters, a call is a leaf and returns the contraction. -/ +example (p : ℕ) : + contractRec O hO (fun _ _ => none) pts 0 2 1 net₁ (fun v => decide (v = 0)) p = + net₁.contraction := + contractRec_of_sched_eq_none rfl p /-- **The recursion is correct**: at every precision, the root call returns the contraction of `net₁` modulo `2 ^ p`. -/ From bafb62e4272d58d0e9795ad624a3a42a8cc6f085 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:00:47 -0400 Subject: [PATCH 26/38] feat(circuit/frontier): parameter schedule of the contraction recursion, (D.11) and (D.15) The schedule (D.4)-(D.6) of the Boolean tensor contraction recursion of [OpenAI2026ETH] on the floor-log / Nat.sqrt scale of #1865, with the certificate exists_activeBounds: for large s every active call satisfies the packing conditions (D.6), the precision bound (D.11) p_t u <= 34 M D_t, polynomial precisions and depth <= log s - 2. The mode count (D.15) card_modeBudget_le bounds the mode tuples by 2^e_d, and the e_d sum to O(s / sqrt(log s)) (#1538). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../Frontier/TensorNetwork/Schedule.lean | 724 ++++++++++++++++++ .../Frontier/TensorNetworkSchedule.lean | 90 +++ 3 files changed, 815 insertions(+) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean diff --git a/Complexity.lean b/Complexity.lean index 68fc1497c..6c584d06f 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -874,6 +874,7 @@ 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.Schedule 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/Schedule.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean new file mode 100644 index 000000000..6fa306e29 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean @@ -0,0 +1,724 @@ +/- +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.ModeExpansion +public import Complexity.Foundations.Data.Nat.SqrtLog +import Complexity.Foundations.Combinatorics.WeightBudget +import Complexity.Foundations.Data.Nat.Asymptotics +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# The parameter schedule of the tensor contraction recursion + +This file fixes the parameters of the recursion in the proof of the Boolean tensor contraction +theorem (Theorem D.1) of [OpenAI2026ETH] (§D.4.2, (D.4)–(D.6), p. 58) and proves the inequalities +its running time rests on: the conditions of the packing lemma, the precision bound (D.11) +(p. 61) and the count (D.15) of the mode tuples (pp. 62–63). Correctness of the recursion does not +depend on these choices; they bound the number of its calls by `2 ^ O(s / √⌊log₂ s⌋)` (§D.4.7, +p. 64). + +**The schedule.** Fix the size `s` and write `λ = ⌊log₂ s⌋`. + +* A call with at most `H = ⌊s / ⌊√λ⌋⌋` edges is a leaf (`leafSize`). +* At depth `d` the taper is `u_d = ⌊λ / 2 ^ (17 + d)⌋` (`taper`), the manuscript's + `⌊ε λ / 2 ^ d⌋` with `ε = 1 / (128 · 1024) = 2 ^ (-17)`. +* Labels have `a_d = 2 ∑_{j ≤ d} u_j` coordinates (`slotBits`), packets `L = 2 ^ a_d` slots, and + there are `D_arr = 64 ^ (2 a_d)` pairs of labels (`r = 64`). +* The degree bound is `Δ = 2 ^ (⌊9 λ / 10⌋ + 2)` (`degreeBound`), a power of two that is at least + `s ^ (9/10)` (`pow_nine_le_degreeBound_pow_ten`) and at most `2 ^ 20 s ^ 9` in tenth power. +* The node `t` of valuation `i` is recovered from its children at precision + `p_t = p + 8 h 2 ^ (a - i) + 8 M (a + 1)` (`nodePrecision`), as in Lemma D.5, where + `D_t = L / 2 ^ i`. +* Precisions stay below `precisionBound C s d`, which starts at `C s` and grows by + `8 C s (4 · 2 ^ a_d + a_d + 1)` per level. + +**Active calls.** `Active C s d M p` says that a call at depth `d` has `M` edges, with +`16 ^ d M ≤ C s` and `H < M`, and precision `p ≤ precisionBound C s d`. The root is active when +it has more than `H` edges and edges and precision are at most `C s` (`Active.root`), and an +active call's children are active when they have more than `H` edges (`Active.child`): Lemma D.3 +gives them at most `M / 16` edges. + +**The certificate.** For every `C` there is `s₀` such that for all `s ≥ s₀` +(`exists_activeBounds`): + +* precisions up to depth `λ` are at most `s ^ 2`; +* every active call satisfies `ActiveBounds`: its depth is at most `λ - 2`; `1 ≤ u_d ≤ M`; the + conditions (D.6) of the packing lemma, `2 Δ D_arr² ≤ M`, `L D_arr ≤ M` and `2 L ≤ Δ`, in the form + `Cslib.PacketPacking.exists_packing` takes them; `M (2 L - 1) + 1 ≤ 2 ^ (4 λ)` interpolation + points; and, for each node `t` with `q_t > 0` and `h = ⌈3 M / u⌉`, the precision bound (D.11) + `p_t u_d ≤ 34 M D_t` together with `p_t ≤ precisionBound C s (d + 1)`. + +From (D.11), the mode count (D.15) gives at most `2 ^ e_d` mode tuples with +`e_d = 4 ⌊15360 C s / (⌊√λ⌋ 8 ^ d)⌋ + 4` (`card_modeBudget_le`, `branchExponent`), and the +exponents sum to `O(s / √⌊log₂ s⌋)` over the levels (`sum_branchExponent_le`). With the factor +`2 ^ (2 λ)` for the nodes, interpolation points and groups (`ActiveBounds.branching_le`), a call at +depth `d` has at most `2 ^ (2 λ + e_d)` children. These are the hypotheses of a recursion tree with +`2 ^ O(s / √⌊log₂ s⌋)` nodes. + +**Differences from the source.** We use `λ = ⌊log₂ s⌋` and `H = ⌊s / ⌊√λ⌋⌋`, the scale of the +consumer interface of #1833, where the manuscript has `⌈log₂ s⌉` and `⌈s / ⌊√λ⌋⌉`; we take +`Δ = 2 ^ (⌊9 λ / 10⌋ + 2)` instead of `⌈s ^ (9/10)⌉`; and we prove the weaker lower bound +`u_d ≥ ⌊√λ⌋ / (2 ^ 17 C)` instead of `u_d ≥ λ ^ (4/5)`, which suffices for (D.11). The manuscript's +precision bound `p ≤ s λ^{O(1)} 2 ^ (a_{d-1})` is `precisionBound_le`. + +## Main definitions + +* `leafSize`, `taper`, `prefixBits`, `slotBits`, `degreeBound`, `nodePrecision`, + `precisionBound`, `branchExponent`: the schedule. +* `Active`: an active call within the schedule. +* `ActiveBounds`: the inequalities that hold at an active call for large `s`. + +## Main results + +* `exists_activeBounds`: the parameter certificate. +* `card_modeBudget_le`: the mode count (D.15) at an active call. +* `sum_branchExponent_le`: the excess exponents sum to `O(s / √⌊log₂ s⌋)`. +* `precisionBound_le`: the precision bound `p ≤ C s 2 ^ (a_{d-1}) (1 + d (8 λ + 40))`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.2 (D.4)–(D.6) + (p. 58), Lemma D.5 and (D.11) (pp. 60–61), (D.13)–(D.15) (pp. 62–63) and §D.4.7 (p. 64). +-/ + +public section + +namespace Cslib.Circuits.Frontier.ContractionSchedule + +open Finset Filter + +/-! ### The schedule -/ + +/-- The leaf size `H = ⌊s / ⌊√⌊log₂ s⌋⌋⌋`: calls with at most `H` edges are evaluated by direct +summation. -/ +@[expose] def leafSize (s : ℕ) : ℕ := s / Nat.sqrt (Nat.log 2 s) + +/-- The taper `u_d = ⌊⌊log₂ s⌋ / 2 ^ (17 + d)⌋` at depth `d`, the manuscript's `⌊ε λ / 2 ^ d⌋` +with `ε = 2 ^ (-17)`. -/ +@[expose] def taper (s d : ℕ) : ℕ := Nat.log 2 s / 2 ^ (17 + d) + +/-- `2 ∑_{j < d} u_j`, the manuscript's `a_{d - 1}`. -/ +@[expose] def prefixBits (s d : ℕ) : ℕ := 2 * ∑ j ∈ range d, taper s j + +/-- The number `a_d = 2 ∑_{j ≤ d} u_j` of label coordinates at depth `d`: packets have `2 ^ a_d` +slots. -/ +@[expose] def slotBits (s d : ℕ) : ℕ := prefixBits s (d + 1) + +/-- The degree bound `Δ = 2 ^ (⌊9 ⌊log₂ s⌋ / 10⌋ + 2)`, a power of two at least `s ^ (9/10)`. -/ +@[expose] def degreeBound (s : ℕ) : ℕ := 2 ^ (9 * Nat.log 2 s / 10 + 2) + +/-- The precision `p_t = p + 8 h 2 ^ (a - i) + 8 M (a + 1)` at which a node `t` of valuation `i` +is recovered by Lemma D.5, for input precision `p`, `h = ⌈3 M / u⌉` and `2 ^ a` slots. -/ +@[expose] def nodePrecision (p h a M i : ℕ) : ℕ := p + 8 * h * 2 ^ (a - i) + 8 * M * (a + 1) + +/-- The precision schedule: `C s` at the root, growing by `8 C s (4 · 2 ^ a_d + a_d + 1)` from +depth `d` to `d + 1`. -/ +@[expose] def precisionBound (C s : ℕ) : ℕ → ℕ + | 0 => C * s + | d + 1 => precisionBound C s d + 8 * C * s * (4 * 2 ^ slotBits s d + slotBits s d + 1) + +/-- The excess exponent `e_d = 4 ⌊15360 C s / (⌊√⌊log₂ s⌋⌋ 8 ^ d)⌋ + 4` of the mode count at +depth `d`. -/ +@[expose] def branchExponent (C s d : ℕ) : ℕ := + 4 * (15360 * C * s / (Nat.sqrt (Nat.log 2 s) * 8 ^ d)) + 4 + +/-- An *active* call at depth `d` of the recursion for size `s` and constant `C`: it has `M` +edges, at most `C s / 16 ^ d` and more than the leaf size, and precision `p` within the +schedule. -/ +structure Active (C s d M p : ℕ) : Prop where + /-- Each level divides the number of edges by at least `16`. -/ + edges_le : 16 ^ d * M ≤ C * s + /-- The call is not a leaf. -/ + leafSize_lt : leafSize s < M + /-- The precision is within the schedule. -/ + precision_le : p ≤ precisionBound C s d + +/-- The root is active when it is not a leaf and its edges and precision are at most `C s`. -/ +theorem Active.root {C s M p : ℕ} (hM : M ≤ C * s) (hH : leafSize s < M) (hp : p ≤ C * s) : + Active C s 0 M p := + ⟨by simpa using hM, hH, hp⟩ + +/-- A child of an active call at depth `d` with at most a sixteenth of its edges, precision +within the schedule and more than the leaf size is an active call at depth `d + 1`. -/ +theorem Active.child {C s d M p M' p' : ℕ} (h : Active C s d M p) (hM : 16 * M' ≤ M) + (hH : leafSize s < M') (hp : p' ≤ precisionBound C s (d + 1)) : Active C s (d + 1) M' p' := + ⟨by rw [pow_succ, mul_assoc]; exact (Nat.mul_le_mul_left _ hM).trans h.edges_le, hH, hp⟩ + +/-! ### Arithmetic of the schedule -/ + +theorem slotBits_eq (s d : ℕ) : slotBits s d = prefixBits s d + 2 * taper s d := by + simp only [slotBits, prefixBits, sum_range_succ, mul_add] + +theorem prefixBits_le_slotBits (s d : ℕ) : prefixBits s d ≤ slotBits s d := by + rw [slotBits_eq] + exact Nat.le_add_right _ _ + +/-- `∑_{j < d} ⌊y / 2 ^ (j + 1)⌋ ≤ y`. -/ +private theorem sum_div_two_pow_succ_le (y d : ℕ) : ∑ j ∈ range d, y / 2 ^ (j + 1) ≤ y := by + induction d generalizing y with + | zero => simp + | succ d ih => + rw [sum_range_succ'] + have h : ∑ j ∈ range d, y / 2 ^ (j + 1 + 1) = ∑ j ∈ range d, y / 2 / 2 ^ (j + 1) := + sum_congr rfl fun j _ => by rw [Nat.div_div_eq_div_mul, ← pow_succ'] + rw [h] + have := ih (y / 2) + simp only [zero_add, pow_one] + omega + +/-- **`a ≤ 4 ε λ`**: `2 ∑_{j < d} u_j ≤ ⌊λ / 2 ^ 15⌋`. -/ +theorem prefixBits_le (s d : ℕ) : prefixBits s d ≤ Nat.log 2 s / 32768 := by + have h : ∑ j ∈ range d, taper s j ≤ Nat.log 2 s / 65536 := by + have : ∀ j, taper s j = Nat.log 2 s / 65536 / 2 ^ (j + 1) := fun j => by + rw [taper, Nat.div_div_eq_div_mul, show 17 + j = 16 + (j + 1) by omega, pow_add] + rfl + simp only [this] + exact sum_div_two_pow_succ_le _ d + unfold prefixBits + omega + +theorem slotBits_le (s d : ℕ) : slotBits s d ≤ Nat.log 2 s / 32768 := + prefixBits_le s (d + 1) + +/-- **The degree bound is at least `s ^ (9/10)`**: `s ^ 9 ≤ Δ ^ 10`. -/ +theorem pow_nine_le_degreeBound_pow_ten (s : ℕ) : s ^ 9 ≤ degreeBound s ^ 10 := by + have hs : s < 2 ^ (Nat.log 2 s + 1) := Nat.lt_pow_succ_log_self one_lt_two s + calc s ^ 9 ≤ (2 ^ (Nat.log 2 s + 1)) ^ 9 := Nat.pow_le_pow_left hs.le 9 + _ = 2 ^ (9 * Nat.log 2 s + 9) := by rw [← pow_mul]; ring_nf + _ ≤ 2 ^ (10 * (9 * Nat.log 2 s / 10 + 2)) := Nat.pow_le_pow_right two_pos (by omega) + _ = degreeBound s ^ 10 := by rw [degreeBound, ← pow_mul, mul_comm] + +/-- A degree `x` with `x ^ 10 ≤ s ^ 9` is at most the degree bound. -/ +theorem le_degreeBound_of_pow_ten_le {x s : ℕ} (h : x ^ 10 ≤ s ^ 9) : x ≤ degreeBound s := + (Nat.pow_le_pow_iff_left (by norm_num)).1 (h.trans (pow_nine_le_degreeBound_pow_ten s)) + +/-- **The degree bound is at most `2 ^ 2 s ^ (9/10)`**: `Δ ^ 10 ≤ 2 ^ 20 s ^ 9`. -/ +theorem degreeBound_pow_ten_le {s : ℕ} (hs : s ≠ 0) : degreeBound s ^ 10 ≤ 2 ^ 20 * s ^ 9 := by + have h2 : 2 ^ Nat.log 2 s ≤ s := Nat.pow_log_le_self 2 hs + calc degreeBound s ^ 10 = 2 ^ (10 * (9 * Nat.log 2 s / 10) + 20) := by + rw [degreeBound, ← pow_mul]; ring_nf + _ ≤ 2 ^ (9 * Nat.log 2 s + 20) := Nat.pow_le_pow_right two_pos (by omega) + _ = 2 ^ 20 * (2 ^ Nat.log 2 s) ^ 9 := by rw [← pow_mul, ← pow_add]; ring_nf + _ ≤ 2 ^ 20 * s ^ 9 := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left h2 9) + +/-- **The precision bound** of (D.11), `p ≤ s λ^{O(1)} 2 ^ (a_{d - 1})`: +`precisionBound C s d ≤ C s 2 ^ (a_{d - 1}) (1 + d (8 λ + 40))`. -/ +theorem precisionBound_le (C s d : ℕ) : + precisionBound C s d ≤ C * s * 2 ^ prefixBits s d * (1 + d * (8 * Nat.log 2 s + 40)) := by + induction d with + | zero => simp [precisionBound, prefixBits] + | succ d ih => + set n := Nat.log 2 s + set a := slotBits s d + have ha : a ≤ n := (slotBits_le s d).trans (Nat.div_le_self _ _) + have hpow : 2 ^ prefixBits s d ≤ 2 ^ a := + Nat.pow_le_pow_right two_pos (prefixBits_le_slotBits s d) + have h1 : 1 ≤ 2 ^ a := Nat.one_le_two_pow + have hstep : 8 * (4 * 2 ^ a + a + 1) ≤ 2 ^ a * (8 * n + 40) := by + have : a + 1 ≤ 2 ^ a * (n + 1) := by nlinarith + nlinarith + have hP : precisionBound C s d ≤ C * s * 2 ^ a * (1 + d * (8 * n + 40)) := + ih.trans (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ hpow)) + calc precisionBound C s (d + 1) + = precisionBound C s d + C * s * (8 * (4 * 2 ^ a + a + 1)) := by + rw [precisionBound]; ring + _ ≤ C * s * 2 ^ a * (1 + d * (8 * n + 40)) + C * s * (2 ^ a * (8 * n + 40)) := + Nat.add_le_add hP (Nat.mul_le_mul_left _ hstep) + _ = C * s * 2 ^ prefixBits s (d + 1) * (1 + (d + 1) * (8 * n + 40)) := by + rw [show prefixBits s (d + 1) = a from rfl]; ring + +/-! ### Active calls -/ + +section Active + +variable {C s d M p : ℕ} + +theorem Active.one_le_edges (h : Active C s d M p) : 1 ≤ M := + Nat.one_le_iff_ne_zero.2 fun h0 => by have := h.leafSize_lt; omega + +theorem Active.edges_le_mul (h : Active C s d M p) : M ≤ C * s := + (Nat.le_mul_of_pos_left M (Nat.pow_pos (by norm_num))).trans h.edges_le + +theorem Active.one_le_const (h : Active C s d M p) : 1 ≤ C := by + have := h.one_le_edges.trans h.edges_le_mul + rcases Nat.eq_zero_or_pos C with rfl | hC + · simp at this + · exact hC + +/-- At an active call `s < M ⌊√λ⌋`, since `⌊s / ⌊√λ⌋⌋ < M`. -/ +theorem Active.lt_mul_sqrt (h : Active C s d M p) (hs : 2 ≤ s) : + s < M * Nat.sqrt (Nat.log 2 s) := + (Nat.div_lt_iff_lt_mul (Nat.one_le_sqrt_log_two hs)).1 h.leafSize_lt + +/-- **The depth bound**: `16 ^ d < C ⌊√λ⌋` at an active call. -/ +theorem Active.sixteen_pow_lt (h : Active C s d M p) (hs : 2 ≤ s) : + 16 ^ d < C * Nat.sqrt (Nat.log 2 s) := by + have h1 := h.lt_mul_sqrt hs + have h2 : 16 ^ d * s < C * Nat.sqrt (Nat.log 2 s) * s := + calc 16 ^ d * s < 16 ^ d * (M * Nat.sqrt (Nat.log 2 s)) := + Nat.mul_lt_mul_of_pos_left h1 (Nat.pow_pos (by norm_num)) + _ = 16 ^ d * M * Nat.sqrt (Nat.log 2 s) := by ring + _ ≤ C * s * Nat.sqrt (Nat.log 2 s) := Nat.mul_le_mul_right _ h.edges_le + _ = C * Nat.sqrt (Nat.log 2 s) * s := by ring + exact Nat.lt_of_mul_lt_mul_right h2 + +/-- **The taper stays large**: `u_d ≥ ⌊√λ⌋ / (2 ^ 17 C)` at an active call. -/ +theorem Active.le_taper (h : Active C s d M p) (hs : 2 ≤ s) : + Nat.sqrt (Nat.log 2 s) / (2 ^ 17 * C) ≤ taper s d := by + set n := Nat.log 2 s + set σ := Nat.sqrt n + have hσ : 1 ≤ σ := Nat.one_le_sqrt_log_two hs + have hC := h.one_le_const + have h2d : 2 ^ d ≤ C * σ := + (Nat.pow_le_pow_left (by norm_num : 2 ≤ 16) d).trans (h.sixteen_pow_lt hs).le + calc σ / (2 ^ 17 * C) = σ * σ / (2 ^ 17 * C * σ) := (Nat.mul_div_mul_right _ _ hσ).symm + _ ≤ n / (2 ^ 17 * C * σ) := Nat.div_le_div_right (Nat.sqrt_le n) + _ ≤ n / (2 ^ 17 * 2 ^ d) := by + refine Nat.div_le_div_left ?_ (by positivity) + rw [mul_assoc] + exact Nat.mul_le_mul_left _ h2d + _ = taper s d := by rw [taper, pow_add] + +end Active + +/-- `⌊√λ⌋ ≤ 2 ^ (10 + d) ⌊√u_d⌋` once `u_d ≥ 1`: the taper loses at most a factor `2 ^ (17 + d)` +against `λ`. -/ +theorem sqrt_log_two_le_mul_sqrt_taper {s d : ℕ} (hu : 1 ≤ taper s d) : + Nat.sqrt (Nat.log 2 s) ≤ 2 ^ (10 + d) * Nat.sqrt (taper s d) := by + set n := Nat.log 2 s + set u := taper s d + set y := Nat.sqrt u + have hy : 1 ≤ y := Nat.sqrt_pos.2 hu + have hn : n < (u + 1) * 2 ^ (17 + d) := by + have := Nat.lt_div_mul_add (a := n) (b := 2 ^ (17 + d)) (by positivity) + simpa only [u, taper, add_mul, one_mul] using this + have hu1 : u + 1 ≤ 4 * y ^ 2 := by + have := Nat.lt_succ_sqrt' u + nlinarith + have hsq : Nat.sqrt n ^ 2 < (2 ^ (10 + d) * y) ^ 2 := by + calc Nat.sqrt n ^ 2 ≤ n := Nat.sqrt_le' n + _ < (u + 1) * 2 ^ (17 + d) := hn + _ ≤ 4 * y ^ 2 * 2 ^ (17 + d) := Nat.mul_le_mul_right _ hu1 + _ = 2 ^ (19 + d) * y ^ 2 := by rw [show 19 + d = 2 + (17 + d) by omega, pow_add]; ring + _ ≤ 2 ^ (20 + 2 * d) * y ^ 2 := Nat.mul_le_mul_right _ (Nat.pow_le_pow_right two_pos + (by omega)) + _ = (2 ^ (10 + d) * y) ^ 2 := by rw [mul_pow, ← pow_mul]; ring_nf + exact ((Nat.pow_lt_pow_iff_left (by norm_num)).1 hsq).le + +/-! ### The certificate -/ + +/-- The inequalities that hold at an active call at depth `d` with `M` edges and precision `p`, for +`s` large enough. Write `u = taper s d`, `a = slotBits s d`, `L = 2 ^ a`, `Δ = degreeBound s` and +`λ = ⌊log₂ s⌋`. -/ +structure ActiveBounds (C s d M p : ℕ) : Prop where + /-- The call and its children lie in the first `λ` levels. -/ + depth_add_two_le : d + 2 ≤ Nat.log 2 s + /-- `1 ≤ u`, as Lemma D.5 requires. -/ + one_le_taper : 1 ≤ taper s d + /-- `u ≤ M`, as Lemma D.5 requires. -/ + taper_le_edges : taper s d ≤ M + /-- (D.6): `2 Δ D_arr² ≤ M`. -/ + degree_mul_pairs_sq_le : 2 * degreeBound s * (64 ^ (2 * slotBits s d)) ^ 2 ≤ M + /-- (D.6): `L D_arr ≤ M`. -/ + slots_mul_pairs_le : 2 ^ slotBits s d * 64 ^ (2 * slotBits s d) ≤ M + /-- (D.6): `2 L ≤ Δ`, so packet vertices respect the degree bound. -/ + two_mul_slots_le : 2 * 2 ^ slotBits s d ≤ degreeBound s + /-- There are `2 ^ (4 λ)` residue-field lifts for the `M (2 L - 1) + 1` interpolation points. -/ + points_le : M * (2 * 2 ^ slotBits s d - 1) + 1 ≤ 2 ^ (4 * Nat.log 2 s) + /-- The nodes, interpolation points and `64` groups contribute a factor `2 ^ (2 λ)` to the + branching. -/ + branching_le : + 2 * 2 ^ slotBits s d * (M * (2 * 2 ^ slotBits s d - 1) + 1) * 64 ≤ 2 ^ (2 * Nat.log 2 s) + /-- The children's precisions are within the schedule. -/ + nodePrecision_le : ∀ i h, i < taper s d → h * taper s d ≤ 3 * M + taper s d → + nodePrecision p h (slotBits s d) M i ≤ precisionBound C s (d + 1) + /-- **(D.11)**: `p_t u ≤ 34 M D_t`. -/ + nodePrecision_mul_taper_le : ∀ i h, i < taper s d → h * taper s d ≤ 3 * M + taper s d → + nodePrecision p h (slotBits s d) M i * taper s d ≤ 34 * M * 2 ^ (slotBits s d - i) + +/-! #### Arithmetic lemmas -/ + +private theorem one_add_mul_le_sq (n : ℕ) : 1 + n * (8 * n + 40) ≤ 49 * (n + 1) ^ 2 := by + nlinarith + +private theorem one_add_mul_le_sq' {n : ℕ} (hn : 1 ≤ n) : 1 + n * (8 * n + 40) ≤ 49 * n ^ 2 := by + nlinarith + +/-- The children's precisions are within the schedule. -/ +private theorem nodePrecision_le_of {p P h u M C s a i : ℕ} (hp : p ≤ P) + (hh : h * u ≤ 3 * M + u) (hu : 1 ≤ u) (huM : u ≤ M) (hM : M ≤ C * s) : + nodePrecision p h a M i ≤ P + 8 * C * s * (4 * 2 ^ a + a + 1) := by + have hh4 : h ≤ 4 * M := by + have : h ≤ h * u := Nat.le_mul_of_pos_right h hu + omega + have hDt : 2 ^ (a - i) ≤ 2 ^ a := Nat.pow_le_pow_right two_pos (Nat.sub_le a i) + calc nodePrecision p h a M i = p + 8 * h * 2 ^ (a - i) + 8 * M * (a + 1) := rfl + _ ≤ P + 8 * (4 * (C * s)) * 2 ^ a + 8 * (C * s) * (a + 1) := + Nat.add_le_add (Nat.add_le_add hp (Nat.mul_le_mul + (Nat.mul_le_mul_left 8 (hh4.trans (Nat.mul_le_mul_left 4 hM))) hDt)) + (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left 8 hM)) + _ = P + 8 * C * s * (4 * 2 ^ a + a + 1) := by ring + +/-- The inherited precision is absorbed by the taper: `p u ≤ M 2 ^ (a_{d-1} + u)`. -/ +private theorem mul_taper_le_of {p C s A d n σ u M : ℕ} + (hp : p ≤ C * s * 2 ^ A * (1 + d * (8 * n + 40))) (hsM : s ≤ M * σ) (hσ : σ ≤ n) + (hd : d ≤ n) (hu : u ≤ n) (hn : 1 ≤ n) (hpow : 49 * C * n ^ 4 ≤ 2 ^ u) : + p * u ≤ M * 2 ^ (A + u) := by + have h0 : d * (8 * n + 40) ≤ n * (8 * n + 40) := Nat.mul_le_mul_right _ hd + have h1 : 1 + d * (8 * n + 40) ≤ 49 * n ^ 2 := (Nat.add_le_add_left h0 1).trans + (one_add_mul_le_sq' hn) + have h2 : σ * u ≤ n ^ 2 := by rw [sq]; exact Nat.mul_le_mul hσ hu + have hfac : C * σ * (1 + d * (8 * n + 40)) * u ≤ 49 * C * n ^ 4 := + calc C * σ * (1 + d * (8 * n + 40)) * u = C * (σ * u) * (1 + d * (8 * n + 40)) := by ring + _ ≤ C * n ^ 2 * (49 * n ^ 2) := Nat.mul_le_mul (Nat.mul_le_mul_left _ h2) h1 + _ = 49 * C * n ^ 4 := by ring + calc p * u ≤ C * s * 2 ^ A * (1 + d * (8 * n + 40)) * u := Nat.mul_le_mul_right _ hp + _ ≤ C * (M * σ) * 2 ^ A * (1 + d * (8 * n + 40)) * u := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ + (Nat.mul_le_mul_left C hsM))) + _ = M * 2 ^ A * (C * σ * (1 + d * (8 * n + 40)) * u) := by ring + _ ≤ M * 2 ^ A * 2 ^ u := Nat.mul_le_mul_left _ (hfac.trans hpow) + _ = M * 2 ^ (A + u) := by rw [mul_assoc, ← pow_add] + +/-- The evaluation loss is absorbed by the taper: `8 (a + 1) u ≤ 2 ^ u`. -/ +private theorem eight_mul_le_two_pow {a u n C : ℕ} (hC : 1 ≤ C) (hn : 1 ≤ n) (ha : a ≤ n) + (hu : u ≤ n) (hpow : 49 * C * n ^ 4 ≤ 2 ^ u) : 8 * (a + 1) * u ≤ 2 ^ u := by + have h1 : 8 * (a + 1) * u ≤ 16 * n ^ 2 := by + calc 8 * (a + 1) * u ≤ 8 * (n + n) * n := + Nat.mul_le_mul (Nat.mul_le_mul_left 8 (Nat.add_le_add ha hn)) hu + _ = 16 * n ^ 2 := by ring + have h2 : n ^ 2 ≤ n ^ 4 := Nat.pow_le_pow_right hn (by norm_num) + have h3 : 16 * n ^ 4 ≤ 49 * C * n ^ 4 := + Nat.mul_le_mul_right _ (by omega) + omega + +/-- **(D.11)** from its ingredients: `p_t u ≤ 34 M D_t`. -/ +private theorem nodePrecision_mul_le_of {p h a M i u A : ℕ} (hpu : p * u ≤ M * 2 ^ (A + u)) + (ha : a = A + 2 * u) (hi : i < u) (hh : h * u ≤ 3 * M + u) (huM : u ≤ M) + (h8 : 8 * (a + 1) * u ≤ 2 ^ u) : + nodePrecision p h a M i * u ≤ 34 * M * 2 ^ (a - i) := by + have hDt : 2 ^ (A + u) ≤ 2 ^ (a - i) := Nat.pow_le_pow_right two_pos (by omega) + have hu2 : 2 ^ u ≤ 2 ^ (a - i) := Nat.pow_le_pow_right two_pos (by omega) + have hhu : h * u ≤ 4 * M := by omega + calc nodePrecision p h a M i * u + = p * u + 8 * (h * u) * 2 ^ (a - i) + M * (8 * (a + 1) * u) := by + rw [nodePrecision]; ring + _ ≤ M * 2 ^ (a - i) + 8 * (4 * M) * 2 ^ (a - i) + M * 2 ^ (a - i) := + Nat.add_le_add (Nat.add_le_add (hpu.trans (Nat.mul_le_mul_left _ hDt)) + (Nat.mul_le_mul_right _ (Nat.mul_le_mul_left 8 hhu))) + (Nat.mul_le_mul_left _ (h8.trans hu2)) + _ = 34 * M * 2 ^ (a - i) := by ring + +/-- `64 ^ (2 a) = 2 ^ (12 a)`. -/ +private theorem sixtyFour_pow (a : ℕ) : 64 ^ (2 * a) = 2 ^ (12 * a) := by + rw [show (64 : ℕ) = 2 ^ 6 by norm_num, ← pow_mul]; ring_nf + +/-- A power of two is below `M` when it is below `s / 2 ^ (n / 20)`, since `s < M σ` and +`σ ≤ 2 ^ (n / 20)`. -/ +private theorem two_pow_lt_of {k n s M σ : ℕ} (hk : k + n / 20 ≤ n) (hσ : σ ≤ 2 ^ (n / 20)) + (h2n : 2 ^ n ≤ s) (hsM : s < M * σ) : 2 ^ k < M := by + have : 2 ^ k * σ < M * σ := + calc 2 ^ k * σ ≤ 2 ^ k * 2 ^ (n / 20) := Nat.mul_le_mul_left _ hσ + _ = 2 ^ (k + n / 20) := (pow_add _ _ _).symm + _ ≤ 2 ^ n := Nat.pow_le_pow_right two_pos hk + _ ≤ s := h2n + _ < M * σ := hsM + exact Nat.lt_of_mul_lt_mul_right this + +/-- The interpolation points: `M (2 L - 1) + 1 ≤ 2 ^ (n / 8 + n + a + 2)`. -/ +private theorem points_le_of {M a C s n : ℕ} (hM1 : 1 ≤ M) (hM : M ≤ C * s) + (hC : C ≤ 2 ^ (n / 8)) (hs : s < 2 ^ (n + 1)) : + M * (2 * 2 ^ a - 1) + 1 ≤ 2 ^ (n / 8 + n + a + 2) := by + have h1 : M * (2 * 2 ^ a - 1) + 1 ≤ M * (2 * 2 ^ a) := by + have h2a : 1 ≤ 2 * 2 ^ a := by have := Nat.one_le_two_pow (n := a); omega + rw [Nat.mul_sub, mul_one] + have : M ≤ M * (2 * 2 ^ a) := Nat.le_mul_of_pos_right M (by omega) + omega + calc M * (2 * 2 ^ a - 1) + 1 ≤ M * (2 * 2 ^ a) := h1 + _ ≤ C * s * (2 * 2 ^ a) := Nat.mul_le_mul_right _ hM + _ ≤ 2 ^ (n / 8) * 2 ^ (n + 1) * (2 * 2 ^ a) := + Nat.mul_le_mul_right _ (Nat.mul_le_mul hC hs.le) + _ = 2 ^ (n / 8 + n + a + 2) := by + rw [← pow_succ', ← pow_add, ← pow_add]; ring_nf + +/-- The asymptotic inequalities behind the certificate, in `n = ⌊log₂ s⌋`. -/ +private theorem eventually_large (C : ℕ) (hC : 1 ≤ C) : ∀ᶠ n : ℕ in atTop, + 200 ≤ n ∧ 49 * C * (Nat.sqrt n + 1) ^ 8 ≤ 2 ^ (Nat.sqrt n / (2 ^ 17 * C)) ∧ + C * (n + 1) ^ 1 ≤ 2 ^ (n / 8) ∧ 8 * (n + 1) ^ 1 ≤ 2 ^ (n / 20) ∧ + 49 * C * (n + 1) ^ 2 ≤ 2 ^ (n / 2) := by + have hsqrt : Tendsto Nat.sqrt atTop atTop := + tendsto_atTop_atTop.2 fun b => ⟨b * b, fun n hn => Nat.le_sqrt.2 hn⟩ + filter_upwards [eventually_ge_atTop 200, + hsqrt.eventually (Nat.eventually_mul_add_one_pow_le_pow_div (49 * C) 8 (2 ^ 17 * C) + one_lt_two (by positivity)), + Nat.eventually_mul_add_one_pow_le_pow_div C 1 8 one_lt_two (by norm_num), + Nat.eventually_mul_add_one_pow_le_pow_div 8 1 20 one_lt_two (by norm_num), + Nat.eventually_mul_add_one_pow_le_pow_div (49 * C) 2 2 one_lt_two (by norm_num)] + with n h1 h2 h3 h4 h5 + exact ⟨h1, h2, h3, h4, h5⟩ + +/-! #### The certificate -/ + +/-- Precisions up to depth `⌊log₂ s⌋` are at most `s ^ 2` once `49 C (λ + 1)² ≤ 2 ^ (λ / 2)`. -/ +private theorem precisionBound_le_sq_of {C s d : ℕ} (hd : d ≤ Nat.log 2 s) (hs : s ≠ 0) + (h : 49 * C * (Nat.log 2 s + 1) ^ 2 ≤ 2 ^ (Nat.log 2 s / 2)) : + precisionBound C s d ≤ s ^ 2 := by + set n := Nat.log 2 s + have h2n : 2 ^ n ≤ s := Nat.pow_log_le_self 2 hs + have hpow : 2 ^ prefixBits s d ≤ 2 ^ (n / 32768) := + Nat.pow_le_pow_right two_pos (prefixBits_le s d) + have hfac : C * (1 + d * (8 * n + 40)) ≤ 49 * C * (n + 1) ^ 2 := by + have h1 : d * (8 * n + 40) ≤ n * (8 * n + 40) := Nat.mul_le_mul_right _ hd + have h2 := one_add_mul_le_sq n + calc C * (1 + d * (8 * n + 40)) ≤ C * (49 * (n + 1) ^ 2) := + Nat.mul_le_mul_left _ (by omega) + _ = 49 * C * (n + 1) ^ 2 := by ring + calc precisionBound C s d ≤ C * s * 2 ^ prefixBits s d * (1 + d * (8 * n + 40)) := + precisionBound_le C s d + _ = s * (C * (1 + d * (8 * n + 40))) * 2 ^ prefixBits s d := by ring + _ ≤ s * 2 ^ (n / 2) * 2 ^ (n / 32768) := + Nat.mul_le_mul (Nat.mul_le_mul_left _ (hfac.trans h)) hpow + _ = s * 2 ^ (n / 2 + n / 32768) := by rw [mul_assoc, ← pow_add] + _ ≤ s * 2 ^ n := Nat.mul_le_mul_left _ (Nat.pow_le_pow_right two_pos (by omega)) + _ ≤ s * s := Nat.mul_le_mul_left _ h2n + _ = s ^ 2 := (sq s).symm + +/-- The certificate at one active call, from the asymptotic inequalities at `λ = ⌊log₂ s⌋`. -/ +private theorem activeBounds_of {C s d M p : ℕ} (hA : Active C s d M p) + (h200 : 200 ≤ Nat.log 2 s) + (hA1 : 49 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 ≤ + 2 ^ (Nat.sqrt (Nat.log 2 s) / (2 ^ 17 * C))) + (hA2 : C * (Nat.log 2 s + 1) ≤ 2 ^ (Nat.log 2 s / 8)) + (hA3 : 8 * (Nat.log 2 s + 1) ≤ 2 ^ (Nat.log 2 s / 20)) : + ActiveBounds C s d M p := by + have hs0 : s ≠ 0 := by + rintro rfl + simp at h200 + have hC := hA.one_le_const + have hM1 := hA.one_le_edges + have hMCs := hA.edges_le_mul + have h2n : 2 ^ Nat.log 2 s ≤ s := Nat.pow_log_le_self 2 hs0 + have hsn : s < 2 ^ (Nat.log 2 s + 1) := Nat.lt_pow_succ_log_self one_lt_two s + have hs2 : 2 ≤ s := (Nat.pow_le_pow_right two_pos (by omega : 1 ≤ Nat.log 2 s)).trans h2n + have hsM := hA.lt_mul_sqrt hs2 + have h16 := hA.sixteen_pow_lt hs2 + have hσn : Nat.sqrt (Nat.log 2 s) ≤ Nat.log 2 s := Nat.sqrt_le_self _ + have hnσ : Nat.log 2 s < (Nat.sqrt (Nat.log 2 s) + 1) ^ 2 := Nat.lt_succ_sqrt' _ + have hσpow : Nat.sqrt (Nat.log 2 s) ≤ 2 ^ (Nat.log 2 s / 20) := by omega + have hCpow : C ≤ 2 ^ (Nat.log 2 s / 8) := (Nat.le_mul_of_pos_right C (by omega)).trans hA2 + have haA : slotBits s d = prefixBits s d + 2 * taper s d := slotBits_eq s d + have ha : slotBits s d ≤ Nat.log 2 s / 32768 := slotBits_le s d + have han : slotBits s d ≤ Nat.log 2 s := ha.trans (Nat.div_le_self _ _) + -- The depth. + have hdepth : d + 2 ≤ Nat.log 2 s := by + have h1 : 2 ^ (4 * d) < 2 ^ (Nat.log 2 s / 8) := by + calc 2 ^ (4 * d) = 16 ^ d := by rw [pow_mul]; norm_num + _ < C * Nat.sqrt (Nat.log 2 s) := h16 + _ ≤ C * (Nat.log 2 s + 1) := Nat.mul_le_mul_left _ (by omega) + _ ≤ 2 ^ (Nat.log 2 s / 8) := hA2 + have := (Nat.pow_lt_pow_iff_right one_lt_two).1 h1 + omega + -- The taper. + have hpowu : 49 * C * Nat.log 2 s ^ 4 ≤ 2 ^ taper s d := by + have hn4 : Nat.log 2 s ^ 4 ≤ (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := by + calc Nat.log 2 s ^ 4 ≤ ((Nat.sqrt (Nat.log 2 s) + 1) ^ 2) ^ 4 := + Nat.pow_le_pow_left hnσ.le 4 + _ = (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := by rw [← pow_mul] + calc 49 * C * Nat.log 2 s ^ 4 ≤ 49 * C * (Nat.sqrt (Nat.log 2 s) + 1) ^ 8 := + Nat.mul_le_mul_left _ hn4 + _ ≤ 2 ^ (Nat.sqrt (Nat.log 2 s) / (2 ^ 17 * C)) := hA1 + _ ≤ 2 ^ taper s d := Nat.pow_le_pow_right two_pos (hA.le_taper hs2) + have hu1 : 1 ≤ taper s d := by + by_contra hu0 + have h0 : taper s d = 0 := by omega + rw [h0, pow_zero] at hpowu + have : 1 ≤ Nat.log 2 s ^ 4 := Nat.one_le_pow _ _ (by omega) + have : 49 ≤ 49 * C * Nat.log 2 s ^ 4 := + calc 49 = 49 * 1 * 1 := rfl + _ ≤ 49 * C * Nat.log 2 s ^ 4 := Nat.mul_le_mul (Nat.mul_le_mul_left _ hC) this + omega + have hun : taper s d ≤ Nat.log 2 s := Nat.div_le_self _ _ + have huM : taper s d ≤ M := by + have := Nat.log_two_le_div_sqrt_log_two s + have := hA.leafSize_lt + simp only [leafSize] at this + omega + have hpu : p * taper s d ≤ M * 2 ^ (prefixBits s d + taper s d) := + mul_taper_le_of (hA.precision_le.trans (precisionBound_le C s d)) hsM.le hσn + (by omega) hun (by omega) hpowu + have h8 : 8 * (slotBits s d + 1) * taper s d ≤ 2 ^ taper s d := + eight_mul_le_two_pow hC (by omega) han hun hpowu + have hpoints := points_le_of (a := slotBits s d) hM1 hMCs hCpow hsn + refine ⟨hdepth, hu1, huM, ?_, ?_, ?_, ?_, ?_, fun i h _ hh => ?_, fun i h hi hh => ?_⟩ + · -- `2 Δ D_arr² ≤ M`. + have := two_pow_lt_of (k := 3 + 9 * Nat.log 2 s / 10 + 24 * slotBits s d) (by omega) + hσpow h2n hsM + rw [degreeBound, sixtyFour_pow] + calc 2 * 2 ^ (9 * Nat.log 2 s / 10 + 2) * (2 ^ (12 * slotBits s d)) ^ 2 + = 2 ^ (3 + 9 * Nat.log 2 s / 10 + 24 * slotBits s d) := by + rw [← pow_mul, ← pow_succ', ← pow_add]; ring_nf + _ ≤ M := this.le + · -- `L D_arr ≤ M`. + have := two_pow_lt_of (k := 13 * slotBits s d) (by omega) hσpow h2n hsM + rw [sixtyFour_pow, ← pow_add] + calc 2 ^ (slotBits s d + 12 * slotBits s d) = 2 ^ (13 * slotBits s d) := by ring_nf + _ ≤ M := this.le + · -- `2 L ≤ Δ`. + rw [degreeBound, ← pow_succ'] + exact Nat.pow_le_pow_right two_pos (by omega) + · -- Interpolation points. + exact hpoints.trans (Nat.pow_le_pow_right two_pos (by omega)) + · -- The polynomial branching factor. + calc 2 * 2 ^ slotBits s d * (M * (2 * 2 ^ slotBits s d - 1) + 1) * 64 + ≤ 2 * 2 ^ slotBits s d * 2 ^ (Nat.log 2 s / 8 + Nat.log 2 s + slotBits s d + 2) * 64 := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ hpoints) + _ = 2 ^ (Nat.log 2 s / 8 + Nat.log 2 s + 2 * slotBits s d + 9) := by + rw [show (64 : ℕ) = 2 ^ 6 by norm_num, ← pow_succ', ← pow_add, ← pow_add]; ring_nf + _ ≤ 2 ^ (2 * Nat.log 2 s) := Nat.pow_le_pow_right two_pos (by omega) + · -- Children's precisions. + exact nodePrecision_le_of hA.precision_le hh hu1 huM hMCs + · -- (D.11). + exact nodePrecision_mul_le_of hpu haA hi hh huM h8 + +/-- **The parameter certificate.** For every `C` there is `s₀` such that for all `s ≥ s₀` the +precisions up to depth `⌊log₂ s⌋` are at most `s ^ 2`, and every active call satisfies +`ActiveBounds`. -/ +theorem exists_activeBounds (C : ℕ) : ∃ s₀, ∀ s, s₀ ≤ s → + (∀ d ≤ Nat.log 2 s, precisionBound C s d ≤ s ^ 2) ∧ + ∀ d M p, Active C s d M p → ActiveBounds C s d M p := by + rcases Nat.eq_zero_or_pos C with rfl | hC + · refine ⟨0, fun s _ => ⟨fun d _ => ?_, fun d M p h => absurd h.one_le_const (by norm_num)⟩⟩ + have := precisionBound_le 0 s d + simp only [zero_mul] at this + exact this.trans (Nat.zero_le _) + obtain ⟨n₀, hn₀⟩ := eventually_atTop.1 (eventually_large C hC) + refine ⟨2 ^ n₀, fun s hs => ?_⟩ + have hs0 : s ≠ 0 := by + rintro rfl + have := Nat.one_le_two_pow (n := n₀) + omega + obtain ⟨h200, hA1, hA2, hA3, hA5⟩ := hn₀ (Nat.log 2 s) (Nat.le_log_of_pow_le one_lt_two hs) + simp only [pow_one] at hA2 hA3 + exact ⟨fun d hd => precisionBound_le_sq_of hd hs0 hA5, + fun d M p hA => activeBounds_of hA h200 hA1 hA2 hA3⟩ + +/-! ### The mode count -/ + +/-- **The mode count (D.15)** at an active call: if the node `t` of valuation `i ≤ a` satisfies +the precision bound (D.11) `p_t u ≤ 34 M D_t` with `u ≥ 1`, and the packing has `#P 2 ^ a ≤ 3 M` +packets (Lemma D.3 (1)), then there are at most `2 ^ e_d` tuples of exponents of the modes +`P × Fin (2 ^ (i + 1)) × Fin p_t` (packet, tile, `b`) of weight `b + 1` below `p_t`. There are +`#P 2 ^ (i + 1) ≤ 6 M / D_t` modes of each weight (D.13). -/ +theorem card_modeBudget_le {C s d M p : ℕ} (hA : Active C s d M p) (hs : 2 ≤ s) + (hu : 1 ≤ taper s d) {i h : ℕ} (hi : i ≤ slotBits s d) + (hpt : nodePrecision p h (slotBits s d) M i * taper s d ≤ + 34 * M * 2 ^ (slotBits s d - i)) + {P : Type*} [Fintype P] [DecidableEq P] + (hP : Fintype.card P * 2 ^ slotBits s d ≤ 3 * M) : + #(TensorNetwork.modeBudget + (fun Z : P × Fin (2 ^ (i + 1)) × Fin (nodePrecision p h (slotBits s d) M i) => + (Z.2.2 : ℕ) + 1) + (nodePrecision p h (slotBits s d) M i)) ≤ 2 ^ branchExponent C s d := by + set a := slotBits s d + set pt := nodePrecision p h a M i + set u := taper s d + set m := Fintype.card P * 2 ^ (i + 1) + set σ := Nat.sqrt (Nat.log 2 s) + -- At most `m` modes of each weight. + have hfib : ∀ j, #{Z : P × Fin (2 ^ (i + 1)) × Fin pt | (Z.2.2 : ℕ) + 1 = j} ≤ m := by + intro j + calc #{Z : P × Fin (2 ^ (i + 1)) × Fin pt | (Z.2.2 : ℕ) + 1 = j} + ≤ #(univ : Finset (P × Fin (2 ^ (i + 1)))) := by + refine card_le_card_of_injOn (fun Z => (Z.1, Z.2.1)) (fun _ _ => mem_univ _) ?_ + intro Z hZ Z' hZ' hZZ + have hZ := (mem_filter.1 (mem_coe.1 hZ)).2 + have hZ' := (mem_filter.1 (mem_coe.1 hZ')).2 + simp only [Prod.mk.injEq] at hZZ + exact Prod.ext hZZ.1 (Prod.ext hZZ.2 (Fin.ext (by omega))) + _ = m := by simp [m] + have hcard := card_le_two_pow_of_sum_mul_lt (fun _ => Nat.succ_pos _) hfib + (S := TensorNetwork.modeBudget _ pt) fun β hβ => + (TensorNetwork.mem_modeBudget (fun _ => Nat.succ_pos _)).1 hβ + refine hcard.trans (Nat.pow_le_pow_right two_pos ?_) + -- `⌊√(m p_t)⌋ ⌊√u⌋ ≤ 15 M`. + set x := Nat.sqrt (m * pt) + set y := Nat.sqrt u + have hmD : m * 2 ^ (a - i) ≤ 6 * M := by + have : m * 2 ^ (a - i) = 2 * (Fintype.card P * 2 ^ a) := by + rw [show a = i + (a - i) by omega, Nat.add_sub_cancel_left] + simp only [m, pow_succ, pow_add] + ring + omega + have hxy : x * y ≤ 15 * M := by + have h1 : (x * y) ^ 2 ≤ m * pt * u := by + rw [mul_pow] + exact Nat.mul_le_mul (Nat.sqrt_le' _) (Nat.sqrt_le' _) + have h2 : m * pt * u ≤ 204 * M ^ 2 := + calc m * pt * u = m * (pt * u) := by ring + _ ≤ m * (34 * M * 2 ^ (a - i)) := Nat.mul_le_mul_left _ hpt + _ = 34 * M * (m * 2 ^ (a - i)) := by ring + _ ≤ 34 * M * (6 * M) := Nat.mul_le_mul_left _ hmD + _ = 204 * M ^ 2 := by ring + have h3 : (x * y) ^ 2 ≤ (15 * M) ^ 2 := by + rw [mul_pow (15 : ℕ)] + omega + exact (Nat.pow_le_pow_iff_left (by norm_num)).1 h3 + have hσ : σ ≤ 2 ^ (10 + d) * y := sqrt_log_two_le_mul_sqrt_taper hu + have hσ1 : 1 ≤ σ := Nat.one_le_sqrt_log_two hs + have hx : x ≤ 15360 * C * s / (σ * 8 ^ d) := by + rw [Nat.le_div_iff_mul_le (by positivity)] + calc x * (σ * 8 ^ d) ≤ x * (2 ^ (10 + d) * y * 8 ^ d) := + Nat.mul_le_mul_left _ (Nat.mul_le_mul_right _ hσ) + _ = x * y * 2 ^ 10 * (2 ^ d * 8 ^ d) := by rw [pow_add]; ring + _ ≤ 15 * M * 2 ^ 10 * 16 ^ d := by + rw [← mul_pow] + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ hxy) + _ = 15360 * (16 ^ d * M) := by ring + _ ≤ 15360 * (C * s) := Nat.mul_le_mul_left _ hA.edges_le + _ = 15360 * C * s := by ring + change 4 * x + 4 ≤ 4 * (15360 * C * s / (σ * 8 ^ d)) + 4 + omega + +/-- **The excess exponents sum to `O(s / √⌊log₂ s⌋)`**: over at most `⌊log₂ s⌋` levels, +`∑_d e_d ≤ (245760 C + 4) ⌊s / ⌊√⌊log₂ s⌋⌋⌋`. The terms decrease geometrically, as in the +manuscript's `(√2 / 8) ^ d` (§D.4.7, p. 64). -/ +theorem sum_branchExponent_le (C : ℕ) {s D : ℕ} (hs : 2 ≤ s) (hD : D ≤ Nat.log 2 s) : + ∑ d ∈ range D, branchExponent C s d ≤ + (245760 * C + 4) * (s / Nat.sqrt (Nat.log 2 s)) := by + set σ := Nat.sqrt (Nat.log 2 s) + set S := s / σ + set X := 15360 * C * s / σ + have hσ1 : 1 ≤ σ := Nat.one_le_sqrt_log_two hs + have hS1 : 1 ≤ S := Nat.one_le_div_sqrt_log_two hs + have hDS : D ≤ S := hD.trans (Nat.log_two_le_div_sqrt_log_two s) + have hX : X ≤ 15360 * C * S + 15360 * C := Nat.mul_div_le_mul_div_add _ _ _ + have hterm : ∀ d, 15360 * C * s / (σ * 8 ^ d) ≤ X / 2 ^ d := fun d => by + rw [← Nat.div_div_eq_div_mul] + exact Nat.div_le_div_left (Nat.pow_le_pow_left (by norm_num) d) (by positivity) + have hgeo : ∑ d ∈ range D, X / 2 ^ d ≤ 2 * X := by + have h := sum_div_two_pow_succ_le (2 * X) D + have heq : ∀ d, 2 * X / 2 ^ (d + 1) = X / 2 ^ d := fun d => by + rw [pow_succ', Nat.mul_div_mul_left X (2 ^ d) two_pos] + simpa only [heq] using h + calc ∑ d ∈ range D, branchExponent C s d + = 4 * ∑ d ∈ range D, 15360 * C * s / (σ * 8 ^ d) + 4 * D := by + simp only [branchExponent, sum_add_distrib, ← mul_sum, sum_const, card_range, + smul_eq_mul] + ring + _ ≤ 4 * (2 * X) + 4 * S := + Nat.add_le_add (Nat.mul_le_mul_left _ ((sum_le_sum fun d _ => hterm d).trans hgeo)) + (Nat.mul_le_mul_left _ hDS) + _ ≤ (245760 * C + 4) * S := by + have hCS : C ≤ C * S := Nat.le_mul_of_pos_right C hS1 + have h1 : 15360 * C * S = 15360 * (C * S) := by ring + have h2 : (245760 * C + 4) * S = 245760 * (C * S) + 4 * S := by ring + omega + +end Cslib.Circuits.Frontier.ContractionSchedule diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean new file mode 100644 index 000000000..ef6a4eedd --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkSchedule.lean @@ -0,0 +1,90 @@ +/- +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.Schedule + +/-! +# Tests for the parameter schedule of the contraction recursion + +Concrete values of the schedule at `s = 2 ^ 10`, an active call there, and a nonvacuity check of +the certificate: some root call satisfies `ActiveBounds`. +-/ + +@[expose] public section + +namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Schedule + +open Cslib.Circuits.Frontier.ContractionSchedule + +/-! ### Values at `s = 2 ^ 10`, where `⌊log₂ s⌋ = 10` and `⌊√10⌋ = 3` -/ + +theorem log_two_1024 : Nat.log 2 1024 = 10 := by + rw [show (1024 : ℕ) = 2 ^ 10 by norm_num, Nat.log_pow one_lt_two] + +theorem sqrt_10 : Nat.sqrt 10 = 3 := + (Nat.eq_sqrt.2 ⟨by norm_num, by norm_num⟩).symm + +example : leafSize 1024 = 341 := by + rw [leafSize, log_two_1024, sqrt_10] + +/-- `ε λ` is far below one at this size, so the taper and the labels are trivial. -/ +example (d : ℕ) : taper 1024 d = 0 := by + rw [taper, log_two_1024] + exact Nat.div_eq_of_lt (lt_of_lt_of_le (by norm_num) (Nat.pow_le_pow_right two_pos + (Nat.le_add_right 17 d))) + +example : degreeBound 1024 = 2 ^ 11 := by + rw [degreeBound, log_two_1024] + +example : precisionBound 3 1024 0 = 3072 := rfl + +/-- One level adds `8 C s (4 · 2 ^ a_0 + a_0 + 1)` with `a_0 = 0`. -/ +example : precisionBound 3 1024 1 = 3072 + 8 * 3 * 1024 * 5 := by + have h : slotBits 1024 0 = 0 := by + simp [slotBits, prefixBits, taper, log_two_1024] + simp [precisionBound, h] + +example : nodePrecision 5 2 3 7 1 = 5 + 8 * 2 * 4 + 8 * 7 * 4 := rfl + +/-- `Δ ^ 10 ≥ s ^ 9` and `Δ ^ 10 ≤ 2 ^ 20 s ^ 9`. -/ +example : 1024 ^ 9 ≤ degreeBound 1024 ^ 10 ∧ degreeBound 1024 ^ 10 ≤ 2 ^ 20 * 1024 ^ 9 := + ⟨pow_nine_le_degreeBound_pow_ten 1024, degreeBound_pow_ten_le (by norm_num)⟩ + +/-! ### Active calls -/ + +/-- A root call with `1000` edges and precision `5` at `s = 1024`, `C = 1`. -/ +example : Active 1 1024 0 1000 5 := + Active.root (by norm_num) (by rw [show leafSize 1024 = 341 by + rw [leafSize, log_two_1024, sqrt_10]]; norm_num) (by norm_num) + +/-- A child with `62` edges is not active at `s = 1024`: it is a leaf. -/ +example : ¬ Active 1 1024 1 62 5 := fun h => by + have := h.leafSize_lt + rw [show leafSize 1024 = 341 by rw [leafSize, log_two_1024, sqrt_10]] at this + omega + +/-- **The certificate is not vacuous**: for each `C ≥ 1` some root call satisfies +`ActiveBounds`. -/ +example (C : ℕ) (hC : 1 ≤ C) : ∃ s M p, ActiveBounds C s 0 M p := by + obtain ⟨s₀, h⟩ := exists_activeBounds C + set s := max s₀ 16 + have hs : 16 ≤ s := le_max_right _ _ + have hlog : 4 ≤ Nat.log 2 s := Nat.le_log_of_pow_le one_lt_two (by simpa using hs) + have hσ : 2 ≤ Nat.sqrt (Nat.log 2 s) := Nat.le_sqrt.2 (by omega) + refine ⟨s, s, 0, (h s (le_max_left _ _)).2 0 s 0 (Active.root ?_ ?_ (Nat.zero_le _))⟩ + · exact Nat.le_mul_of_pos_left s hC + · exact Nat.div_lt_self (by omega) (by omega) + +/-- The excess exponents at `s = 1024` and `C = 1` over two levels. -/ +example : ∑ d ∈ Finset.range 2, branchExponent 1 1024 d ≤ 245764 * (1024 / Nat.sqrt 10) := by + have := sum_branchExponent_le 1 (s := 1024) (D := 2) (by norm_num) + (by rw [log_two_1024]; norm_num) + rwa [log_two_1024] at this + +end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Schedule From 5328bbefbd02e8d0cd232cdcf7280e706ee3bff2 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:01:23 -0400 Subject: [PATCH 27/38] feat(circuit/frontier): fields of the active-node splitting; expose oracle correctness Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Recursion.lean | 42 +++++++++++++++---- .../Frontier/TensorNetworkRecursion.lean | 4 ++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean index 26e9824a2..a969b3f83 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -73,6 +73,9 @@ binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub * `Cslib.Circuits.Frontier.TensorNetwork.exists_recoverFromValues_eq_add_of_children`: one call is correct when its values are the sums of the products of the children's contractions. +* `Cslib.Circuits.Frontier.TensorNetwork.callSplitting_side`, + `Cslib.Circuits.Frontier.TensorNetwork.callSplitting_group`, + `Cslib.Circuits.Frontier.TensorNetwork.callSplitting_packet`: the fields of the splitting. * `Cslib.Circuits.Frontier.TensorNetwork.exists_callValue_eq_add`: one call is correct for a correct oracle. * `Cslib.Circuits.Frontier.TensorNetwork.mul_card_edges_child_callSplitting_le`: under (D.6), the @@ -199,6 +202,34 @@ noncomputable def callSplitting (N : TensorNetwork B Bool V E) (side : V → Boo side hsrc htgt Q _ _ (two_pow_eq_zero_twoPowIdeal _ _) ht (padicValInt_lt_of_taperMult_pos hq hua) +section Fields + +variable (N : TensorNetwork B Bool V E) (side : V → Bool) (hsrc : ∀ e, side (N.src e) = true) + (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) + (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) + +omit [Fintype V] [DecidableEq V] [DecidableEq E] in +/-- The rows of the splitting at an active node are the rows of the call. -/ +@[simp] theorem callSplitting_side : + (callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).side = side := + cauchyModeSplitting_side .. + +omit [Fintype V] [DecidableEq V] [DecidableEq E] in +/-- The groups at an active node of valuation `i` are the symbols in coordinate `i` of the +packing. -/ +@[simp] theorem callSplitting_group : + (callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).group = + packingGroup side Q ⟨_, padicValInt_lt_of_taperMult_pos hq hua⟩ := + cauchyModeSplitting_group .. + +omit [Fintype V] [DecidableEq V] [DecidableEq E] in +/-- The packets of the splitting at an active node are the packets of the packing. -/ +@[simp] theorem callSplitting_packet : + (callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).packet = Q.packet := + cauchyModeSplitting_packet .. + +end Fields + /-- **Correctness of one active call from its children** ([OpenAI2026ETH], §D.4.4–§D.4.7). Let `N` have entries in `B`, edges from rows to columns, and a packing `Q` into packets of `2 ^ a` slots. Let `1 ≤ u ≤ M`, `u ≤ a` and `3 M ≤ h u ≤ 3 M + u` for `M` edges, and let the @@ -245,7 +276,7 @@ variable {B : Type*} [CommRing B] /-- An oracle is *correct* when each of its values agrees with the contraction modulo the precision `2 ^ p` it is given. -/ -def Correct (rec : ContractionOracle B) : Prop := +@[expose] def Correct (rec : ContractionOracle B) : Prop := ∀ n m (N : TensorNetwork B Bool (Fin n) (Fin m)) side p, ∃ c, rec n m N side p = N.contraction + 2 ^ p * c @@ -382,18 +413,13 @@ theorem mul_card_edges_child_callSplitting_le (N : TensorNetwork B Bool V E) (si {e // (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h hua ht hq z).group (N.tgt e) = g}) ≤ 4 * Fintype.card E := by - have hg : (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h - hua ht hq z).group = - packingGroup side (PacketPacking.packing N.src N.tgt a 64) - ⟨_, padicValInt_lt_of_taperMult_pos hq hua⟩ := by - exact cauchyModeSplitting_group .. have := ModeSplitting.mul_card_edges_child_le (c := 2) (r := 64) (callSplitting B hB N side hsrc htgt (PacketPacking.packing N.src N.tgt a 64) p u h hua ht hq z) g (by - rw [hg] + rw [callSplitting_group] exact (congrArg (64 * ·) (card_packingGroup_src side hsrc _ _ g)).trans_le (PacketPacking.row_ports_packing_le N.src N.tgt a 64 hrow hcol hM _ g)) (by - rw [hg] + rw [callSplitting_group] exact (congrArg (64 * ·) (card_packingGroup_tgt side htgt _ _ g)).trans_le (PacketPacking.col_ports_packing_le N.src N.tgt a 64 hrow hcol hM _ g)) exact this.trans_eq (by ring) diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean index 69be86417..76bfcec66 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean @@ -100,6 +100,10 @@ theorem pts_pairwise : Pairwise fun k l => IsUnit (pts k - pts l) := fun _ _ hkl /-! ### The recursion over `O` -/ +/-- Direct contraction is a correct oracle. -/ +example : ContractionOracle.Correct (B := O) fun _ _ N _ _ => N.contraction := + fun _ _ _ _ _ => ⟨0, by simp⟩ + /-- The constant schedule `a = u = 1`, `h = 3`. -/ def sched (_ _ : ℕ) : Option TensorNetwork.CallParams := some ⟨1, 1, 3⟩ From d9a734324a1bf53b70ff5ca89acdb2166b57587b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:02:48 -0400 Subject: [PATCH 28/38] feat(circuit/frontier): mode count at a certified active call ActiveBounds.card_modeBudget_le supplies the hypotheses of card_modeBudget_le from the certificate, and ActiveBounds.two_le. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/Schedule.lean | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean index 6fa306e29..66eafea95 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Schedule.lean @@ -78,7 +78,8 @@ precision bound `p ≤ s λ^{O(1)} 2 ^ (a_{d-1})` is `precisionBound_le`. ## Main results * `exists_activeBounds`: the parameter certificate. -* `card_modeBudget_le`: the mode count (D.15) at an active call. +* `card_modeBudget_le`, `ActiveBounds.card_modeBudget_le`: the mode count (D.15) at an active + call. * `sum_branchExponent_le`: the excess exponents sum to `O(s / √⌊log₂ s⌋)`. * `precisionBound_le`: the precision bound `p ≤ C s 2 ^ (a_{d-1}) (1 + d (8 λ + 40))`. @@ -686,6 +687,28 @@ theorem card_modeBudget_le {C s d M p : ℕ} (hA : Active C s d M p) (hs : 2 ≤ change 4 * x + 4 ≤ 4 * (15360 * C * s / (σ * 8 ^ d)) + 4 omega +/-- At an active call satisfying the certificate, `s ≥ 2`. -/ +theorem ActiveBounds.two_le {C s d M p : ℕ} (hB : ActiveBounds C s d M p) : 2 ≤ s := by + have h := hB.depth_add_two_le + rcases Nat.lt_or_ge s 2 with hs | hs + · rw [Nat.log_of_lt hs] at h + omega + · exact hs + +/-- **The mode count (D.15) at a certified active call**: for a node of valuation `i < u` and +`h = ⌈3 M / u⌉`, and a packing with `#P 2 ^ a ≤ 3 M` (Lemma D.3 (1)), there are at most `2 ^ e_d` +mode tuples. This is `card_modeBudget_le` with its hypotheses supplied by `ActiveBounds`. -/ +theorem ActiveBounds.card_modeBudget_le {C s d M p : ℕ} (hB : ActiveBounds C s d M p) + (hA : Active C s d M p) {i h : ℕ} (hi : i < taper s d) + (hh : h * taper s d ≤ 3 * M + taper s d) {P : Type*} [Fintype P] [DecidableEq P] + (hP : Fintype.card P * 2 ^ slotBits s d ≤ 3 * M) : + #(TensorNetwork.modeBudget + (fun Z : P × Fin (2 ^ (i + 1)) × Fin (nodePrecision p h (slotBits s d) M i) => + (Z.2.2 : ℕ) + 1) + (nodePrecision p h (slotBits s d) M i)) ≤ 2 ^ branchExponent C s d := + ContractionSchedule.card_modeBudget_le hA hB.two_le hB.one_le_taper + (by have := slotBits_eq s d; omega) (hB.nodePrecision_mul_taper_le i h hi hh) hP + /-- **The excess exponents sum to `O(s / √⌊log₂ s⌋)`**: over at most `⌊log₂ s⌋` levels, `∑_d e_d ≤ (245760 C + 4) ⌊s / ⌊√⌊log₂ s⌋⌋⌋`. The terms decrease geometrically, as in the manuscript's `(√2 / 8) ^ d` (§D.4.7, p. 64). -/ From 5fef5d3af669f4d4b39c03e442d12594a952bde2 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:18:23 -0400 Subject: [PATCH 29/38] feat(circuit/frontier): run the recursion with the parameter schedule of #1908 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recursion now uses ContractionSchedule.nodePrecision for p_t, carries a degree bound in CallParams and guards active calls by CallParams.Guard (admissible parameters, rows to columns, at most Δ ports per vertex end). Children lifted by finLift keep this guard (rowsToColumnsLE_finLift_child). ScheduledRecursion.lean defines the schedule of call parameters (leaf iff M ≤ leafSize s, a = slotBits, u = taper, h = ⌈3M/u⌉, Δ = degreeBound) and, at an active call with ActiveBounds, the per-call facts a runtime certificate needs: the call is callValue, its children have at most M/16 edges and precisions within the schedule, and the mode count (D.15). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 3 +- .../Frontier/TensorNetwork/Recursion.lean | 323 +++++++++++++----- .../Frontier/TensorNetwork/Reindex.lean | 19 +- .../TensorNetwork/ScheduledRecursion.lean | 206 +++++++++++ .../Frontier/TensorNetworkRecursion.lean | 39 ++- 5 files changed, 505 insertions(+), 85 deletions(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/ScheduledRecursion.lean diff --git a/Complexity.lean b/Complexity.lean index ad777cb57..e4bb6dc63 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -881,8 +881,9 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDefor public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex -public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ScheduledRecursion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.TaperedRecovery 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/Recursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean index a969b3f83..a6606b51b 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Recursion.lean @@ -9,6 +9,7 @@ module public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule /-! # The contraction recursion and its correctness @@ -22,7 +23,8 @@ valuation `i < a`, group the vertices by their symbol in coordinate `i` of `Q` ( The packing bridge `packingGroup_src_tgt` orients the groups, so the Cauchy matrix at `T = t + α ζ` has the mode splitting `TensorNetwork.cauchyModeSplitting` (§D.4.5, p. 62). At a call with input precision `p`, `TensorNetwork.callSplitting` is this splitting for the network reduced -modulo `2 ^ p_t`, `p_t = p + 8 h D_t + 8 M (a + 1)` (`nodePrecision`). +modulo `2 ^ p_t`, `p_t = p + 8 h D_t + 8 M (a + 1)`: the schedule's +`ContractionSchedule.nodePrecision` at the valuation of `t`, the precision of Lemma D.5. **One call from its children.** If every value `v t k` reduces modulo `2 ^ p_t` to the sum over the mode tuples of the products of the contractions of the children (the right side of (D.16)), @@ -30,22 +32,26 @@ then interpolation and tapered recovery return the contraction modulo `2 ^ p` (`TensorNetwork.exists_recoverFromValues_eq_add_of_children`). The children's contractions may come from any *contraction oracle* (`TensorNetwork.ContractionOracle`) that is correct modulo the precision it is given: a child over `B ⧸ (2 ^ p_t)` is lifted to `B` along a section of the -reduction and renumbered by `Fin` (`TensorNetwork.ContractionOracle.evalLift`), and the oracle's -value then reduces to its contraction (`TensorNetwork.ContractionOracle.mk_evalLift`). The value of +reduction and renumbered by `Fin` (`TensorNetwork.finLift`, +`TensorNetwork.ContractionOracle.evalLift`), and the oracle's value then reduces to its contraction +(`TensorNetwork.ContractionOracle.mk_evalLift`). The value of an active node is `TensorNetwork.nodeValue`, the values of a call are `TensorNetwork.callValues`, and the recovered value is `TensorNetwork.callValue` (`TensorNetwork.exists_callValue_eq_add`). **The recursion** (`TensorNetwork.contractRec`). A call receives a network on `Fin n` vertices and `Fin m` edges, its sides, its depth `d` and its precision `p`. A *schedule* gives the -parameters `a`, `u`, `h` of a call from its depth and its number of edges (`CallParams`), or none -at a leaf. The call is active when the parameters meet the hypotheses of Lemma D.5 -(`CallParams.Admissible`, with `u ≤ a` and enough interpolation points) and the edges run from -rows to columns. It packs the edges with `PacketPacking.packing` (Lemma D.3, `r = 64`), takes the -first `M (2 L - 1) + 1` of a fixed family of interpolation points, and evaluates each child by a -recursive call at depth `d + 1` and precision `p_t`. A leaf, an inactive call, and a child that -does not have fewer edges than its parent are evaluated by the contraction itself, the direct -summation of the manuscript's leaves. The recursion is well founded on the number of edges. +parameters `a`, `u`, `h` and the degree bound `Δ` of a call from its depth and its number of edges +(`CallParams`), or none at a leaf; the manuscript's schedule is +`ContractionSchedule.callParams`. The call is active when its guard holds (`CallParams.Guard`): +the parameters meet the hypotheses of Lemma D.5 (`CallParams.Admissible`, with `u ≤ a` and enough +interpolation points), and the network runs from rows to columns with at most `Δ` ports at each +end of each vertex (`TensorNetwork.RowsToColumnsLE`). It packs the edges with +`PacketPacking.packing` (Lemma D.3, `r = 64`), takes the first `M (2 L - 1) + 1` of a fixed family +of interpolation points, and evaluates each child by a recursive call at depth `d + 1` and precision +`p_t`. A leaf, an inactive call, and a child that does not have fewer edges than its parent are +evaluated by the contraction itself, the direct summation of the manuscript's leaves. The recursion +is well founded on the number of edges. **Correctness** (`TensorNetwork.exists_contractRec_eq_add`). For interpolation points with unit differences, every call returns the contraction modulo `2 ^ p`, for every schedule: the manuscript's @@ -53,20 +59,25 @@ differences, every call returns the contraction modulo `2 ^ p`, for every schedu parameter schedule (D.4)–(D.6) matters only for the running time, which this file does not treat. Under the first inequality of (D.6), every child has at most `M / 16` edges (`TensorNetwork.mul_card_edges_child_callSplitting_le`, from Lemma D.3 (3)), so an active call with -an edge never falls back for its children. In the manuscript's ring `B = O = ℤ_(2)[U] / (f)`, the -binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub`). +an edge never falls back for its children, and when `2 L ≤ Δ` the lifted children again run from +rows to columns within the degree bound (`TensorNetwork.rowsToColumnsLE_finLift_child`). In the +manuscript's ring `B = O = ℤ_(2)[U] / (f)`, the binary-coordinate lifts are such points +(`AdjoinRoot.isUnit_integerDigitLift_sub`). ## Main definitions -* `Cslib.Circuits.Frontier.nodePrecision`: the child precision `p_t`. +* `Cslib.Circuits.Frontier.TensorNetwork.RowsToColumnsLE`: a network from rows to columns with a + degree bound. * `Cslib.Circuits.Frontier.TensorNetwork.nodeSplitting`, `Cslib.Circuits.Frontier.TensorNetwork.callSplitting`: the mode splitting at an active node. +* `Cslib.Circuits.Frontier.TensorNetwork.finLift`: a child lifted to `B` and numbered by `Fin`. * `Cslib.Circuits.Frontier.TensorNetwork.ContractionOracle`: values for child calls, with `ContractionOracle.Correct` and `ContractionOracle.evalLift`. * `Cslib.Circuits.Frontier.TensorNetwork.nodeValue`, `Cslib.Circuits.Frontier.TensorNetwork.callValues`, `Cslib.Circuits.Frontier.TensorNetwork.callValue`: one active call from an oracle. -* `Cslib.Circuits.Frontier.TensorNetwork.CallParams`: the parameters of a call. +* `Cslib.Circuits.Frontier.TensorNetwork.CallParams`: the parameters of a call, with + `CallParams.Admissible` and the guard `CallParams.Guard`. * `Cslib.Circuits.Frontier.TensorNetwork.contractRec`: the recursion. ## Main results @@ -80,6 +91,9 @@ binary-coordinate lifts are such points (`AdjoinRoot.isUnit_integerDigitLift_sub correct oracle. * `Cslib.Circuits.Frontier.TensorNetwork.mul_card_edges_child_callSplitting_le`: under (D.6), the children have at most `M / 16` edges. +* `Cslib.Circuits.Frontier.TensorNetwork.ModeSplitting.rowsToColumnsLE_child`, + `Cslib.Circuits.Frontier.TensorNetwork.rowsToColumnsLE_finLift_child`: the children keep the + degree bound. * `Cslib.Circuits.Frontier.TensorNetwork.contractRec_eq_callValue`, `Cslib.Circuits.Frontier.TensorNetwork.contractRec_of_sched_eq_none`: an active call and a leaf. * `Cslib.Circuits.Frontier.TensorNetwork.exists_contractRec_eq_add`, @@ -98,25 +112,25 @@ open Polynomial Finset namespace Cslib.Circuits.Frontier +open ContractionSchedule (nodePrecision) + /-- The subring `ℤ_(2)` of rationals with odd denominator. -/ local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) /-! ### Node precisions -/ -/-- The precision `p_t = p + 8 h D_t + 8 M (a + 1)`, `D_t = 2 ^ (a - v₂ t)`, that a call of input -precision `p` with `M` edges passes to the children of the node `t` (Lemma D.5). -/ -@[expose] def nodePrecision (M a h p : ℕ) (t : ℤ) : ℕ := - p + 8 * h * 2 ^ (a - padicValInt 2 t) + 8 * M * (a + 1) +/-- A node of positive multiplicity has valuation below `u`. -/ +theorem padicValInt_lt_of_taperMult_pos_left {u h : ℕ} {t : ℤ} (hq : 0 < taperMult u h t) : + padicValInt 2 t < u := by + unfold taperMult at hq + by_contra hc + rw [Nat.sub_eq_zero_of_le (not_lt.mp hc), mul_zero] at hq + exact lt_irrefl 0 hq /-- A node of positive multiplicity has valuation below `u`, so below `a` when `u ≤ a`. -/ theorem padicValInt_lt_of_taperMult_pos {u h a : ℕ} {t : ℤ} (hq : 0 < taperMult u h t) - (hua : u ≤ a) : padicValInt 2 t < a := by - unfold taperMult at hq - have : padicValInt 2 t < u := by - by_contra hc - rw [Nat.sub_eq_zero_of_le (not_lt.mp hc), mul_zero] at hq - exact lt_irrefl 0 hq - omega + (hua : u ≤ a) : padicValInt 2 t < a := + (padicValInt_lt_of_taperMult_pos_left hq).trans_le hua /-- The ideal `(2 ^ n)` of a commutative ring. -/ abbrev twoPowIdeal (B : Type*) [CommRing B] (n : ℕ) : Ideal B := @@ -129,8 +143,35 @@ theorem two_pow_eq_zero_twoPowIdeal (B : Type*) [CommRing B] (n : ℕ) : Ideal.Quotient.eq_zero_iff_mem.mpr (Ideal.mem_span_singleton_self _) rwa [map_pow, map_ofNat] at this +/-- The ideal `(2 ^ p_t)` of the node `t` of a call with `M` edges, packets of `2 ^ a` slots, +multiplicity step `h` and input precision `p`. -/ +abbrev nodeIdeal (B : Type*) [CommRing B] (M a h p : ℕ) (t : ℤ) : Ideal B := + twoPowIdeal B (nodePrecision p h a M (padicValInt 2 t)) + namespace TensorNetwork +/-! ### Networks from rows to columns -/ + +/-- Every edge of `N` runs from a row (`side v = true`) to a column, and every vertex has at most +`Δ` ports at each end: the bipartite form and the degree bound `Δ` of the networks of the +recursion (§D.4.2, p. 58). -/ +structure RowsToColumnsLE {R U V E : Type*} [Fintype E] [DecidableEq V] + (N : TensorNetwork R U V E) (side : V → Bool) (Δ : ℕ) : Prop where + /-- Edges start at rows. -/ + side_src : ∀ e, side (N.src e) = true + /-- Edges end at columns. -/ + side_tgt : ∀ e, side (N.tgt e) = false + /-- Every vertex is the source of at most `Δ` edges. -/ + src_le : ∀ v, #{e | N.src e = v} ≤ Δ + /-- Every vertex is the target of at most `Δ` edges. -/ + tgt_le : ∀ w, #{e | N.tgt e = w} ≤ Δ + +instance {R U : Type*} {n m : ℕ} (N : TensorNetwork R U (Fin n) (Fin m)) (side : Fin n → Bool) + (Δ : ℕ) : Decidable (N.RowsToColumnsLE side Δ) := + decidable_of_iff ((∀ e, side (N.src e) = true) ∧ (∀ e, side (N.tgt e) = false) ∧ + (∀ v, #{e | N.src e = v} ≤ Δ) ∧ ∀ w, #{e | N.tgt e = w} ≤ Δ) + ⟨fun ⟨h₁, h₂, h₃, h₄⟩ => ⟨h₁, h₂, h₃, h₄⟩, fun ⟨h₁, h₂, h₃, h₄⟩ => ⟨h₁, h₂, h₃, h₄⟩⟩ + /-! ### The modes of an active node -/ section Node @@ -189,16 +230,16 @@ noncomputable def callSplitting (N : TensorNetwork B Bool V E) (side : V → Boo (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : ModeSplitting - (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) + (N.map (Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t))) (cauchyValueMatrix - ((Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t))).comp + ((Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t)).comp (integerHom B hB)) - (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) z) a t + (Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t) z) a t Q.packet fun e => Q.slot e) (Fin r) (Fin Q.numPackets) (Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × - Fin (nodePrecision (Fintype.card E) a h p t)) := - nodeSplitting (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) + Fin (nodePrecision p h a (Fintype.card E) (padicValInt 2 t))) := + nodeSplitting (N.map (Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t))) side hsrc htgt Q _ _ (two_pow_eq_zero_twoPowIdeal _ _) ht (padicValInt_lt_of_taperMult_pos hq hua) @@ -246,22 +287,61 @@ theorem exists_recoverFromValues_eq_add_of_children (N : TensorNetwork B Bool V (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) (hn : Fintype.card E * (2 * 2 ^ a - 1) < n) (v : ℤ → Fin n → B) (hv : ∀ t (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) k, - Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) (v t k) = + Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t) (v t k) = ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × - Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) - (nodePrecision (Fintype.card E) a h p t), + Fin (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)) => (Z.2.2 : ℕ) + 1) + (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)), ∏ g, ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq (ζ k)).child β g).contraction) : ∃ b ∈ B, recoverFromValues B a u h ζ v = (N.contraction : K) + 2 ^ p * b := exists_recoverFromValues_eq_add_of_mk B hB N side (packing_slot_lt Q) (packing_slot_injective Q) hu huM hh hh' ζ hζ hn v fun t ht hq k => (hv t ht hq k).trans (deformedContraction_eq_sum_nodeSplitting - (N.map (Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)))) side + (N.map (Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t))) side hsrc htgt Q _ _ (two_pow_eq_zero_twoPowIdeal _ _) ht (padicValInt_lt_of_taperMult_pos hq hua)).symm end Children +/-! ### Lifting child networks -/ + +section FinLift + +variable {B : Type*} [CommRing B] {V E : Type*} [Fintype V] [Fintype E] {p : ℕ} + +/-- A network over `B ⧸ (2 ^ p)` on finite types, lifted to `B` along a section of the reduction +(`TensorNetwork.mapFun`) and numbered by `Fin` (`TensorNetwork.reindex`): the network that a child +call receives. -/ +noncomputable def finLift (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) : + TensorNetwork B Bool (Fin (Fintype.card V)) (Fin (Fintype.card E)) := + (N.mapFun (Function.surjInv Ideal.Quotient.mk_surjective)).reindex (Fintype.equivFin V) + (Fintype.equivFin E) + +/-- The contraction of a lifted network reduces to the contraction. -/ +theorem mk_contraction_finLift [DecidableEq E] (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) : + Ideal.Quotient.mk (twoPowIdeal B p) (finLift N).contraction = N.contraction := by + rw [finLift, contraction_reindex, + contraction_map_mapFun _ (Function.surjInv_eq Ideal.Quotient.mk_surjective)] + +/-- Lifting keeps a network running from rows to columns, with its degree bound. -/ +theorem RowsToColumnsLE.finLift [DecidableEq V] {N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E} + {side : V → Bool} {Δ : ℕ} (h : N.RowsToColumnsLE side Δ) : + (TensorNetwork.finLift N).RowsToColumnsLE (side ∘ (Fintype.equivFin V).symm) Δ where + side_src e := by + simp only [TensorNetwork.finLift, Function.comp_apply, reindex_src, Equiv.symm_apply_apply] + exact h.side_src _ + side_tgt e := by + simp only [TensorNetwork.finLift, Function.comp_apply, reindex_tgt, Equiv.symm_apply_apply] + exact h.side_tgt _ + src_le v := by + rw [TensorNetwork.finLift, card_reindex_src] + exact h.src_le _ + tgt_le w := by + rw [TensorNetwork.finLift, card_reindex_tgt] + exact h.tgt_le _ + +end FinLift + /-! ### Contraction oracles -/ /-- A *contraction oracle*: a value for every Boolean tensor network on the vertices `Fin n` and the @@ -280,13 +360,11 @@ precision `2 ^ p` it is given. -/ ∀ n m (N : TensorNetwork B Bool (Fin n) (Fin m)) side p, ∃ c, rec n m N side p = N.contraction + 2 ^ p * c -/-- The value of an oracle on a network over `B ⧸ (2 ^ p)` on finite types, at precision `p`: lift -the entries to `B` along a section of the reduction (`TensorNetwork.mapFun`), number the vertices -and edges (`TensorNetwork.reindex`), and query. -/ +/-- The value of an oracle on a network over `B ⧸ (2 ^ p)` on finite types, at precision `p`: the +query on the lifted network `finLift N`. -/ noncomputable def evalLift (rec : ContractionOracle B) {V E : Type*} [Fintype V] [Fintype E] {p : ℕ} (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) (side : V → Bool) : B := - rec _ _ ((N.mapFun (Function.surjInv Ideal.Quotient.mk_surjective)).reindex - (Fintype.equivFin V) (Fintype.equivFin E)) (side ∘ (Fintype.equivFin V).symm) p + rec _ _ (finLift N) (side ∘ (Fintype.equivFin V).symm) p /-- **A correct oracle evaluates a lifted network**: its value reduces modulo `2 ^ p` to the contraction. -/ @@ -294,12 +372,10 @@ theorem mk_evalLift {rec : ContractionOracle B} (hrec : rec.Correct) {V E : Type [Fintype E] [DecidableEq E] {p : ℕ} (N : TensorNetwork (B ⧸ twoPowIdeal B p) Bool V E) (side : V → Bool) : Ideal.Quotient.mk (twoPowIdeal B p) (rec.evalLift N side) = N.contraction := by - obtain ⟨c, hc⟩ := hrec _ _ ((N.mapFun (Function.surjInv Ideal.Quotient.mk_surjective)).reindex - (Fintype.equivFin V) (Fintype.equivFin E)) (side ∘ (Fintype.equivFin V).symm) p + obtain ⟨c, hc⟩ := hrec _ _ (finLift N) (side ∘ (Fintype.equivFin V).symm) p have h2 : Ideal.Quotient.mk (twoPowIdeal B p) ((2 : B) ^ p) = 0 := Ideal.Quotient.eq_zero_iff_mem.mpr (Ideal.mem_span_singleton_self _) - rw [evalLift, hc, map_add, map_mul, h2, zero_mul, add_zero, contraction_reindex, - contraction_map_mapFun _ (Function.surjInv_eq Ideal.Quotient.mk_surjective)] + rw [evalLift, hc, map_add, map_mul, h2, zero_mul, add_zero, mk_contraction_finLift] end ContractionOracle @@ -319,8 +395,8 @@ noncomputable def nodeValue (rec : ContractionOracle B) (N : TensorNetwork B Boo {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : B := ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × - Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) - (nodePrecision (Fintype.card E) a h p t), + Fin (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)) => (Z.2.2 : ℕ) + 1) + (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)), ∏ g, rec.evalLift ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).child β g) Sum.isLeft @@ -331,11 +407,11 @@ theorem mk_nodeValue {rec : ContractionOracle B} (hrec : rec.Correct) (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) : - Ideal.Quotient.mk (twoPowIdeal B (nodePrecision (Fintype.card E) a h p t)) + Ideal.Quotient.mk (nodeIdeal B (Fintype.card E) a h p t) (nodeValue B hB rec N side hsrc htgt Q p u h hua ht hq z) = ∑ β ∈ modeBudget (fun Z : Fin Q.numPackets × Fin (2 ^ (padicValInt 2 t + 1)) × - Fin (nodePrecision (Fintype.card E) a h p t) => (Z.2.2 : ℕ) + 1) - (nodePrecision (Fintype.card E) a h p t), + Fin (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)) => (Z.2.2 : ℕ) + 1) + (nodePrecision p h a (Fintype.card E) (padicValInt 2 t)), ∏ g, ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).child β g).contraction := by simp only [nodeValue, map_sum, map_prod, ContractionOracle.mk_evalLift hrec] @@ -387,7 +463,51 @@ theorem exists_callValue_eq_add {rec : ContractionOracle B} (hrec : rec.Correct) end Call -/-! ### Children are smaller under (D.6) -/ +/-! ### The children of an active call -/ + +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 : N.ModeSplitting W G P M) [Fintype E] [DecidableEq E] [DecidableEq P] + [DecidableEq G] [DecidableEq M] [DecidableEq V] + +/-- **Children run from rows to columns within the degree bound.** If every vertex of `N` has at +most `Δ` ports at each end, and the packets have `2 ^ a` slots with `2 · 2 ^ a ≤ Δ`, then each +child runs from its old vertices to its packets with at most `Δ` ports at each vertex: old +vertices keep their degrees, and a packet vertex has at most `2 L` ports (§D.4.6, p. 63). -/ +theorem rowsToColumnsLE_child {Δ a : ℕ} {slot : E → ℕ} (hrow : ∀ v, #{e | N.src e = v} ≤ Δ) + (hcol : ∀ w, #{e | N.tgt e = w} ≤ Δ) (hslot : ∀ e, slot e < 2 ^ a) + (hinj : ∀ e e', S.packet e = S.packet e' → slot e = slot e' → e = e') (hL : 2 * 2 ^ a ≤ Δ) + (β : M → ℕ) (g : G) : (S.child β g).RowsToColumnsLE Sum.isLeft Δ where + side_src := by rintro (e | e) <;> rfl + side_tgt := by rintro (e | e) <;> rfl + src_le := by + rintro (v | q) + · rw [S.card_child_src_inl] + by_cases hv : S.side v.1 + · have h0 : #{e | N.tgt e = v.1} = 0 := card_eq_zero.mpr <| filter_eq_empty_iff.mpr + fun e _ he => by + have := S.side_tgt e + rw [he, hv] at this + exact absurd this (by decide) + rw [h0, add_zero] + exact hrow _ + · have h0 : #{e | N.src e = v.1} = 0 := card_eq_zero.mpr <| filter_eq_empty_iff.mpr + fun e _ he => by + have := S.side_src e + rw [he] at this + exact hv this + rw [h0, zero_add] + exact hcol _ + · rw [S.card_child_src_inr] + exact Nat.zero_le _ + tgt_le := by + rintro (v | q) + · rw [S.card_child_tgt_inl] + exact Nat.zero_le _ + · exact (S.card_child_tgt_inr_le hslot hinj β g q).trans hL + +end ModeSplitting section ChildSize @@ -424,12 +544,28 @@ theorem mul_card_edges_child_callSplitting_le (N : TensorNetwork B Bool V E) (si (PacketPacking.col_ports_packing_le N.src N.tgt a 64 hrow hcol hM _ g)) exact this.trans_eq (by ring) +omit [LinearOrder E] [FinEnum V] in +/-- **The lifted children of an active call run from rows to columns within the degree bound** +`Δ`, when the call does and its packets have `2 · 2 ^ a ≤ Δ` slots: the networks the child calls +receive meet the network part of their guard (`CallParams.Guard`). -/ +theorem rowsToColumnsLE_finLift_child (N : TensorNetwork B Bool V E) (side : V → Bool) + (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) {a r : ℕ} + (Q : PacketPacking.Packing N.src N.tgt a r) (p u h : ℕ) (hua : u ≤ a) {t : ℤ} + (ht : t ∈ recoveryNodes a) (hq : 0 < taperMult u h t) (z : B) {Δ : ℕ} + (hrow : ∀ v, #{e | N.src e = v} ≤ Δ) (hcol : ∀ w, #{e | N.tgt e = w} ≤ Δ) + (hL : 2 * 2 ^ a ≤ Δ) (β) (g : Fin r) : + (finLift ((callSplitting B hB N side hsrc htgt Q p u h hua ht hq z).child β g)).RowsToColumnsLE + (Sum.isLeft ∘ (Fintype.equivFin _).symm) Δ := + RowsToColumnsLE.finLift <| ModeSplitting.rowsToColumnsLE_child + (callSplitting B hB N side hsrc htgt Q p u h hua ht hq z) hrow hcol (packing_slot_lt Q) + (by rw [callSplitting_packet]; exact packing_slot_injective Q) hL β g + end ChildSize /-! ### The recursion -/ -/-- The parameters of an active call: packets of `L = 2 ^ a` slots, and the tapered multiplicities -`q_t = h max(0, u - v₂ t)` of Lemma D.5. -/ +/-- The parameters of an active call: packets of `L = 2 ^ a` slots, the tapered multiplicities +`q_t = h max(0, u - v₂ t)` of Lemma D.5, and the degree bound `Δ` of (D.6). -/ structure CallParams where /-- The packets have `2 ^ a` slots. -/ a : ℕ @@ -437,17 +573,47 @@ structure CallParams where u : ℕ /-- The step of the multiplicities between levels. -/ h : ℕ + /-- The degree bound `Δ`. -/ + degree : ℕ /-- The parameters of a call with `M` edges and `n` interpolation points are *admissible* when they meet the hypotheses of Lemma D.5, `1 ≤ u ≤ M`, `u ≤ a` and `3 M ≤ h u ≤ 3 M + u`, and there are more than `M (2 L - 1)` points. -/ -@[expose] def CallParams.Admissible (θ : CallParams) (M n : ℕ) : Prop := - 1 ≤ θ.u ∧ θ.u ≤ M ∧ θ.u ≤ θ.a ∧ 3 * M ≤ θ.h * θ.u ∧ θ.h * θ.u ≤ 3 * M + θ.u ∧ - M * (2 * 2 ^ θ.a - 1) < n - -instance (θ : CallParams) (M n : ℕ) : Decidable (θ.Admissible M n) := by - unfold CallParams.Admissible - infer_instance +structure CallParams.Admissible (θ : CallParams) (M n : ℕ) : Prop where + /-- `1 ≤ u`. -/ + one_le_u : 1 ≤ θ.u + /-- `u ≤ M`. -/ + u_le_edges : θ.u ≤ M + /-- `u ≤ a`. -/ + u_le_a : θ.u ≤ θ.a + /-- `3 M ≤ h u`. -/ + le_h_mul : 3 * M ≤ θ.h * θ.u + /-- `h u ≤ 3 M + u`, as for `h = ⌈3 M / u⌉`. -/ + h_mul_le : θ.h * θ.u ≤ 3 * M + θ.u + /-- There are more than `M (2 L - 1)` interpolation points. -/ + points_lt : M * (2 * 2 ^ θ.a - 1) < n + +instance (θ : CallParams) (M n : ℕ) : Decidable (θ.Admissible M n) := + decidable_of_iff (1 ≤ θ.u ∧ θ.u ≤ M ∧ θ.u ≤ θ.a ∧ 3 * M ≤ θ.h * θ.u ∧ + θ.h * θ.u ≤ 3 * M + θ.u ∧ M * (2 * 2 ^ θ.a - 1) < n) + ⟨fun ⟨h₁, h₂, h₃, h₄, h₅, h₆⟩ => ⟨h₁, h₂, h₃, h₄, h₅, h₆⟩, + fun ⟨h₁, h₂, h₃, h₄, h₅, h₆⟩ => ⟨h₁, h₂, h₃, h₄, h₅, h₆⟩⟩ + +/-- The *guard* of an active call on a network `N` with `m` edges, its sides `side` and `n` +interpolation points: the parameters `θ` are admissible, and `N` runs from rows to columns with +at most `Δ = θ.degree` ports at each end of each vertex, the degree bound under which Lemma D.3 +makes the children smaller (`mul_card_edges_child_callSplitting_le`). Correctness does not use the +degree bound. -/ +structure CallParams.Guard (θ : CallParams) {R : Type*} {n m : ℕ} + (N : TensorNetwork R Bool (Fin n) (Fin m)) (side : Fin n → Bool) (N₀ : ℕ) : Prop + extends N.RowsToColumnsLE side θ.degree where + /-- The parameters are admissible. -/ + admissible : θ.Admissible m N₀ + +instance (θ : CallParams) {R : Type*} {n m : ℕ} (N : TensorNetwork R Bool (Fin n) (Fin m)) + (side : Fin n → Bool) (N₀ : ℕ) : Decidable (θ.Guard N side N₀) := + decidable_of_iff (N.RowsToColumnsLE side θ.degree ∧ θ.Admissible m N₀) + ⟨fun ⟨h₁, h₂⟩ => ⟨h₁, h₂⟩, fun h => ⟨h.toRowsToColumnsLE, h.admissible⟩⟩ section Recursion @@ -457,41 +623,42 @@ variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) /-- **The contraction recursion** ([OpenAI2026ETH], §D.4.7). A call at depth `d` on a network `N` with `m` edges, sides `side` and precision `p` asks the schedule for its parameters. When there -are none, the parameters are not admissible, or an edge does not run from a row to a column, it -returns the contraction (direct summation). Otherwise it is an active call (`callValue`) with the -packing of Lemma D.3 (`r = 64`) and the first `M (2 L - 1) + 1` points of `pts`, whose children -are recursive calls at depth `d + 1`; a child that does not have fewer edges returns its -contraction. -/ +are none, or the guard fails (`CallParams.Guard`), it returns the contraction (direct +summation). Otherwise it is an active call (`callValue`) with the packing of Lemma D.3 (`r = 64`) +and the first `M (2 L - 1) + 1` points of `pts`, whose children are recursive calls at depth +`d + 1`; a child that does not have fewer edges returns its contraction. -/ noncomputable def contractRec (d n m : ℕ) (N : TensorNetwork B Bool (Fin n) (Fin m)) (side : Fin n → Bool) (p : ℕ) : B := match sched d m with | none => N.contraction | some θ => - if hθ : θ.Admissible m N₀ ∧ (∀ e, side (N.src e) = true) ∧ ∀ e, side (N.tgt e) = false then + if hθ : θ.Guard N side N₀ then callValue B hB (fun n' m' N' side' p' => if hm : m' < m then contractRec (d + 1) n' m' N' side' p' else N'.contraction) - N side hθ.2.1 hθ.2.2 (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h hθ.1.2.2.1 - fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.1.2.2.2.2.2 k) + N side hθ.side_src hθ.side_tgt (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h + hθ.admissible.u_le_a + fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.admissible.points_lt k) else N.contraction termination_by m variable {B hB sched pts} in -/-- **An active call** of the recursion: when the schedule gives admissible parameters and the edges -run from rows to columns, the call is `callValue` with the packing of Lemma D.3, the first -`M (2 L - 1) + 1` points, and recursive calls at depth `d + 1` for the children with fewer edges. -/ +/-- **An active call** of the recursion: when the schedule gives parameters whose guard holds, the +call is `callValue` with the packing of Lemma D.3, the first `M (2 L - 1) + 1` points, and +recursive calls at depth `d + 1` for the children with fewer edges. -/ theorem contractRec_eq_callValue {d n m : ℕ} {N : TensorNetwork B Bool (Fin n) (Fin m)} - {side : Fin n → Bool} {θ : CallParams} (hs : sched d m = some θ) (hθ : θ.Admissible m N₀) - (hsrc : ∀ e, side (N.src e) = true) (htgt : ∀ e, side (N.tgt e) = false) (p : ℕ) : + {side : Fin n → Bool} {θ : CallParams} (hs : sched d m = some θ) (hθ : θ.Guard N side N₀) + (p : ℕ) : contractRec B hB sched pts d n m N side p = callValue B hB (fun n' m' N' side' p' => if m' < m then contractRec B hB sched pts (d + 1) n' m' N' side' p' else N'.contraction) - N side hsrc htgt (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h hθ.2.2.1 - fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.2.2.2.2.2 k) := by + N side hθ.side_src hθ.side_tgt (PacketPacking.packing N.src N.tgt θ.a 64) p θ.u θ.h + hθ.admissible.u_le_a + fun k : Fin (m * (2 * 2 ^ θ.a - 1) + 1) => pts (Fin.castLE hθ.admissible.points_lt k) := by rw [contractRec.eq_def] simp only [hs] - rw [dite_eq_left ⟨hθ, hsrc, htgt⟩] + rw [dite_eq_left hθ] rfl variable {B hB sched pts} in @@ -515,9 +682,9 @@ theorem exists_contractRec_eq_add (hpts : Pairwise fun k l => IsUnit (pts k - pt · exact ⟨0, by simp⟩ · rename_i θ _ split_ifs with hθ - · obtain ⟨hu, huM, hua, hh, hh', hn⟩ := hθ.1 - refine exists_callValue_eq_add B hB (fun n' m' N' side' p' => ?_) N side hθ.2.1 hθ.2.2 _ - hu (by simpa using huM) hua (by simpa using hh) (by simpa using hh') _ + · obtain ⟨hu, huM, hua, hh, hh', hn⟩ := hθ.admissible + refine exists_callValue_eq_add B hB (fun n' m' N' side' p' => ?_) N side hθ.side_src + hθ.side_tgt _ hu (by simpa using huM) hua (by simpa using hh) (by simpa using hh') _ (fun k l hkl => hpts fun h => hkl (Fin.castLE_injective _ h)) (by simp) split_ifs with hm · exact ih m' hm (d + 1) n' N' side' p' diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean index 306a069d3..468d7f065 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/Reindex.lean @@ -32,7 +32,8 @@ coefficient ring change. ## Main results -* `Cslib.Circuits.Frontier.TensorNetwork.contraction_reindex`: renaming preserves the contraction. +* `Cslib.Circuits.Frontier.TensorNetwork.contraction_reindex`: renaming preserves the contraction, + and `card_reindex_src`, `card_reindex_tgt` the degrees. * `Cslib.Circuits.Frontier.TensorNetwork.map_mapFun`: reducing a lift along `f` recovers the network. * `Cslib.Circuits.Frontier.TensorNetwork.contraction_map_mapFun`: the contraction of a lift @@ -48,6 +49,8 @@ public section namespace Cslib.Circuits.Frontier.TensorNetwork +open Finset + variable {R S U V E V' E' : Type*} /-! ### Renaming the vertices and edges -/ @@ -78,6 +81,20 @@ variable (eV : V ≃ V') (eE : E ≃ E') (N : TensorNetwork R U V E) (N.reindex eV eE).tensor v x = N.tensor (eV.symm v) (x ∘ eE) := rfl +/-- Renaming keeps the number of edges leaving each vertex. -/ +theorem card_reindex_src [Fintype E] [Fintype E'] [DecidableEq V] [DecidableEq V'] (v : V') : + #{e | (N.reindex eV eE).src e = v} = #{e | N.src e = eV.symm v} := by + refine Finset.card_equiv eE.symm fun e => ?_ + rw [Finset.mem_filter, Finset.mem_filter] + simp [Equiv.eq_symm_apply] + +/-- Renaming keeps the number of edges entering each vertex. -/ +theorem card_reindex_tgt [Fintype E] [Fintype E'] [DecidableEq V] [DecidableEq V'] (v : V') : + #{e | (N.reindex eV eE).tgt e = v} = #{e | N.tgt e = eV.symm v} := by + refine Finset.card_equiv eE.symm fun e => ?_ + rw [Finset.mem_filter, Finset.mem_filter] + simp [Equiv.eq_symm_apply] + /-- **Renaming preserves the contraction.** -/ theorem contraction_reindex [CommSemiring R] [Fintype U] [Fintype V] [Fintype E] [DecidableEq E] [Fintype V'] [Fintype E'] [DecidableEq E'] : diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ScheduledRecursion.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ScheduledRecursion.lean new file mode 100644 index 000000000..3a70376cc --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ScheduledRecursion.lean @@ -0,0 +1,206 @@ +/- +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.Recursion + +/-! +# The contraction recursion with the parameter schedule + +This file runs the contraction recursion (`TensorNetwork.contractRec`) with the parameter schedule +of [OpenAI2026ETH] (§D.4.2, (D.4)–(D.6), p. 58) from `Cslib.Circuits.Frontier.ContractionSchedule`, +so that its correctness and the running-time certificate refer to the same calls. + +**The schedule of call parameters** (`ContractionSchedule.callParams s`). For size `s`, a call at +depth `d` with `M` edges is a leaf when `M ≤ H = leafSize s`. Otherwise it has `a = slotBits s d` +slot bits, taper `u = taper s d`, multiplicity step `h = ⌈3 M / u⌉` (`taperStep`), and degree +bound `Δ = degreeBound s`. Its children are recovered at the precisions `p_t = nodePrecision`. +Correctness of the recursion holds for this schedule as for every other +(`TensorNetwork.exists_contractRec_eq_add`). + +**An active call.** At a call with `Active C s d M p` and `ActiveBounds C s d M p` (the certificate +`exists_activeBounds` gives the second from the first for `s ≥ s₀`) on a network that runs from +rows to columns with at most `Δ` ports at each end, and with at least `2 ^ (4 ⌊log₂ s⌋)` +interpolation points: + +* the guard holds, so the call is the active call `callValue` + (`contractRec_callParams_eq_callValue`); +* every lifted child runs from rows to columns within the degree bound + (`TensorNetwork.rowsToColumnsLE_finLift_child`), has at most `M / 16` edges + (`sixteen_mul_card_edges_child_le`), and is evaluated at a precision within the schedule + (`nodePrecision_le_of_taperMult_pos`), so it is a leaf or an active call at depth `d + 1` + (`Active.child`); +* there are at most `2 ^ e_d` mode tuples at each active node (`card_modeBudget_callParams_le`), and + the nodes, points and groups contribute `2 ^ (2 ⌊log₂ s⌋)` (`ActiveBounds.branching_le`). + +These are the per-call facts that a certificate of the running time instantiates along the +recursion tree. + +## Main definitions + +* `Cslib.Circuits.Frontier.ContractionSchedule.taperStep`: `h = ⌈3 M / u⌉`. +* `Cslib.Circuits.Frontier.ContractionSchedule.callParams`: the schedule of call parameters. + +## Main results + +* `Cslib.Circuits.Frontier.ContractionSchedule.ActiveBounds.admissible`: the parameters of an active + call are admissible. +* `Cslib.Circuits.Frontier.ContractionSchedule.contractRec_callParams_eq_callValue`: an active call. +* `Cslib.Circuits.Frontier.ContractionSchedule.sixteen_mul_card_edges_child_le`, + `Cslib.Circuits.Frontier.ContractionSchedule.nodePrecision_le_of_taperMult_pos`, + `Cslib.Circuits.Frontier.ContractionSchedule.card_modeBudget_callParams_le`: its children and + their number. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.2 (p. 58), + §D.4.4–§D.4.7, pp. 60–64. +-/ + +public section + +open Finset + +namespace Cslib.Circuits.Frontier.ContractionSchedule + +open TensorNetwork + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### The schedule of call parameters -/ + +/-- The multiplicity step `h = ⌈3 M / u⌉` of Lemma D.5 at depth `d`, with `u = taper s d`. -/ +@[expose] def taperStep (s d M : ℕ) : ℕ := + (3 * M + taper s d - 1) / taper s d + +theorem le_taperStep_mul {s d M : ℕ} (hu : 1 ≤ taper s d) : + 3 * M ≤ taperStep s d M * taper s d := by + have h1 := Nat.lt_div_mul_add (a := 3 * M + taper s d - 1) (b := taper s d) (by omega) + unfold taperStep + generalize (3 * M + taper s d - 1) / taper s d * taper s d = y at h1 ⊢ + omega + +theorem taperStep_mul_le (s d M : ℕ) : taperStep s d M * taper s d ≤ 3 * M + taper s d := + (Nat.div_mul_le_self _ _).trans (Nat.sub_le _ _) + +/-- **The schedule of call parameters** for size `s`: a call at depth `d` with `M` edges is a leaf +when `M ≤ leafSize s`, and otherwise has `a = slotBits s d`, `u = taper s d`, `h = ⌈3 M / u⌉` and +the degree bound `Δ = degreeBound s`. -/ +@[expose] def callParams (s d M : ℕ) : Option CallParams := + if M ≤ leafSize s then none + else some ⟨slotBits s d, taper s d, taperStep s d M, degreeBound s⟩ + +theorem callParams_of_le {s d M : ℕ} (h : M ≤ leafSize s) : callParams s d M = none := + ite_eq_left h + +theorem callParams_of_lt {s d M : ℕ} (h : leafSize s < M) : + callParams s d M = some ⟨slotBits s d, taper s d, taperStep s d M, degreeBound s⟩ := + ite_eq_right (not_le.mpr h) + +/-- **The parameters of an active call are admissible**, given `2 ^ (4 ⌊log₂ s⌋)` interpolation +points. -/ +theorem ActiveBounds.admissible {C s d M p : ℕ} (hB : ActiveBounds C s d M p) {N₀ : ℕ} + (hN₀ : 2 ^ (4 * Nat.log 2 s) ≤ N₀) : + (CallParams.mk (slotBits s d) (taper s d) (taperStep s d M) (degreeBound s)).Admissible M + N₀ where + one_le_u := hB.one_le_taper + u_le_edges := hB.taper_le_edges + u_le_a := by + have := slotBits_eq s d + simp only + omega + le_h_mul := le_taperStep_mul hB.one_le_taper + h_mul_le := taperStep_mul_le s d M + points_lt := by + have := hB.points_le + simp only + omega + +/-! ### An active call -/ + +section Active + +variable {K : Type*} [CommRing K] [Algebra ℚ K] (B : Subring K) + (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {N₀ : ℕ} (pts : Fin N₀ → B) + +/-- **An active call of the scheduled recursion.** At an active call (`Active`, `ActiveBounds`) on a +network that runs from rows to columns within the degree bound, with at least +`2 ^ (4 ⌊log₂ s⌋)` interpolation points, the recursion is the active call `callValue` with the +packing of Lemma D.3 at `a = slotBits s d`, `u = taper s d` and `h = ⌈3 M / u⌉`, and recursive +calls at depth `d + 1` for the children. -/ +theorem contractRec_callParams_eq_callValue {C s d n m p : ℕ} + {N : TensorNetwork B Bool (Fin n) (Fin m)} {side : Fin n → Bool} (hA : Active C s d m p) + (hAB : ActiveBounds C s d m p) (hN : N.RowsToColumnsLE side (degreeBound s)) + (hN₀ : 2 ^ (4 * Nat.log 2 s) ≤ N₀) : + contractRec B hB (callParams s) pts d n m N side p = + callValue B hB + (fun n' m' N' side' p' => + if m' < m then contractRec B hB (callParams s) pts (d + 1) n' m' N' side' p' + else N'.contraction) + N side hN.side_src hN.side_tgt (PacketPacking.packing N.src N.tgt (slotBits s d) 64) p + (taper s d) (taperStep s d m) (hAB.admissible hN₀).u_le_a + fun k : Fin (m * (2 * 2 ^ slotBits s d - 1) + 1) => + pts (Fin.castLE (hAB.admissible hN₀).points_lt k) := + contractRec_eq_callValue (callParams_of_lt hA.leafSize_lt) ⟨hN, hAB.admissible hN₀⟩ p + +variable {C s d n m p : ℕ} {N : TensorNetwork B Bool (Fin n) (Fin m)} {side : Fin n → Bool} + +/-- **The children of an active call have at most `M / 16` edges**, as `Active.child` requires +(Lemma D.3 (3) under (D.6), `TensorNetwork.mul_card_edges_child_callSplitting_le`). -/ +theorem sixteen_mul_card_edges_child_le (hAB : ActiveBounds C s d m p) + (hN : N.RowsToColumnsLE side (degreeBound s)) (hua : taper s d ≤ slotBits s d) {t : ℤ} + (ht : t ∈ recoveryNodes (slotBits s d)) (hq : 0 < taperMult (taper s d) (taperStep s d m) t) + (z : B) (g : Fin 64) : + 16 * Fintype.card + ({e // (callSplitting B hB N side hN.side_src hN.side_tgt + (PacketPacking.packing N.src N.tgt (slotBits s d) 64) p (taper s d) (taperStep s d m) + hua ht hq z).group (N.src e) = g} ⊕ + {e // (callSplitting B hB N side hN.side_src hN.side_tgt + (PacketPacking.packing N.src N.tgt (slotBits s d) 64) p (taper s d) (taperStep s d m) + hua ht hq z).group (N.tgt e) = g}) ≤ + m := by + have h := mul_card_edges_child_callSplitting_le B hB N side hN.side_src hN.side_tgt hN.src_le + hN.tgt_le (by rw [Fintype.card_fin]; exact hAB.degree_mul_pairs_sq_le) p (taper s d) + (taperStep s d m) hua ht hq z g + have := Fintype.card_fin m + omega + +/-- **The children of an active call are evaluated within the precision schedule**: a node of +positive multiplicity passes `p_t ≤ precisionBound C s (d + 1)` to its children. -/ +theorem nodePrecision_le_of_taperMult_pos (hAB : ActiveBounds C s d m p) {t : ℤ} + (hq : 0 < taperMult (taper s d) (taperStep s d m) t) : + nodePrecision p (taperStep s d m) (slotBits s d) (Fintype.card (Fin m)) (padicValInt 2 t) ≤ + precisionBound C s (d + 1) := by + rw [Fintype.card_fin] + exact hAB.nodePrecision_le _ _ (padicValInt_lt_of_taperMult_pos_left hq) (taperStep_mul_le s d m) + +omit [Algebra ℚ K] in +/-- **The mode count (D.15) at an active call**: at a node of positive multiplicity, there are at +most `2 ^ e_d` mode tuples for the packing of Lemma D.3, which has at most `3 M / L` packets. -/ +theorem card_modeBudget_callParams_le (hA : Active C s d m p) (hAB : ActiveBounds C s d m p) + (hN : N.RowsToColumnsLE side (degreeBound s)) {t : ℤ} + (hq : 0 < taperMult (taper s d) (taperStep s d m) t) : + #(modeBudget + (fun Z : Fin (PacketPacking.packing N.src N.tgt (slotBits s d) 64).numPackets × + Fin (2 ^ (padicValInt 2 t + 1)) × + Fin (nodePrecision p (taperStep s d m) (slotBits s d) (Fintype.card (Fin m)) + (padicValInt 2 t)) => (Z.2.2 : ℕ) + 1) + (nodePrecision p (taperStep s d m) (slotBits s d) (Fintype.card (Fin m)) + (padicValInt 2 t))) ≤ + 2 ^ branchExponent C s d := by + have hP := PacketPacking.numPackets_packing_mul_le N.src N.tgt (slotBits s d) 64 hN.src_le + hN.tgt_le (by rw [Fintype.card_fin]; exact hAB.degree_mul_pairs_sq_le) + (by rw [Fintype.card_fin]; exact hAB.slots_mul_pairs_le) + rw [Fintype.card_fin] at hP ⊢ + exact hAB.card_modeBudget_le hA (padicValInt_lt_of_taperMult_pos_left hq) + (taperStep_mul_le s d m) (by rwa [Fintype.card_fin]) + +end Active + +end Cslib.Circuits.Frontier.ContractionSchedule diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean index 76bfcec66..624befa8d 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkRecursion.lean @@ -4,7 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Samuel Schlesinger -/ -import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion +import Complexity.Computability.Circuit.Frontier.TensorNetwork.ScheduledRecursion import Complexity.Foundations.NumberTheory.Padics.AdjoinRootDigitLift import Mathlib.Algebra.Field.ZMod import Mathlib.Algebra.Polynomial.SpecificDegree @@ -23,6 +23,8 @@ import Mathlib.Tactic.FinCases `a = u = 1`, `h = 3`. On a one-edge network these parameters are admissible (`3 M ≤ h u ≤ 3 M + u` and `M (2 L - 1) = 3 < 4`), so the root call is active (`contractRec_eq_callValue`); its children are not smaller and are evaluated directly. Without parameters the call is a leaf. +* **The scheduled recursion** (`ContractionSchedule.callParams`): small calls are leaves, the + recursion is correct, and for large `s` the parameters of every active call are admissible. -/ namespace ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Recursion @@ -104,11 +106,11 @@ theorem pts_pairwise : Pairwise fun k l => IsUnit (pts k - pts l) := fun _ _ hkl example : ContractionOracle.Correct (B := O) fun _ _ N _ _ => N.contraction := fun _ _ _ _ _ => ⟨0, by simp⟩ -/-- The constant schedule `a = u = 1`, `h = 3`. -/ -def sched (_ _ : ℕ) : Option TensorNetwork.CallParams := some ⟨1, 1, 3⟩ +/-- The constant schedule `a = u = 1`, `h = 3`, with degree bound `1`. -/ +def sched (_ _ : ℕ) : Option TensorNetwork.CallParams := some ⟨1, 1, 3, 1⟩ /-- On one edge with four points the schedule is admissible. -/ -example : (TensorNetwork.CallParams.mk 1 1 3).Admissible 1 4 := by decide +example : (TensorNetwork.CallParams.mk 1 1 3 1).Admissible 1 4 := by decide /-- One edge from row `0` to column `1`, with tensor `(1, 2)` at the row and `(1, 1)` at the column. -/ @@ -128,7 +130,9 @@ example (p : ℕ) : if m' < 1 then contractRec O hO sched pts 1 n' m' N' side' p' else N'.contraction) net₁ (fun v => decide (v = 0)) (fun _ => rfl) (fun _ => rfl) (Cslib.PacketPacking.packing net₁.src net₁.tgt 1 64) p 1 3 le_rfl pts := - contractRec_eq_callValue (θ := ⟨1, 1, 3⟩) rfl (by decide) (fun _ => rfl) (fun _ => rfl) p + contractRec_eq_callValue (θ := ⟨1, 1, 3, 1⟩) rfl + ⟨⟨fun _ => rfl, fun _ => rfl, fun _ => (Finset.card_filter_le _ _).trans (by simp), + fun _ => (Finset.card_filter_le _ _).trans (by simp)⟩, by decide⟩ p /-- With no parameters, a call is a leaf and returns the contraction. -/ example (p : ℕ) : @@ -150,4 +154,29 @@ example (sched' : ℕ → ℕ → Option TensorNetwork.CallParams) (n m p : ℕ) ∃ c : O, contractRec O hO sched' pts 0 n m N side p = N.contraction + 2 ^ p * c := exists_contractRec_eq_add O hO sched' pts pts_pairwise 0 n m N side p +/-! ### The scheduled recursion -/ + +open Cslib.Circuits.Frontier.ContractionSchedule in +/-- With the parameter schedule, a call with at most `leafSize s` edges is a leaf. -/ +example (s n m p : ℕ) (hm : m ≤ leafSize s) (N : TensorNetwork O Bool (Fin n) (Fin m)) + (side : Fin n → Bool) : + contractRec O hO (callParams s) pts 0 n m N side p = N.contraction := + contractRec_of_sched_eq_none (callParams_of_le hm) p + +open Cslib.Circuits.Frontier.ContractionSchedule in +/-- With the parameter schedule, the recursion is correct. -/ +example (s n m p : ℕ) (N : TensorNetwork O Bool (Fin n) (Fin m)) (side : Fin n → Bool) : + ∃ c : O, contractRec O hO (callParams s) pts 0 n m N side p = N.contraction + 2 ^ p * c := + exists_contractRec_eq_add O hO (callParams s) pts pts_pairwise 0 n m N side p + +open Cslib.Circuits.Frontier.ContractionSchedule in +/-- For large `s`, the parameters of every active call are admissible with `2 ^ (4 ⌊log₂ s⌋)` +points, so its guard reduces to the network running from rows to columns within the degree +bound. -/ +example (C : ℕ) : ∃ s₀, ∀ s, s₀ ≤ s → ∀ d M p, Active C s d M p → + (TensorNetwork.CallParams.mk (slotBits s d) (taper s d) (taperStep s d M) + (degreeBound s)).Admissible M (2 ^ (4 * Nat.log 2 s)) := by + obtain ⟨s₀, hs₀⟩ := exists_activeBounds C + exact ⟨s₀, fun s hs d M p hA => ((hs₀ s hs).2 d M p hA).admissible le_rfl⟩ + end ComplexityTests.Computability.Circuit.Frontier.TensorNetwork.Recursion From c1407c7afcd472e9346d40afc65f261bf8976085 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:37:40 -0400 Subject: [PATCH 30/38] feat(PolyTime): Newton inversion inverts every unit of a Galois ring GaloisRingArith.inv's correctness assumed that the seed x^(2^e-2) inverts x modulo 2. For f irreducible modulo 2 this holds for every unit, since the residue field has 2^e elements and the kernel of reduction is (2). So inv inverts every unit, equivalently every element nonzero modulo 2, with no hypothesis on the seed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 1 + .../PolyTime/GaloisRingUnits.lean | 127 ++++++++++++++++++ ComplexityTests/GaloisRingUnits.lean | 51 +++++++ 3 files changed, 179 insertions(+) create mode 100644 Complexity/Computability/PolyTime/GaloisRingUnits.lean create mode 100644 ComplexityTests/GaloisRingUnits.lean diff --git a/Complexity.lean b/Complexity.lean index de719dda8..6175ed3d3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1997,6 +1997,7 @@ 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.GaloisRingUnits 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/GaloisRingUnits.lean b/Complexity/Computability/PolyTime/GaloisRingUnits.lean new file mode 100644 index 000000000..fb25f4ca2 --- /dev/null +++ b/Complexity/Computability/PolyTime/GaloisRingUnits.lean @@ -0,0 +1,127 @@ +/- +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.PolyTime.GaloisRingArithmetic +public import Complexity.Foundations.RingTheory.GaloisRing +import Mathlib.FieldTheory.Finite.Basic + +/-! +# Newton inversion inverts every unit of a Galois ring + +`Complexity/Computability/PolyTime/GaloisRingArithmetic.lean` computes in +`A_p = (ℤ / 2 ^ p)[U] / (f)` on coefficient lists, and inverts by `p` Newton steps from the seed +`x ^ (2 ^ e - 2)`, `e = deg f` (`GaloisRingArith.inv`). Its correctness +(`GaloisRingArith.val_mul_inv`) assumes that the seed inverts `x` modulo `2`. This file discharges +that assumption when `f` is irreducible modulo `2`, so that `A_p` is the Galois ring +`GaloisRing 2 p f`. + +Reduction modulo `2` maps `A_p` onto the field `𝔽_2[U] / (f)` with `2 ^ e` elements, where every +nonzero `r` has `r ^ (2 ^ e - 1) = 1`. A unit `y` reduces to a nonzero element, so +`y y ^ (2 ^ e - 2) - 1` lies in the kernel `(2)` of reduction (`GaloisRing.ker_residue`). The seed +is computed natively (`GaloisRingArith.seed`), so no inversion modulo `2` is needed separately. + +So `GaloisRingArith.inv` inverts every unit, that is every element that is nonzero modulo `2` +(`GaloisRing.isUnit_iff`), in polynomial time (`GaloisRingArith.computableInPolyTime_inv`). This +is the inversion of units by inversion modulo two and Newton lifting of [OpenAI2026ETH] +(Appendix D.4.1, p. 58), with `ℓ = 1`. + +## Main results + +* `Cslib.GaloisRingArith.natDegree_monicOf`: `monicOf fl` has degree `fl.length`. +* `Cslib.GaloisRingArith.exists_mul_pow_eq_one_add_two_mul`: the seed inverts a unit modulo `2`. +* `Cslib.GaloisRingArith.val_mul_inv_of_isUnit`, `Cslib.GaloisRingArith.val_inv_of_isUnit`: + Newton inversion inverts every unit. +* `Cslib.GaloisRingArith.val_mul_inv_of_residue_ne_zero`: it inverts every element that is nonzero + modulo `2`. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.1 (pp. 57–58). +-/ + +public section + +namespace Cslib.GaloisRingArith + +open Polynomial Finset + +/-- `monicOf fl` has degree `fl.length`. -/ +theorem natDegree_monicOf (fl : List ℕ) : (monicOf fl).natDegree = fl.length := by + unfold monicOf + rw [natDegree_add_eq_left_of_degree_lt, natDegree_X_pow] + rw [degree_X_pow] + 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)) + +variable {p : ℕ} {fl : List ℕ} + +/-- `A_p` is the Galois ring `GaloisRing 2 p (monicOf fl)`. -/ +theorem ring_eq_galoisRing : Ring p fl = GaloisRing 2 p (monicOf fl) := rfl + +/-- **The seed inverts a unit modulo `2`.** In a Galois ring of rank `e`, the residue field has +`2 ^ e` elements, so a unit `y` satisfies `y y ^ (2 ^ e - 2) = 1 + 2 w`. -/ +theorem exists_mul_pow_eq_one_add_two_mul [NeZero p] + (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) {y : Ring p fl} + (hy : IsUnit y) : ∃ w : Ring p fl, y * y ^ (2 ^ fl.length - 2) = 1 + 2 * w := by + set F := AdjoinRoot ((monicOf fl).map (Int.castRingHom (ZMod 2))) + have : Fact (Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) := ⟨hirr⟩ + have hcard : Nat.card F = 2 ^ fl.length := by + rw [GaloisRing.natCard_adjoinRoot (monic_monicOf fl), natDegree_monicOf] + have : Finite F := Nat.finite_of_card_ne_zero (by rw [hcard]; positivity) + let : Fintype F := Fintype.ofFinite F + have he : 1 ≤ fl.length := by + have := natDegree_pos_iff_degree_pos.mpr (degree_pos_of_irreducible hirr) + rwa [(monic_monicOf fl).natDegree_map, natDegree_monicOf] at this + have h2 : 2 ≤ 2 ^ fl.length := by + calc 2 = 2 ^ 1 := rfl + _ ≤ 2 ^ fl.length := Nat.pow_le_pow_right (by norm_num) he + -- The residue of `y` is nonzero, so its `(2 ^ e - 1)`-th power is `1`. + have hr : GaloisRing.residue 2 p (monicOf fl) y ≠ 0 := + (GaloisRing.isUnit_iff (monic_monicOf fl) hirr).mp hy + have hpow : GaloisRing.residue 2 p (monicOf fl) y ^ (2 ^ fl.length - 1) = 1 := by + have := FiniteField.pow_card_sub_one_eq_one _ hr + rwa [Fintype.card_eq_nat_card, hcard] at this + have hker : y * y ^ (2 ^ fl.length - 2) - 1 ∈ + RingHom.ker (GaloisRing.residue 2 p (monicOf fl)) := by + rw [RingHom.mem_ker, map_sub, map_mul, map_pow, map_one, ← pow_succ', + show 2 ^ fl.length - 2 + 1 = 2 ^ fl.length - 1 by omega, hpow, sub_self] + rw [GaloisRing.ker_residue (monic_monicOf fl), Ideal.mem_span_singleton] at hker + obtain ⟨w, hw⟩ := hker + exact ⟨w, by rw [← sub_eq_iff_eq_add', hw]; push_cast; rfl⟩ + +/-- **Newton inversion inverts every unit** of the Galois ring `A_p`, `f` irreducible modulo +`2`. -/ +theorem val_mul_inv_of_isUnit (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + {x : List ℕ} (hx : IsUnit (val p fl x)) : val p fl x * val p fl (inv p fl x) = 1 := by + rcases Nat.eq_zero_or_pos p with rfl | hp + · -- `A_0` is the zero ring. + have : Subsingleton (ZMod (2 ^ 0)) := by rw [pow_zero]; infer_instance + have := (AdjoinRoot.of ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ 0))))).codomain_trivial + exact Subsingleton.elim _ _ + · have : NeZero p := ⟨hp.ne'⟩ + obtain ⟨w, hw⟩ := exists_mul_pow_eq_one_add_two_mul hirr hx + exact val_mul_inv x hw + +/-- **Newton inversion computes the inverse of a unit.** -/ +theorem val_inv_of_isUnit (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + {x : List ℕ} (hx : IsUnit (val p fl x)) : + val p fl (inv p fl x) = _root_.Ring.inverse (val p fl x) := by + rw [← _root_.Ring.inverse_mul_cancel_left _ (val p fl (inv p fl x)) hx, + val_mul_inv_of_isUnit hirr hx, mul_one] + +/-- **Newton inversion inverts every element that is nonzero modulo `2`.** -/ +theorem val_mul_inv_of_residue_ne_zero [NeZero p] + (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) {x : List ℕ} + (hx : GaloisRing.residue 2 p (monicOf fl) (val p fl x) ≠ 0) : + val p fl x * val p fl (inv p fl x) = 1 := + val_mul_inv_of_isUnit hirr ((GaloisRing.isUnit_iff (monic_monicOf fl) hirr).mpr hx) + +end Cslib.GaloisRingArith diff --git a/ComplexityTests/GaloisRingUnits.lean b/ComplexityTests/GaloisRingUnits.lean new file mode 100644 index 000000000..7325e3bca --- /dev/null +++ b/ComplexityTests/GaloisRingUnits.lean @@ -0,0 +1,51 @@ +/- +Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Samuel Schlesinger +-/ + +import Complexity.Computability.PolyTime.GaloisRingUnits +import Mathlib.Algebra.Polynomial.SpecificDegree + +/-! # Newton inversion of units in a Galois ring + +For `f = U ^ 2 + U + 1`, irreducible modulo `2`, Newton inversion inverts every unit of +`(ℤ / 8)[U] / (f)` (`p = 3`, `fl = [1, 1]`) with no hypothesis on the seed. -/ + +namespace ComplexityTests.GaloisRingUnits + +open Cslib.GaloisRingArith Polynomial + +/-- `U ^ 2 + U + 1` is irreducible modulo `2`: it has degree two and no root in `𝔽_2`. -/ +theorem irreducible_monicOf_one_one : + Irreducible ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2))) := by + have hmap : (monicOf [1, 1]).map (Int.castRingHom (ZMod 2)) = X ^ 2 + X + 1 := by + simp [monicOf, Finset.sum_range_succ] + ring + have hmon : ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2))).Monic := + (monic_monicOf _).map _ + have hdeg : ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2))).natDegree = 2 := by + rw [(monic_monicOf _).natDegree_map, natDegree_monicOf]; rfl + rw [hmon.irreducible_iff_roots_eq_zero_of_degree_le_three (by omega) (by omega)] + refine Multiset.eq_zero_of_forall_notMem fun x hx => ?_ + rw [mem_roots hmon.ne_zero, IsRoot, hmap] at hx + simp only [eval_add, eval_pow, eval_X, eval_one] at hx + fin_cases x <;> exact absurd hx (by decide) + +/-- **Every unit is inverted**, with no hypothesis on the seed. -/ +example (x : List ℕ) (hx : IsUnit (val 3 [1, 1] x)) : + val 3 [1, 1] x * val 3 [1, 1] (inv 3 [1, 1] x) = 1 := + val_mul_inv_of_isUnit irreducible_monicOf_one_one hx + +/-- Every element that is nonzero modulo `2` is inverted. -/ +example (x : List ℕ) (hx : GaloisRing.residue 2 3 (monicOf [1, 1]) (val 3 [1, 1] x) ≠ 0) : + val 3 [1, 1] x * val 3 [1, 1] (inv 3 [1, 1] x) = 1 := + val_mul_inv_of_residue_ne_zero irreducible_monicOf_one_one hx + +/-- The seed inverts every unit modulo `2`: here `e = 2`, so the seed is `x ^ 2`. -/ +example (y : Ring 3 [1, 1]) (hy : IsUnit y) : ∃ w, y * y ^ (2 ^ 2 - 2) = 1 + 2 * w := + exists_mul_pow_eq_one_add_two_mul irreducible_monicOf_one_one hy + +example : (monicOf [1, 1]).natDegree = 2 := natDegree_monicOf _ + +end ComplexityTests.GaloisRingUnits From bcc7b10f810423dfdc9763b338be3ae9fba3ab93 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:48:24 -0400 Subject: [PATCH 31/38] wip: native recovery definitions and constants Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/NativeRecovery.lean | 263 ++++++++++++++++++ .../TensorNetwork/TaperedRecovery.lean | 10 +- .../PolyTime/GaloisRingSeries.lean | 236 ++++++++++++++++ 3 files changed, 504 insertions(+), 5 deletions(-) create mode 100644 Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean create mode 100644 Complexity/Computability/PolyTime/GaloisRingSeries.lean diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean new file mode 100644 index 000000000..11c89c702 --- /dev/null +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -0,0 +1,263 @@ +/- +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.ActiveCall +public import Complexity.Computability.PolyTime.GaloisRingSeries + +/-! +# Native tapered recovery (Lemma D.5) on coefficient lists + +This file gives the native procedure behind the recovery step of the contraction algorithm of +[OpenAI2026ETH] (§D.4.4, Lemma D.5, pp. 60–61), and proves that it computes the mathematical +recovery `Cslib.Circuits.Frontier.recoverFromValues` of +`Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean`. + +**Input.** A precision `p`, a shift `Δ`, the monic polynomial `fl` of +`Complexity/Computability/PolyTime/GaloisRingArithmetic.lean`, the parameters `a, u, h`, the +interpolation points `ζ` and, for each node index `k < 2 ^ (a + 1)`, `k ≠ 2 ^ a` (the node +`t = k - 2 ^ a`, `NativeRecovery.node`), the values at the points. All arithmetic is in +`A_N = (ℤ / 2 ^ N)[U] / (f)` with `N = p + Δ`. + +**Procedure** (`NativeRecovery.recover`). For each node `t` of multiplicity `J = q_t`: + +1. interpolate the values (`GaloisRingArith.interp`); +2. multiply by the reciprocal of `E_t(t + α z)` below `z ^ J` (`NativeRecovery.recip`). Each factor + of `E_t(t + α z)` is a power of `1 + c_q z` with `c_q = α / (t - q)`, a `2`-adic integer + (`NativeRecovery.slopeConst`), whose reciprocal is the geometric series of `-c_q`; +3. combine the `J` coefficients with the multipliers `2 ^ Δ E_t(0) (-t / α) ^ j` + (`NativeRecovery.coeffMult`). Since `E_t(0) = ∏_q (-q c_q / α) ^ (q_q)`, the multiplier is + `2 ^ (Δ - (a + 1)(M_t + j)) ∏_q (-q c_q) ^ (q_q) (-t) ^ j` with `M_t = ∑_q q_q`, a `2`-adic + integer once `Δ ≥ (a + 1)(M_t + j)`. + +The sum over the nodes is `2 ^ Δ` times the recovered value. Dividing its coordinates by +`2 ^ Δ` (`GaloisRingArith.shiftDown`) gives the recovered value modulo `2 ^ p`. + +**Precision.** The values at node `t` need only be known modulo `2 ^ p_t`: the multipliers have +`2`-adic valuation at least `Δ + p - p_t` when `p + 6 h D_t + q_t (a + 1) ≤ p_t` +((D.10) and the evaluation loss), so the error is killed in `A_N`. This is the precision estimate +of Lemma D.5 (`Cslib.Circuits.Frontier.exists_taperedRecovery_eq_add`), carried out natively. + +## Main definitions + +* `Cslib.Circuits.Frontier.NativeRecovery.slopeConst`: the constant `α / (t - q)`. +* `Cslib.Circuits.Frontier.NativeRecovery.recip`: the truncated reciprocal of `E_t(t + α z)`. +* `Cslib.Circuits.Frontier.NativeRecovery.coeffMult`: the multipliers. +* `Cslib.Circuits.Frontier.NativeRecovery.recover`: native tapered recovery. + +## Main results + +* `Cslib.Circuits.Frontier.NativeRecovery.val_slopeConst`: the constant is correct. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], §D.4.4, Lemma D.5, + (D.9)–(D.10), pp. 60–61. +-/ + +@[expose] public section + +namespace Cslib.Circuits.Frontier.NativeRecovery + +open Cslib.GaloisRingArith Polynomial Finset + +/-- The subring `ℤ_(2)` of rationals with odd denominator. -/ +local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) + +/-! ### Nodes -/ + +/-- The node `t = k - 2 ^ a` of index `k`. -/ +def node (a k : ℕ) : ℤ := (k : ℤ) - 2 ^ a + +/-- The multiplicity `q_t = h (u - v₂ t)` of the node of index `k`. -/ +def mult (a u h k : ℕ) : ℕ := h * (u - padicValNat 2 (k - 2 ^ a + (2 ^ a - k))) + +theorem mult_eq (a u h k : ℕ) : mult a u h k = taperMult u h (node a k) := by + have : ((k : ℤ) - 2 ^ a).natAbs = k - 2 ^ a + (2 ^ a - k) := by + rw [show (2 : ℤ) ^ a = ((2 ^ a : ℕ) : ℤ) by push_cast; rfl] + omega + rw [mult, taperMult, node, padicValInt, this] + +/-- The indices of the nodes other than the node of index `k`. -/ +def others (a k : ℕ) : List ℕ := + (List.range (2 ^ (a + 1))).filter fun k' => k' ≠ 2 ^ a ∧ k' ≠ k + +/-- The total multiplicity `M_t = ∑_{q ≠ t} q_q` of the other nodes. -/ +def multSum (a u h k : ℕ) : ℕ := ((others a k).map (mult a u h)).sum + +/-! ### Constants -/ + +/-- The integer `x - y` as a constant of `A_N`. -/ +def diffConst (N : ℕ) (fl : List ℕ) (x y : ℕ) : List ℕ := + if x < y then neg N fl [y - x] else [x - y] + +/-- The `2`-adic integer `2 ^ (a + 1) / (x - y)` as a constant of `A_N`, for +`0 < |x - y| ≤ 2 ^ (a + 1)`: with `|x - y| = 2 ^ v o`, `o` odd, it is +`± 2 ^ (a + 1 - v) o⁻¹`. -/ +def slopeConst (N : ℕ) (fl : List ℕ) (a x y : ℕ) : List ℕ := + let d := x - y + (y - x) + let v := padicValNat 2 d + let w := mul N fl [2 ^ (a + 1 - v)] (inv N fl [d / 2 ^ v]) + if x < y then neg N fl w else w + +/-! ### The reciprocal of `E_t(t + α z)` -/ + +/-- The geometric series `∑_{i < J} (-c) ^ i z ^ i`, the reciprocal of `1 + c z` below +`z ^ J`. -/ +def geom (N : ℕ) (fl : List ℕ) (J : ℕ) (c : List ℕ) : List (List ℕ) := + (List.range J).map fun i => pow N fl (neg N fl c) i + +/-- Multiply by the reciprocal of `(1 + c z) ^ m` below `z ^ J`. -/ +def mulGeomPow (N : ℕ) (fl : List ℕ) (J : ℕ) (c : List ℕ) (m : ℕ) (R : List (List ℕ)) : + List (List ℕ) := + (List.range m).foldl (fun R _ => tmul N fl J R (geom N fl J c)) R + +/-- **The reciprocal of `E_t(t + α z)` below `z ^ J`**, for the node of index `k`: the product of +the reciprocals of `(1 + c_q z) ^ (q_q)` over the other nodes `q`, `c_q = α / (t - q)`. -/ +def recip (N : ℕ) (fl : List ℕ) (a u h k J : ℕ) : List (List ℕ) := + (others a k).foldl (fun R k' => mulGeomPow N fl J (slopeConst N fl a k k') (mult a u h k') R) + [one fl] + +/-! ### The multipliers -/ + +/-- `∏_{q ≠ t} (-q c_q) ^ (q_q)` for the node of index `k`. -/ +def basisNum (N : ℕ) (fl : List ℕ) (a u h k : ℕ) : List ℕ := + (others a k).foldl (fun acc k' => mul N fl acc + (pow N fl (mul N fl (diffConst N fl (2 ^ a) k') (slopeConst N fl a k k')) (mult a u h k'))) + (one fl) + +/-- The multiplier `2 ^ Δ E_t(0) (-t / α) ^ j` of the coefficient of `z ^ j` at the node of +index `k`, as `2 ^ (Δ - (a + 1)(M_t + j)) ∏_{q ≠ t} (-q c_q) ^ (q_q) (-t) ^ j`. -/ +def coeffMult (N : ℕ) (fl : List ℕ) (a u h Δ k j : ℕ) : List ℕ := + mul N fl (mul N fl [2 ^ (Δ - (a + 1) * (multSum a u h k + j))] (basisNum N fl a u h k)) + (pow N fl (diffConst N fl (2 ^ a) k) j) + +/-! ### Recovery -/ + +/-- The contribution of the node of index `k`, from its values `w` at the points `ζ`: +interpolate, divide by `E_t(t + α z)` below `z ^ q_t`, and combine the coefficients with the +multipliers. -/ +def nodeTerm (N : ℕ) (fl : List ℕ) (a u h Δ : ℕ) (ζ : List (List ℕ)) (k : ℕ) + (w : List (List ℕ)) : List ℕ := + let J := mult a u h k + let Q := tmul N fl J (interp N fl ζ w) (recip N fl a u h k J) + (List.range J).foldl (fun S j => add N fl S (mul N fl (coeffMult N fl a u h Δ k j) (Q.getD j []))) + (zero fl) + +/-- **Native tapered recovery.** From the values `W.getD k []` at the points `ζ` for each node +index `k`, the value recovered modulo `2 ^ p`, computing in `A_(p + Δ)` and dividing by +`2 ^ Δ` at the end. -/ +def recover (p Δ : ℕ) (fl : List ℕ) (a u h : ℕ) (ζ : List (List ℕ)) (W : List (List (List ℕ))) : + List ℕ := + shiftDown Δ ((List.range (2 ^ (a + 1))).foldl (fun S k => + if k = 2 ^ a then S else add (p + Δ) fl S (nodeTerm (p + Δ) fl a u h Δ ζ k (W.getD k []))) + (zero fl)) + +/-! ### Correctness of the constants -/ + +variable {N : ℕ} {fl : List ℕ} + +theorem val_diffConst (x y : ℕ) : + val N fl (diffConst N fl x y) = (((x : ℤ) - y : ℤ) : Ring N fl) := by + unfold diffConst + split_ifs with h + · rw [val_neg, val_singleton, Nat.cast_sub h.le] + push_cast + ring + · rw [val_singleton, Nat.cast_sub (not_lt.mp h)] + push_cast + ring + +/-- The `2`-adic integer `2 ^ (a + 1) / (t - q)` for distinct grid points. -/ +noncomputable def slope (a : ℕ) {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) + (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : ℤ₍₂₎ := + ⟨2 ^ (a + 1) / ((t : ℚ) - q), padicValuation_two_pow_div_le_one ht hq hne⟩ + +theorem node_mem_cauchyGrid {a k : ℕ} (hk : k < 2 ^ (a + 1)) : node a k ∈ cauchyGrid (2 ^ a) := by + rw [cauchyGrid_two_pow, mem_Ico, node] + have : (k : ℤ) < 2 ^ (a + 1) := by exact_mod_cast hk + rw [pow_succ] at this + constructor <;> omega + +theorem node_injective (a : ℕ) : Function.Injective (node a) := fun k k' h => by + unfold node at h + omega + +/-- **The slope constant is correct**: for every ring homomorphism `ρ` from `ℤ_(2)`, it is the +image of `2 ^ (a + 1) / (t - q)`. -/ +theorem val_slopeConst (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (ρ : ℤ₍₂₎ →+* Ring N fl) {a k k' : ℕ} (hk : k < 2 ^ (a + 1)) (hk' : k' < 2 ^ (a + 1)) + (hne : k' ≠ k) : + val N fl (slopeConst N fl a k k') = + ρ (slope a (node_mem_cauchyGrid hk) (node_mem_cauchyGrid hk') + (fun h => hne (node_injective a h))) := by + set d := k - k' + (k' - k) + set v := padicValNat 2 d + set o := d / 2 ^ v + have hd0 : d ≠ 0 := by omega + have hdlt : d < 2 ^ (a + 1) := by omega + have hdv : d = 2 ^ v * o := (Nat.mul_div_cancel' pow_padicValNat_dvd).symm + have hodd : Odd o := by + rw [Nat.odd_iff, ← Nat.two_dvd_ne_zero] + intro h2 + have hnd : ¬ 2 ^ (v + 1) ∣ d := pow_succ_padicValNat_not_dvd hd0 + refine hnd ?_ + have h' : 2 ^ (v + 1) ∣ 2 ^ v * o := by + rw [pow_succ]; exact Nat.mul_dvd_mul_left _ h2 + rwa [← hdv] at h' + have hva : v ≤ a + 1 := by + have : 2 ^ v ≤ d := Nat.le_of_dvd (Nat.pos_of_ne_zero hd0) pow_padicValNat_dvd + have : v < a + 1 := (Nat.pow_lt_pow_iff_right (by norm_num)).mp (this.trans_lt hdlt) + omega + have hunit := isUnit_natCast_of_odd (p := N) (fl := fl) hodd + -- In `ℤ_(2)`, `slope * o = ± 2 ^ (a + 1 - v)`. + set c := slope a (node_mem_cauchyGrid hk) (node_mem_cauchyGrid hk') + (fun h => hne (node_injective a h)) + have hc : c * (o : ℤ₍₂₎) = if k < k' then -((2 ^ (a + 1 - v) : ℕ) : ℤ₍₂₎) + else ((2 ^ (a + 1 - v) : ℕ) : ℤ₍₂₎) := by + apply Subtype.val_injective + have hdiff : ((node a k : ℤ) : ℚ) - (node a k' : ℤ) = + if k < k' then -((2 : ℚ) ^ v * o) else (2 : ℚ) ^ v * o := by + have : ((d : ℕ) : ℚ) = (2 : ℚ) ^ v * o := by exact_mod_cast hdv + rw [← this] + simp only [node, d] + split_ifs with h + · rw [Nat.cast_add, Nat.cast_sub h.le, Nat.sub_eq_zero_of_le h.le]; push_cast; ring + · rw [Nat.sub_eq_zero_of_le (not_lt.mp h), Nat.cast_add, Nat.cast_sub (not_lt.mp h)] + push_cast; ring + have ho : (o : ℚ) ≠ 0 := by exact_mod_cast hodd.pos.ne' + have h2v : (2 : ℚ) ^ (a + 1) = 2 ^ (a + 1 - v) * 2 ^ v := by + rw [← pow_add, Nat.sub_add_cancel hva] + simp only [slope, c, Subring.coe_mul, Subring.coe_natCast, apply_ite Subtype.val, + Subring.coe_neg] + rw [hdiff] + split_ifs + · push_cast + field_simp + rw [h2v] + ring + · push_cast + field_simp + rw [h2v] + ring + have hρ := congrArg ρ hc + rw [map_mul, map_natCast] at hρ + have hρc : ρ c = ρ (if k < k' then -((2 ^ (a + 1 - v) : ℕ) : ℤ₍₂₎) + else ((2 ^ (a + 1 - v) : ℕ) : ℤ₍₂₎)) * + _root_.Ring.inverse (o : Ring N fl) := by + rw [← hρ, mul_assoc, _root_.Ring.mul_inverse_cancel _ hunit, mul_one] + rw [hρc] + have hw : val N fl (mul N fl [2 ^ (a + 1 - v)] (inv N fl [o])) = + ((2 ^ (a + 1 - v) : ℕ) : Ring N fl) * _root_.Ring.inverse (o : Ring N fl) := by + rw [val_mul, val_singleton, val_inv_of_isUnit hirr (by rwa [val_singleton]), val_singleton] + unfold slopeConst + split_ifs with h + · rw [val_neg, hw, map_neg, map_natCast, neg_mul] + · rw [hw, map_natCast] + +end Cslib.Circuits.Frontier.NativeRecovery diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean index 4d914e464..5fc26599d 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean @@ -147,7 +147,7 @@ private theorem coe_twoPowUnit_inv (n : ℕ) : simp [twoPowUnit, Units.val_inv_eq_inv_val] /-- Distinct integers are units of a `ℚ`-algebra apart. -/ -private theorem pairwise_isUnit_sub (s : Finset ℤ) : +theorem pairwise_isUnit_sub (s : Finset ℤ) : (s : Set ℤ).Pairwise fun i j => IsUnit ((i : K) - (j : K)) := by intro i _ j _ hij have h : ((i : ℚ) - j) ≠ 0 := sub_ne_zero.mpr (by exact_mod_cast hij) @@ -170,7 +170,7 @@ theorem eval_zero_eq_taperedRecovery {a u h : ℕ} {F : K[X]} /-! ### Integrality of the Hermite basis in local coordinates -/ /-- The rationals `α / (t - q)` with `t ≠ q` on the grid, `α = 2 ^ (a + 1)`, lie in `ℤ_(2)`. -/ -private theorem padicValuation_two_pow_div_le_one {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) +theorem padicValuation_two_pow_div_le_one {a : ℕ} {t q : ℤ} (ht : t ∈ cauchyGrid (2 ^ a)) (hq : q ∈ cauchyGrid (2 ^ a)) (hne : q ≠ t) : Rat.padicValuation 2 ((2 : ℚ) ^ (a + 1) / ((t : ℚ) - q)) ≤ 1 := by have hle := padicValuation_two_pow_le_sub ht hq hne @@ -184,7 +184,7 @@ private theorem padicValuation_two_pow_div_le_one {a : ℕ} {t q : ℤ} (ht : t /-- The Hermite basis over `ℚ` in local coordinates is a product of powers of `1 + α / (t - q) z`. -/ -private theorem hermiteBasis_comp_rat (a u h : ℕ) (t : ℤ) : +theorem hermiteBasis_comp_rat (a u h : ℕ) (t : ℤ) : (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).comp (C (t : ℚ) + C (2 ^ (a + 1)) * X) = ∏ q ∈ (recoveryNodes a).erase t, @@ -342,7 +342,7 @@ private theorem inTwoPow_coeff_hermiteQuotient {B : Subring K} · exact inTwoPow_zero B n /-- The value of `E_t(0)` in `K` is the image of the rational one. -/ -private theorem eval_zero_hermiteBasis_eq (a u h : ℕ) {t : ℤ} (ht : t ∈ recoveryNodes a) : +theorem eval_zero_hermiteBasis_eq (a u h : ℕ) {t : ℤ} (ht : t ∈ recoveryNodes a) : (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) t).eval 0 = algebraMap ℚ K ((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).eval 0) := by @@ -352,7 +352,7 @@ private theorem eval_zero_hermiteBasis_eq (a u h : ℕ) {t : ℤ} (ht : t ∈ re ← map_zero (algebraMap ℚ K), eval₂_at_apply] /-- `E_t(0) ≠ 0`: its factors are `-q / (t - q)` with `q ≠ 0`. -/ -private theorem eval_zero_hermiteBasis_ne_zero (a u h : ℕ) {t : ℤ} : +theorem eval_zero_hermiteBasis_ne_zero (a u h : ℕ) {t : ℤ} : (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) t).eval 0 ≠ 0 := by rw [eval_hermiteBasis, prod_ne_zero_iff] intro q hq diff --git a/Complexity/Computability/PolyTime/GaloisRingSeries.lean b/Complexity/Computability/PolyTime/GaloisRingSeries.lean new file mode 100644 index 000000000..c42ccbdf9 --- /dev/null +++ b/Complexity/Computability/PolyTime/GaloisRingSeries.lean @@ -0,0 +1,236 @@ +/- +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.PolyTime.GaloisRingInterpolation +public import Complexity.Computability.PolyTime.GaloisRingUnits +import Mathlib.Data.List.GetD + +/-! +# Coordinates, precision changes and truncated products over `(ℤ / 2 ^ p)[U] / (f)` + +Supporting operations on the coefficient lists of +`Complexity/Computability/PolyTime/GaloisRingArithmetic.lean`, for the native recovery step of +the contraction algorithm of [OpenAI2026ETH] (Appendix D.4.4, pp. 60–61). + +* **Coordinates.** The coordinates of an element of `A_p` in the basis `1, U, …, U ^ (e - 1)` are + determined modulo `2 ^ p` (`GaloisRingArith.mod_eq_of_val_eq`), and every element is the value + of a reduced coefficient list (`GaloisRingArith.exists_val_eq`). +* **Precision.** Reduction `A_N → A_n` for `n ≤ N` (`GaloisRingArith.reduce`) keeps the + coordinates (`GaloisRingArith.reduce_val`). Dividing the coordinates of `2 ^ Δ y` by `2 ^ Δ` + gives `y` modulo `2 ^ (N - Δ)` (`GaloisRingArith.val_shiftDown`): this is how a sum that is + known to be divisible by `2 ^ Δ` loses its common power of two. +* **Truncated products** (`GaloisRingArith.tmul`) of polynomials in `z` over `A_p`: the first + `J` coefficients of a product, by the Cauchy formula (`GaloisRingArith.val_tmul`). + +## Main definitions + +* `Cslib.GaloisRingArith.reduce`: reduction to a lower precision. +* `Cslib.GaloisRingArith.shiftDown`: division of the coordinates by `2 ^ Δ`. +* `Cslib.GaloisRingArith.tmul`: truncated products. + +## Main results + +* `Cslib.GaloisRingArith.mod_eq_of_val_eq`, `Cslib.GaloisRingArith.exists_val_eq`: coordinates. +* `Cslib.GaloisRingArith.val_shiftDown`: cancelling a common power of two. +* `Cslib.GaloisRingArith.val_tmul`: truncated products are correct. + +## References + +* [OpenAI, *The Exponential Time Hypothesis is False*][OpenAI2026ETH], Appendix D.4.1 and D.4.4 + (pp. 57–61). +-/ + +@[expose] public section + +namespace Cslib.GaloisRingArith + +open Polynomial Finset + +variable {p : ℕ} {fl : List ℕ} + +/-! ### Coordinates -/ + +/-- `A_0` is the zero ring. -/ +theorem subsingleton_ring_zero : Subsingleton (Ring 0 fl) := by + have : Subsingleton (ZMod (2 ^ 0)) := by rw [pow_zero]; infer_instance + exact (AdjoinRoot.of ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ 0))))).codomain_trivial + +private theorem nontrivial_zmod (hp : 0 < p) : Nontrivial (ZMod (2 ^ p)) := + have : Fact (1 < 2 ^ p) := ⟨Nat.one_lt_two_pow hp.ne'⟩ + ZMod.nontrivial _ + +/-- A coefficient list is the class of the polynomial with those coefficients. -/ +theorem val_eq_mk (c : List ℕ) : val p fl c = AdjoinRoot.mk _ + (∑ i ∈ range fl.length, C ((c.getD i 0 : ℕ) : ZMod (2 ^ p)) * X ^ i) := by + simp only [val, map_sum, map_mul, map_pow, AdjoinRoot.mk_X, map_natCast] + +/-- The single coordinate list `[c]` has value `c`. -/ +@[simp] theorem val_singleton (c : ℕ) : val p fl [c] = (c : Ring p fl) := 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] + exact (subsingleton_of_zero_eq_one h.symm).elim _ _ + · rw [val, ← sum_range_add_sum_Ico _ he, sum_range_one, sum_eq_zero fun i hi => by + rw [List.getD_eq_default _ _ (by simp at hi ⊢; omega)]; simp] + simp + +/-- The coordinates of the value of a coefficient list, read off by division by the monic +polynomial. -/ +theorem coeff_modByMonicHom_val (c : List ℕ) (i : ℕ) : + (AdjoinRoot.modByMonicHom ((monic_monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))) + (val p fl c)).coeff i = if i < fl.length then ((c.getD i 0 : ℕ) : ZMod (2 ^ p)) else 0 := by + rcases Nat.eq_zero_or_pos p with rfl | hp + · have : Subsingleton (ZMod (2 ^ 0)) := by rw [pow_zero]; infer_instance + exact Subsingleton.elim _ _ + have := nontrivial_zmod hp + rw [val_eq_mk, AdjoinRoot.modByMonicHom_mk, (modByMonic_eq_self_iff ((monic_monicOf fl).map _)).mpr] + · simp only [finsetSum_coeff, coeff_C_mul, coeff_X_pow, mul_ite, mul_one, mul_zero, + sum_ite_eq, mem_range] + · rw [(monic_monicOf fl).degree_map, degree_eq_natDegree (monic_monicOf fl).ne_zero, + natDegree_monicOf] + refine (degree_sum_le _ _).trans_lt ((Finset.sup_lt_iff (WithBot.bot_lt_coe _)).mpr + fun i hi => ?_) + exact (degree_C_mul_X_pow_le _ _).trans_lt (by exact_mod_cast mem_range.mp hi) + +/-- **Coordinates are determined modulo `2 ^ p`.** -/ +theorem mod_eq_of_val_eq {c d : List ℕ} (h : val p fl c = val p fl d) {i : ℕ} + (hi : i < fl.length) : c.getD i 0 % 2 ^ p = d.getD i 0 % 2 ^ p := by + have := congrArg (fun y => (AdjoinRoot.modByMonicHom + ((monic_monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))) y).coeff i) h + simp only [coeff_modByMonicHom_val, hi, ↓reduceIte] at this + exact (ZMod.natCast_eq_natCast_iff' _ _ _).mp this + +/-- **Every element is the value of a reduced coefficient list.** -/ +theorem exists_val_eq (y : Ring p fl) : + ∃ r : List ℕ, r.length = fl.length ∧ (∀ x ∈ r, x < 2 ^ p) ∧ val p fl r = y := by + rcases Nat.eq_zero_or_pos p with rfl | hp + · have := subsingleton_ring_zero (fl := fl) + exact ⟨zero fl, by simp, by simp [zero], Subsingleton.elim _ _⟩ + have := nontrivial_zmod hp + have hg : ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))).Monic := (monic_monicOf fl).map _ + have he : ((monicOf fl).map (Int.castRingHom (ZMod (2 ^ p)))).natDegree = fl.length := by + rw [(monic_monicOf fl).natDegree_map, natDegree_monicOf] + set P := AdjoinRoot.modByMonicHom hg y + refine ⟨(List.range fl.length).map fun i => (P.coeff i).val, by simp, ?_, ?_⟩ + · intro x hx + obtain ⟨i, -, rfl⟩ := List.mem_map.mp hx + exact ZMod.val_lt _ + · have hsum := (AdjoinRoot.powerBasis' hg).basis.sum_repr y + have hrepr : ∀ i, (AdjoinRoot.powerBasis' hg).basis.repr y i = P.coeff i := fun _ => rfl + rw [PowerBasis.coe_basis] at hsum + simp only [AdjoinRoot.powerBasis'_gen, hrepr] at hsum + rw [Fin.sum_univ_eq_sum_range (fun i => P.coeff i • AdjoinRoot.root _ ^ i), + AdjoinRoot.powerBasis'_dim, he] at hsum + conv_rhs => rw [← hsum] + rw [val] + refine sum_congr rfl fun i hi => ?_ + rw [List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, List.getElem_range, + Algebra.smul_def, ← map_natCast (algebraMap (ZMod (2 ^ p)) _), ZMod.natCast_zmod_val] + +/-- Mapping a function that fixes `0` commutes with reading a coordinate. -/ +theorem getD_map_of_map_zero {f : ℕ → ℕ} (hf : f 0 = 0) (c : List ℕ) (i : ℕ) : + (c.map f).getD i 0 = f (c.getD i 0) := by + rw [List.getD_eq_getElem?_getD, List.getD_eq_getElem?_getD, List.getElem?_map] + cases c[i]? <;> simp [hf] + +/-- Scaling every coordinate by `k` multiplies the value by `k`. -/ +theorem val_map_mul (k : ℕ) (c : List ℕ) : val p fl (c.map (k * ·)) = k * val p fl c := by + rw [val, val, mul_sum] + refine sum_congr rfl fun i _ => ?_ + rw [getD_map_of_map_zero (mul_zero k)] + push_cast + ring + +/-! ### Changing the precision -/ + +/-- Reduction `A_N → A_n` from a higher precision to a lower one. -/ +noncomputable def reduce {n N : ℕ} (h : n ≤ N) (fl : List ℕ) : Ring N fl →+* Ring n fl := + AdjoinRoot.map (ZMod.castHom (pow_dvd_pow 2 h) (ZMod (2 ^ n))) _ _ + (GaloisRing.map_castHom_map (monicOf fl) (pow_dvd_pow 2 h)).symm.dvd + +/-- **Reduction keeps the coordinates.** -/ +theorem reduce_val {n N : ℕ} (h : n ≤ N) (c : List ℕ) : + reduce h fl (val N fl c) = val n fl c := by + simp only [reduce, val, map_sum, map_mul, map_pow, map_natCast, AdjoinRoot.map_root] + +/-- Division of each coordinate by `2 ^ Δ`. -/ +def shiftDown (Δ : ℕ) (c : List ℕ) : List ℕ := c.map (· / 2 ^ Δ) + +/-- **Cancelling a common power of two.** If a coefficient list has value `2 ^ Δ y` at precision +`n + Δ`, dividing its coordinates by `2 ^ Δ` gives the reduction of `y` to precision `n`. -/ +theorem val_shiftDown {n Δ : ℕ} {s : List ℕ} {y : Ring (n + Δ) fl} + (h : val (n + Δ) fl s = 2 ^ Δ * y) : + val n fl (shiftDown Δ s) = reduce (Nat.le_add_right n Δ) fl y := by + obtain ⟨r, -, -, rfl⟩ := exists_val_eq y + rw [reduce_val, val, val] + refine sum_congr rfl fun i hi => ?_ + have hi' := mem_range.mp hi + have hmod := mod_eq_of_val_eq (h.trans (by + rw [show (2 : Ring (n + Δ) fl) ^ Δ = ((2 ^ Δ : ℕ) : Ring (n + Δ) fl) by push_cast; rfl, + ← val_map_mul])) hi' + rw [getD_map_of_map_zero (mul_zero _), pow_add, mul_comm (2 ^ n), + Nat.mul_mod_mul_left] at hmod + rw [shiftDown, getD_map_of_map_zero (f := (· / 2 ^ Δ)) (Nat.zero_div _)] + congr 1 + set x := s.getD i 0 + -- `x = 2 ^ Δ (r_i mod 2 ^ n) + 2 ^ Δ 2 ^ n k`. + have hx : x = 2 ^ Δ * (r.getD i 0 % 2 ^ n + 2 ^ n * (x / (2 ^ Δ * 2 ^ n))) := by + conv_lhs => rw [← Nat.mod_add_div x (2 ^ Δ * 2 ^ n), hmod] + ring + rw [hx, Nat.mul_div_cancel_left _ (Nat.two_pow_pos Δ), Nat.cast_add, natCast_mod_two_pow, + Nat.cast_mul, natCast_two_pow, zero_mul, add_zero] + +/-! ### Truncated products -/ + +/-- The coefficient of `z ^ j` in the product of two polynomials over `A_p`, by the Cauchy +formula `∑ i ≤ j, P_i R_(j - i)`. -/ +def cauchyCoeff (p : ℕ) (fl : List ℕ) (P R : List (List ℕ)) (j : ℕ) : List ℕ := + (List.range (j + 1)).foldl (fun acc i => add p fl acc (mul p fl (P.getD i []) (R.getD (j - i) []))) + (zero fl) + +/-- The **truncated product**: the first `J` coefficients of the product of two polynomials over +`A_p`. -/ +def tmul (p : ℕ) (fl : List ℕ) (J : ℕ) (P R : List (List ℕ)) : List (List ℕ) := + (List.range J).map (cauchyCoeff p fl P R) + +theorem val_cauchyCoeff (P R : List (List ℕ)) (j : ℕ) : + val p fl (cauchyCoeff p fl P R j) = + ∑ i ∈ range (j + 1), val p fl (P.getD i []) * val p fl (R.getD (j - i) []) := by + have h (m : ℕ) : val p fl ((List.range m).foldl + (fun acc i => add p fl acc (mul p fl (P.getD i []) (R.getD (j - i) []))) (zero fl)) = + ∑ i ∈ range m, val p fl (P.getD i []) * val p fl (R.getD (j - i) []) := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, val_add, val_mul, + ih, sum_range_succ] + exact h _ + +@[simp] theorem length_tmul (J : ℕ) (P R : List (List ℕ)) : (tmul p fl J P R).length = J := by + simp [tmul] + +/-- **Truncated products are correct**: below `z ^ J` the coefficients are those of the product. -/ +theorem val_tmul {J : ℕ} (P R : List (List ℕ)) {j : ℕ} (hj : j < J) : + val p fl ((tmul p fl J P R).getD j []) = + ∑ i ∈ range (j + 1), val p fl (P.getD i []) * val p fl (R.getD (j - i) []) := by + rw [tmul, List.getD_eq_getElem _ _ (by simpa using hj), List.getElem_map, List.getElem_range, + val_cauchyCoeff] + +/-! ### Odd scalars are units -/ + +/-- An odd natural number is a unit of `A_p`. -/ +theorem isUnit_natCast_of_odd {o : ℕ} (ho : Odd o) : IsUnit (o : Ring p fl) := by + have hcop : Nat.Coprime o (2 ^ p) := (Nat.coprime_two_right.mpr ho).pow_right p + have := (ZMod.unitOfCoprime o hcop).isUnit.map (algebraMap (ZMod (2 ^ p)) (Ring p fl)) + simpa using this + +end Cslib.GaloisRingArith From 7999990a6fc5a2970a7b95b92122923bd0d355ae Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:52:05 -0400 Subject: [PATCH 32/38] wip: reciprocal and multiplier correctness Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/NativeRecovery.lean | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index 11c89c702..17729dea7 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -9,6 +9,7 @@ module public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.PolyTime.GaloisRingSeries +import Mathlib.Data.List.GetD /-! # Native tapered recovery (Lemma D.5) on coefficient lists @@ -260,4 +261,234 @@ theorem val_slopeConst (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (Z · rw [val_neg, hw, map_neg, map_natCast, neg_mul] · rw [hw, map_natCast] +/-- The slope `2 ^ (a + 1) / (t - q)` for node indices `k, k'`, and `0` off the grid. -/ +noncomputable def slope' (a k k' : ℕ) : ℤ₍₂₎ := + if h : k < 2 ^ (a + 1) ∧ k' < 2 ^ (a + 1) ∧ k' ≠ k then + slope a (node_mem_cauchyGrid h.1) (node_mem_cauchyGrid h.2.1) + (fun e => h.2.2 (node_injective a e)) + else 0 + +theorem val_slopeConst' (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (ρ : ℤ₍₂₎ →+* Ring N fl) {a k k' : ℕ} (hk : k < 2 ^ (a + 1)) (hk' : k' < 2 ^ (a + 1)) + (hne : k' ≠ k) : val N fl (slopeConst N fl a k k') = ρ (slope' a k k') := by + rw [val_slopeConst hirr ρ hk hk' hne, slope', dite_eq_left_of_eq_true (eq_true ⟨hk, hk', hne⟩)] + +/-! ### Correctness of the reciprocal -/ + +/-- The geometric series `∑ (-c) ^ i z ^ i`, the inverse of `1 + c z`. -/ +noncomputable def geomSeries {R : Type*} [CommRing R] (c : R) : PowerSeries R := + PowerSeries.mk fun i => (-c) ^ i + +theorem one_add_mul_geomSeries {R : Type*} [CommRing R] (c : R) : + (1 + PowerSeries.C c * PowerSeries.X) * geomSeries c = 1 := by + ext n + rw [add_mul, one_mul, map_add, PowerSeries.coeff_one, mul_assoc] + rcases n with _ | n + · simp [geomSeries] + · rw [PowerSeries.coeff_C_mul, PowerSeries.coeff_succ_X_mul] + simp only [geomSeries, PowerSeries.coeff_mk, pow_succ] + simp only [Nat.succ_ne_zero, ↓reduceIte, mul_neg] + ring + +/-- The reciprocal of `E_t(t + α z)` over `ℤ_(2)` for the node of index `k`, in the order of +`NativeRecovery.recip`. -/ +noncomputable def recipSeries (a u h k : ℕ) : PowerSeries ℤ₍₂₎ := + (others a k).foldl (fun ψ k' => ψ * geomSeries (slope' a k k') ^ mult a u h k') 1 + +theorem mem_others {a k k' : ℕ} : k' ∈ others a k ↔ k' < 2 ^ (a + 1) ∧ k' ≠ 2 ^ a ∧ k' ≠ k := by + simp [others] + +/-- A truncated product computes the coefficients of the product of power series. -/ +theorem val_tmul_eq {ρ : ℤ₍₂₎ →+* Ring N fl} {J : ℕ} {P G : List (List ℕ)} + {φ χ : PowerSeries ℤ₍₂₎} (hP : ∀ i < J, val N fl (P.getD i []) = ρ (PowerSeries.coeff i φ)) + (hG : ∀ i < J, val N fl (G.getD i []) = ρ (PowerSeries.coeff i χ)) {i : ℕ} (hi : i < J) : + val N fl ((tmul N fl J P G).getD i []) = ρ (PowerSeries.coeff i (φ * χ)) := by + rw [val_tmul _ _ hi, PowerSeries.coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ + (fun l m => PowerSeries.coeff l φ * PowerSeries.coeff m χ), map_sum] + refine sum_congr rfl fun l hl => ?_ + have hl' := mem_range.mp hl + rw [hP l (by omega), hG (i - l) (by omega), map_mul] + +theorem val_geom {ρ : ℤ₍₂₎ →+* Ring N fl} {J : ℕ} {c : List ℕ} {γ : ℤ₍₂₎} + (hc : val N fl c = ρ γ) {i : ℕ} (hi : i < J) : + val N fl ((geom N fl J c).getD i []) = ρ (PowerSeries.coeff i (geomSeries γ)) := by + rw [geom, List.getD_eq_getElem _ _ (by simpa using hi), List.getElem_map, List.getElem_range, + val_pow, val_neg, hc, geomSeries, PowerSeries.coeff_mk, map_pow, map_neg] + +/-- **The native reciprocal is correct**: its coefficients below `z ^ J` are the images of those +of `NativeRecovery.recipSeries`. -/ +theorem val_recip (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (ρ : ℤ₍₂₎ →+* Ring N fl) {a u h k J : ℕ} (hk : k < 2 ^ (a + 1)) {i : ℕ} (hi : i < J) : + val N fl ((recip N fl a u h k J).getD i []) = ρ (PowerSeries.coeff i (recipSeries a u h k)) := by + -- The invariant of the fold over the other nodes. + suffices H : ∀ (l : List ℕ), (∀ k' ∈ l, k' < 2 ^ (a + 1) ∧ k' ≠ k) → + ∀ (R : List (List ℕ)) (ψ : PowerSeries ℤ₍₂₎), + (∀ i < J, val N fl (R.getD i []) = ρ (PowerSeries.coeff i ψ)) → + ∀ i < J, val N fl ((l.foldl (fun R k' => + mulGeomPow N fl J (slopeConst N fl a k k') (mult a u h k') R) R).getD i []) = + ρ (PowerSeries.coeff i (l.foldl (fun ψ k' => ψ * geomSeries (slope' a k k') ^ + mult a u h k') ψ)) by + refine H _ (fun k' hk' => ⟨(mem_others.mp hk').1, (mem_others.mp hk').2.2⟩) _ _ ?_ i hi + intro i _ + rcases i with _ | i + · simp [PowerSeries.coeff_one] + · rw [List.getD_cons_succ, List.getD_nil, val_nil, PowerSeries.coeff_one, if_neg (by omega), map_zero] + intro l + induction l with + | nil => intro _ R ψ hR; simpa using hR + | cons k' l ih => + intro hl R ψ hR + rw [List.foldl_cons, List.foldl_cons] + refine ih (fun k'' hk'' => hl k'' (List.mem_cons_of_mem _ hk'')) _ _ ?_ + obtain ⟨hk', hne⟩ := hl k' List.mem_cons_self + have hc := val_slopeConst' hirr ρ hk hk' hne + -- `m` multiplications by the geometric series. + have hm (m : ℕ) : ∀ i < J, val N fl ((mulGeomPow N fl J (slopeConst N fl a k k') m R).getD + i []) = ρ (PowerSeries.coeff i (ψ * geomSeries (slope' a k k') ^ m)) := by + induction m with + | zero => simpa [mulGeomPow] using hR + | succ m ihm => + intro i hi + rw [mulGeomPow, List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + ← mulGeomPow, pow_succ, ← mul_assoc] + exact val_tmul_eq ihm (fun i hi => val_geom hc hi) hi + exact hm _ + +/-! ### Products over the nodes -/ + +/-- The other nodes, as integers, are the nonzero grid points other than `t`. -/ +theorem prod_others {M : Type*} [CommMonoid M] (f : ℤ → M) {a k : ℕ} : + ((others a k).map fun k' => f (node a k')).prod = + ∏ q ∈ (recoveryNodes a).erase (node a k), f q := by + have hnd : ((others a k).map (node a)).Nodup := + (List.nodup_range.filter _).map (node_injective a) + have hset : ((others a k).map (node a)).toFinset = (recoveryNodes a).erase (node a k) := by + ext q + simp only [List.mem_toFinset, List.mem_map, mem_others, recoveryNodes_eq, mem_erase, mem_Ico] + obtain ⟨L, hL⟩ : ∃ L : ℕ, 2 ^ a = L := ⟨_, rfl⟩ + have hLz : (2 : ℤ) ^ a = (L : ℤ) := by rw [← hL]; push_cast; rfl + have hL2 : (2 : ℕ) ^ (a + 1) = 2 * L := by rw [← hL]; ring + simp only [node, hLz, hL, hL2] + constructor + · rintro ⟨k', ⟨hk'1, hk'2, hk'3⟩, rfl⟩ + omega + · rintro ⟨hqk, hq0, hq1, hq2⟩ + exact ⟨(q + L).toNat, ⟨by omega, by omega, by omega⟩, by omega⟩ + rw [← hset, List.prod_toFinset _ hnd, List.map_map] + rfl + +/-- The nodes are the images of the indices `k < 2 ^ (a + 1)`, `k ≠ 2 ^ a`. -/ +theorem recoveryNodes_eq_image (a : ℕ) : + recoveryNodes a = ((range (2 ^ (a + 1))).filter (· ≠ 2 ^ a)).image (node a) := by + ext q + simp only [recoveryNodes_eq, mem_erase, mem_Ico, mem_image, mem_filter, mem_range] + obtain ⟨L, hL⟩ : ∃ L : ℕ, 2 ^ a = L := ⟨_, rfl⟩ + have hLz : (2 : ℤ) ^ a = (L : ℤ) := by rw [← hL]; push_cast; rfl + have hL2 : (2 : ℕ) ^ (a + 1) = 2 * L := by rw [← hL]; ring + simp only [node, hLz, hL, hL2] + constructor + · rintro ⟨hq0, hq1, hq2⟩ + exact ⟨(q + L).toNat, ⟨by omega, by omega⟩, by omega⟩ + · rintro ⟨k', ⟨hk'1, hk'2⟩, rfl⟩ + omega + +/-- A sum over the nodes is a sum over the node indices. -/ +theorem sum_recoveryNodes {M : Type*} [AddCommMonoid M] (f : ℤ → M) (a : ℕ) : + ∑ t ∈ recoveryNodes a, f t = + ∑ k ∈ range (2 ^ (a + 1)), if k = 2 ^ a then 0 else f (node a k) := by + rw [recoveryNodes_eq_image, sum_image fun _ _ _ _ e => node_injective a e, sum_ite, + sum_const_zero, zero_add] + +/-! ### Correctness of the multipliers -/ + +/-- A fold of multiplications is a product. -/ +theorem val_foldl_mul {ι : Type*} (g : ι → List ℕ) (l : List ι) (c : List ℕ) : + val N fl (l.foldl (fun acc x => mul N fl acc (g x)) c) = + val N fl c * (l.map fun x => val N fl (g x)).prod := by + induction l generalizing c with + | nil => simp + | cons x l ih => rw [List.foldl_cons, ih, val_mul, List.map_cons, List.prod_cons, mul_assoc] + +/-- The multiplier `2 ^ Δ E_t(0) (-t / α) ^ j` as a `2`-adic integer: +`2 ^ (Δ - (a + 1)(M_t + j)) ∏_{q ≠ t} (-q c_q) ^ (q_q) (-t) ^ j`. -/ +noncomputable def gamma (a u h Δ k j : ℕ) : ℤ₍₂₎ := + ((2 ^ (Δ - (a + 1) * (multSum a u h k + j)) : ℕ) : ℤ₍₂₎) * + ((others a k).map fun k' : ℕ => + (((((2 ^ a : ℕ) : ℤ) - (k' : ℤ) : ℤ) : ℤ₍₂₎) * slope' a k k') ^ mult a u h k').prod * + ((((2 ^ a : ℕ) : ℤ) - k : ℤ) : ℤ₍₂₎) ^ j + +/-- **The native multiplier is correct.** -/ +theorem val_coeffMult (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (ρ : ℤ₍₂₎ →+* Ring N fl) {a u h Δ k : ℕ} (hk : k < 2 ^ (a + 1)) (j : ℕ) : + val N fl (coeffMult N fl a u h Δ k j) = ρ (gamma a u h Δ k j) := by + rw [coeffMult, val_mul, val_mul, val_singleton, val_pow, val_diffConst, basisNum, + val_foldl_mul, val_one, one_mul, gamma, map_mul, map_mul, map_natCast, map_pow, map_intCast, + map_list_prod, List.map_map] + congr 2 + refine congrArg List.prod (List.map_congr_left fun k' hk' => ?_) + obtain ⟨hk'1, -, hk'3⟩ := mem_others.mp hk' + simp only [Function.comp_apply, val_pow, val_mul, val_diffConst, val_slopeConst' hirr ρ hk hk'1 + hk'3, map_pow, map_mul, map_intCast] + +/-- The value `E_t(0)` over `ℚ` as a product over the other node indices. -/ +theorem eval_zero_hermiteBasis_eq_prod (a u h k : ℕ) : + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) (node a k)).eval 0 = + ((others a k).map fun k' => + (_root_.Ring.inverse ((node a k : ℚ) - node a k') * (0 - node a k')) ^ + mult a u h k').prod := by + rw [eval_hermiteBasis, ← prod_others] + simp only [mult_eq] + +/-- **The multiplier is `2 ^ Δ E_t(0) (-t / α) ^ j`.** -/ +theorem coe_gamma {a u h Δ k j : ℕ} (hk : k < 2 ^ (a + 1)) + (hΔ : (a + 1) * (multSum a u h k + j) ≤ Δ) : + ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) = 2 ^ Δ * + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) (node a k)).eval 0 * + (-(node a k : ℚ) / 2 ^ (a + 1)) ^ j := by + rw [eval_zero_hermiteBasis_eq_prod] + -- Each factor is `(-q c_q / α) ^ (q_q)`. + set X : ℕ → ℚ := fun k' => ((((((2 ^ a : ℕ) : ℤ) - (k' : ℤ) : ℤ) : ℤ₍₂₎) * slope' a k k' : + ℤ₍₂₎) : ℚ) + have hfac : ∀ k' ∈ others a k, + (_root_.Ring.inverse ((node a k : ℚ) - node a k') * (0 - node a k')) ^ mult a u h k' = + X k' ^ mult a u h k' / (2 ^ (a + 1)) ^ mult a u h k' := by + intro k' hk' + obtain ⟨hk'1, -, hk'3⟩ := mem_others.mp hk' + have hne : (node a k : ℚ) - node a k' ≠ 0 := by + rw [sub_ne_zero]; exact_mod_cast fun e => hk'3 (node_injective a e).symm + have hs : ((slope' a k k' : ℤ₍₂₎) : ℚ) = 2 ^ (a + 1) / ((node a k : ℚ) - node a k') := by + rw [slope', dite_eq_left_of_eq_true (eq_true ⟨hk, hk'1, hk'3⟩)]; rfl + rw [← div_pow] + congr 1 + simp only [X, Subring.coe_mul, hs, Subring.coe_intCast, _root_.Ring.inverse_eq_inv'] + have hq : ((Int.cast (((2 ^ a : ℕ) : ℤ) - (k' : ℤ))) : ℚ) = -(node a k' : ℚ) := by + simp only [node]; push_cast; ring + rw [hq, zero_sub] + field_simp + have hprod : ∀ l : List ℕ, (l.map fun k' => X k' ^ mult a u h k' / + (2 ^ (a + 1) : ℚ) ^ mult a u h k').prod = + (l.map fun k' => X k' ^ mult a u h k').prod / (2 ^ (a + 1) : ℚ) ^ (l.map (mult a u h)).sum := by + intro l + induction l with + | nil => simp + | cons x l ih => rw [List.map_cons, List.prod_cons, ih, List.map_cons, List.prod_cons, + List.map_cons, List.sum_cons, div_mul_div_comm, ← pow_add] + rw [List.map_congr_left hfac, hprod, gamma, Subring.coe_mul, Subring.coe_mul, Subring.coe_pow, + Subring.coe_natCast, Subring.coe_intCast] + have hlist : ((((others a k).map fun k' : ℕ => + (((((2 ^ a : ℕ) : ℤ) - (k' : ℤ) : ℤ) : ℤ₍₂₎) * slope' a k k') ^ mult a u h k').prod : + ℤ₍₂₎) : ℚ) = ((others a k).map fun k' => X k' ^ mult a u h k').prod := by + refine (map_list_prod (ℤ₍₂₎).subtype _).trans ?_ + rw [List.map_map] + rfl + rw [hlist, ← multSum] + have h2 : (2 : ℚ) ^ Δ = 2 ^ (Δ - (a + 1) * (multSum a u h k + j)) * + ((2 : ℚ) ^ (a + 1)) ^ (multSum a u h k + j) := by + rw [← pow_mul, ← pow_add, Nat.sub_add_cancel hΔ] + rw [h2, show (-(node a k : ℚ)) = ((Int.cast (((2 ^ a : ℕ) : ℤ) - (k : ℤ))) : ℚ) by + simp only [node]; push_cast; ring, div_pow, pow_add] + field_simp + push_cast + ring + end Cslib.Circuits.Frontier.NativeRecovery From 3efcd6b73732f14af256fc443750d49cf6cad346 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Fri, 30 Oct 2026 23:56:25 -0400 Subject: [PATCH 33/38] feat(circuit/frontier): native tapered recovery components (Lemma D.5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native recovery in A_(p+Δ) on coefficient lists: interpolation, the truncated reciprocal of E_t(t + αz) as a product of geometric series, the multipliers 2^Δ E_t(0)(-t/α)^j as 2-adic integers, and cancellation of 2^Δ. Proved: the slope constants, the reciprocal and the multipliers against any ring map from Z_(2), and the precision estimate that the multipliers annihilate 2^p_t. Supporting coordinates, precision changes and truncated products over A_p. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- Complexity.lean | 2 + .../TensorNetwork/NativeRecovery.lean | 139 ++++++++++++++++-- .../PolyTime/GaloisRingSeries.lean | 7 +- .../Frontier/TensorNetworkNativeRecovery.lean | 52 +++++++ 4 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean diff --git a/Complexity.lean b/Complexity.lean index b1df660e8..d71743443 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -885,6 +885,7 @@ public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Explicit public import Complexity.Computability.Circuit.Frontier.TensorNetwork.IntegralCoordinates public import Complexity.Computability.Circuit.Frontier.TensorNetwork.LocalDeformation public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ModeExpansion +public import Complexity.Computability.Circuit.Frontier.TensorNetwork.NativeRecovery public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Recursion public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Reindex public import Complexity.Computability.Circuit.Frontier.TensorNetwork.Schedule @@ -2007,6 +2008,7 @@ public import Complexity.Computability.PolyTime.CommonCountingAccum public import Complexity.Computability.PolyTime.FineGrained.ParameterCheck public import Complexity.Computability.PolyTime.GaloisRingArithmetic public import Complexity.Computability.PolyTime.GaloisRingInterpolation +public import Complexity.Computability.PolyTime.GaloisRingSeries public import Complexity.Computability.PolyTime.GaloisRingUnits public import Complexity.Computability.PolyTime.GreedyLabelSchedule public import Complexity.Computability.PolyTime.GreedySlotScore diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index 17729dea7..ea11c070b 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -44,16 +44,34 @@ The sum over the nodes is `2 ^ Δ` times the recovered value. Dividing its coord ((D.10) and the evaluation loss), so the error is killed in `A_N`. This is the precision estimate of Lemma D.5 (`Cslib.Circuits.Frontier.exists_taperedRecovery_eq_add`), carried out natively. +**Status.** This file proves the components: the constants, the reciprocal, the multipliers and +the precision estimate, each against an arbitrary ring homomorphism `ρ : ℤ_(2) → A_N`. The +assembly into `recover = recoverFromValues mod 2 ^ p` (through a reduction `B → A_N` and the +interpolation of `GaloisRingArith.interp`) and the polynomial-time certificate are not yet done. + ## Main definitions * `Cslib.Circuits.Frontier.NativeRecovery.slopeConst`: the constant `α / (t - q)`. * `Cslib.Circuits.Frontier.NativeRecovery.recip`: the truncated reciprocal of `E_t(t + α z)`. * `Cslib.Circuits.Frontier.NativeRecovery.coeffMult`: the multipliers. * `Cslib.Circuits.Frontier.NativeRecovery.recover`: native tapered recovery. +* `Cslib.Circuits.Frontier.NativeRecovery.recipSeries`, + `Cslib.Circuits.Frontier.NativeRecovery.gamma`: the reciprocal and the multipliers over + `ℤ_(2)`. ## Main results -* `Cslib.Circuits.Frontier.NativeRecovery.val_slopeConst`: the constant is correct. +* `Cslib.Circuits.Frontier.NativeRecovery.val_slopeConst`: the constant `α / (t - q)` is correct. +* `Cslib.Circuits.Frontier.NativeRecovery.val_recip`: the native reciprocal is correct below + `z ^ J`. +* `Cslib.Circuits.Frontier.NativeRecovery.val_coeffMult`, + `Cslib.Circuits.Frontier.NativeRecovery.coe_gamma`: the native multiplier is the image of + `2 ^ Δ E_t(0) (-t / α) ^ j`. +* `Cslib.Circuits.Frontier.NativeRecovery.gamma_mul_two_pow_eq_zero`: under the precision + hypothesis of Lemma D.5, the multipliers annihilate `2 ^ p_t` in `A_(p + Δ)`. +* `Cslib.Circuits.Frontier.NativeRecovery.sum_recoveryNodes`, + `Cslib.Circuits.Frontier.NativeRecovery.prod_others`: sums and products over the nodes as + iterations over node indices. ## References @@ -75,14 +93,36 @@ local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) /-- The node `t = k - 2 ^ a` of index `k`. -/ def node (a k : ℕ) : ℤ := (k : ℤ) - 2 ^ a +/-- The `2`-adic valuation of `d`, by at most `b` halvings: `v₂ d` when `d < 2 ^ b` +(`NativeRecovery.twoVal_eq`), and `0` for `d = 0`. -/ +def twoVal : ℕ → ℕ → ℕ + | 0, _ => 0 + | b + 1, d => if d ≠ 0 ∧ d % 2 = 0 then twoVal b (d / 2) + 1 else 0 + +theorem twoVal_eq : ∀ {b d : ℕ}, d < 2 ^ b → twoVal b d = padicValNat 2 d + | 0, d, hd => by rw [pow_zero] at hd; obtain rfl : d = 0 := by omega + simp [twoVal] + | b + 1, d, hd => by + rw [twoVal] + split_ifs with h + · obtain ⟨hd0, h2⟩ := h + have hd' : d = 2 * (d / 2) := by omega + rw [twoVal_eq (by rw [pow_succ] at hd; omega)] + conv_rhs => rw [hd'] + rw [padicValNat.mul (by norm_num) (by omega), padicValNat_self, add_comm] + · rcases Nat.eq_zero_or_pos d with rfl | hpos + · simp + · exact (padicValNat.eq_zero_of_not_dvd (by omega)).symm + /-- The multiplicity `q_t = h (u - v₂ t)` of the node of index `k`. -/ -def mult (a u h k : ℕ) : ℕ := h * (u - padicValNat 2 (k - 2 ^ a + (2 ^ a - k))) +def mult (a u h k : ℕ) : ℕ := h * (u - twoVal (a + 1) (k - 2 ^ a + (2 ^ a - k))) -theorem mult_eq (a u h k : ℕ) : mult a u h k = taperMult u h (node a k) := by +theorem mult_eq {a k : ℕ} (u h : ℕ) (hk : k < 2 ^ (a + 1)) : + mult a u h k = taperMult u h (node a k) := by have : ((k : ℤ) - 2 ^ a).natAbs = k - 2 ^ a + (2 ^ a - k) := by rw [show (2 : ℤ) ^ a = ((2 ^ a : ℕ) : ℤ) by push_cast; rfl] omega - rw [mult, taperMult, node, padicValInt, this] + rw [mult, taperMult, node, padicValInt, this, twoVal_eq (by rw [pow_succ] at hk ⊢; omega)] /-- The indices of the nodes other than the node of index `k`. -/ def others (a k : ℕ) : List ℕ := @@ -102,7 +142,7 @@ def diffConst (N : ℕ) (fl : List ℕ) (x y : ℕ) : List ℕ := `± 2 ^ (a + 1 - v) o⁻¹`. -/ def slopeConst (N : ℕ) (fl : List ℕ) (a x y : ℕ) : List ℕ := let d := x - y + (y - x) - let v := padicValNat 2 d + let v := twoVal (a + 1) d let w := mul N fl [2 ^ (a + 1 - v)] (inv N fl [d / 2 ^ v]) if x < y then neg N fl w else w @@ -256,7 +296,8 @@ theorem val_slopeConst (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (Z have hw : val N fl (mul N fl [2 ^ (a + 1 - v)] (inv N fl [o])) = ((2 ^ (a + 1 - v) : ℕ) : Ring N fl) * _root_.Ring.inverse (o : Ring N fl) := by rw [val_mul, val_singleton, val_inv_of_isUnit hirr (by rwa [val_singleton]), val_singleton] - unfold slopeConst + simp only [slopeConst] + rw [show twoVal (a + 1) (k - k' + (k' - k)) = v from twoVal_eq hdlt] split_ifs with h · rw [val_neg, hw, map_neg, map_natCast, neg_mul] · rw [hw, map_natCast] @@ -319,7 +360,8 @@ theorem val_geom {ρ : ℤ₍₂₎ →+* Ring N fl} {J : ℕ} {c : List ℕ} { of `NativeRecovery.recipSeries`. -/ theorem val_recip (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) (ρ : ℤ₍₂₎ →+* Ring N fl) {a u h k J : ℕ} (hk : k < 2 ^ (a + 1)) {i : ℕ} (hi : i < J) : - val N fl ((recip N fl a u h k J).getD i []) = ρ (PowerSeries.coeff i (recipSeries a u h k)) := by + val N fl ((recip N fl a u h k J).getD i []) = + ρ (PowerSeries.coeff i (recipSeries a u h k)) := by -- The invariant of the fold over the other nodes. suffices H : ∀ (l : List ℕ), (∀ k' ∈ l, k' < 2 ^ (a + 1) ∧ k' ≠ k) → ∀ (R : List (List ℕ)) (ψ : PowerSeries ℤ₍₂₎), @@ -332,7 +374,8 @@ theorem val_recip (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2 intro i _ rcases i with _ | i · simp [PowerSeries.coeff_one] - · rw [List.getD_cons_succ, List.getD_nil, val_nil, PowerSeries.coeff_one, if_neg (by omega), map_zero] + · rw [List.getD_cons_succ, List.getD_nil, val_nil, PowerSeries.coeff_one, + ite_eq_right_of_eq_false _ _ (eq_false (Nat.succ_ne_zero i)), map_zero] intro l induction l with | nil => intro _ R ψ hR; simpa using hR @@ -437,7 +480,8 @@ theorem eval_zero_hermiteBasis_eq_prod (a u h k : ℕ) : (_root_.Ring.inverse ((node a k : ℚ) - node a k') * (0 - node a k')) ^ mult a u h k').prod := by rw [eval_hermiteBasis, ← prod_others] - simp only [mult_eq] + refine congrArg List.prod (List.map_congr_left fun k' hk' => ?_) + rw [mult_eq u h (mem_others.mp hk').1] /-- **The multiplier is `2 ^ Δ E_t(0) (-t / α) ^ j`.** -/ theorem coe_gamma {a u h Δ k j : ℕ} (hk : k < 2 ^ (a + 1)) @@ -467,7 +511,8 @@ theorem coe_gamma {a u h Δ k j : ℕ} (hk : k < 2 ^ (a + 1)) field_simp have hprod : ∀ l : List ℕ, (l.map fun k' => X k' ^ mult a u h k' / (2 ^ (a + 1) : ℚ) ^ mult a u h k').prod = - (l.map fun k' => X k' ^ mult a u h k').prod / (2 ^ (a + 1) : ℚ) ^ (l.map (mult a u h)).sum := by + (l.map fun k' => X k' ^ mult a u h k').prod / + (2 ^ (a + 1) : ℚ) ^ (l.map (mult a u h)).sum := by intro l induction l with | nil => simp @@ -491,4 +536,78 @@ theorem coe_gamma {a u h Δ k j : ℕ} (hk : k < 2 ^ (a + 1)) push_cast ring +/-! ### Precision -/ + +theorem node_mem_recoveryNodes {a k : ℕ} (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) : + node a k ∈ recoveryNodes a := by + rw [recoveryNodes_eq_image] + exact mem_image_of_mem _ (mem_filter.mpr ⟨mem_range.mpr hk, hkL⟩) + +/-- **The multipliers kill the input error.** If `p + 6 h D_t + q_t (a + 1) ≤ p_t`, the +multiplier of every coefficient below `z ^ q_t` is divisible by `2 ^ (N - p_t)` in `ℤ_(2)`, +`N = p + Δ`, so it annihilates `2 ^ p_t` in `A_N`. -/ +theorem gamma_mul_two_pow_eq_zero (ρ : ℤ₍₂₎ →+* Ring N fl) {a u h Δ p pt k j : ℕ} + (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) (hj : j < mult a u h k) + (hΔ : (a + 1) * (multSum a u h k + j) ≤ Δ) + (hpt : p + 6 * h * 2 ^ (a - padicValInt 2 (node a k)) + mult a u h k * (a + 1) ≤ pt) + (hN : N = p + Δ) : ρ (gamma a u h Δ k j) * 2 ^ pt = 0 := by + rcases le_or_gt N pt with hle | hlt + · rw [show (2 : Ring N fl) ^ pt = ((2 ^ pt : ℕ) : Ring N fl) by push_cast; rfl, + ← Nat.sub_add_cancel hle, pow_add, Nat.cast_mul, natCast_two_pow, mul_zero, mul_zero] + set e := N - pt + have ht := node_mem_recoveryNodes hk hkL + set E0 := (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) + (node a k)).eval 0 + have hE0 : E0 ≠ 0 := eval_zero_hermiteBasis_ne_zero a u h + have ht0 : (node a k : ℚ) ≠ 0 := by exact_mod_cast ne_of_mem_erase ht + have hx0 : (-(node a k : ℚ) / 2 ^ (a + 1)) ≠ 0 := + div_ne_zero (neg_ne_zero.mpr ht0) (by positivity) + have hγ := coe_gamma (u := u) (h := h) hk hΔ + have hγ0 : ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) ≠ 0 := by + rw [hγ]; exact mul_ne_zero (mul_ne_zero (by positivity) hE0) (pow_ne_zero _ hx0) + -- The valuation of the multiplier is at least `e`. + have h2 : padicValRat 2 (2 : ℚ) = 1 := by exact_mod_cast padicValRat.self (p := 2) one_lt_two + have hvE : -(6 * (h : ℤ) * 2 ^ (a - padicValInt 2 (node a k))) ≤ padicValRat 2 E0 := by + have := neg_le_padicValRat_eval_zero_hermiteBasis_taper (a := a) u h + (mem_of_mem_erase (by rw [← recoveryNodes_eq]; exact ht)) (ne_of_mem_erase ht) + rw [← recoveryNodes_eq] at this + exact this + have hvx : -((a : ℤ) + 1) ≤ padicValRat 2 (-(node a k : ℚ) / 2 ^ (a + 1)) := by + rw [padicValRat.div (neg_ne_zero.mpr ht0) (by positivity), padicValRat.neg, + padicValRat.of_int, padicValRat.pow, h2] + push_cast + omega + have hv : (e : ℤ) ≤ padicValRat 2 ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) := by + rw [hγ, padicValRat.mul (mul_ne_zero (by positivity) hE0) (pow_ne_zero _ hx0), + padicValRat.mul (by positivity) hE0, padicValRat.pow, padicValRat.pow, h2] + have hjv : (j : ℤ) * padicValRat 2 (-(node a k : ℚ) / 2 ^ (a + 1)) ≥ + -((mult a u h k : ℤ) * ((a : ℤ) + 1)) := by + have : (j : ℤ) ≤ mult a u h k := by exact_mod_cast hj.le + nlinarith + have hpt' : (p : ℤ) + 6 * h * 2 ^ (a - padicValInt 2 (node a k)) + + (mult a u h k : ℤ) * (a + 1) ≤ pt := by exact_mod_cast hpt + have he : (e : ℤ) = N - pt := by omega + rw [he, hN] + push_cast + linarith + -- So `γ = 2 ^ e γ'` with `γ' ∈ ℤ_(2)`. + have hmem : ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) / 2 ^ e ∈ ℤ₍₂₎ := by + have hne : ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) / 2 ^ e ≠ 0 := div_ne_zero hγ0 (by positivity) + have hval : 0 ≤ padicValRat 2 (((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) / 2 ^ e) := by + rw [padicValRat.div hγ0 (by positivity), padicValRat.pow, h2] + omega + change Rat.padicValuation 2 _ ≤ 1 + simp only [Rat.padicValuation, Valuation.coe_mk, MonoidWithZeroHom.coe_mk, ZeroHom.coe_mk, + hne, ↓reduceIte] + rw [← WithZero.exp_zero, WithZero.exp_le_exp] + omega + have hsplit : gamma a u h Δ k j = ((2 ^ e : ℕ) : ℤ₍₂₎) * ⟨_, hmem⟩ := by + apply Subtype.val_injective + simp only [Subring.coe_mul, Subring.coe_natCast] + push_cast + field_simp + rw [hsplit, map_mul, map_natCast, mul_comm, ← mul_assoc, + show (2 : Ring N fl) ^ pt = ((2 ^ pt : ℕ) : Ring N fl) by push_cast; rfl, ← Nat.cast_mul, + ← pow_add, show pt + e = N by omega, natCast_two_pow, zero_mul] + end Cslib.Circuits.Frontier.NativeRecovery diff --git a/Complexity/Computability/PolyTime/GaloisRingSeries.lean b/Complexity/Computability/PolyTime/GaloisRingSeries.lean index c42ccbdf9..7f3935612 100644 --- a/Complexity/Computability/PolyTime/GaloisRingSeries.lean +++ b/Complexity/Computability/PolyTime/GaloisRingSeries.lean @@ -92,7 +92,8 @@ theorem coeff_modByMonicHom_val (c : List ℕ) (i : ℕ) : · have : Subsingleton (ZMod (2 ^ 0)) := by rw [pow_zero]; infer_instance exact Subsingleton.elim _ _ have := nontrivial_zmod hp - rw [val_eq_mk, AdjoinRoot.modByMonicHom_mk, (modByMonic_eq_self_iff ((monic_monicOf fl).map _)).mpr] + rw [val_eq_mk, AdjoinRoot.modByMonicHom_mk, + (modByMonic_eq_self_iff ((monic_monicOf fl).map _)).mpr] · simp only [finsetSum_coeff, coeff_C_mul, coeff_X_pow, mul_ite, mul_one, mul_zero, sum_ite_eq, mem_range] · rw [(monic_monicOf fl).degree_map, degree_eq_natDegree (monic_monicOf fl).ne_zero, @@ -194,8 +195,8 @@ theorem val_shiftDown {n Δ : ℕ} {s : List ℕ} {y : Ring (n + Δ) fl} /-- The coefficient of `z ^ j` in the product of two polynomials over `A_p`, by the Cauchy formula `∑ i ≤ j, P_i R_(j - i)`. -/ def cauchyCoeff (p : ℕ) (fl : List ℕ) (P R : List (List ℕ)) (j : ℕ) : List ℕ := - (List.range (j + 1)).foldl (fun acc i => add p fl acc (mul p fl (P.getD i []) (R.getD (j - i) []))) - (zero fl) + (List.range (j + 1)).foldl + (fun acc i => add p fl acc (mul p fl (P.getD i []) (R.getD (j - i) []))) (zero fl) /-- The **truncated product**: the first `J` coefficients of the product of two polynomials over `A_p`. -/ diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean new file mode 100644 index 000000000..c368f3ddc --- /dev/null +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean @@ -0,0 +1,52 @@ +/- +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.NativeRecovery + +/-! # Native tapered recovery on small instances + +Computations over `(ℤ / 2 ^ N)[U] / (U ^ 2 + U + 1)` (`fl = [1, 1]`) with `L = 2` slots +(`a = 1`), and the correctness statements of the components. -/ + +namespace ComplexityTests.TensorNetworkNativeRecovery + +open Cslib.GaloisRingArith Cslib.Circuits.Frontier Cslib.Circuits.Frontier.NativeRecovery + +-- `2 - 5 = -3 = 5` modulo `8`. +example : diffConst 3 [1, 1] 2 5 = [5, 0] := by decide + +-- `α / (t - q) = 4 / 2 = 2`, and `4 / (0 - 3) = -4 / 3 = 4` modulo `8` (since `3⁻¹ = 3`). +example : slopeConst 3 [1, 1] 1 3 1 = [2, 0] := by decide +example : slopeConst 3 [1, 1] 1 0 3 = [4, 0] := by decide + +-- `(1 + z) ^ 2 = 1 + 2 z + z ^ 2`, truncated below `z ^ 2`. +example : tmul 3 [1, 1] 2 [[1, 0], [1, 0]] [[1, 0], [1, 0]] = [[1, 0], [2, 0]] := by decide + +-- Cancelling `2`: `6 + 2 U = 2 (3 + U)`. +example : shiftDown 1 [6, 2] = [3, 1] := by decide + +-- With `u = h = 1`, the odd nodes `t = ±1` have multiplicity `1` and `t = -2` has `0`. +example : (mult 1 1 1 1, mult 1 1 1 3, mult 1 1 1 0) = (1, 1, 0) := by decide + +/-- **Recovery of `F(0)` for `F(T) = T + 5`.** Here `q_{±1} = 1`, so recovery reads the values +`F(-1) = 4` and `F(1) = 6` at the single point `ζ = 0`, and returns +`E_{-1}(0) F(-1) + E_1(0) F(1) = (4 + 6) / 2 = 5` modulo `8`, computing modulo `2 ^ (3 + 2)`. -/ +example : recover 3 2 [1, 1] 1 1 1 [[0, 0]] [[], [[4, 0]], [], [[6, 0]]] = [5, 0] := by decide + +/-- The reciprocal of `E_t(t + α z)` is correct, for every ring homomorphism from `ℤ_(2)`. -/ +example (hirr : Irreducible ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2)))) + (ρ : Valuation.integer (Rat.padicValuation 2) →+* Ring 5 [1, 1]) {i : ℕ} (hi : i < 2) : + val 5 [1, 1] ((recip 5 [1, 1] 1 1 1 1 2).getD i []) = + ρ (PowerSeries.coeff i (recipSeries 1 1 1 1)) := + val_recip hirr ρ (by norm_num) hi + +/-- The multipliers are `2 ^ Δ E_t(0) (-t / α) ^ j`. -/ +example : ((gamma 1 1 1 2 1 0 : Valuation.integer (Rat.padicValuation 2)) : ℚ) = 2 ^ 2 * + (Polynomial.hermiteBasis (recoveryNodes 1) (fun q : ℤ => (q : ℚ)) (taperMult 1 1) + (node 1 1)).eval 0 * (-(node 1 1 : ℚ) / 2 ^ (1 + 1)) ^ 0 := + coe_gamma (by norm_num) (by decide) + +end ComplexityTests.TensorNetworkNativeRecovery From 9dde334a6d8734195b34d228ec6868bf3aa021cc Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:09:53 -0400 Subject: [PATCH 34/38] feat(circuit/frontier): native recovery computes recoverFromValues mod 2^p MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bridge #1906's native interpolation to `Lagrange.ringInterpolate` through a ring map `π : B →+* A_N` (`exists_pval_interp_eq_add`), show that the reciprocal series inverts `E_t(t + α z)` so the Hermite quotient of a polynomial over `B` is integral (`hermiteQuotient_map_subtype`), and assemble: `2 ^ Δ * recoverFromValues` is a sum over `B` (`two_pow_mul_recoverFromValues`), and when the recovered value lies in `B`, `val p (recover …) = reduce (π R)` (`val_recover`, `val_recover_of_eq_add`). Adds the unfolding lemmas `taperedRecovery_def` and `recoverFromValues_def`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Frontier/TensorNetwork/ActiveCall.lean | 6 + .../TensorNetwork/NativeRecovery.lean | 358 +++++++++++++++++- .../TensorNetwork/TaperedRecovery.lean | 7 + .../Frontier/TensorNetworkNativeRecovery.lean | 3 + 4 files changed, 369 insertions(+), 5 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean index afa5522d4..5a747682d 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean @@ -133,6 +133,12 @@ noncomputable def recoverFromValues (B : Subring K) (a u h : ℕ) {n : ℕ} (ζ (v : ℤ → Fin n → B) : K := taperedRecovery a u h fun t => (Lagrange.ringInterpolate univ ζ (v t)).map B.subtype +theorem recoverFromValues_def (B : Subring K) (a u h : ℕ) {n : ℕ} (ζ : Fin n → B) + (v : ℤ → Fin n → B) : + recoverFromValues B a u h ζ v = + taperedRecovery a u h fun t => (Lagrange.ringInterpolate univ ζ (v t)).map B.subtype := by + rw [recoverFromValues] + /-- A nonzero integer `t` of valuation `i` is divisible by `2 ^ i` but not by `2 ^ (i + 1)`. -/ theorem two_pow_padicValInt_dvd_and_not_dvd {t : ℤ} (ht : t ≠ 0) : (2 : ℤ) ^ padicValInt 2 t ∣ t ∧ ¬ (2 : ℤ) ^ (padicValInt 2 t + 1) ∣ t := by diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index ea11c070b..53f3c4940 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -17,7 +17,7 @@ import Mathlib.Data.List.GetD This file gives the native procedure behind the recovery step of the contraction algorithm of [OpenAI2026ETH] (§D.4.4, Lemma D.5, pp. 60–61), and proves that it computes the mathematical recovery `Cslib.Circuits.Frontier.recoverFromValues` of -`Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean`. +`Complexity/Computability/Circuit/Frontier/TensorNetwork/ActiveCall.lean` modulo `2 ^ p`. **Input.** A precision `p`, a shift `Δ`, the monic polynomial `fl` of `Complexity/Computability/PolyTime/GaloisRingArithmetic.lean`, the parameters `a, u, h`, the @@ -44,10 +44,14 @@ The sum over the nodes is `2 ^ Δ` times the recovered value. Dividing its coord ((D.10) and the evaluation loss), so the error is killed in `A_N`. This is the precision estimate of Lemma D.5 (`Cslib.Circuits.Frontier.exists_taperedRecovery_eq_add`), carried out natively. -**Status.** This file proves the components: the constants, the reciprocal, the multipliers and -the precision estimate, each against an arbitrary ring homomorphism `ρ : ℤ_(2) → A_N`. The -assembly into `recover = recoverFromValues mod 2 ^ p` (through a reduction `B → A_N` and the -interpolation of `GaloisRingArith.interp`) and the polynomial-time certificate are not yet done. +**Correctness.** Let `π : B → A_N` be a ring homomorphism from a subring `B ⊇ ℤ_(2)` of a +`ℚ`-algebra `K` (in the manuscript, `B = O`). The native interpolant is the image of the +interpolant over `B` modulo `2 ^ p_t` (`NativeRecovery.exists_pval_interp_eq_add`), the Hermite +quotient over `K` of a polynomial over `B` is the image of its product with the reciprocal series +(`NativeRecovery.hermiteQuotient_map_subtype`), and `2 ^ Δ` times the recovered value is the sum +of the node contributions over `B` (`NativeRecovery.two_pow_mul_recoverFromValues`). So when the +recovered value `recoverFromValues B a u h ζ v` lies in `B`, native recovery computes its image +modulo `2 ^ p` (`NativeRecovery.val_recover`). ## Main definitions @@ -58,6 +62,7 @@ interpolation of `GaloisRingArith.interp`) and the polynomial-time certificate a * `Cslib.Circuits.Frontier.NativeRecovery.recipSeries`, `Cslib.Circuits.Frontier.NativeRecovery.gamma`: the reciprocal and the multipliers over `ℤ_(2)`. +* `Cslib.Circuits.Frontier.NativeRecovery.nodeSum`: the contribution of a node over `B`. ## Main results @@ -72,6 +77,15 @@ interpolation of `GaloisRingArith.interp`) and the polynomial-time certificate a * `Cslib.Circuits.Frontier.NativeRecovery.sum_recoveryNodes`, `Cslib.Circuits.Frontier.NativeRecovery.prod_others`: sums and products over the nodes as iterations over node indices. +* `Cslib.Circuits.Frontier.NativeRecovery.exists_pval_interp_eq_add`: the interpolation bridge. +* `Cslib.Circuits.Frontier.NativeRecovery.invOfUnit_hermiteBasis_comp`, + `Cslib.Circuits.Frontier.NativeRecovery.hermiteQuotient_map_subtype`: the reciprocal series + inverts `E_t(t + α z)`, so the Hermite quotient of a polynomial over `B` is integral. +* `Cslib.Circuits.Frontier.NativeRecovery.two_pow_mul_recoverFromValues`: `2 ^ Δ` times the + recovered value as a sum over `B`. +* `Cslib.Circuits.Frontier.NativeRecovery.val_recover`, + `Cslib.Circuits.Frontier.NativeRecovery.val_recover_of_eq_add`: native recovery computes the + mathematical recovery modulo `2 ^ p`. ## References @@ -400,6 +414,7 @@ theorem val_recip (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2 /-! ### Products over the nodes -/ /-- The other nodes, as integers, are the nonzero grid points other than `t`. -/ +@[to_additive /-- The other nodes, as integers, are the nonzero grid points other than `t`. -/] theorem prod_others {M : Type*} [CommMonoid M] (f : ℤ → M) {a k : ℕ} : ((others a k).map fun k' => f (node a k')).prod = ∏ q ∈ (recoveryNodes a).erase (node a k), f q := by @@ -610,4 +625,337 @@ theorem gamma_mul_two_pow_eq_zero (ρ : ℤ₍₂₎ →+* Ring N fl) {a u h Δ show (2 : Ring N fl) ^ pt = ((2 ^ pt : ℕ) : Ring N fl) by push_cast; rfl, ← Nat.cast_mul, ← pow_add, show pt + e = N by omega, natCast_two_pow, zero_mul] +/-! ### The interpolation bridge -/ + +/-- The coefficients of a polynomial over `A_N` given by a list of coefficient lists. -/ +theorem coeff_pval (P : List (List ℕ)) (l : ℕ) : + (pval N fl P).coeff l = val N fl (P.getD l []) := by + rw [pval, finsetSum_coeff] + simp only [coeff_C_mul, coeff_X_pow, mul_ite, mul_one, mul_zero, sum_ite_eq, mem_range] + split_ifs with h + · rfl + · rw [List.getD_eq_default _ _ (by omega), val_nil] + +/-- Interpolation at `n` nodes has degree below `n`. -/ +theorem degree_pval_interp_lt (ζl w : List (List ℕ)) : + (pval N fl (interp N fl ζl w)).degree < ζl.length := by + rw [← mem_degreeLT, pval_interp] + refine Submodule.sum_mem _ fun i hi => ?_ + rw [← smul_eq_C_mul] + refine Submodule.smul_mem _ _ (mem_degreeLT.mpr ((degree_le_natDegree).trans_lt ?_)) + have hle : (∏ j ∈ (range ζl.length).erase i, (X - C (val N fl (ζl.getD j [])))).natDegree ≤ + #((range ζl.length).erase i) := + (natDegree_prod_le _ _).trans ((sum_le_sum fun j _ => natDegree_X_sub_C_le _).trans_eq + (card_eq_sum_ones _).symm) + rw [card_erase_of_mem hi, card_range] at hle + have := mem_range.mp hi + exact_mod_cast (show _ < ζl.length by omega) + +/-- **The interpolation bridge.** Let `π : B → A_N` be a ring homomorphism, and let the native +nodes `ζl` represent the images of nodes `ζ i ∈ B` with unit differences. If the native values +`w` agree with the images of `r i ∈ B` modulo `2 ^ e`, then the native interpolant agrees with the +image of the interpolant of `r` over `B` modulo `2 ^ e`. -/ +theorem exists_pval_interp_eq_add (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + {B : Type*} [CommRing B] (π : B →+* Ring N fl) {n : ℕ} (ζ : Fin n → B) + (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) (ζl : List (List ℕ)) (hlen : ζl.length = n) + (hζl : ∀ i : Fin n, val N fl (ζl.getD i []) = π (ζ i)) (r : Fin n → B) + (w : List (List ℕ)) (e : ℕ) + (hw : ∀ i : Fin n, ∃ c, val N fl (w.getD i []) = π (r i) + 2 ^ e * c) : + ∃ g : (Ring N fl)[X], + pval N fl (interp N fl ζl w) = (Lagrange.ringInterpolate univ ζ r).map π + C (2 ^ e) * g := by + subst hlen + have hvs : ((univ : Finset (Fin ζl.length)) : Set (Fin ζl.length)).Pairwise + fun i j => IsUnit (π (ζ i) - π (ζ j)) := fun i _ j _ hij => by + simp only + rw [← map_sub]; exact (hζ hij).map π + -- The native interpolant is the interpolant of the native values at the images of the nodes. + have hinv : ∀ i < ζl.length, val N fl (basisDen N fl ζl i) * + val N fl (inv N fl (basisDen N fl ζl i)) = 1 := fun i hi => by + refine val_mul_inv_of_isUnit hirr ?_ + rw [val_basisDen, IsUnit.prod_iff] + intro j hj + have hj' := mem_range.mp (mem_of_mem_erase hj) + rw [show i = ((⟨i, hi⟩ : Fin ζl.length) : ℕ) from rfl, hζl, + show j = ((⟨j, hj'⟩ : Fin ζl.length) : ℕ) from rfl, hζl] + exact hvs (mem_univ _) (mem_univ _) fun h => ne_of_mem_erase hj (congrArg Fin.val h).symm + have hnat : pval N fl (interp N fl ζl w) = + Lagrange.ringInterpolate univ (fun i => π (ζ i)) fun i => val N fl (w.getD i []) := by + conv_lhs => rw [Lagrange.eq_ringInterpolate (f := pval N fl (interp N fl ζl w)) hvs (by + rw [card_univ, Fintype.card_fin]; exact degree_pval_interp_lt ζl w)] + congr 1 + funext i + rw [← hζl, eval_pval_interp ζl w hinv i.2] + -- The image of the interpolant over `B` takes the values `π (r i)`. + have hdeg : ((Lagrange.ringInterpolate univ ζ r).map π).degree < + #(univ : Finset (Fin ζl.length)) := + (degree_map_le).trans_lt (Lagrange.degree_ringInterpolate_lt r) + obtain ⟨g, hg⟩ := Lagrange.exists_ringInterpolate_eq_add hvs hdeg (π := 2 ^ e) + (r := fun i => val N fl (w.getD i [])) fun i _ => by + obtain ⟨c, hc⟩ := hw i + refine ⟨c, ?_⟩ + rw [hc, eval_map, eval₂_at_apply, + Lagrange.eval_ringInterpolate_at_node (fun i _ j _ hij => hζ hij) r (mem_univ i)] + exact ⟨g, hnat.trans hg⟩ + +/-! ### The Hermite quotient over `B` -/ + +/-- A fold of multiplications is a list product. -/ +theorem foldl_mul_eq {M ι : Type*} [CommMonoid M] (g : ι → M) (l : List ι) (c : M) : + l.foldl (fun x y => x * g y) c = c * (l.map g).prod := by + induction l generalizing c with + | nil => simp + | cons x l ih => rw [List.foldl_cons, ih, List.map_cons, List.prod_cons, mul_assoc] + +theorem map_geomSeries {R S : Type*} [CommRing R] [CommRing S] (f : R →+* S) (c : R) : + PowerSeries.map f (geomSeries c) = geomSeries (f c) := by + ext n + simp [geomSeries, PowerSeries.coeff_map] + +theorem coe_slope' {a k k' : ℕ} (hk : k < 2 ^ (a + 1)) (hk' : k' < 2 ^ (a + 1)) (hne : k' ≠ k) : + ((slope' a k k' : ℤ₍₂₎) : ℚ) = 2 ^ (a + 1) / ((node a k : ℚ) - node a k') := by + rw [slope', dite_eq_left_of_eq_true (eq_true ⟨hk, hk', hne⟩)] + rfl + +/-- Over `ℚ`, `E_t(t + α z)` times the reciprocal series is one. -/ +theorem coe_hermiteBasis_comp_mul_recipSeries {a k : ℕ} (u h : ℕ) (hk : k < 2 ^ (a + 1)) : + ((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) (node a k)).comp + (C (node a k : ℚ) + C (2 ^ (a + 1)) * X) : PowerSeries ℚ) * + PowerSeries.map (ℤ₍₂₎).subtype (recipSeries a u h k) = 1 := by + rw [hermiteBasis_comp_rat, ← prod_others (fun q : ℤ => + (1 + C ((2 : ℚ) ^ (a + 1) / ((node a k : ℚ) - q)) * X) ^ taperMult u h q), + recipSeries, foldl_mul_eq, one_mul, ← Polynomial.coeToPowerSeries.ringHom_apply, map_list_prod, + map_list_prod, List.map_map, List.map_map, ← List.prod_map_mul] + refine List.prod_eq_one fun x hx => ?_ + obtain ⟨k', hk', rfl⟩ := List.mem_map.mp hx + obtain ⟨hk'1, -, hk'3⟩ := mem_others.mp hk' + simp only [Function.comp_apply, map_pow, Polynomial.coeToPowerSeries.ringHom_apply, + Polynomial.coe_add, Polynomial.coe_one, Polynomial.coe_mul, + Polynomial.coe_C, Polynomial.coe_X, map_geomSeries, Subring.coe_subtype, + coe_slope' hk hk'1 hk'3, ← mult_eq u h hk'1] + rw [← mul_pow, one_add_mul_geomSeries, one_pow] + +section Hermite + +variable {K : Type*} [CommRing K] [Algebra ℚ K] + +/-- **The reciprocal series is the inverse of `E_t(t + α z)`** over every `ℚ`-algebra. -/ +theorem invOfUnit_hermiteBasis_comp {a k : ℕ} (u h : ℕ) (hk : k < 2 ^ (a + 1)) + (hkL : k ≠ 2 ^ a) : + PowerSeries.invOfUnit (((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) + (taperMult u h) (node a k)).comp (C (node a k : K) + C (2 ^ (a + 1)) * X) : K[X]) : + PowerSeries K) 1 = + PowerSeries.map ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype) (recipSeries a u h k) := by + have ht := node_mem_recoveryNodes hk hkL + set φ : PowerSeries K := (((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) + (taperMult u h) (node a k)).comp (C (node a k : K) + C (2 ^ (a + 1)) * X) : K[X]) : + PowerSeries K) + have hv : (fun q : ℤ => algebraMap ℚ K (q : ℚ)) = fun q : ℤ => (q : K) := + funext fun q => map_intCast _ q + have hlin : (C (node a k : ℚ) + C ((2 : ℚ) ^ (a + 1)) * X).map (algebraMap ℚ K) = + C (node a k : K) + C (2 ^ (a + 1)) * 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 hφ : φ = PowerSeries.map (algebraMap ℚ K) + (((hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : ℚ)) (taperMult u h) (node a k)).comp + (C (node a k : ℚ) + C (2 ^ (a + 1)) * X) : ℚ[X]) : PowerSeries ℚ) := by + rw [← Polynomial.polynomial_map_coe, Polynomial.map_comp, + map_hermiteBasis _ (pairwise_isUnit_sub _) ht, hv, hlin] + have h1 : φ * PowerSeries.invOfUnit φ 1 = 1 := + PowerSeries.mul_invOfUnit _ _ (by + rw [Polynomial.constantCoeff_coe, coeff_zero_eq_eval_zero, eval_comp, eval_add, eval_C, + eval_mul, eval_C, eval_X, mul_zero, add_zero, + eval_hermiteBasis_self (pairwise_isUnit_sub _) ht, Units.val_one]) + have h2 : φ * PowerSeries.map ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype) (recipSeries a u h k) = + 1 := by + rw [hφ, PowerSeries.map_comp, RingHom.comp_apply, ← map_mul, + coe_hermiteBasis_comp_mul_recipSeries u h hk, map_one] + calc PowerSeries.invOfUnit φ 1 + = PowerSeries.invOfUnit φ 1 * (φ * PowerSeries.map ((algebraMap ℚ K).comp (ℤ₍₂₎).subtype) + (recipSeries a u h k)) := by rw [h2, mul_one] + _ = _ := by rw [← mul_assoc, mul_comm _ φ, h1, one_mul] + +/-- **The Hermite quotient over `B`.** For a polynomial `P` over `B`, the Hermite quotient over +`K` at the node of index `k` is the image of the truncation below `z ^ q_t` of the product of `P` +with the reciprocal series. -/ +theorem hermiteQuotient_map_subtype (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) + {a k : ℕ} (u h : ℕ) (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) (P : B[X]) : + hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) (2 ^ (a + 1)) + (node a k) ((P.map B.subtype : K[X]) : PowerSeries K) = + (PowerSeries.trunc (mult a u h k) ((P : PowerSeries B) * + PowerSeries.map (integerHom B hB) (recipSeries a u h k))).map B.subtype := by + rw [hermiteQuotient, invOfUnit_hermiteBasis_comp u h hk hkL, ← subtype_comp_integerHom B hB, + PowerSeries.map_comp, RingHom.comp_apply, Polynomial.polynomial_map_coe, ← map_mul, + PowerSeries.trunc_map, mult_eq u h hk] + +/-! ### Assembly -/ + +/-- The contribution of the node of index `k` to `2 ^ Δ` times the recovered value, over `B`: +`∑_{j < q_t} γ_{t,j} c_j`, where `c_j` is the coefficient of `z ^ j` in the product of `P` with +the reciprocal series and `γ_{t,j} = 2 ^ Δ E_t(0) (-t / α) ^ j` (`NativeRecovery.gamma`). -/ +noncomputable def nodeSum (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) + (a u h Δ k : ℕ) (P : B[X]) : B := + ∑ j ∈ range (mult a u h k), integerHom B hB (gamma a u h Δ k j) * + PowerSeries.coeff j ((P : PowerSeries B) * + PowerSeries.map (integerHom B hB) (recipSeries a u h k)) + +theorem multSum_add_mult {a k : ℕ} (u h : ℕ) (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) : + multSum a u h k + mult a u h k = ∑ t ∈ recoveryNodes a, taperMult u h t := by + rw [multSum, List.map_congr_left (g := fun k' => taperMult u h (node a k')) + fun k' hk' => mult_eq u h (mem_others.mp hk').1, + sum_others (fun q => taperMult u h q), mult_eq u h hk, + sum_erase_add _ _ (node_mem_recoveryNodes hk hkL)] + +theorem le_of_lt_mult {a u h Δ k j : ℕ} (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) + (hj : j < mult a u h k) (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) : + (a + 1) * (multSum a u h k + j) ≤ Δ := + (Nat.mul_le_mul_left _ (by rw [← multSum_add_mult u h hk hkL]; omega)).trans hΔ + +/-- **`2 ^ Δ` times the recovered value lies in `B`**, as the sum of the node contributions +`NativeRecovery.nodeSum`, once `Δ ≥ (a + 1) ∑_t q_t`. -/ +theorem two_pow_mul_recoverFromValues (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) + {a u h Δ : ℕ} (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) {n : ℕ} + (ζ : Fin n → B) (v : ℤ → Fin n → B) : + 2 ^ Δ * recoverFromValues B a u h ζ v = + ((∑ k ∈ range (2 ^ (a + 1)), if k = 2 ^ a then 0 else + nodeSum B hB a u h Δ k (Lagrange.ringInterpolate univ ζ (v (node a k))) : B) : K) := by + rw [recoverFromValues_def, taperedRecovery_def, sum_recoveryNodes, mul_sum, + AddSubmonoidClass.coe_finsetSum] + refine sum_congr rfl fun k hk => ?_ + have hk' := mem_range.mp hk + split_ifs with hkL + · simp + rw [hermiteQuotient_map_subtype B hB u h hk' hkL, eval_map, + PowerSeries.eval₂_trunc_eq_sum_range, eval_zero_hermiteBasis_eq a u h + (node_mem_recoveryNodes hk' hkL), nodeSum, AddSubmonoidClass.coe_finsetSum, mul_sum, + mul_sum] + refine sum_congr rfl fun j hj => ?_ + have hγ := coe_gamma (u := u) (h := h) hk' (le_of_lt_mult hk' hkL (mem_range.mp hj) hΔ) + have hint : ((integerHom B hB (gamma a u h Δ k j) : B) : K) = + algebraMap ℚ K ((gamma a u h Δ k j : ℤ₍₂₎) : ℚ) := rfl + rw [Subring.coe_mul, hint, hγ, Subring.coe_subtype] + simp only [map_mul, map_pow, map_ofNat] + ring + +/-- A fold that adds a term at each index of a range sums the terms. -/ +theorem val_foldl_range (F : List ℕ → ℕ → List ℕ) (g : ℕ → Ring N fl) + (hF : ∀ S k, val N fl (F S k) = val N fl S + g k) (c : List ℕ) (m : ℕ) : + val N fl ((List.range m).foldl F c) = val N fl c + ∑ k ∈ range m, g k := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, hF, ih, + sum_range_succ, add_assoc] + +/-- **The native contribution of a node is correct**: if the native values agree with the +images of `r i` modulo `2 ^ p_t`, where `p + 6 h D_t + q_t (a + 1) ≤ p_t`, then the native +contribution of the node of index `k` is the image of `NativeRecovery.nodeSum`. -/ +theorem val_nodeTerm (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) (π : B →+* Ring N fl) + {p a u h Δ pt k : ℕ} (hk : k < 2 ^ (a + 1)) (hkL : k ≠ 2 ^ a) + (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) (hN : N = p + Δ) + (hpt : 0 < mult a u h k → + p + 6 * h * 2 ^ (a - padicValInt 2 (node a k)) + mult a u h k * (a + 1) ≤ pt) + {n : ℕ} (ζ : Fin n → B) (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) + (ζl : List (List ℕ)) (hlen : ζl.length = n) + (hζl : ∀ i : Fin n, val N fl (ζl.getD i []) = π (ζ i)) (r : Fin n → B) (w : List (List ℕ)) + (hw : 0 < mult a u h k → ∀ i : Fin n, ∃ c, val N fl (w.getD i []) = π (r i) + 2 ^ pt * c) : + val N fl (nodeTerm N fl a u h Δ ζl k w) = + π (nodeSum B hB a u h Δ k (Lagrange.ringInterpolate univ ζ r)) := by + rcases Nat.eq_zero_or_pos (mult a u h k) with h0 | hpos + · simp [nodeTerm, nodeSum, h0] + obtain ⟨g, hg⟩ := exists_pval_interp_eq_add hirr π ζ hζ ζl hlen hζl r w pt (hw hpos) + set ρ := π.comp (integerHom B hB) + set P := Lagrange.ringInterpolate univ ζ r + rw [nodeTerm, val_foldl_range _ (fun j => val N fl (coeffMult N fl a u h Δ k j) * + val N fl ((tmul N fl (mult a u h k) (interp N fl ζl w) + (recip N fl a u h k (mult a u h k))).getD j [])) + (fun S j => by rw [val_add, val_mul]), val_zero, zero_add, nodeSum, map_sum] + refine sum_congr rfl fun j hj => ?_ + have hj' := mem_range.mp hj + have hcoef : ∀ l, val N fl ((interp N fl ζl w).getD l []) = π (P.coeff l) + 2 ^ pt * g.coeff l := + fun l => by rw [← coeff_pval, hg, coeff_add, coeff_map, coeff_C_mul] + have hconv : π (PowerSeries.coeff j ((P : PowerSeries B) * + PowerSeries.map (integerHom B hB) (recipSeries a u h k))) = + ∑ l ∈ range (j + 1), + π (P.coeff l) * ρ (PowerSeries.coeff (j - l) (recipSeries a u h k)) := by + rw [PowerSeries.coeff_mul, Finset.Nat.sum_antidiagonal_eq_sum_range_succ (fun l m => + PowerSeries.coeff l (P : PowerSeries B) * + PowerSeries.coeff m (PowerSeries.map (integerHom B hB) (recipSeries a u h k))), map_sum] + refine sum_congr rfl fun l _ => ?_ + rw [Polynomial.coeff_coe, PowerSeries.coeff_map, map_mul] + rfl + have hkill : ρ (gamma a u h Δ k j) * 2 ^ pt = 0 := + gamma_mul_two_pow_eq_zero ρ hk hkL hj' (le_of_lt_mult hk hkL hj' hΔ) (hpt hpos) hN + rw [val_coeffMult hirr ρ hk j, val_tmul _ _ hj', map_mul, hconv, + sum_congr rfl fun l hl => by + rw [hcoef l, val_recip hirr ρ hk (show j - l < mult a u h k by omega)]] + simp only [add_mul, sum_add_distrib, mul_add, mul_assoc] + rw [← mul_sum, ← mul_assoc, hkill, zero_mul, add_zero] + rfl + + +/-- **Native recovery computes the mathematical recovery modulo `2 ^ p`.** Let `π : B → A_N`, +`N = p + Δ`, be a ring homomorphism, with `Δ ≥ (a + 1) ∑_t q_t`. Let the native points `ζl` +represent the images of points `ζ i ∈ B` with unit differences, and at every node index `k` of +positive multiplicity let the native values `W.getD k []` agree with the images of the values +`v t i` at `t = node a k` modulo `2 ^ p_t`, where `p + 6 h D_t + q_t (a + 1) ≤ p_t`. If the +recovered value `recoverFromValues B a u h ζ v` is an element `R` of `B`, then native recovery +computes the reduction of `π R` modulo `2 ^ p`. -/ +theorem val_recover (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {p Δ : ℕ} + (π : B →+* Ring (p + Δ) fl) {a u h : ℕ} + (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) (pt : ℤ → ℕ) + (hpt : ∀ t ∈ recoveryNodes a, 0 < taperMult u h t → + p + 6 * h * 2 ^ (a - padicValInt 2 t) + taperMult u h t * (a + 1) ≤ pt t) + {n : ℕ} (ζ : Fin n → B) (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) + (ζl : List (List ℕ)) (hlen : ζl.length = n) + (hζl : ∀ i : Fin n, val (p + Δ) fl (ζl.getD i []) = π (ζ i)) (v : ℤ → Fin n → B) + (W : List (List (List ℕ))) + (hW : ∀ k < 2 ^ (a + 1), k ≠ 2 ^ a → 0 < mult a u h k → ∀ i : Fin n, ∃ c, + val (p + Δ) fl ((W.getD k []).getD i []) = π (v (node a k) i) + 2 ^ pt (node a k) * c) + {R : B} (hR : recoverFromValues B a u h ζ v = R) : + val p fl (recover p Δ fl a u h ζl W) = reduce (Nat.le_add_right p Δ) fl (π R) := by + refine val_shiftDown ?_ + have hsum := two_pow_mul_recoverFromValues B hB hΔ ζ v + rw [hR] at hsum + have hsum' : (2 : B) ^ Δ * R = _ := Subtype.val_injective (by push_cast; exact hsum) + rw [← map_ofNat π, ← map_pow, ← map_mul, hsum', + val_foldl_range _ (fun k => if k = 2 ^ a then 0 else + val (p + Δ) fl (nodeTerm (p + Δ) fl a u h Δ ζl k (W.getD k []))) (fun S k => by + split_ifs <;> simp [val_add]), val_zero, zero_add, map_sum] + refine sum_congr rfl fun k hk => ?_ + have hk' := mem_range.mp hk + split_ifs with hkL + · rw [map_zero] + have ht := node_mem_recoveryNodes hk' hkL + exact val_nodeTerm hirr B hB π hk' hkL hΔ rfl + (fun hq => by rw [mult_eq u h hk'] at hq ⊢; exact hpt _ ht hq) ζ hζ ζl hlen hζl _ _ + (hW k hk' hkL) + +/-- **Native recovery is correct modulo `2 ^ p`.** Under the hypotheses of +`NativeRecovery.val_recover`, if the recovered value agrees with an element `x` of `B` modulo +`2 ^ p` (as `TensorNetwork.exists_recoverFromValues_eq_add_of_children` gives for the +contraction), then native recovery computes the reduction of `π x` modulo `2 ^ p`. -/ +theorem val_recover_of_eq_add + (hirr : Irreducible ((monicOf fl).map (Int.castRingHom (ZMod 2)))) + (B : Subring K) (hB : ∀ c ∈ ℤ₍₂₎, algebraMap ℚ K c ∈ B) {p Δ : ℕ} + (π : B →+* Ring (p + Δ) fl) {a u h : ℕ} + (hΔ : (a + 1) * ∑ t ∈ recoveryNodes a, taperMult u h t ≤ Δ) (pt : ℤ → ℕ) + (hpt : ∀ t ∈ recoveryNodes a, 0 < taperMult u h t → + p + 6 * h * 2 ^ (a - padicValInt 2 t) + taperMult u h t * (a + 1) ≤ pt t) + {n : ℕ} (ζ : Fin n → B) (hζ : Pairwise fun k l => IsUnit (ζ k - ζ l)) + (ζl : List (List ℕ)) (hlen : ζl.length = n) + (hζl : ∀ i : Fin n, val (p + Δ) fl (ζl.getD i []) = π (ζ i)) (v : ℤ → Fin n → B) + (W : List (List (List ℕ))) + (hW : ∀ k < 2 ^ (a + 1), k ≠ 2 ^ a → 0 < mult a u h k → ∀ i : Fin n, ∃ c, + val (p + Δ) fl ((W.getD k []).getD i []) = π (v (node a k) i) + 2 ^ pt (node a k) * c) + (x : B) {b : K} (hb : b ∈ B) (hx : recoverFromValues B a u h ζ v = (x : K) + 2 ^ p * b) : + val p fl (recover p Δ fl a u h ζl W) = reduce (Nat.le_add_right p Δ) fl (π x) := by + rw [val_recover hirr B hB π hΔ pt hpt ζ hζ ζl hlen hζl v W hW (R := x + 2 ^ p * ⟨b, hb⟩) + (by rw [hx]; rfl), map_add, map_add, map_mul, map_mul, map_pow, map_pow, map_ofNat, + map_ofNat, show (2 : Ring p fl) ^ p = ((2 ^ p : ℕ) : Ring p fl) by push_cast; rfl, + natCast_two_pow, zero_mul, add_zero] + +end Hermite + end Cslib.Circuits.Frontier.NativeRecovery diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean index 5fc26599d..6331ce206 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/TaperedRecovery.lean @@ -135,6 +135,13 @@ noncomputable def taperedRecovery (a u h : ℕ) (G : ℤ → K[X]) : K := (hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) (2 ^ (a + 1)) t (G t)).eval (algebraMap ℚ K (-(t : ℚ) / 2 ^ (a + 1))) +theorem taperedRecovery_def (a u h : ℕ) (G : ℤ → K[X]) : + taperedRecovery a u h G = ∑ t ∈ recoveryNodes a, + (hermiteBasis (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) t).eval 0 * + (hermiteQuotient (recoveryNodes a) (fun q : ℤ => (q : K)) (taperMult u h) (2 ^ (a + 1)) t + (G t)).eval (algebraMap ℚ K (-(t : ℚ) / 2 ^ (a + 1))) := by + rw [taperedRecovery] + /-- `2 ^ n` as a unit of a `ℚ`-algebra. -/ private noncomputable def twoPowUnit (K : Type*) [CommRing K] [Algebra ℚ K] (n : ℕ) : Kˣ := Units.map (algebraMap ℚ K : ℚ →* K) (Units.mk0 ((2 : ℚ) ^ n) (by positivity)) diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean index c368f3ddc..dcc0b2ff2 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean @@ -36,6 +36,9 @@ example : (mult 1 1 1 1, mult 1 1 1 3, mult 1 1 1 0) = (1, 1, 0) := by decide `E_{-1}(0) F(-1) + E_1(0) F(1) = (4 + 6) / 2 = 5` modulo `8`, computing modulo `2 ^ (3 + 2)`. -/ example : recover 3 2 [1, 1] 1 1 1 [[0, 0]] [[], [[4, 0]], [], [[6, 0]]] = [5, 0] := by decide +/-- The same recovery with `Δ = (a + 1) ∑_t q_t = 4`, the shift that `val_recover` assumes. -/ +example : recover 3 4 [1, 1] 1 1 1 [[0, 0]] [[], [[4, 0]], [], [[6, 0]]] = [5, 0] := by decide + /-- The reciprocal of `E_t(t + α z)` is correct, for every ring homomorphism from `ℤ_(2)`. -/ example (hirr : Irreducible ((monicOf [1, 1]).map (Int.castRingHom (ZMod 2)))) (ρ : Valuation.integer (Rat.padicValuation 2) →+* Ring 5 [1, 1]) {i : ℕ} (hi : i < 2) : From 5280f8c663ae5ac1c5d088fa6eeba7e020aed0d5 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:19:08 -0400 Subject: [PATCH 35/38] feat(circuit/frontier): native recovery is polynomial-time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Certify `NativeRecovery.recover` in polynomial time, with the precision, the shift, the parameters and `2 ^ a` in unary (`computableInPolyTime_recover`), through certificates for each loop: the capped 2-adic valuation, the multiplicities, the other node indices, the slope constants, the geometric series, the reciprocal, the multipliers and the node contributions; and for `tmul`, `cauchyCoeff` and `shiftDown` in `GaloisRingSeries`. `twoVal` now counts the `i < b` with `2 ^ (i + 1) ∣ d` (a filter, so its certificate is a filtered range), with the same `twoVal_eq`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/NativeRecovery.lean | 463 +++++++++++++++++- .../PolyTime/GaloisRingSeries.lean | 130 +++++ .../Frontier/TensorNetworkNativeRecovery.lean | 9 + 3 files changed, 582 insertions(+), 20 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index 53f3c4940..34d46b193 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -9,6 +9,17 @@ module public import Complexity.Computability.Circuit.Frontier.TensorNetwork.ActiveCall public import Complexity.Computability.PolyTime.GaloisRingSeries +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.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 Mathlib.Data.List.GetD /-! @@ -53,6 +64,10 @@ of the node contributions over `B` (`NativeRecovery.two_pow_mul_recoverFromValue recovered value `recoverFromValues B a u h ζ v` lies in `B`, native recovery computes its image modulo `2 ^ p` (`NativeRecovery.val_recover`). +**Polynomial time.** Every loop is a fold over a range or over the other node indices, so native +recovery is polynomial-time with the precision, the shift, the parameters and `2 ^ a` in unary +(`NativeRecovery.computableInPolyTime_recover`). + ## Main definitions * `Cslib.Circuits.Frontier.NativeRecovery.slopeConst`: the constant `α / (t - q)`. @@ -86,6 +101,8 @@ modulo `2 ^ p` (`NativeRecovery.val_recover`). * `Cslib.Circuits.Frontier.NativeRecovery.val_recover`, `Cslib.Circuits.Frontier.NativeRecovery.val_recover_of_eq_add`: native recovery computes the mathematical recovery modulo `2 ^ p`. +* `Cslib.Circuits.Frontier.NativeRecovery.computableInPolyTime_recover`: native recovery is + polynomial-time. ## References @@ -107,26 +124,33 @@ local notation "ℤ₍₂₎" => Valuation.integer (Rat.padicValuation 2) /-- The node `t = k - 2 ^ a` of index `k`. -/ def node (a k : ℕ) : ℤ := (k : ℤ) - 2 ^ a -/-- The `2`-adic valuation of `d`, by at most `b` halvings: `v₂ d` when `d < 2 ^ b` -(`NativeRecovery.twoVal_eq`), and `0` for `d = 0`. -/ -def twoVal : ℕ → ℕ → ℕ - | 0, _ => 0 - | b + 1, d => if d ≠ 0 ∧ d % 2 = 0 then twoVal b (d / 2) + 1 else 0 - -theorem twoVal_eq : ∀ {b d : ℕ}, d < 2 ^ b → twoVal b d = padicValNat 2 d - | 0, d, hd => by rw [pow_zero] at hd; obtain rfl : d = 0 := by omega - simp [twoVal] - | b + 1, d, hd => by - rw [twoVal] - split_ifs with h - · obtain ⟨hd0, h2⟩ := h - have hd' : d = 2 * (d / 2) := by omega - rw [twoVal_eq (by rw [pow_succ] at hd; omega)] - conv_rhs => rw [hd'] - rw [padicValNat.mul (by norm_num) (by omega), padicValNat_self, add_comm] - · rcases Nat.eq_zero_or_pos d with rfl | hpos - · simp - · exact (padicValNat.eq_zero_of_not_dvd (by omega)).symm +/-- The `2`-adic valuation of `d`, capped at `b`: the number of `i < b` with `2 ^ (i + 1) ∣ d`, +and `0` for `d = 0`. It is `v₂ d` when `d < 2 ^ b` (`NativeRecovery.twoVal_eq`). -/ +def twoVal (b d : ℕ) : ℕ := + if d = 0 then 0 else ((List.range b).filter fun i => d % 2 ^ (i + 1) = 0).length + +theorem twoVal_eq {b d : ℕ} (hd : d < 2 ^ b) : twoVal b d = padicValNat 2 d := by + rw [twoVal] + split_ifs with h0 + · simp [h0] + set v := padicValNat 2 d + have hvb : v < b := by + have : 2 ^ v ≤ d := Nat.le_of_dvd (Nat.pos_of_ne_zero h0) pow_padicValNat_dvd + exact (Nat.pow_lt_pow_iff_right (by norm_num)).mp (this.trans_lt hd) + have key (m : ℕ) : ((List.range m).filter fun i => d % 2 ^ (i + 1) = 0).length = min m v := by + induction m with + | zero => simp + | succ m ih => + rw [List.range_succ, List.filter_append, List.length_append, ih] + have hiff : d % 2 ^ (m + 1) = 0 ↔ m + 1 ≤ v := by + rw [← Nat.dvd_iff_mod_eq_zero] + exact padicValNat_dvd_iff_le h0 + by_cases hm : m + 1 ≤ v + · simp [hiff.mpr hm] + omega + · simp [mt hiff.mp hm] + omega + rw [key, min_eq_right hvb.le] /-- The multiplicity `q_t = h (u - v₂ t)` of the node of index `k`. -/ def mult (a u h k : ℕ) : ℕ := h * (u - twoVal (a + 1) (k - 2 ^ a + (2 ^ a - k))) @@ -700,6 +724,7 @@ theorem exists_pval_interp_eq_add (hirr : Irreducible ((monicOf fl).map (Int.cas /-! ### The Hermite quotient over `B` -/ /-- A fold of multiplications is a list product. -/ +@[to_additive /-- A fold of additions is a list sum. -/] theorem foldl_mul_eq {M ι : Type*} [CommMonoid M] (g : ι → M) (l : List ι) (c : M) : l.foldl (fun x y => x * g y) c = c * (l.map g).prod := by induction l generalizing c with @@ -958,4 +983,402 @@ theorem val_recover_of_eq_add end Hermite +/-! ### Polynomial time -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {α : Type} {encIn : α ↪ List Bool} + +private theorem lengthPolyTime {β : Type} {encB : β ↪ List Bool} {l : α → List β} + (hl : ComputableInPolyTime l encIn (encList encB)) : + ComputableInPolyTime (fun x => (l x).length) encIn encUnary := + (ComputableInPolyTime.length_list encB).comp hl + +private theorem getDPolyTime {P : α → List (List ℕ)} {k : α → ℕ} + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => (P x).getD (k x) []) encIn (encList encNat) := + (ComputableInPolyTime.listGetD (encA := encList encNat) []).comp (hP.prod hk) + +/-- A unary natural as a one-coordinate list. -/ +private theorem singletonPolyTime {n : α → ℕ} (hn : ComputableInPolyTime n encIn encNat) : + ComputableInPolyTime (fun x => [n x]) encIn (encList encNat) := + ComputableInPolyTime.cons' hn (ComputableInPolyTime.const []) + +private theorem polyBounded_unary {n : α → ℕ} (hn : ComputableInPolyTime n encIn encUnary) : + PolyBounded (fun x => (encIn x).length) n := by + simpa using ComputableInPolyTime.polyBounded_length hn + +/-- The length of a polynomial with `J` small coefficients is polynomially bounded. -/ +private theorem polyBounded_polyLength {p J : α → ℕ} {fl : α → List ℕ} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hJ : ComputableInPolyTime J encIn encUnary) : + PolyBounded (fun x => (encIn x).length) + (fun x => J x * (2 * ((fl x).length * (2 * (p x + 1) + 1)) + 1)) := by + have hpl := polyBounded_unary hp + have hfll : PolyBounded (fun x => (encIn x).length) fun x => (fl x).length := + (lengthPolyTime hfl).polyBounded_length.mono fun x => by simp + exact (polyBounded_unary hJ).mul (((PolyBounded.const 2).mul (hfll.mul (((PolyBounded.const 2).mul + (hpl.add (PolyBounded.const 1))).add (PolyBounded.const 1)))).add (PolyBounded.const 1)) + +private theorem length_encList_mul_le (N : ℕ) (fl c d : List ℕ) : + (encList encNat (mul N fl c d)).length ≤ fl.length * (2 * N + 1) := + length_encList_le (length_mul N fl c d) fun _ h => lt_of_mem_mul h + +private theorem length_encList_add_le (N : ℕ) (fl c d : List ℕ) : + (encList encNat (add N fl c d)).length ≤ fl.length * (2 * N + 1) := + length_encList_le (length_add N fl c d) fun _ h => lt_of_mem_add h + +/-- **The capped `2`-adic valuation is polynomial-time**, in unary. -/ +@[fun_prop] +theorem computableInPolyTime_twoVal {b d : α → ℕ} (hb : ComputableInPolyTime b encIn encUnary) + (hd : ComputableInPolyTime d encIn encUnary) : + ComputableInPolyTime (fun x => twoVal (b x) (d x)) encIn encUnary := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have htest : ComputableInPolyTime (fun q : α × ℕ => d q.1 % 2 ^ (q.2 + 1) == 0) + (encPair encIn encUnary) encBool := + ComputableInPolyTime.beq (ComputableInPolyTime.binary_mod.comp + ((ComputableInPolyTime.unary_to_binary.comp (hd.comp h1)).prod + (ComputableInPolyTime.binary_two_pow.comp + (ComputableInPolyTime.unary_add' h2 (ComputableInPolyTime.const 1))))) + (ComputableInPolyTime.const 0) + refine (ComputableInPolyTime.ite (ComputableInPolyTime.beq hd (ComputableInPolyTime.const 0)) + (ComputableInPolyTime.const 0) (lengthPolyTime (ComputableInPolyTime.filterWithInput + (test := fun x i => d x % 2 ^ (i + 1) == 0) (ComputableInPolyTime.range.comp hb) + htest))).of_enc_eq fun x => ?_ + by_cases h : d x = 0 <;> simp [twoVal, h, beq_eq_decide] + +/-- `2 ^ (a + 1)` in unary, from `2 ^ a` in unary. -/ +private theorem twoPowSuccPolyTime {a : α → ℕ} + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) : + ComputableInPolyTime (fun x => 2 ^ (a x + 1)) encIn encUnary := + (ComputableInPolyTime.unary_add' hL hL).of_enc_eq fun x => by rw [pow_succ, mul_two] + +/-- **The multiplicities are polynomial-time**, in unary, given `2 ^ a` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_mult {a u h k : α → ℕ} + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => mult (a x) (u x) (h x) (k x)) encIn encUnary := + ComputableInPolyTime.unary_mul' hh (ComputableInPolyTime.unary_sub' hu + (computableInPolyTime_twoVal (ComputableInPolyTime.unary_add' ha (ComputableInPolyTime.const 1)) + (ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_sub' hk hL) + (ComputableInPolyTime.unary_sub' hL hk)))) + +/-- **The other node indices are polynomial-time**, given `2 ^ a` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_others {a k : α → ℕ} + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => others (a x) (k x)) encIn (encList encUnary) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + refine (ComputableInPolyTime.filterWithInput + (test := fun x k' => !(k' == 2 ^ a x) && !(k' == k x)) + (ComputableInPolyTime.range.comp (twoPowSuccPolyTime hL)) + (ComputableInPolyTime.bool_and' + (ComputableInPolyTime.bool_not.comp (ComputableInPolyTime.beq h2 (hL.comp h1))) + (ComputableInPolyTime.bool_not.comp (ComputableInPolyTime.beq h2 (hk.comp h1))))).of_enc_eq + fun x => ?_ + congr 1 + +/-- **The total multiplicity of the other nodes is polynomial-time**, in unary. -/ +@[fun_prop] +theorem computableInPolyTime_multSum {a u h k : α → ℕ} + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => multSum (a x) (u x) (h x) (k x)) encIn encUnary := by + have hq := ComputableInPolyTime.fst (encPair encIn encUnary) encUnary + have hx := (ComputableInPolyTime.fst encIn encUnary).comp hq + have hs := (ComputableInPolyTime.snd encIn encUnary).comp hq + have hk' := ComputableInPolyTime.snd (encPair encIn encUnary) encUnary + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x s k' => s + mult (a x) (u x) (h x) k') (ComputableInPolyTime.const 0) + ⟨computableInPolyTime_others hL hk, ComputableInPolyTime.unary_add' hs + (computableInPolyTime_mult (ha.comp hx) (hL.comp hx) (hu.comp hx) (hh.comp hx) hk'), + inferInstance, inferInstance⟩ + ⟨(polyBounded_unary hh).mul (polyBounded_unary hu), fun x s k' _ => by + have : mult (a x) (u x) (h x) k' ≤ h x * u x := + Nat.mul_le_mul_left _ (Nat.sub_le _ _) + simp only [encUnary_apply, List.length_replicate] + omega⟩ + refine hfold.of_enc_eq fun x => ?_ + rw [multSum, foldl_add_eq, zero_add] + +/-- **Integer differences as constants are polynomial-time**, with `x, y` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_diffConst {N x y : α → ℕ} {fl : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hx : ComputableInPolyTime x encIn encUnary) (hy : ComputableInPolyTime y encIn encUnary) : + ComputableInPolyTime (fun z => diffConst (N z) (fl z) (x z) (y z)) encIn (encList encNat) := + ComputableInPolyTime.ite (ComputableInPolyTime.unary_lt' hx hy) + (computableInPolyTime_neg hN hfl (singletonPolyTime + (ComputableInPolyTime.unary_to_binary.comp (ComputableInPolyTime.unary_sub' hy hx)))) + (singletonPolyTime (ComputableInPolyTime.unary_to_binary.comp + (ComputableInPolyTime.unary_sub' hx hy))) |>.of_enc_eq fun z => by simp [diffConst] + +/-- **The slope constants are polynomial-time**, with `a, x, y` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_slopeConst {N a x y : α → ℕ} {fl : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hx : ComputableInPolyTime x encIn encUnary) (hy : ComputableInPolyTime y encIn encUnary) : + ComputableInPolyTime (fun z => slopeConst (N z) (fl z) (a z) (x z) (y z)) encIn + (encList encNat) := by + have hd := ComputableInPolyTime.unary_add' (ComputableInPolyTime.unary_sub' hx hy) + (ComputableInPolyTime.unary_sub' hy hx) + have ha1 := ComputableInPolyTime.unary_add' ha (ComputableInPolyTime.const 1) + have hv := computableInPolyTime_twoVal ha1 hd + have hw := computableInPolyTime_mul hN hfl + (singletonPolyTime (ComputableInPolyTime.binary_two_pow.comp + (ComputableInPolyTime.unary_sub' ha1 hv))) + (computableInPolyTime_inv hN hfl (singletonPolyTime (ComputableInPolyTime.binary_div.comp + ((ComputableInPolyTime.unary_to_binary.comp hd).prod + (ComputableInPolyTime.binary_two_pow.comp hv))))) + exact (ComputableInPolyTime.ite (ComputableInPolyTime.unary_lt' hx hy) + (computableInPolyTime_neg hN hfl hw) hw).of_enc_eq fun z => by simp [slopeConst] + +/-- **Truncated geometric series are polynomial-time**, with `J` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_geom {N J : α → ℕ} {fl c : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hJ : ComputableInPolyTime J encIn encUnary) + (hc : ComputableInPolyTime c encIn (encList encNat)) : + ComputableInPolyTime (fun x => geom (N x) (fl x) (J x) (c x)) encIn + (encList (encList encNat)) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp hJ) + (computableInPolyTime_pow (hN.comp h1) (hfl.comp h1) + (computableInPolyTime_neg (hN.comp h1) (hfl.comp h1) (hc.comp h1)) h2) + +theorem mulGeomPow_succ (N : ℕ) (fl : List ℕ) (J : ℕ) (c : List ℕ) (m : ℕ) + (R : List (List ℕ)) : + mulGeomPow N fl J c (m + 1) R = tmul N fl J (mulGeomPow N fl J c m R) (geom N fl J c) := by + rw [mulGeomPow, List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + ← mulGeomPow] + +/-- A multiplication by a power of a geometric series is the input or a truncated product. -/ +theorem mulGeomPow_eq_or (N : ℕ) (fl : List ℕ) (J : ℕ) (c : List ℕ) (m : ℕ) + (R : List (List ℕ)) : mulGeomPow N fl J c m R = R ∨ + ∃ R', mulGeomPow N fl J c m R = tmul N fl J R' (geom N fl J c) := by + rcases m with _ | m + · exact Or.inl (by simp [mulGeomPow]) + · exact Or.inr ⟨_, mulGeomPow_succ N fl J c m R⟩ + +/-- The encoded length of a truncated product is bounded. -/ +private theorem length_encPoly_tmul_le (N : ℕ) (fl : List ℕ) (J : ℕ) (P R : List (List ℕ)) : + (encList (encList encNat) (tmul N fl J P R)).length ≤ + J * (2 * (fl.length * (2 * (N + 1) + 1)) + 1) := + length_encPoly_le (length_tmul J P R).le fun _ hc => small_of_mem_tmul hc + +/-- **Multiplication by a power of a geometric series is polynomial-time**, with `J, m` in +unary. -/ +@[fun_prop] +theorem computableInPolyTime_mulGeomPow {N J m : α → ℕ} {fl c : α → List ℕ} + {R : α → List (List ℕ)} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hJ : ComputableInPolyTime J encIn encUnary) + (hc : ComputableInPolyTime c encIn (encList encNat)) + (hm : ComputableInPolyTime m encIn encUnary) + (hR : ComputableInPolyTime R encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => mulGeomPow (N x) (fl x) (J x) (c x) (m x) (R x)) encIn + (encList (encList encNat)) := by + let encS := encList (encList encNat) + have hq := ComputableInPolyTime.fst (encPair encIn encS) encUnary + have hx := (ComputableInPolyTime.fst encIn encS).comp hq + have hP := (ComputableInPolyTime.snd encIn encS).comp hq + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x R _ => tmul (N x) (fl x) (J x) R (geom (N x) (fl x) (J x) (c x))) hR + ⟨ComputableInPolyTime.range.comp hm, computableInPolyTime_tmul (hN.comp hx) (hfl.comp hx) + (hJ.comp hx) hP (computableInPolyTime_geom (hN.comp hx) (hfl.comp hx) (hJ.comp hx) + (hc.comp hx)), inferInstance, inferInstance⟩ + ⟨polyBounded_polyLength hN hfl hJ, fun x R _ _ => by + have := length_encPoly_tmul_le (N x) (fl x) (J x) R (geom (N x) (fl x) (J x) (c x)) + omega⟩ + +/-- **The truncated reciprocal of `E_t(t + α z)` is polynomial-time**, given `2 ^ a` in +unary. -/ +@[fun_prop] +theorem computableInPolyTime_recip {N a u h k J : α → ℕ} {fl : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) (hJ : ComputableInPolyTime J encIn encUnary) : + ComputableInPolyTime (fun x => recip (N x) (fl x) (a x) (u x) (h x) (k x) (J x)) encIn + (encList (encList encNat)) := by + let encS := encList (encList encNat) + have hq := ComputableInPolyTime.fst (encPair encIn encS) encUnary + have hx := (ComputableInPolyTime.fst encIn encS).comp hq + have hP := (ComputableInPolyTime.snd encIn encS).comp hq + have hk' := ComputableInPolyTime.snd (encPair encIn encS) encUnary + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x R k' => mulGeomPow (N x) (fl x) (J x) (slopeConst (N x) (fl x) (a x) (k x) k') + (mult (a x) (u x) (h x) k') R) + (ComputableInPolyTime.cons' (computableInPolyTime_one hfl) (ComputableInPolyTime.const [])) + ⟨computableInPolyTime_others hL hk, computableInPolyTime_mulGeomPow (hN.comp hx) + (hfl.comp hx) (hJ.comp hx) (computableInPolyTime_slopeConst (hN.comp hx) (hfl.comp hx) + (ha.comp hx) (hk.comp hx) hk') + (computableInPolyTime_mult (ha.comp hx) (hL.comp hx) (hu.comp hx) (hh.comp hx) hk') hP, + inferInstance, inferInstance⟩ + ⟨polyBounded_polyLength hN hfl hJ, fun x R k' _ => by + rcases mulGeomPow_eq_or (N x) (fl x) (J x) (slopeConst (N x) (fl x) (a x) (k x) k') + (mult (a x) (u x) (h x) k') R with h0 | ⟨R', h1⟩ + · rw [h0]; omega + · have := length_encPoly_tmul_le (N x) (fl x) (J x) R' + (geom (N x) (fl x) (J x) (slopeConst (N x) (fl x) (a x) (k x) k')) + rw [h1]; omega⟩ + +/-- **The products `∏_q (-q c_q) ^ (q_q)` are polynomial-time**, given `2 ^ a` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_basisNum {N a u h k : α → ℕ} {fl : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => basisNum (N x) (fl x) (a x) (u x) (h x) (k x)) encIn + (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 + have hk' := ComputableInPolyTime.snd (encPair encIn (encList encNat)) encUnary + have hN' := hN.comp hx + have hfl' := hfl.comp hx + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x acc k' => mul (N x) (fl x) acc (pow (N x) (fl x) + (mul (N x) (fl x) (diffConst (N x) (fl x) (2 ^ a x) k') + (slopeConst (N x) (fl x) (a x) (k x) k')) (mult (a x) (u x) (h x) k'))) + (computableInPolyTime_one hfl) + ⟨computableInPolyTime_others hL hk, computableInPolyTime_mul hN' hfl' hacc + (computableInPolyTime_pow hN' hfl' (computableInPolyTime_mul hN' hfl' + (computableInPolyTime_diffConst hN' hfl' (hL.comp hx) hk') + (computableInPolyTime_slopeConst hN' hfl' (ha.comp hx) (hk.comp hx) hk')) + (computableInPolyTime_mult (ha.comp hx) (hL.comp hx) (hu.comp hx) (hh.comp hx) hk')), + inferInstance, inferInstance⟩ + ⟨polyBounded_reducedLength hN hfl, fun x acc k' _ => + (length_encList_mul_le _ _ _ _).trans (Nat.le_add_left _ _)⟩ + +/-- **The multipliers are polynomial-time**, given `2 ^ a` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_coeffMult {N a u h Δ k j : α → ℕ} {fl : α → List ℕ} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hΔ : ComputableInPolyTime Δ encIn encUnary) (hk : ComputableInPolyTime k encIn encUnary) + (hj : ComputableInPolyTime j encIn encUnary) : + ComputableInPolyTime (fun x => coeffMult (N x) (fl x) (a x) (u x) (h x) (Δ x) (k x) (j x)) + encIn (encList encNat) := + computableInPolyTime_mul hN hfl (computableInPolyTime_mul hN hfl + (singletonPolyTime (ComputableInPolyTime.binary_two_pow.comp + (ComputableInPolyTime.unary_sub' hΔ (ComputableInPolyTime.unary_mul' + (ComputableInPolyTime.unary_add' ha (ComputableInPolyTime.const 1)) + (ComputableInPolyTime.unary_add' (computableInPolyTime_multSum ha hL hu hh hk) hj))))) + (computableInPolyTime_basisNum hN hfl ha hL hu hh hk)) + (computableInPolyTime_pow hN hfl (computableInPolyTime_diffConst hN hfl hL hk) hj) + +/-- **The contribution of a node is polynomial-time**, given `2 ^ a` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_nodeTerm {N a u h Δ k : α → ℕ} {fl : α → List ℕ} + {ζ w : α → List (List ℕ)} + (hN : ComputableInPolyTime N encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hΔ : ComputableInPolyTime Δ encIn encUnary) + (hζ : ComputableInPolyTime ζ encIn (encList (encList encNat))) + (hk : ComputableInPolyTime k encIn encUnary) + (hw : ComputableInPolyTime w encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => nodeTerm (N x) (fl x) (a x) (u x) (h x) (Δ x) (ζ x) (k x) (w x)) + encIn (encList encNat) := by + have hJ := computableInPolyTime_mult ha hL hu hh hk + have hQ := computableInPolyTime_tmul hN hfl hJ (computableInPolyTime_interp hN hfl hζ hw) + (computableInPolyTime_recip hN hfl ha hL hu hh hk hJ) + have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) encUnary + have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq + have hS := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + have hj := ComputableInPolyTime.snd (encPair encIn (encList encNat)) encUnary + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x S j => add (N x) (fl x) S (mul (N x) (fl x) + (coeffMult (N x) (fl x) (a x) (u x) (h x) (Δ x) (k x) j) + ((tmul (N x) (fl x) (mult (a x) (u x) (h x) (k x)) (interp (N x) (fl x) (ζ x) (w x)) + (recip (N x) (fl x) (a x) (u x) (h x) (k x) (mult (a x) (u x) (h x) (k x)))).getD j []))) + (computableInPolyTime_zero hfl) + ⟨ComputableInPolyTime.range.comp hJ, computableInPolyTime_add (hN.comp hx) (hfl.comp hx) hS + (computableInPolyTime_mul (hN.comp hx) (hfl.comp hx) + (computableInPolyTime_coeffMult (hN.comp hx) (hfl.comp hx) (ha.comp hx) (hL.comp hx) + (hu.comp hx) (hh.comp hx) (hΔ.comp hx) (hk.comp hx) hj) + (getDPolyTime (hQ.comp hx) hj)), inferInstance, inferInstance⟩ + ⟨polyBounded_reducedLength hN hfl, fun x S j _ => + (length_encList_add_le _ _ _ _).trans (Nat.le_add_left _ _)⟩ + +/-- **Native tapered recovery is polynomial-time**, with the precision `p`, the shift `Δ`, the +parameters `a, u, h` and `2 ^ a` in unary, and the points and values as coefficient lists. -/ +@[fun_prop] +theorem computableInPolyTime_recover {p Δ a u h : α → ℕ} {fl : α → List ℕ} + {ζ : α → List (List ℕ)} {W : α → List (List (List ℕ))} + (hp : ComputableInPolyTime p encIn encUnary) (hΔ : ComputableInPolyTime Δ encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (ha : ComputableInPolyTime a encIn encUnary) + (hL : ComputableInPolyTime (fun x => 2 ^ a x) encIn encUnary) + (hu : ComputableInPolyTime u encIn encUnary) (hh : ComputableInPolyTime h encIn encUnary) + (hζ : ComputableInPolyTime ζ encIn (encList (encList encNat))) + (hW : ComputableInPolyTime W encIn (encList (encList (encList encNat)))) : + ComputableInPolyTime (fun x => recover (p x) (Δ x) (fl x) (a x) (u x) (h x) (ζ x) (W x)) + encIn (encList encNat) := by + have hN := ComputableInPolyTime.unary_add' hp hΔ + have hq := ComputableInPolyTime.fst (encPair encIn (encList encNat)) encUnary + have hx := (ComputableInPolyTime.fst encIn (encList encNat)).comp hq + have hS := (ComputableInPolyTime.snd encIn (encList encNat)).comp hq + have hk := ComputableInPolyTime.snd (encPair encIn (encList encNat)) encUnary + have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × ℕ => + if (q.2 == 2 ^ a q.1.1) = true then q.1.2 else add (p q.1.1 + Δ q.1.1) (fl q.1.1) q.1.2 + (nodeTerm (p q.1.1 + Δ q.1.1) (fl q.1.1) (a q.1.1) (u q.1.1) (h q.1.1) (Δ q.1.1) + (ζ q.1.1) q.2 ((W q.1.1).getD q.2 []))) + (encPair (encPair encIn (encList encNat)) encUnary) (encList encNat) := + ComputableInPolyTime.ite (ComputableInPolyTime.beq hk (hL.comp hx)) hS + (computableInPolyTime_add (hN.comp hx) (hfl.comp hx) hS + (computableInPolyTime_nodeTerm (hN.comp hx) (hfl.comp hx) (ha.comp hx) (hL.comp hx) + (hu.comp hx) (hh.comp hx) (hΔ.comp hx) (hζ.comp hx) hk + ((ComputableInPolyTime.listGetD (encA := encList (encList encNat)) []).comp + ((hW.comp hx).prod hk)))) + have hfold := ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x S k => if (k == 2 ^ a x) = true then S else add (p x + Δ x) (fl x) S + (nodeTerm (p x + Δ x) (fl x) (a x) (u x) (h x) (Δ x) (ζ x) k ((W x).getD k []))) + (computableInPolyTime_zero hfl) + ⟨ComputableInPolyTime.range.comp (twoPowSuccPolyTime hL), hstep, inferInstance, + inferInstance⟩ + ⟨polyBounded_reducedLength hN hfl, fun x S k _ => by + split + · omega + · exact (length_encList_add_le _ _ _ _).trans (Nat.le_add_left _ _)⟩ + refine (computableInPolyTime_shiftDown hΔ hfold).of_enc_eq fun x => ?_ + simp only [recover, beq_iff_eq, Function.comp_apply] + +end PolyTime + end Cslib.Circuits.Frontier.NativeRecovery diff --git a/Complexity/Computability/PolyTime/GaloisRingSeries.lean b/Complexity/Computability/PolyTime/GaloisRingSeries.lean index 7f3935612..455838f14 100644 --- a/Complexity/Computability/PolyTime/GaloisRingSeries.lean +++ b/Complexity/Computability/PolyTime/GaloisRingSeries.lean @@ -9,6 +9,15 @@ module public import Complexity.Computability.PolyTime.GaloisRingInterpolation public import Complexity.Computability.PolyTime.GaloisRingUnits +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.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.Data.List.GetD /-! @@ -28,6 +37,10 @@ the contraction algorithm of [OpenAI2026ETH] (Appendix D.4.4, pp. 60–61). * **Truncated products** (`GaloisRingArith.tmul`) of polynomials in `z` over `A_p`: the first `J` coefficients of a product, by the Cauchy formula (`GaloisRingArith.val_tmul`). +Truncated products and `GaloisRingArith.shiftDown` are polynomial-time, with the precision, the +length and the shift in unary (`GaloisRingArith.computableInPolyTime_tmul`, +`GaloisRingArith.computableInPolyTime_shiftDown`). + ## Main definitions * `Cslib.GaloisRingArith.reduce`: reduction to a lower precision. @@ -39,6 +52,8 @@ the contraction algorithm of [OpenAI2026ETH] (Appendix D.4.4, pp. 60–61). * `Cslib.GaloisRingArith.mod_eq_of_val_eq`, `Cslib.GaloisRingArith.exists_val_eq`: coordinates. * `Cslib.GaloisRingArith.val_shiftDown`: cancelling a common power of two. * `Cslib.GaloisRingArith.val_tmul`: truncated products are correct. +* `Cslib.GaloisRingArith.computableInPolyTime_tmul`, + `Cslib.GaloisRingArith.computableInPolyTime_shiftDown`: polynomial time. ## References @@ -234,4 +249,119 @@ theorem isUnit_natCast_of_odd {o : ℕ} (ho : Odd o) : IsUnit (o : Ring p fl) := have := (ZMod.unitOfCoprime o hcop).isUnit.map (algebraMap (ZMod (2 ^ p)) (Ring p fl)) simpa using this +/-! ### Polynomial time -/ + +section PolyTime + +open Turing.MultiTapeTM + +variable {α : Type} {encIn : α ↪ List Bool} + +private theorem lengthPolyTime {β : Type} {encB : β ↪ List Bool} {l : α → List β} + (hl : ComputableInPolyTime l encIn (encList encB)) : + ComputableInPolyTime (fun x => (l x).length) encIn encUnary := + (ComputableInPolyTime.length_list encB).comp hl + +private theorem getDPolyTime {P : α → List (List ℕ)} {k : α → ℕ} + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hk : ComputableInPolyTime k encIn encUnary) : + ComputableInPolyTime (fun x => (P x).getD (k x) []) encIn (encList encNat) := + (ComputableInPolyTime.listGetD (encA := encList encNat) []).comp (hP.prod hk) + +/-- The zero element is polynomial-time. -/ +theorem computableInPolyTime_zero {fl : α → List ℕ} + (hfl : ComputableInPolyTime fl encIn (encList encNat)) : + ComputableInPolyTime (fun x => zero (fl x)) encIn (encList encNat) := + computableInPolyTime_tabulate (g := fun _ _ => 0) (lengthPolyTime hfl) + (ComputableInPolyTime.const 0) + +/-- The length `e (2 p + 1)` of a reduced coefficient list is polynomially bounded. -/ +theorem polyBounded_reducedLength {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 : PolyBounded (fun x => (encIn x).length) p := by + simpa using (ComputableInPolyTime.polyBounded_length hp) + have hfll : PolyBounded (fun x => (encIn x).length) fun x => (fl x).length := + (lengthPolyTime hfl).polyBounded_length.mono fun x => by simp + exact hfll.mul (((PolyBounded.const 2).mul hpl).add (PolyBounded.const 1)) + +/-- **Division of the coordinates by `2 ^ Δ` is polynomial-time**, with `Δ` in unary. -/ +@[fun_prop] +theorem computableInPolyTime_shiftDown {Δ : α → ℕ} {c : α → List ℕ} + (hΔ : ComputableInPolyTime Δ encIn encUnary) + (hc : ComputableInPolyTime c encIn (encList encNat)) : + ComputableInPolyTime (fun x => shiftDown (Δ x) (c x)) encIn (encList encNat) := + ComputableInPolyTime.mapWithInput hc (ComputableInPolyTime.binary_div.comp + ((ComputableInPolyTime.snd encIn encNat).prod (ComputableInPolyTime.binary_two_pow.comp + (hΔ.comp (ComputableInPolyTime.fst encIn encNat))))) + +theorem lt_of_mem_cauchyCoeff {P R : List (List ℕ)} {j x : ℕ} (hx : x ∈ cauchyCoeff p fl P R j) : + x < 2 ^ p := by + rw [cauchyCoeff, List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil] at hx + exact lt_of_mem_add hx + +theorem length_cauchyCoeff (P R : List (List ℕ)) (j : ℕ) : + (cauchyCoeff p fl P R j).length = fl.length := by + rw [cauchyCoeff, List.range_succ, List.foldl_append, List.foldl_cons, List.foldl_nil, + length_add] + +/-- **Cauchy coefficients are polynomial-time**, with the precision and the index in unary. -/ +@[fun_prop] +theorem computableInPolyTime_cauchyCoeff {p j : α → ℕ} {fl : α → List ℕ} + {P R : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hR : ComputableInPolyTime R encIn (encList (encList encNat))) + (hj : ComputableInPolyTime j encIn encUnary) : + ComputableInPolyTime (fun x => cauchyCoeff (p x) (fl x) (P x) (R x) (j x)) encIn + (encList encNat) := by + have hstep : ComputableInPolyTime (fun q : (α × List ℕ) × ℕ => + add (p q.1.1) (fl q.1.1) q.1.2 (mul (p q.1.1) (fl q.1.1) ((P q.1.1).getD q.2 []) + ((R q.1.1).getD (j q.1.1 - q.2) []))) + (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 + have hi := ComputableInPolyTime.snd (encPair encIn (encList encNat)) encUnary + exact computableInPolyTime_add (hp.comp hx) (hfl.comp hx) hacc + (computableInPolyTime_mul (hp.comp hx) (hfl.comp hx) (getDPolyTime (hP.comp hx) hi) + (getDPolyTime (hR.comp hx) (ComputableInPolyTime.unary_sub' (hj.comp hx) hi))) + exact ComputableInPolyTime.foldlWithInput_of_bounded_growth + (encA := encUnary) (encL := encList encUnary) + (step := fun x acc i => add (p x) (fl x) acc (mul (p x) (fl x) ((P x).getD i []) + ((R x).getD (j x - i) []))) + (computableInPolyTime_zero hfl) + ⟨ComputableInPolyTime.range.comp + (ComputableInPolyTime.unary_add' hj (ComputableInPolyTime.const 1)), + hstep, inferInstance, inferInstance⟩ + ⟨polyBounded_reducedLength hp hfl, fun x acc i _ => by + have := length_encList_le (length_add (p x) (fl x) acc (mul (p x) (fl x) ((P x).getD i []) + ((R x).getD (j x - i) []))) fun _ h => lt_of_mem_add h + omega⟩ + +/-- **Truncated products are polynomial-time**, with the precision and the length in unary. -/ +@[fun_prop] +theorem computableInPolyTime_tmul {p J : α → ℕ} {fl : α → List ℕ} {P R : α → List (List ℕ)} + (hp : ComputableInPolyTime p encIn encUnary) + (hfl : ComputableInPolyTime fl encIn (encList encNat)) + (hJ : ComputableInPolyTime J encIn encUnary) + (hP : ComputableInPolyTime P encIn (encList (encList encNat))) + (hR : ComputableInPolyTime R encIn (encList (encList encNat))) : + ComputableInPolyTime (fun x => tmul (p x) (fl x) (J x) (P x) (R x)) encIn + (encList (encList encNat)) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + exact ComputableInPolyTime.mapWithInput (ComputableInPolyTime.range.comp hJ) + (computableInPolyTime_cauchyCoeff (hp.comp h1) (hfl.comp h1) (hP.comp h1) (hR.comp h1) h2) + +/-- Every coefficient of a truncated product is small. -/ +theorem small_of_mem_tmul {J : ℕ} {P R : List (List ℕ)} {c : List ℕ} + (hc : c ∈ tmul p fl J P R) : Small p fl c := by + obtain ⟨j, -, rfl⟩ := List.mem_map.mp hc + exact small_of_lt (length_cauchyCoeff P R j) fun _ h => lt_of_mem_cauchyCoeff h + +end PolyTime + end Cslib.GaloisRingArith diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean index dcc0b2ff2..35cced1e5 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean @@ -28,6 +28,9 @@ example : tmul 3 [1, 1] 2 [[1, 0], [1, 0]] [[1, 0], [1, 0]] = [[1, 0], [2, 0]] : -- Cancelling `2`: `6 + 2 U = 2 (3 + U)`. example : shiftDown 1 [6, 2] = [3, 1] := by decide +-- The capped `2`-adic valuation: `v₂ 12 = 2`, `v₂ 5 = 0`, and `0` at `0`. +example : (twoVal 4 12, twoVal 4 5, twoVal 4 0) = (2, 0, 0) := by decide + -- With `u = h = 1`, the odd nodes `t = ±1` have multiplicity `1` and `t = -2` has `0`. example : (mult 1 1 1 1, mult 1 1 1 3, mult 1 1 1 0) = (1, 1, 0) := by decide @@ -52,4 +55,10 @@ example : ((gamma 1 1 1 2 1 0 : Valuation.integer (Rat.padicValuation 2)) : ℚ) (node 1 1)).eval 0 * (-(node 1 1 : ℚ) / 2 ^ (1 + 1)) ^ 0 := coe_gamma (by norm_num) (by decide) +/-- The capped `2`-adic valuation is polynomial-time in unary. -/ +example : Turing.MultiTapeTM.ComputableInPolyTime (fun n : ℕ => twoVal n n) + Turing.MultiTapeTM.encUnary Turing.MultiTapeTM.encUnary := + computableInPolyTime_twoVal Turing.MultiTapeTM.ComputableInPolyTime.id' + Turing.MultiTapeTM.ComputableInPolyTime.id' + end ComplexityTests.TensorNetworkNativeRecovery From 6f54c2ddb83a021f4f67304dc31259d580b0311f Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:30:46 -0400 Subject: [PATCH 36/38] fix(circuit/frontier): terminal rewrites in twoVal_eq Replace the two non-terminal `simp` calls flagged by source hygiene with explicit rewrites. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../Circuit/Frontier/TensorNetwork/NativeRecovery.lean | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index 34d46b193..daa823e54 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -145,10 +145,11 @@ theorem twoVal_eq {b d : ℕ} (hd : d < 2 ^ b) : twoVal b d = padicValNat 2 d := have hiff : d % 2 ^ (m + 1) = 0 ↔ m + 1 ≤ v := by rw [← Nat.dvd_iff_mod_eq_zero] exact padicValNat_dvd_iff_le h0 + rw [List.filter_singleton] by_cases hm : m + 1 ≤ v - · simp [hiff.mpr hm] + · rw [decide_eq_true (hiff.mpr hm), Bool.cond_true, List.length_singleton] omega - · simp [mt hiff.mp hm] + · rw [decide_eq_false (mt hiff.mp hm), Bool.cond_false, List.length_nil] omega rw [key, min_eq_right hvb.le] From b1ee3e5bda2ca3923b4ff2349a326436ae5f48a1 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:33:41 -0400 Subject: [PATCH 37/38] feat(circuit/frontier): the value table of native recovery `NativeRecovery.storeValue` appends a value to the row of a node index in the table that `recover` reads: the accumulator update of a recursive call once the value at an interpolation point is complete. Storing a sequence of values gives each node index its values in order (`getD_foldl_storeValue`), in polynomial time (`computableInPolyTime_storeValue`). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../TensorNetwork/NativeRecovery.lean | 70 +++++++++++++++++++ .../Frontier/TensorNetworkNativeRecovery.lean | 5 ++ 2 files changed, 75 insertions(+) diff --git a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean index daa823e54..48f134989 100644 --- a/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean +++ b/Complexity/Computability/Circuit/Frontier/TensorNetwork/NativeRecovery.lean @@ -74,6 +74,8 @@ recovery is polynomial-time with the precision, the shift, the parameters and `2 * `Cslib.Circuits.Frontier.NativeRecovery.recip`: the truncated reciprocal of `E_t(t + α z)`. * `Cslib.Circuits.Frontier.NativeRecovery.coeffMult`: the multipliers. * `Cslib.Circuits.Frontier.NativeRecovery.recover`: native tapered recovery. +* `Cslib.Circuits.Frontier.NativeRecovery.storeValue`: storing a value in the table that `recover` + reads, the accumulator update of a recursive call. * `Cslib.Circuits.Frontier.NativeRecovery.recipSeries`, `Cslib.Circuits.Frontier.NativeRecovery.gamma`: the reciprocal and the multipliers over `ℤ_(2)`. @@ -103,6 +105,9 @@ recovery is polynomial-time with the precision, the shift, the parameters and `2 mathematical recovery modulo `2 ^ p`. * `Cslib.Circuits.Frontier.NativeRecovery.computableInPolyTime_recover`: native recovery is polynomial-time. +* `Cslib.Circuits.Frontier.NativeRecovery.getD_foldl_storeValue`, + `Cslib.Circuits.Frontier.NativeRecovery.computableInPolyTime_storeValue`: storing a sequence of + values gives each node index its values in order, in polynomial time. ## References @@ -238,6 +243,39 @@ def recover (p Δ : ℕ) (fl : List ℕ) (a u h : ℕ) (ζ : List (List ℕ)) (W if k = 2 ^ a then S else add (p + Δ) fl S (nodeTerm (p + Δ) fl a u h Δ ζ k (W.getD k []))) (zero fl)) +/-! ### Storing the values -/ + +/-- Store the value `y` at node index `k`, after the values already stored there. This is the +accumulator update of a recursive call once the value at an interpolation point is complete; +`NativeRecovery.recover` reads the table. -/ +def storeValue (W : List (List (List ℕ))) (k : ℕ) (y : List ℕ) : List (List (List ℕ)) := + (List.range (max W.length (k + 1))).map fun k' => + if k' = k then W.getD k' [] ++ [y] else W.getD k' [] + +theorem getD_storeValue (W : List (List (List ℕ))) (k : ℕ) (y : List ℕ) (k' : ℕ) : + (storeValue W k y).getD k' [] = if k' = k then W.getD k [] ++ [y] else W.getD k' [] := by + unfold storeValue + by_cases hk' : k' < max W.length (k + 1) + · rw [List.getD_eq_getElem _ _ (by simpa using hk'), List.getElem_map, List.getElem_range] + split_ifs with h + · rw [h] + · rfl + · have hne : k' ≠ k := by omega + rw [List.getD_eq_default _ _ (by rw [List.length_map, List.length_range]; omega), + ite_eq_right_of_eq_false _ _ (eq_false hne), List.getD_eq_default _ _ (by omega)] + +/-- **Storing a sequence of values**: each node index receives its values in order. -/ +theorem getD_foldl_storeValue (W : List (List (List ℕ))) (l : List (ℕ × List ℕ)) (k : ℕ) : + (l.foldl (fun W e => storeValue W e.1 e.2) W).getD k [] = + W.getD k [] ++ (l.filter fun e => e.1 = k).map Prod.snd := by + induction l generalizing W with + | nil => simp + | cons e l ih => + rw [List.foldl_cons, ih, getD_storeValue, List.filter_cons] + by_cases h : e.1 = k + · simp [h] + · simp [h, Ne.symm h] + /-! ### Correctness of the constants -/ variable {N : ℕ} {fl : List ℕ} @@ -1008,6 +1046,12 @@ private theorem singletonPolyTime {n : α → ℕ} (hn : ComputableInPolyTime n ComputableInPolyTime (fun x => [n x]) encIn (encList encNat) := ComputableInPolyTime.cons' hn (ComputableInPolyTime.const []) +/-- A coefficient list as a one-element list. -/ +private theorem singletonPolyTime' {c : α → List ℕ} + (hc : ComputableInPolyTime c encIn (encList encNat)) : + ComputableInPolyTime (fun x => [c x]) encIn (encList (encList encNat)) := + ComputableInPolyTime.cons' hc (ComputableInPolyTime.const []) + private theorem polyBounded_unary {n : α → ℕ} (hn : ComputableInPolyTime n encIn encUnary) : PolyBounded (fun x => (encIn x).length) n := by simpa using ComputableInPolyTime.polyBounded_length hn @@ -1380,6 +1424,32 @@ theorem computableInPolyTime_recover {p Δ a u h : α → ℕ} {fl : α → List refine (computableInPolyTime_shiftDown hΔ hfold).of_enc_eq fun x => ?_ simp only [recover, beq_iff_eq, Function.comp_apply] +/-- **Storing a value is polynomial-time**, with the node index in unary. -/ +@[fun_prop] +theorem computableInPolyTime_storeValue {W : α → List (List (List ℕ))} {k : α → ℕ} + {y : α → List ℕ} + (hW : ComputableInPolyTime W encIn (encList (encList (encList encNat)))) + (hk : ComputableInPolyTime k encIn encUnary) + (hy : ComputableInPolyTime y encIn (encList encNat)) : + ComputableInPolyTime (fun x => storeValue (W x) (k x) (y x)) encIn + (encList (encList (encList encNat))) := by + have h1 := ComputableInPolyTime.fst encIn encUnary + have h2 := ComputableInPolyTime.snd encIn encUnary + have hrow := (ComputableInPolyTime.listGetD (encA := encList (encList encNat)) []).comp + ((hW.comp h1).prod h2) + have hlen := lengthPolyTime hW + have hmax : ComputableInPolyTime (fun x => max (W x).length (k x + 1)) encIn encUnary := + (ComputableInPolyTime.unary_add' hlen (ComputableInPolyTime.unary_sub' + (ComputableInPolyTime.unary_add' hk (ComputableInPolyTime.const 1)) hlen)).of_enc_eq + fun x => by congr 1; omega + refine (ComputableInPolyTime.mapWithInput + (f := fun x k' => if (k' == k x) = true then (W x).getD k' [] ++ [y x] else (W x).getD k' []) + (ComputableInPolyTime.range.comp hmax) + (ComputableInPolyTime.ite (ComputableInPolyTime.beq h2 (hk.comp h1)) + (ComputableInPolyTime.listAppend' hrow (singletonPolyTime' (hy.comp h1))) hrow)).of_enc_eq + fun x => ?_ + simp only [storeValue, beq_iff_eq, Function.comp_apply] + end PolyTime end Cslib.Circuits.Frontier.NativeRecovery diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean index 35cced1e5..d3c422fa8 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean @@ -39,6 +39,11 @@ example : (mult 1 1 1 1, mult 1 1 1 3, mult 1 1 1 0) = (1, 1, 0) := by decide `E_{-1}(0) F(-1) + E_1(0) F(1) = (4 + 6) / 2 = 5` modulo `8`, computing modulo `2 ^ (3 + 2)`. -/ example : recover 3 2 [1, 1] 1 1 1 [[0, 0]] [[], [[4, 0]], [], [[6, 0]]] = [5, 0] := by decide +/-- The value table of that recovery, built by storing `F(-1)` at node index `1` and `F(1)` at +node index `3`. -/ +example : storeValue (storeValue [] 1 [4, 0]) 3 [6, 0] = [[], [[4, 0]], [], [[6, 0]]] := by + decide + /-- The same recovery with `Δ = (a + 1) ∑_t q_t = 4`, the shift that `val_recover` assumes. -/ example : recover 3 4 [1, 1] 1 1 1 [[0, 0]] [[], [[4, 0]], [], [[6, 0]]] = [5, 0] := by decide From 2bfdbb3987491b80db6822102f3848b1f9f2099a Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Sat, 31 Oct 2026 00:36:30 -0400 Subject: [PATCH 38/38] =?UTF-8?q?feat(PolyTime):=20the=20kernel=20of=20red?= =?UTF-8?q?uction=20A=5FN=20=E2=86=92=20A=5Fn=20is=202^n=20A=5FN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `exists_eq_add_of_reduce_eq`: elements of `(ℤ/2^N)[U]/(f)` with equal reductions to precision `n` differ by `2 ^ n` times an element; so a native value computed at precision `p_t` lifts to `A_(p+Δ)` in the form that `NativeRecovery.val_recover` assumes (`exists_val_eq_add_of_val_eq`). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015DLg8kwPs8yi7ePPdtoNvG --- .../PolyTime/GaloisRingSeries.lean | 26 ++++++++++++++++++- .../Frontier/TensorNetworkNativeRecovery.lean | 5 ++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Complexity/Computability/PolyTime/GaloisRingSeries.lean b/Complexity/Computability/PolyTime/GaloisRingSeries.lean index 455838f14..082432783 100644 --- a/Complexity/Computability/PolyTime/GaloisRingSeries.lean +++ b/Complexity/Computability/PolyTime/GaloisRingSeries.lean @@ -31,7 +31,8 @@ the contraction algorithm of [OpenAI2026ETH] (Appendix D.4.4, pp. 60–61). determined modulo `2 ^ p` (`GaloisRingArith.mod_eq_of_val_eq`), and every element is the value of a reduced coefficient list (`GaloisRingArith.exists_val_eq`). * **Precision.** Reduction `A_N → A_n` for `n ≤ N` (`GaloisRingArith.reduce`) keeps the - coordinates (`GaloisRingArith.reduce_val`). Dividing the coordinates of `2 ^ Δ y` by `2 ^ Δ` + coordinates (`GaloisRingArith.reduce_val`), and its kernel is `2 ^ n A_N` + (`GaloisRingArith.exists_eq_add_of_reduce_eq`). Dividing the coordinates of `2 ^ Δ y` by `2 ^ Δ` gives `y` modulo `2 ^ (N - Δ)` (`GaloisRingArith.val_shiftDown`): this is how a sum that is known to be divisible by `2 ^ Δ` loses its common power of two. * **Truncated products** (`GaloisRingArith.tmul`) of polynomials in `z` over `A_p`: the first @@ -51,6 +52,8 @@ length and the shift in unary (`GaloisRingArith.computableInPolyTime_tmul`, * `Cslib.GaloisRingArith.mod_eq_of_val_eq`, `Cslib.GaloisRingArith.exists_val_eq`: coordinates. * `Cslib.GaloisRingArith.val_shiftDown`: cancelling a common power of two. +* `Cslib.GaloisRingArith.exists_eq_add_of_reduce_eq`, + `Cslib.GaloisRingArith.exists_val_eq_add_of_val_eq`: the kernel of reduction is `2 ^ n A_N`. * `Cslib.GaloisRingArith.val_tmul`: truncated products are correct. * `Cslib.GaloisRingArith.computableInPolyTime_tmul`, `Cslib.GaloisRingArith.computableInPolyTime_shiftDown`: polynomial time. @@ -178,6 +181,27 @@ theorem reduce_val {n N : ℕ} (h : n ≤ N) (c : List ℕ) : reduce h fl (val N fl c) = val n fl c := by simp only [reduce, val, map_sum, map_mul, map_pow, map_natCast, AdjoinRoot.map_root] +/-- **Equal reductions differ by a multiple of `2 ^ n`**: the kernel of reduction +`A_N → A_n` is `2 ^ n A_N`. -/ +theorem exists_eq_add_of_reduce_eq {n N : ℕ} (h : n ≤ N) {x y : Ring N fl} + (hxy : reduce h fl x = reduce h fl y) : ∃ c, x = y + 2 ^ n * c := by + obtain ⟨r, -, -, rfl⟩ := exists_val_eq x + obtain ⟨s, -, -, rfl⟩ := exists_val_eq y + rw [reduce_val, reduce_val] at hxy + choose! q hq using fun i (hi : i < fl.length) => Nat.modEq_iff_dvd.mp (mod_eq_of_val_eq hxy hi) + refine ⟨-∑ i ∈ range fl.length, ((q i : ℤ) : Ring N fl) * AdjoinRoot.root _ ^ i, ?_⟩ + rw [val, val, mul_neg, mul_sum, ← sub_eq_add_neg, ← sum_sub_distrib] + refine sum_congr rfl fun i hi => ?_ + have hi' := congrArg (Int.cast : ℤ → Ring N fl) (hq i (mem_range.mp hi)) + push_cast at hi' + linear_combination (-(AdjoinRoot.root _ ^ i : Ring N fl)) * hi' + +/-- **A value at a lower precision lifts with an error divisible by `2 ^ n`**: a coefficient list +whose value in `A_n` is the reduction of `y ∈ A_N` has value `y + 2 ^ n c` in `A_N`. -/ +theorem exists_val_eq_add_of_val_eq {n N : ℕ} (h : n ≤ N) {w : List ℕ} {y : Ring N fl} + (hw : val n fl w = reduce h fl y) : ∃ c, val N fl w = y + 2 ^ n * c := + exists_eq_add_of_reduce_eq h (by rw [reduce_val, hw]) + /-- Division of each coordinate by `2 ^ Δ`. -/ def shiftDown (Δ : ℕ) (c : List ℕ) : List ℕ := c.map (· / 2 ^ Δ) diff --git a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean index d3c422fa8..f0afe5bb9 100644 --- a/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean +++ b/ComplexityTests/Computability/Circuit/Frontier/TensorNetworkNativeRecovery.lean @@ -66,4 +66,9 @@ example : Turing.MultiTapeTM.ComputableInPolyTime (fun n : ℕ => twoVal n n) computableInPolyTime_twoVal Turing.MultiTapeTM.ComputableInPolyTime.id' Turing.MultiTapeTM.ComputableInPolyTime.id' +/-- A value computed at precision `3` lifts to precision `5` with an error divisible by `2 ^ 3`. -/ +example (y : Ring 5 [1, 1]) (hw : val 3 [1, 1] [5, 0] = reduce (by norm_num) [1, 1] y) : + ∃ c, val 5 [1, 1] [5, 0] = y + 2 ^ 3 * c := + exists_val_eq_add_of_val_eq _ hw + end ComplexityTests.TensorNetworkNativeRecovery