From 6094ec9cc975f8e361f8ea49146ac4bea36aa115 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 05:13:43 -0400 Subject: [PATCH 01/32] feat(exactTriangle): relate lopsided queries and matrix products Adapt the finite counting interface and signed digit expansion from anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5 for milestone #1438. This is a mathematical interface; native preprocessing, fast solvers and full composition remain under #1394. --- Complexity.lean | 2 + .../Algorithms/ExactTriangle/Lopsided.lean | 265 ++++++++++++++++++ .../Foundations/Data/Matrix/IntegerBits.lean | 112 ++++++++ ComplexityTests/Lopsided.lean | 163 +++++++++++ NOTICE | 5 +- ROADMAP.md | 8 +- UPSTREAM.md | 11 + 7 files changed, 561 insertions(+), 5 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Lopsided.lean create mode 100644 Complexity/Foundations/Data/Matrix/IntegerBits.lean create mode 100644 ComplexityTests/Lopsided.lean diff --git a/Complexity.lean b/Complexity.lean index bcc3d2303d..d661ead1c1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1,6 +1,7 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs +public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2084,6 +2085,7 @@ public import Complexity.Foundations.Data.List.OfFn public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary +public import Complexity.Foundations.Data.Matrix.IntegerBits public import Complexity.Foundations.Data.Matrix.Permanent public import Complexity.Foundations.Data.Matrix.Reindex public import Complexity.Foundations.Data.Matrix.TotallyRegular diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean new file mode 100644 index 0000000000..d52e779437 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Lopsided.lean @@ -0,0 +1,265 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Matrix.IntegerBits +public import Mathlib.Data.Fintype.EquivFin +import Mathlib.Tactic.Ring + +/-! +# Finite lopsided triangle queries as matrix products + +Two outer parts have `n` vertices each; the finite middle part embeds into `Fin D`. +The matrices count common neighbors at every outer pair. Only pairs in `W` are graph edges, +so only those counts are triangle counts. Answers outside `W` are unconstrained. + +This mathematical interface gives no native program, preprocessing cost, sparsity bound, +fast solver or subquadratic running time. + +## Main definitions + +* `LopsidedGraph`: two adjacency relations, a finite middle part and queried outer edges. +* `LopsidedGraph.matX`, `LopsidedGraph.matY`: padded biadjacency matrices. +* `LopsidedGraph.signedBitInstance`: four sign choices for each pair of binary digits. + +## Main results + +* `LopsidedGraph.matX_mul_matY_apply`: entries count common middle neighbors. +* `LopsidedGraph.matX_mul_matY_apply_ne_zero_iff`: nonzero entries detect common neighbors. +* `LopsidedGraph.zero_one`: the converse for zero/one matrices. +* `LopsidedGraph.entries_from_counts`: bounded signed products from `4 * β²` counts. + +## References + +Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean) +and the lopsided definitions in [PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L798-L852). +The adaptation uses `Set.EqOn`, pointwise signed parts, and direct finite cardinalities. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +/-- An unweighted tripartite graph whose outer parts each have `n` vertices. -/ +structure LopsidedGraph (n : ℕ) where + /-- The middle vertices. -/ + M : Type + /-- The middle part is finite. -/ + [fintypeM : Fintype M] + /-- Edges from the first outer part to the middle. -/ + adjA : Fin n → M → Prop + /-- Edges from the middle to the second outer part. -/ + adjB : M → Fin n → Prop + /-- Queried outer edges. -/ + W : Finset (Fin n × Fin n) + +attribute [instance] LopsidedGraph.fintypeM + +namespace LopsidedGraph + +/-- On a pair in `W`, a common middle neighbor closes a triangle. +For a pair outside `W`, this predicate does not assert an outer edge. -/ +def HasCommonNeighbor {n : ℕ} (I : LopsidedGraph n) (a b : Fin n) : Prop := + ∃ v : I.M, I.adjA a v ∧ I.adjB v b + +open Classical in +/-- Number of common middle neighbors. At pairs in `W` this is the triangle count. -/ +noncomputable def commonNeighborCount {n : ℕ} (I : LopsidedGraph n) (a b : Fin n) : ℕ := + (Finset.univ.filter fun v : I.M => I.adjA a v ∧ I.adjB v b).card + +/-- Two thin matrices describe adjacency where their entries equal one. +The counting theorem `zero_one` requires all entries to be zero or one. -/ +def ofMatrices {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) : LopsidedGraph n where + M := Fin D + adjA a v := X a v = 1 + adjB v b := Y v b = 1 + W := W + +open Classical in +/-- The count for a matrix-defined graph uses the ordinary finite middle indices. -/ +theorem commonNeighborCount_ofMatrices {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (a b : Fin n) : + (ofMatrices X Y W).commonNeighborCount a b = + (Finset.univ.filter fun v : Fin D => X a v = 1 ∧ Y v b = 1).card := by + unfold ofMatrices commonNeighborCount + congr 1 + ext v + simp + +open Classical in +/-- The first biadjacency matrix, with zero columns outside the injection range. -/ +noncomputable def matX {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) : + Matrix (Fin n) (Fin D) ℤ := + Matrix.of fun a j => if ∃ v : I.M, e v = j ∧ I.adjA a v then 1 else 0 + +open Classical in +/-- The second biadjacency matrix, with zero rows outside the injection range. -/ +noncomputable def matY {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) : + Matrix (Fin D) (Fin n) ℤ := + Matrix.of fun j b => if ∃ v : I.M, e v = j ∧ I.adjB v b then 1 else 0 + +variable {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) + +/-- Every first biadjacency entry is zero or one. -/ +theorem matX_eq_zero_or_one (a : Fin n) (j : Fin D) : + I.matX e a j = 0 ∨ I.matX e a j = 1 := by + classical + simp only [matX, Matrix.of_apply] + split_ifs <;> simp + +/-- Every second biadjacency entry is zero or one. -/ +theorem matY_eq_zero_or_one (j : Fin D) (b : Fin n) : + I.matY e j b = 0 ∨ I.matY e j b = 1 := by + classical + simp only [matY, Matrix.of_apply] + split_ifs <;> simp + +/-- A middle part of cardinality at most `D` admits the required numbering. -/ +theorem nonempty_embedding (h : Fintype.card I.M ≤ D) : Nonempty (I.M ↪ Fin D) := + Function.Embedding.nonempty_of_card_le (by simpa using h) + +open Classical in +/-- At a middle vertex, the first matrix records its adjacency. -/ +theorem matX_apply_emb (a : Fin n) (v : I.M) : + I.matX e a (e v) = if I.adjA a v then 1 else 0 := by + simp [matX] + +open Classical in +/-- At a middle vertex, the second matrix records its adjacency. -/ +theorem matY_apply_emb (v : I.M) (b : Fin n) : + I.matY e (e v) b = if I.adjB v b then 1 else 0 := by + simp [matY] + +/-- An unused column is zero. -/ +theorem matX_apply_of_notMem_range (a : Fin n) {j : Fin D} (hj : j ∉ Set.range e) : + I.matX e a j = 0 := + ite_eq_right fun ⟨v, hv, _⟩ => hj ⟨v, hv⟩ + +/-- An unused row is zero. -/ +theorem matY_apply_of_notMem_range {j : Fin D} (hj : j ∉ Set.range e) (b : Fin n) : + I.matY e j b = 0 := + ite_eq_right fun ⟨v, hv, _⟩ => hj ⟨v, hv⟩ + +/-- A pair has a common neighbor if and only if its count is nonzero. -/ +theorem commonNeighborCount_ne_zero_iff (a b : Fin n) : + I.commonNeighborCount a b ≠ 0 ↔ I.HasCommonNeighbor a b := by + classical + rw [commonNeighborCount, Finset.card_ne_zero, Finset.filter_nonempty_iff] + simp [HasCommonNeighbor] + +/-- Every product entry counts common middle neighbors, independently of the numbering. -/ +theorem matX_mul_matY_apply (a b : Fin n) : + (I.matX e * I.matY e) a b = (I.commonNeighborCount a b : ℤ) := by + classical + rw [Matrix.mul_apply, commonNeighborCount, Finset.card_filter, Nat.cast_sum] + -- Only the columns of the middle vertices matter. + rw [← Finset.sum_subset (Finset.subset_univ (Finset.univ.map e)) fun j _ hj => by + rw [I.matX_apply_of_notMem_range e a (by simpa using hj), zero_mul], Finset.sum_map] + refine Finset.sum_congr rfl fun v _ => ?_ + rw [I.matX_apply_emb, I.matY_apply_emb] + by_cases hA : I.adjA a v <;> by_cases hB : I.adjB v b <;> simp [hA, hB] + +/-- A nonzero product entry detects a common middle neighbor. -/ +theorem matX_mul_matY_apply_ne_zero_iff (a b : Fin n) : + (I.matX e * I.matY e) a b ≠ 0 ↔ I.HasCommonNeighbor a b := by + rw [matX_mul_matY_apply, Ne, Nat.cast_eq_zero] + exact I.commonNeighborCount_ne_zero_iff a b + +/-- On queried outer edges, correct natural counts are exactly the integer product entries. +Ordinary `Set.EqOn` leaves all unqueried entries unconstrained. -/ +theorem counting_eqOn_iff (ans : Fin n × Fin n → ℕ) : + Set.EqOn ans (fun q => I.commonNeighborCount q.1 q.2) (I.W : Set _) ↔ + Set.EqOn (fun q => (ans q : ℤ)) (fun q => (I.matX e * I.matY e) q.1 q.2) + (I.W : Set _) := by + simp only [Set.EqOn, matX_mul_matY_apply, Nat.cast_inj] + +/-- Counts on queried edges provide their detection answers. -/ +theorem detection_eqOn_of_counting (ans : Fin n × Fin n → ℕ) + (h : Set.EqOn ans (fun q => I.commonNeighborCount q.1 q.2) (I.W : Set _)) : + Set.EqOn (fun q => ans q ≠ 0) (fun q => I.HasCommonNeighbor q.1 q.2) (I.W : Set _) := by + intro q hq + change (ans q ≠ 0) = I.HasCommonNeighbor q.1 q.2 + apply propext + rw [h hq, commonNeighborCount_ne_zero_iff] + +/-- For zero/one matrices, each product entry is the count in the graph they describe. -/ +theorem zero_one {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (hX : ∀ a j, X a j = 0 ∨ X a j = 1) + (hY : ∀ j b, Y j b = 0 ∨ Y j b = 1) (a b : Fin n) : + (X * Y) a b = ((ofMatrices X Y W).commonNeighborCount a b : ℤ) := by + classical + rw [Matrix.mul_apply, commonNeighborCount, Finset.card_filter, Nat.cast_sum] + refine Finset.sum_congr rfl fun j _ => ?_ + rcases hX a j with hx | hx <;> rcases hY j b with hy | hy <;> + simp [ofMatrices, hx, hy] + +/-- One of four sign choices for a pair of binary digits; `true` selects the negative part. +Every instance has exactly `D` middle vertices and the original queried edges `W`. -/ +def signedBitInstance (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (negativeX negativeY : Bool) (i j : ℕ) : LopsidedGraph n := + ofMatrices (IntMatrix.bit (if negativeX then (X.map negPart) else (X.map posPart)) i) + (IntMatrix.bit (if negativeY then (Y.map negPart) else (Y.map posPart)) j) W + +/-- The finite family of sign/digit instances for a magnitude width `β`. +Different slots may describe the same graph. -/ +def signedBitInstances (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (β : ℕ) : + (Bool × Bool × Fin β × Fin β) → LopsidedGraph n := + fun q => signedBitInstance X Y W q.1 q.2.1 q.2.2.1.val q.2.2.2.val + +/-- The indexed family has `4 * β²` slots, allowing repeated or zero instances. -/ +theorem card_signedBit_indices (β : ℕ) : + Fintype.card (Bool × Bool × Fin β × Fin β) = 4 * β ^ 2 := by + simp [pow_two, Nat.mul_assoc, Nat.mul_left_comm, Nat.mul_comm] + +/-- Each nonnegative product entry is a weighted sum of digit-graph counts. -/ +private theorem mul_apply_eq_sum_counts {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (β : ℕ) + (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) (hY : ∀ j b, 0 ≤ Y j b ∧ Y j b < 2 ^ β) (a b : Fin n) : + (X * Y) a b = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, (2 : ℤ) ^ (i + j) * + ((ofMatrices (IntMatrix.bit X i) (IntMatrix.bit Y j) W).commonNeighborCount a b : ℤ) := by + rw [IntMatrix.product X Y β hX hY, Matrix.sum_apply] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Matrix.sum_apply] + refine Finset.sum_congr rfl fun j _ => ?_ + rw [Matrix.smul_apply, smul_eq_mul, zero_one _ _ W (IntMatrix.bit_eq_zero_or_one X i) + (IntMatrix.bit_eq_zero_or_one Y j)] + +/-- Signed bounded product entries are weighted sums of four count instances per digit +pair, with signs `+, −, −, +`. Only counts at the same queried edge are needed. The absolute +bounds are strict; `β = 0` therefore permits only zero entries. -/ +theorem entries_from_counts {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (β : ℕ) + (hX : ∀ a j, |X a j| < 2 ^ β) (hY : ∀ j b, |Y j b| < 2 ^ β) (a b : Fin n) : + (X * Y) a b = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, (2 : ℤ) ^ (i + j) * + (((signedBitInstance X Y W false false i j).commonNeighborCount + a b : ℤ) + - ((signedBitInstance X Y W false true i j).commonNeighborCount + a b : ℤ) + - ((signedBitInstance X Y W true false i j).commonNeighborCount + a b : ℤ) + + ((signedBitInstance X Y W true true i j).commonNeighborCount + a b : ℤ)) := by + simp only [signedBitInstance, Bool.false_eq_true, ↓reduceIte] + obtain ⟨hXpos, hXneg⟩ := IntMatrix.parts_lt X hX + obtain ⟨hYpos, hYneg⟩ := IntMatrix.parts_lt Y hY + -- `XY` is a signed sum of the four products of a part of `X` and a part of `Y`. + conv_lhs => rw [IntMatrix.parts X, IntMatrix.parts Y] + rw [Matrix.sub_mul, Matrix.mul_sub, Matrix.mul_sub] + simp only [Matrix.sub_apply] + rw [mul_apply_eq_sum_counts _ _ W β hXpos hYpos, + mul_apply_eq_sum_counts _ _ W β hXpos hYneg, + mul_apply_eq_sum_counts _ _ W β hXneg hYpos, + mul_apply_eq_sum_counts _ _ W β hXneg hYneg] + simp only [mul_sub, mul_add, Finset.sum_sub_distrib, Finset.sum_add_distrib] + ring + +end LopsidedGraph + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/Data/Matrix/IntegerBits.lean b/Complexity/Foundations/Data/Matrix/IntegerBits.lean new file mode 100644 index 0000000000..5083b77db3 --- /dev/null +++ b/Complexity/Foundations/Data/Matrix/IntegerBits.lean @@ -0,0 +1,112 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Group.PosPart +public import Mathlib.Data.Matrix.Basic +public import Mathlib.Data.Nat.Log +import Mathlib.Tactic.Positivity +import Mathlib.Tactic.Ring + +/-! +# Binary digits of integer matrices + +Finite binary expansions and their products, using Mathlib's pointwise positive and negative +parts. These are algebraic identities, with no algorithm or running-time assertion. + +## Main definitions + +* `IntMatrix.bit`: the binary digit matrix of a given weight. + +## Main results + +* `IntMatrix.binary`: expansion of a nonnegative bounded matrix. +* `IntMatrix.product`: expansion of a product into products of digit matrices. +* `IntMatrix.abs_lt_two_pow_log`: an explicit integer bit bound, including zero. + +## References + +Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean), +`matBit` and `Footnote8.binary/product`. General index types and the integer bit bound are original +adaptation code. +-/ + +@[expose] public section + +namespace Cslib.IntMatrix + +/-- The binary digit of weight `2^i`, entry by entry. For negative entries this is the +Euclidean remainder of the quotient; reconstruction below assumes nonnegative entries. -/ +def bit {m n : Type*} (X : Matrix m n ℤ) (i : ℕ) : Matrix m n ℤ := + Matrix.of fun a j => X a j / 2 ^ i % 2 + +/-- `x mod 2^β` is the sum of the last `β` binary digits of `x`, with their weights. -/ +private theorem emod_two_pow_eq_sum_digits (x : ℤ) (β : ℕ) : + x % 2 ^ β = ∑ i ∈ Finset.range β, 2 ^ i * (x / 2 ^ i % 2) := by + induction β with + | zero => simp + | succ β ih => + have h : x / (2 ^ β * 2) = x / 2 ^ β / 2 := (Int.ediv_ediv_of_nonneg (by positivity)).symm + rw [Finset.sum_range_succ, ← ih, pow_succ, Int.emod_def, Int.emod_def x (2 ^ β), + Int.emod_def (x / 2 ^ β), h] + ring + +/-- A matrix with entries in `[0, 2^β)` is the weighted sum of its first `β` digit matrices. -/ +theorem binary {m n : Type*} (X : Matrix m n ℤ) (β : ℕ) + (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) : + X = ∑ i ∈ Finset.range β, (2 : ℤ) ^ i • bit X i := by + ext a j + rw [Matrix.sum_apply] + simp only [Matrix.smul_apply, bit, Matrix.of_apply, smul_eq_mul] + rw [← emod_two_pow_eq_sum_digits, Int.emod_eq_of_lt (hX a j).1 (hX a j).2] + +/-- Every digit matrix has entries zero or one, even for signed input. -/ +theorem bit_eq_zero_or_one {m n : Type*} (X : Matrix m n ℤ) (i : ℕ) (a : m) + (j : n) : bit X i a j = 0 ∨ bit X i a j = 1 := by + simp only [bit, Matrix.of_apply] + omega + +/-- Expand a product of nonnegative bounded matrices into products of binary digits. -/ +theorem product {m d n : Type*} [Fintype d] (X : Matrix m d ℤ) (Y : Matrix d n ℤ) + (β : ℕ) (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) (hY : ∀ j b, 0 ≤ Y j b ∧ Y j b < 2 ^ β) : + X * Y = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, + (2 : ℤ) ^ (i + j) • (bit X i * bit Y j) := by + conv_lhs => rw [binary X β hX, binary Y β hY] + rw [Matrix.sum_mul] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Matrix.mul_sum] + refine Finset.sum_congr rfl fun j _ => ?_ + rw [Matrix.smul_mul, Matrix.mul_smul, smul_smul, pow_add] + +/-- Entrywise positive and negative parts reconstruct an integer matrix. +`Matrix.map` reuses scalar signed parts without adding an ordering instance to matrices. -/ +theorem parts {m n : Type*} (X : Matrix m n ℤ) : + X = X.map posPart - X.map negPart := by + ext a j + exact (posPart_sub_negPart (X a j)).symm + +/-- Both pointwise signed parts of an absolutely bounded matrix satisfy the same nonnegative +bound. This uses the existing ordered-group operations, without introducing matrix variants. -/ +theorem parts_lt {m n : Type*} (X : Matrix m n ℤ) {β : ℕ} + (hX : ∀ a j, |X a j| < 2 ^ β) : + (∀ a j, 0 ≤ (X.map posPart) a j ∧ (X.map posPart) a j < 2 ^ β) ∧ + ∀ a j, 0 ≤ (X.map negPart) a j ∧ (X.map negPart) a j < 2 ^ β := by + have hpow : (0 : ℤ) < 2 ^ β := by positivity + exact ⟨fun a j => ⟨le_max_right _ _, max_lt ((le_abs_self _).trans_lt (hX a j)) hpow⟩, + fun a j => ⟨le_max_right _ _, max_lt ((neg_le_abs _).trans_lt (hX a j)) hpow⟩⟩ + +/-- An integer bounded in magnitude by `U` needs at most `log₂ U + 1` magnitude bits. +This also holds for `U = 0`; no real logarithms or asymptotic claim are involved. -/ +theorem abs_lt_two_pow_log {U : ℕ} (x : ℤ) (hx : |x| ≤ U) : + |x| < 2 ^ (Nat.log 2 U + 1) := by + have h := Nat.lt_pow_succ_log_self (by decide : 1 < 2) U + have hcast : (U : ℤ) < 2 ^ (Nat.log 2 U + 1) := by exact_mod_cast h + exact hx.trans_lt hcast + +end Cslib.IntMatrix diff --git a/ComplexityTests/Lopsided.lean b/ComplexityTests/Lopsided.lean new file mode 100644 index 0000000000..737cbf386a --- /dev/null +++ b/ComplexityTests/Lopsided.lean @@ -0,0 +1,163 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Lopsided +import Mathlib.LinearAlgebra.Matrix.Notation + +/-! Concrete tests for padding, counts, queried answers and signed reconstruction. -/ + +open Cslib.Algorithms.ExactTriangle +open LopsidedGraph + +private abbrev twoNeighbors : LopsidedGraph 2 where + M := Fin 2 + adjA a _ := a = 0 + adjB _ b := b = 1 + W := {(0, 1)} + +private def padded : Fin 2 ↪ Fin 3 := ⟨fun v => ⟨v.val, by omega⟩, + fun _ _ h => Fin.ext (show _ from congrArg (fun v : Fin 3 => v.val) h)⟩ + +example : twoNeighbors.commonNeighborCount 0 1 = 2 := by + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +example : twoNeighbors.HasCommonNeighbor 0 1 := ⟨0, rfl, rfl⟩ + +example : ¬twoNeighbors.HasCommonNeighbor 1 1 := by + simp [HasCommonNeighbor, twoNeighbors] + +example : twoNeighbors.matX padded 0 2 = 0 ∧ twoNeighbors.matY padded 2 1 = 0 := by + have h : (2 : Fin 3) ∉ Set.range padded := by + rintro ⟨v, hv⟩ + have hv' : v.val = 2 := congrArg (fun w : Fin 3 => w.val) hv + have := v.isLt + omega + exact ⟨matX_apply_of_notMem_range _ _ _ h, matY_apply_of_notMem_range _ _ h _⟩ + +example : (twoNeighbors.matX padded * twoNeighbors.matY padded) 0 1 = 2 := by + rw [matX_mul_matY_apply] + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +-- Values away from the sole query are deliberately incorrect and still allowed. +example : Set.EqOn (fun q : Fin 2 × Fin 2 => if q = (0, 1) then 2 else 99) + (fun q => twoNeighbors.commonNeighborCount q.1 q.2) (twoNeighbors.W : Set _) := by + intro q hq + have h : q = (0, 1) := by simpa [twoNeighbors] using hq + subst q + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +private abbrev emptyMiddle : LopsidedGraph 1 where + M := Fin 0 + adjA _ _ := True + adjB _ _ := True + W := {(0, 0)} + +example : emptyMiddle.commonNeighborCount 0 0 = 0 := by + simp [commonNeighborCount, emptyMiddle] + +example : ¬emptyMiddle.HasCommonNeighbor 0 0 := by + simp [HasCommonNeighbor, emptyMiddle] + +example : (emptyMiddle.matX (Function.Embedding.refl _) * + emptyMiddle.matY (Function.Embedding.refl _)) 0 0 = 0 := by + rw [matX_mul_matY_apply] + simp [commonNeighborCount, emptyMiddle] + +private def signedX : Matrix (Fin 1) (Fin 2) ℤ := !![-3, 2] +private def signedY : Matrix (Fin 2) (Fin 1) ℤ := !![2; -1] + +example : (signedX * signedY) 0 0 = -8 := by + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : (signedBitInstance signedX signedY {(0, 0)} true false 0 1).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +example : (signedBitInstance signedX signedY {(0, 0)} false true 1 0).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +-- Apply the signed reconstruction theorem to an actual product, including all four signs. +example : (∑ i ∈ Finset.range 2, ∑ j ∈ Finset.range 2, (2 : ℤ) ^ (i + j) * + (((signedBitInstance signedX signedY {(0, 0)} false false i j).commonNeighborCount 0 0 : ℤ) + - ((signedBitInstance signedX signedY {(0, 0)} false true i j).commonNeighborCount 0 0 : ℤ) + - ((signedBitInstance signedX signedY {(0, 0)} true false i j).commonNeighborCount 0 0 : ℤ) + + ((signedBitInstance signedX signedY {(0, 0)} true true i j).commonNeighborCount 0 0 : ℤ))) + = -8 := by + rw [← entries_from_counts signedX signedY {(0, 0)} 2 + (by intro a j; fin_cases a; fin_cases j <;> norm_num [signedX]) + (by intro j b; fin_cases j <;> fin_cases b <;> norm_num [signedY])] + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : Cslib.IntMatrix.bit (!![-1, 0, 3] : Matrix (Fin 1) (Fin 3) ℤ) 1 0 0 = 1 := by + norm_num [Cslib.IntMatrix.bit] + +example : |(-7 : ℤ)| < 2 ^ (Nat.log 2 7 + 1) := by + exact Cslib.IntMatrix.abs_lt_two_pow_log (-7) (by norm_num) + +example : |(0 : ℤ)| < 2 ^ (Nat.log 2 0 + 1) := by + exact Cslib.IntMatrix.abs_lt_two_pow_log 0 (by norm_num) + +example : (signedX * (-signedY)) 0 0 = 8 := by + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : (ofMatrices (!![1, 1] : Matrix (Fin 1) (Fin 2) ℤ) !![1; 1] + {(0, 0)}).commonNeighborCount 0 0 = 2 := by + have h := zero_one (!![1, 1] : Matrix (Fin 1) (Fin 2) ℤ) !![1; 1] {(0, 0)} + (by intro a j; fin_cases a; fin_cases j <;> simp) + (by intro j b; fin_cases j <;> fin_cases b <;> simp) 0 0 + norm_num [Matrix.mul_apply, Fin.sum_univ_two] at h + exact_mod_cast h.symm + +example : (signedBitInstance signedX (-signedY) {(0, 0)} true true 0 1).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +example : (signedBitInstances signedX signedY {(0, 0)} 2 (true, false, 0, 1)).W = + {(0, 0)} := rfl + +example : Fintype.card (Bool × Bool × Fin 2 × Fin 2) = 16 := by decide + +-- Empty outer parts with a genuinely nonempty middle part still give an empty product. +example : (0 : Matrix (Fin 0) (Fin 3) ℤ) * (0 : Matrix (Fin 3) (Fin 0) ℤ) = 0 := by + ext a b + exact Fin.elim0 a + +example : Set.EqOn (fun _ : Fin 0 × Fin 0 => (99 : ℕ)) + (fun q => + (ofMatrices (0 : Matrix (Fin 0) (Fin 3) ℤ) 0 ∅).commonNeighborCount q.1 q.2) + (∅ : Set (Fin 0 × Fin 0)) := by + simp + +-- With beta zero, strict magnitude bounds are satisfied by zero matrices of nonempty shape. +example : ((0 : Matrix (Fin 1) (Fin 2) ℤ) * (0 : Matrix (Fin 2) (Fin 1) ℤ)) 0 0 = 0 := by + have h := entries_from_counts (0 : Matrix (Fin 1) (Fin 2) ℤ) 0 {(0, 0)} 0 + (by simp) (by simp) 0 0 + simpa only [Finset.range_zero, Finset.sum_empty] using h + +-- A queried edge with disjoint middle neighborhoods is a negative triangle query. +example : let I := ofMatrices (!![1, 0] : Matrix (Fin 1) (Fin 2) ℤ) !![0; 1] {(0, 0)} + (0, 0) ∈ I.W ∧ I.commonNeighborCount 0 0 = 0 := by + dsimp only + constructor + · simp [ofMatrices] + · rw [commonNeighborCount_ofMatrices] + norm_num [Finset.univ_fin2, Finset.filter_insert, Finset.filter_singleton] diff --git a/NOTICE b/NOTICE index 81584c2411..3ab01a2df3 100644 --- a/NOTICE +++ b/NOTICE @@ -1,8 +1,9 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/Defs.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Lopsided}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Foundations/Data/Finset/Average.lean and +Complexity/Foundations/Data/Finset/Average.lean, +Complexity/Foundations/Data/Matrix/IntegerBits.lean and Complexity/Foundations/NumberTheory/PrimeCounting.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. diff --git a/ROADMAP.md b/ROADMAP.md index 8d53f5250f..5b3b230013 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -421,8 +421,10 @@ each statement against the numbered result. Organize modules by subject, not cha transfer of Anthropic's modular machine and 3SUM/APSP algorithms; direct 3SUM adaptation ([#1394]) has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including - ordinary distinct-position inputs and explicit size bounds. It still needs native Chan–He - preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ + ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting + interface ([#1438]) adds padding, detection and signed digit reconstruction; it still needs + modular Exact Triangle hashing/false-positive splitting, tensor/sparse-query programs, native + Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded almost-surely terminating structured loops; random-tape equivalence and BPP/RP/ZPP simulation @@ -546,6 +548,6 @@ not planned, not as proved. [#1155]: https://github.com/cslib-community/complexitylib/issues/1155 [#1351]: https://github.com/cslib-community/complexitylib/issues/1351 [#1394]: https://github.com/cslib-community/complexitylib/issues/1394 - [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 +[#1438]: https://github.com/cslib-community/complexitylib/issues/1438 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index f465fed043..87d9cb5288 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -165,6 +165,17 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he length, count and magnitude bounds; `scanPairs` counts candidate-prime/element pairs and is not a charged running time. Generic finite-average and prime-counting lemmas live in Foundations. See milestone [#1435](https://github.com/cslib-community/complexitylib/issues/1435). + `Algorithms/ExactTriangle/Lopsided.lean` adapts the finite unweighted lopsided query/counting + interface from the same pin's `Sec3/MatrixLanguage.lean`: arbitrary-injection zero padding, + common-neighbor counts as product entries, detection, the zero/one converse, and signed products + from `4 * β²` count instances with the original queried edges. Its digit identities live in + `Foundations/Data/Matrix/IntegerBits.lean`, reuse Mathlib's pointwise positive/negative parts, + and give an explicit `Nat.log 2 U + 1` magnitude-bit bound including zero. Query answers use + `Set.EqOn`; no additional weighted-triangle predicate or source machine is introduced. + This is the mathematical interface milestone + [#1438](https://github.com/cslib-community/complexitylib/issues/1438), with no sparsity assumption, + preprocessing/time claim or solver. Modular hashing/false-positive splitting and tensor/sparse + query programs remain in #1394, alongside the native and asymptotic composition below. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full input/time/width/asymptotic composition remain in From dec963a5dd581ae8acecb846e7b87b1bf7e5c78e Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:02:25 -0400 Subject: [PATCH 02/32] feat(exactTriangle): prove modular counts and the selected-prime bound --- Complexity.lean | 4 + .../Algorithms/ExactTriangle/Hashing.lean | 482 ++++++++++++++++++ .../Algorithms/ExactTriangle/Weights.lean | 61 +++ .../Foundations/NumberTheory/PrimeWindow.lean | 346 +++++++++++++ .../Foundations/RingTheory/CyclicIntRing.lean | 84 +++ ComplexityTests/ExactTriangleHashing.lean | 115 +++++ NOTICE | 9 +- ROADMAP.md | 6 +- UPSTREAM.md | 12 +- 9 files changed, 1113 insertions(+), 6 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Hashing.lean create mode 100644 Complexity/Algorithms/ExactTriangle/Weights.lean create mode 100644 Complexity/Foundations/NumberTheory/PrimeWindow.lean create mode 100644 Complexity/Foundations/RingTheory/CyclicIntRing.lean create mode 100644 ComplexityTests/ExactTriangleHashing.lean diff --git a/Complexity.lean b/Complexity.lean index d661ead1c1..e6ac945545 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1,7 +1,9 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs +public import Complexity.Algorithms.ExactTriangle.Hashing public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2190,6 +2192,7 @@ 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.PrimeCounting +public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.Order.BooleanSubalgebra public import Complexity.Foundations.Order.Changes public import Complexity.Foundations.Order.UpperLower @@ -2200,6 +2203,7 @@ public import Complexity.Foundations.Probability.SecondMoment public import Complexity.Foundations.Quantum.Measurement public import Complexity.Foundations.Relation.InductiveCounting public import Complexity.Foundations.Relation.RelatesWithinSteps +public import Complexity.Foundations.RingTheory.CyclicIntRing 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/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean new file mode 100644 index 0000000000..6799e79fc7 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -0,0 +1,482 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Foundations.NumberTheory.PrimeWindow +public import Complexity.Foundations.RingTheory.CyclicIntRing +public import Mathlib.Data.Matrix.Mul + +/-! +# Modular counts and prime selection for integer Exact Triangle + +Let `S(a,b,c)` denote `weightSum`, `F(p)` the false-positive count and `Z₀` the zero count. A +minimum-count prime in `[sqrt D / 2, sqrt D)` also minimises false positives. For weights +bounded by `n^κ`, with `16 ≤ D ≤ n` and `1 ≤ κ`, a universal constant bounds its false positives +by `C * κ * n^3 * log n / sqrt D`. The prime count used here is proved for every admitted `D`. + +## Main definitions + +* `countZeroMod`, `falsePositiveCount`, `zeroCount`: counts of finite triples. +* `IsSelectedPrime`: a minimum-count prime in the specified window. +* `matP`, `matQ`: cyclic polynomial matrices encoding edge residues. + +## Main results + +* `countZeroMod_eq`: modular count is the sum of false positives and true zeros. +* `coeff_matP_mul_matQ`, `falsePositiveCount_add_zeroCount_eq_sum_coeff`: coefficient + identities. +* `exists_isSelectedPrime`: the minimum exists. +* `Hashing.falsePositiveCount_le_const_mul`: the uniform false-positive bound. + +This is a mathematical reduction step. No native program or charged running-time bound is +asserted. The supplied three matrices retain the existing `HasZeroTriangle` semantics. + +## References + +Direct adaptation of [Anthropic's +Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), +with the minimal count/selection definitions extracted from its `PaperStatements.lean`. +Source-derived prose below is paraphrased. The source pin is +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Classical in +/-- Primes in the half-open window `[sqrt D / 2, sqrt D)`. -/ +noncomputable def primesInRange (D : ℕ) : Finset ℕ := + (Finset.range D).filter fun p => p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D + +/-- Number of triangles whose sum vanishes modulo `p`. -/ +def countZeroMod {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (p : ℕ) : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => + weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≡ 0 [ZMOD (p : ℤ)]).card + +/-- A window prime minimises the modular count over all window primes. -/ +def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ) : Prop := + p ∈ primesInRange D ∧ ∀ q ∈ primesInRange D, + countZeroMod wAB wBC wAC p ≤ countZeroMod wAB wBC wAC q + +private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + +private theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : + ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by + have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) + have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) + omega + +/-! +### The primes in the range -/ + +/-- Proof of Theorem 17, a prime p ∈ [√D/2, √D): the primes in the range are the primes `p` with +`√D/2 ≤ p < √D`. (The bound `p < D` in the definition excludes none of them.) +-/ +theorem mem_primesInRange {D p : ℕ} : + p ∈ primesInRange D ↔ p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D := by + unfold primesInRange + rw [Finset.mem_filter, Finset.mem_range] + refine ⟨fun h => h.2, fun h => ⟨?_, h⟩⟩ + -- From `p < √D` we get `p ≤ p² < D`. + have hsq : p ^ 2 < D := by exact_mod_cast (Real.lt_sqrt (Nat.cast_nonneg p)).1 h.2.2 + exact (Nat.le_self_pow two_ne_zero p).trans_lt hsq + +/-- Window membership is an exact integer-square test, suitable for native enumeration. -/ +theorem mem_primesInRange_iff_sq {D p : ℕ} : + p ∈ primesInRange D ↔ p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + rw [mem_primesInRange] + have hp0 : (0 : ℝ) ≤ p := Nat.cast_nonneg p + have hsqrt0 : 0 ≤ Real.sqrt (D : ℝ) := Real.sqrt_nonneg _ + have hsqrt : (Real.sqrt (D : ℝ)) ^ 2 = D := Real.sq_sqrt (Nat.cast_nonneg D) + constructor + · rintro ⟨hp, hlo, hhi⟩ + refine ⟨hp, ?_, ?_⟩ + · have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by nlinarith + exact_mod_cast h + · exact_mod_cast (Real.lt_sqrt hp0).1 hhi + · rintro ⟨hp, hlo, hhi⟩ + refine ⟨hp, ?_, (Real.lt_sqrt hp0).2 (by exact_mod_cast hhi)⟩ + have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by exact_mod_cast hlo + nlinarith + +/-- A computable finite enumeration of exactly the same half-open prime window. -/ +theorem primesInRange_eq_filter (D : ℕ) : + primesInRange D = (Finset.range D).filter fun p => p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + ext p + rw [mem_primesInRange_iff_sq, Finset.mem_filter, Finset.mem_range] + exact ⟨fun h => ⟨(Nat.le_self_pow two_ne_zero p).trans_lt h.2.2, h⟩, fun h => h.2⟩ + +/-- A number `r < p` is the sum of the residues of `u` and `v` in `{0, …, p − 1}`, reduced modulo +`p`, exactly if `u + v ≡ r`. +-/ +private theorem eq_add_toNat_emod_iff {p r : ℕ} (hp : p ≠ 0) (hr : r < p) (u v : ℤ) : + r = ((u % (p : ℤ)).toNat + (v % (p : ℤ)).toNat) % p ↔ u + v ≡ (r : ℤ) [ZMOD (p : ℤ)] := by + rw [← Int.natCast_inj, Int.ModEq, Int.emod_eq_of_lt (Int.natCast_nonneg r) (by exact_mod_cast hr), + eq_comm] + push_cast + rw [natCast_toNat_emod (Nat.pos_of_ne_zero hp), + natCast_toNat_emod (Nat.pos_of_ne_zero hp), + ← Int.add_emod] + +/-! +### Counting the triples with `S(a,b,c) ≡ 0 (mod p)` -/ + +section Counting + +variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- A false positive of p is a triple (a,b,c) with S(a,b,c) ≠ 0 and p ∣ S(a,b,c). -/ +abbrev IsFalsePositive (p : ℕ) (t : Fin n × Fin n × Fin n) : Prop := + weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0 ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 + +/-- let falsePositiveCount(p) denote the number of false positives of p. -/ +def falsePositiveCount (p : ℕ) : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => IsFalsePositive wAB wBC wAC p t).card + +/-- zeroCount, the number of zero triangles. -/ +def zeroCount : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = + 0).card + +/-- A positive zero count is exactly the existing Exact Triangle decision predicate. -/ +theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wBC wAC := by + classical + simp only [zeroCount, Finset.card_pos, HasZeroTriangle] + constructor + · rintro ⟨⟨a, b, c⟩, h⟩ + exact ⟨a, b, c, (Finset.mem_filter.mp h).2⟩ + · rintro ⟨a, b, c, h⟩ + exact ⟨(a, b, c), Finset.mem_filter.mpr ⟨Finset.mem_univ _, h⟩⟩ + +/-- This number is the number of false positives falsePositiveCount(p) plus zeroCount, the number of +zero triangles, which does not depend on p. +-/ +theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiveCount wAB wBC + wAC) p + (zeroCount wAB wBC wAC) := by + classical + simp only [countZeroMod, falsePositiveCount, zeroCount, Finset.card_filter, ← + Finset.sum_add_distrib] + -- A triple with `p ∣ S(a,b,c)` is a zero triangle or a false positive, and not both. + refine Finset.sum_congr rfl fun t _ => ?_ + by_cases h0 : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 <;> + simp [IsFalsePositive, Int.modEq_zero_iff_dvd, h0] + +/-- let P[a,c] := x^{w(a,c) mod p}, a matrix over `ℤ[x]/(x^p − 1)`. `w mod p` is the residue in +`{0, …, p − 1}`. +-/ +noncomputable def matP (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := + Matrix.of fun a c => CyclicIntRing.x p ^ (wAC a c % (p : ℤ)).toNat + +/-- and Q[c,b] := x^{w(b,c) mod p}. -/ +noncomputable def matQ (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := + Matrix.of fun c b => CyclicIntRing.x p ^ (wBC b c % (p : ℤ)).toNat + +/-- the coefficient of x^r in (PQ)[a,b] is the number of c ∈ C with w(a,c) + w(b,c) ≡ r (mod p). -/ +theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr : r < p) : + CyclicIntRing.coeff hp r ((matP wAC p * matQ wBC p) a b) = + ((Finset.univ.filter fun c : Fin n => + wAC a c + wBC b c ≡ (r : ℤ) [ZMOD (p : ℤ)]).card : ℤ) := by + classical + -- `(PQ)[a,b]` is the sum over `c` of `x^(w(a,c) mod p + w(b,c) mod p)`, and the coefficient of + -- `x^r` in each term is 1 or 0. + rw [Matrix.mul_apply] + simp only [matP, matQ, Matrix.of_apply, ← pow_add] + rw [map_sum] + simp only [CyclicIntRing.coeff_x_pow] + rw [Finset.card_filter] + push_cast + refine Finset.sum_congr rfl fun c _ => ?_ + simp only [eq_add_toNat_emod_iff hp hr] + +/-- then falsePositiveCount(p) + zeroCount is the sum over the pairs (a,b) ∈ A × B of the +coefficient of x^{−w(a,b) mod p} in (PQ)[a,b]. +-/ +theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : + (((falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) : ℕ) : ℤ) = + ∑ a : Fin n, ∑ b : Fin n, + CyclicIntRing.coeff hp ((-wAB a b) % (p : ℤ)).toNat ((matP wAC p * matQ wBC p) a b) := by + classical + rw [← countZeroMod_eq] + unfold countZeroMod + rw [Finset.card_filter, Fintype.sum_prod_type] + push_cast + refine Finset.sum_congr rfl fun a _ => ?_ + rw [Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun b _ => ?_ + have hcast := natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + Finset.card_filter] + push_cast + refine Finset.sum_congr rfl fun c _ => ?_ + -- `S(a,b,c) ≡ 0` says the same as `w(a,c) + w(b,c) ≡ -w(a,b)`, and `-w(a,b)` is congruent to its + -- residue. + have hres : (-wAB a b) % (p : ℤ) ≡ -wAB a b [ZMOD (p : ℤ)] := Int.mod_modEq _ _ + have hiff : weightSum wAB wBC wAC a b c ≡ 0 [ZMOD (p : ℤ)] ↔ + wAC a c + wBC b c ≡ ((((-wAB a b) % (p : ℤ)).toNat : ℕ) : ℤ) [ZMOD (p : ℤ)] := by + rw [hcast, show wAC a c + wBC b c ≡ (-wAB a b) % (p : ℤ) [ZMOD (p : ℤ)] ↔ + wAC a c + wBC b c ≡ -wAB a b [ZMOD (p : ℤ)] from + ⟨fun h => h.trans hres, fun h => h.trans hres.symm⟩, + Int.modEq_iff_dvd, Int.modEq_iff_dvd, + show 0 - weightSum wAB wBC wAC a b c = -wAB a b - (wAC a c + wBC b c) by + simp only [weightSum]; ring] + simp only [hiff] + +end Counting + +/-- the fewer than √D primes in the range. -/ +theorem card_primesInRange_lt (D : ℕ) (hD : 16 ≤ D) : + ((primesInRange D).card : ℝ) < Real.sqrt D := by + have hpos : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] + -- The primes in the range are among the numbers `1, …, ⌈√D⌉ - 1`. + have hsub : primesInRange D ⊆ Finset.Ico 1 ⌈Real.sqrt D⌉₊ := by + intro p hp + obtain ⟨hprime, -, hlt⟩ := mem_primesInRange.1 hp + exact Finset.mem_Ico.2 ⟨hprime.one_lt.le, Nat.lt_ceil.2 hlt⟩ + have hcard : (primesInRange D).card + 1 ≤ ⌈Real.sqrt D⌉₊ := by + have hle := Finset.card_le_card hsub + have hone : 1 ≤ ⌈Real.sqrt D⌉₊ := Nat.one_le_ceil_iff.2 hpos + rw [Nat.card_Ico] at hle + omega + have hcast : ((primesInRange D).card : ℝ) + 1 ≤ (⌈Real.sqrt D⌉₊ : ℝ) := by exact_mod_cast hcard + linarith [Nat.ceil_lt_add_one hpos.le] + +/-! +### Selecting the prime -/ + +section Selection + +variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- There is a prime to select. -/ +theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by + obtain ⟨q, hq⟩ := + Nat.exists_prime_half_le_and_lt (Real.sqrt D) (four_le_sqrt hD) + exact Finset.exists_min_image (primesInRange D) (countZeroMod wAB wBC wAC) ⟨q, + mem_primesInRange.2 hq⟩ + +/-- We select the prime with the smallest count, which is also the prime with the fewest false +positives. +-/ +theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : + IsSelectedPrime wAB wBC wAC D p) : + ∀ q ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p ≤ (falsePositiveCount wAB wBC wAC) + q := by + intro q hq + have h := hp.2 q hq + rw [countZeroMod_eq, countZeroMod_eq] at h + omega + +/-! +### The bound on the number of false positives of the selected prime -/ + +open Classical in +/-- a triple with S(a,b,c) ≠ 0 is a false positive exactly of the primes in the range that divide +S(a,b,c). Since 0 < |S(a,b,c)| ≤ 3n^ν and these primes are at least √D/2, there are at most +log_{√D/2}(3n^ν) of them. +-/ +theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (t : Fin n × Fin n × Fin n) (ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0) : + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + have hsqrt := four_le_sqrt hD + set divisors := (primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t + have hmem : ∀ p ∈ divisors, + p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 := by + intro p hp + obtain ⟨hrange, -, hdvd⟩ := Finset.mem_filter.1 hp + obtain ⟨hprime, hge, -⟩ := mem_primesInRange.1 hrange + exact ⟨hprime, hge, hdvd⟩ + -- The product of these primes divides `S(a,b,c)`, so it is at most `|S(a,b,c)| ≤ 3n^κ`. + have hdvd : ∏ p ∈ divisors, p ∣ (weightSum wAB wBC wAC t.1 t.2.1 t.2.2).natAbs := + Finset.prod_primes_dvd _ (fun p hp => (hmem p hp).1.prime) + fun p hp => Int.natCast_dvd.1 (hmem p hp).2.2 + have hprod : ∏ p ∈ divisors, (p : ℝ) ≤ 3 * (n : ℝ) ^ κ := + calc ∏ p ∈ divisors, (p : ℝ) = ((∏ p ∈ divisors, p : ℕ) : ℝ) := (Nat.cast_prod _ _).symm + _ ≤ ((weightSum wAB wBC wAC t.1 t.2.1 t.2.2).natAbs : ℝ) := by + exact_mod_cast Nat.le_of_dvd (Int.natAbs_pos.2 ht) hdvd + _ = ((|weightSum wAB wBC wAC t.1 t.2.1 t.2.2| : ℤ) : ℝ) := Nat.cast_natAbs _ + _ ≤ 3 * (n : ℝ) ^ κ := (abs_weightSum_le wAB wBC wAC) hT _ _ _ + -- Each of these primes is at least `√D/2`. + have hpow : (Real.sqrt D / 2) ^ divisors.card ≤ ∏ p ∈ divisors, (p : ℝ) := by + rw [← Finset.prod_const] + exact Finset.prod_le_prod₀ (fun _ _ => by linarith) fun p hp => (hmem p hp).2.1 + have hY : 0 < 3 * (n : ℝ) ^ κ := lt_of_lt_of_le (by positivity) (hpow.trans hprod) + rw [Real.le_logb_iff_rpow_le (by linarith) hY, Real.rpow_natCast] + exact hpow.trans hprod + +/-- Hence the numbers of false positives of all the primes in the range add up to at most n³ +log_{√D/2}(3n^ν). +-/ +theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC + ((n : ℝ) ^ κ)) : + ((∑ p ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p : ℕ) : ℝ) + ≤ (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + classical + have hsqrt := four_le_sqrt hD + -- Count the pairs (prime, false positive of that prime) triple by triple. + have hswap : ∑ p ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p = ∑ t : Fin n × Fin n × + Fin n, + ((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card := by + simp only [falsePositiveCount, Finset.card_filter] + exact Finset.sum_comm + have hterm : ∀ t : Fin n × Fin n × Fin n, + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + intro t + by_cases ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 + · -- A zero triangle is a false positive of no prime. + rw [Finset.filter_eq_empty_iff.2 fun p _ h => h.1 ht, Finset.card_empty, Nat.cast_zero] + have hn : (1 : ℝ) ≤ n := by exact_mod_cast Fin.pos t.1 + have hpow : (1 : ℝ) ≤ (n : ℝ) ^ κ := Real.one_le_rpow hn (by linarith) + exact Real.logb_nonneg (by linarith) (by linarith) + · -- Otherwise this is the bound of `card_falsePositive_primes_le` for this triple. + convert (card_falsePositive_primes_le wAB wBC wAC) hD hT t ht + rw [hswap, Nat.cast_sum] + calc ∑ t : Fin n × Fin n × Fin n, + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ ∑ _t : Fin n × Fin n × Fin n, Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := + Finset.sum_le_sum fun t _ => hterm t + _ = (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + rw [Finset.sum_const, Finset.card_univ, Fintype.card_prod, Fintype.card_prod, + Fintype.card_fin, nsmul_eq_mul] + push_cast + ring + +end Selection + +/-- By the prime number theorem there are Ω(√D/log D) primes in the range. + +NOTE. The modular reduction needs this for every `D ≥ 16`, not only for large `D`, and so it is +stated. It follows from a counting form of Bertrand's postulate; the prime number theorem is +not needed. +-/ +theorem exists_le_card_primesInRange : + ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → + c * (Real.sqrt D / Real.log D) ≤ ((primesInRange D).card : ℝ) := by + obtain ⟨c, hc, hcount⟩ := Nat.exists_forall_mul_sqrt_div_log_le_card_primes + exact ⟨c, hc, fun D hD => hcount D hD _ + fun _ hp hge hlt => mem_primesInRange.2 ⟨hp, hge, hlt⟩⟩ + +/-- falsePositiveCount(p) is at most the average over them, multiplied out. -/ +theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wBC wAC : Fin n → + Fin n → ℤ} + (hp : IsSelectedPrime wAB wBC wAC D p) : + (falsePositiveCount wAB wBC wAC) p * (primesInRange D).card ≤ ∑ q ∈ primesInRange D, + (falsePositiveCount wAB wBC wAC) q := by + rw [mul_comm, ← smul_eq_mul] + exact Finset.card_nsmul_le_sum _ _ _ hp.falsePositiveCount_le + +/-- log D = O(log(√D/2)) for D ≥ 16. -/ +theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : + Real.log D ≤ 4 * Real.log (Real.sqrt D / 2) := by + have hD16 : (16 : ℝ) ≤ D := by exact_mod_cast hD + have hsqrt : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] + -- `log(√D/2) = (log D)/2 - log 2`, and `4 log 2 = log 16 ≤ log D`. + have hlog16 : 4 * Real.log 2 ≤ Real.log D := by + have h := Real.log_le_log (by norm_num) (show (2 : ℝ) ^ 4 ≤ D by linarith) + rw [Real.log_pow] at h + exact_mod_cast h + rw [Real.log_div hsqrt.ne' (by norm_num), Real.log_sqrt (by linarith)] + linarith [hlog16] + +/-- falsePositiveCount(p) is at most the average over them, so [...] falsePositiveCount(p) = O(n³ +log(3n^ν)/√D): if there are at least `c √D/log D` primes in the range, then +`falsePositiveCount(p) ≤ (4/c) n³ log(3n^κ)/√D`. +-/ +theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ} + (wAB wBC wAC : Fin n → Fin n → ℤ) (hc : 0 < c) + (hcard : c * (Real.sqrt D / Real.log D) ≤ ((primesInRange D).card : ℝ)) (hD : 16 ≤ D) + (hDn : D ≤ n) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : + IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ 4 / c * ((n : ℝ) ^ 3 * Real.log (3 * (n : ℝ) ^ κ) + / Real.sqrt D) := by + -- The quantities of the paper's sentence: `r = √D`, `Y = 3n^κ`, and the number of primes in the + -- range. + set r := Real.sqrt D + set Y := 3 * (n : ℝ) ^ κ with hY + set numPrimes := (primesInRange D).card + have hr : 4 ≤ r := four_le_sqrt hD + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlogD_pos : 0 < Real.log D := Real.log_pos (by exact_mod_cast (by omega : 1 < D)) + have hlogr_pos : 0 < Real.log (r / 2) := Real.log_pos (by linarith) + have hlogY_nonneg : 0 ≤ Real.log Y := by + have : 1 ≤ (n : ℝ) ^ κ := Real.one_le_rpow (by linarith) (by linarith) + exact Real.log_nonneg (by rw [hY]; linarith) + -- The selected prime is at most average, after multiplying by the number of primes. + -- False positives of all primes in the + -- range add up to at most `n³ log_{r/2} Y`. + have average : ((falsePositiveCount wAB wBC wAC) p : ℝ) * numPrimes ≤ (n : ℝ) ^ 3 * Real.logb + (r / 2) Y := + le_trans (by exact_mod_cast hp.falsePositiveCount_mul_card_le_sum) + ((sum_falsePositiveCount_le wAB wBC wAC) hD hκ hT) + -- The base of the logarithm changes: `log_{r/2} Y ≤ 4 log Y / log D`. + have hlogD_le : Real.log D ≤ 4 * Real.log (r / 2) := log_le_four_mul_log_sqrt_div_two D hD + have changeBase : Real.logb (r / 2) Y ≤ 4 * Real.log Y / Real.log D := by + rw [Real.logb, div_le_div_iff₀ hlogr_pos hlogD_pos] + linarith [mul_le_mul_of_nonneg_left hlogD_le hlogY_nonneg] + rw [show 4 / c * ((n : ℝ) ^ 3 * Real.log Y / r) = 4 * (n : ℝ) ^ 3 * Real.log Y / (c * r) by + field_simp, le_div_iff₀ (by positivity)] + calc ((falsePositiveCount wAB wBC wAC) p : ℝ) * (c * r) = (falsePositiveCount wAB wBC wAC) p * + (c * (r / Real.log D)) * Real.log D := by field_simp + _ ≤ (falsePositiveCount wAB wBC wAC) p * numPrimes * Real.log D := by gcongr + _ ≤ (n : ℝ) ^ 3 * Real.logb (r / 2) Y * Real.log D := by gcongr + _ ≤ (n : ℝ) ^ 3 * (4 * Real.log Y / Real.log D) * Real.log D := by gcongr + _ = 4 * (n : ℝ) ^ 3 * Real.log Y := by field_simp + +/-- falsePositiveCount(p) = O(n³ log(3n^ν)/√D) = O(ν n³ log n/√D). -/ +theorem exists_falsePositiveCount_le : + ∃ C : ℝ, 1 ≤ C ∧ ∀ {n D p : ℕ} {κ : ℝ}, 16 ≤ D → D ≤ n → 1 ≤ κ → ∀ wAB wBC wAC : Fin n → Fin + n → ℤ, + WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ) → IsSelectedPrime wAB wBC wAC D p → + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ C * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt + D) := by + obtain ⟨c, hc, hcard⟩ := exists_le_card_primesInRange + refine ⟨max 1 (8 / c), le_max_left _ _, fun {n D p κ} hD hDn hκ wAB wBC wAC hT hp => ?_⟩ + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlogn : 0 ≤ Real.log n := Real.log_nonneg (by linarith) + -- `log(3n^κ) = log 3 + κ log n ≤ 2κ log n`, because `3 ≤ n` and `κ ≥ 1`. + have hlog : Real.log (3 * (n : ℝ) ^ κ) ≤ 2 * κ * Real.log n := by + have hlog3 : Real.log 3 ≤ κ * Real.log n := + (Real.log_le_log (by norm_num) (by linarith)).trans (le_mul_of_one_le_left hlogn hκ) + rw [Real.log_mul (by norm_num) (by positivity), Real.log_rpow (by linarith)] + linarith [hlog3] + calc ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ 4 / c * ((n : ℝ) ^ 3 * Real.log (3 * (n : ℝ) ^ + κ) / Real.sqrt D) := + (falsePositiveCount_le_of_le_card_primesInRange wAB wBC wAC) hc (hcard D hD) hD hDn hκ hT hp + _ ≤ 4 / c * ((n : ℝ) ^ 3 * (2 * κ * Real.log n) / Real.sqrt D) := by gcongr + _ = 8 / c * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt D) := by ring + _ ≤ max 1 (8 / c) * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt D) := by + gcongr + exact le_max_right _ _ + +/-- A name for the constant in falsePositiveCount(p) = [...] = O(ν n³ log n/√D), for the bounds on +running times that are built on this one. +-/ +noncomputable def Hashing.falsePositiveConst : ℝ := Classical.choose exists_falsePositiveCount_le + +/-- The constant is at least 1. -/ +theorem Hashing.one_le_falsePositiveConst : 1 ≤ Hashing.falsePositiveConst := + (Classical.choose_spec exists_falsePositiveCount_le).1 + +/-- falsePositiveCount(p) = [...] = O(ν n³ log n/√D), with `Hashing.falsePositiveConst` as the +constant. +-/ +theorem Hashing.falsePositiveCount_le_const_mul {n D p : ℕ} {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) + (hκ : 1 ≤ κ) (wAB wBC wAC : Fin n → Fin n → ℤ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (hp : IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ Hashing.falsePositiveConst * (κ * (n : ℝ) ^ 3 * + Real.log n / Real.sqrt D) := + (Classical.choose_spec exists_falsePositiveCount_le).2 hD hDn hκ wAB wBC wAC hT hp + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Algorithms/ExactTriangle/Weights.lean b/Complexity/Algorithms/ExactTriangle/Weights.lean new file mode 100644 index 0000000000..d358d0cd80 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Weights.lean @@ -0,0 +1,61 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Defs +public import Mathlib.Algebra.Order.Group.Abs +public import Mathlib.Basic.Real.Basic +import Mathlib.Tactic.Linarith + +/-! +# Integer triangle sums and magnitude bounds + +## Main definitions + +* `weightSum`: the sum of the three supplied integer edge weights. +* `WeightsBoundedBy`: a shared real bound on their absolute values. + +## Main results + +* `abs_weightSum_le`: a triangle's magnitude is at most three times the edge bound. + +## References + +Adapted from the weight sum and bound definitions in [Anthropic's +PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L893-L914) +and the triangle inequality in its +[Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean#L246-L255). +The existing `HasZeroTriangle` remains the decision predicate. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- Sum of the A–B, B–C and A–C weights at a triangle. -/ +def weightSum (a b c : Fin n) : ℤ := wAB a b + wBC b c + wAC a c + +/-- Every supplied edge weight has absolute value at most `U`. -/ +def WeightsBoundedBy (U : ℝ) : Prop := + (∀ a b, ((|wAB a b| : ℤ) : ℝ) ≤ U) ∧ + (∀ b c, ((|wBC b c| : ℤ) : ℝ) ≤ U) ∧ ∀ a c, ((|wAC a c| : ℤ) : ℝ) ≤ U + +/-- The three edge bounds imply a threefold bound on every triangle sum. -/ +theorem abs_weightSum_le {U : ℝ} (h : WeightsBoundedBy wAB wBC wAC U) (a b c : Fin n) : + ((|weightSum wAB wBC wAC a b c| : ℤ) : ℝ) ≤ 3 * U := by + obtain ⟨hAB, hBC, hAC⟩ := h + have habs : |weightSum wAB wBC wAC a b c| ≤ |wAB a b| + |wBC b c| + |wAC a c| := + abs_add_three _ _ _ + have hcast : ((|weightSum wAB wBC wAC a b c| : ℤ) : ℝ) ≤ + ((|wAB a b| : ℤ) : ℝ) + ((|wBC b c| : ℤ) : ℝ) + ((|wAC a c| : ℤ) : ℝ) := by + exact_mod_cast habs + linarith [hAB a b, hBC b c, hAC a c] + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/NumberTheory/PrimeWindow.lean b/Complexity/Foundations/NumberTheory/PrimeWindow.lean new file mode 100644 index 0000000000..dbeb6390e2 --- /dev/null +++ b/Complexity/Foundations/NumberTheory/PrimeWindow.lean @@ -0,0 +1,346 @@ +/- +Copyright (c) 2020 Patrick Stevens. All rights reserved. +Copyright (c) 2026 Anthropic, PBC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Patrick Stevens, Bolton Bailey, Anthropic, PBC +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Analysis.SpecialFunctions.Log.Base +public import Mathlib.Analysis.SpecialFunctions.Pow.Real +public import Mathlib.NumberTheory.PrimeCounting +import Mathlib.Analysis.Complex.ExponentialBounds +import Mathlib.Data.Nat.Prime.Factorial +import Mathlib.NumberTheory.Bertrand +import Mathlib.NumberTheory.Primorial +import Mathlib.Analysis.Convex.SpecificFunctions.Deriv + +/-! +# Explicit prime counts in a window of ratio two + +For every real `x ≥ 4`, the half-open window `[x/2, x)` contains at least `c * x / log x` primes +for one universal positive constant `c`. The proof counts prime factors of a central binomial +coefficient, so no prime-number-theorem hypothesis is assumed. + +## Main results + +* `Nat.exists_prime_half_le_and_lt`: a prime exists in every such window. +* `Nat.exists_forall_mul_div_log_le_card_primes`: the uniform counting bound. +* `Nat.exists_forall_mul_sqrt_div_log_le_card_primes`: the bound at `x = sqrt D`, for all `D ≥ + 16`. + +## References + +Adapted from [Anthropic's +PrimesInWindow.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/PrimesInWindow.lean). +Its central-binomial and concavity estimates adapt `Mathlib/NumberTheory/Bertrand.lean`, +originally by Patrick Stevens and Bolton Bailey; that source credit is retained. +-/ + +@[expose] public section + +open Finset + +namespace Nat + +private theorem one_le_log_of_three_le {x : ℝ} (hx : 3 ≤ x) : 1 ≤ Real.log x := + (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans hx) + + +/-- The primes `p` with `n < p ≤ 2n`: the primes that Bertrand's postulate is about. -/ +private def bertrandPrimes (n : ℕ) : Finset ℕ := (Ioc n (2 * n)).filter Nat.Prime + +/-- Membership in `bertrandPrimes`, spelled out. -/ +private theorem mem_bertrandPrimes {n p : ℕ} : + p ∈ bertrandPrimes n ↔ (n < p ∧ p ≤ 2 * n) ∧ p.Prime := by + simp only [bertrandPrimes, mem_filter, mem_Ioc] + +/-! +## 1. Erdős's upper bound for the central binomial coefficient -/ + +/-- Every prime power in `C(2n, n)` is at most `2n`, so `|s|` of them multiply to at most +`(2n)^|s|`. +-/ +private theorem prod_pow_factorization_le {n : ℕ} (hn : 0 < n) (s : Finset ℕ) : + ∏ p ∈ s, p ^ n.centralBinom.factorization p ≤ (2 * n) ^ s.card := + prod_le_pow_card _ _ _ fun _ _ => Nat.pow_factorization_choose_le (by omega) + +/-- The primes in `(√(2n), n]` contribute at most `4^(2n/3)` to `C(2n, n)`: their exponent is at +most 1, it is 0 above `2n/3`, and the product of the primes up to `2n/3` is at most `4^(2n/3)`. +Adapted from Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0. +-/ +private theorem prod_middle_le {n : ℕ} (hn : 2 < n) (s : Finset ℕ) + (hs : ∀ p ∈ s, p.Prime ∧ Nat.sqrt (2 * n) < p ∧ p ≤ n) : + ∏ p ∈ s, p ^ n.centralBinom.factorization p ≤ 4 ^ (2 * n / 3) := + calc ∏ p ∈ s, p ^ n.centralBinom.factorization p + ≤ ∏ p ∈ s, (if p ≤ 2 * n / 3 then p else 1) := by + refine prod_le_prod fun p hp => ?_ + obtain ⟨hprime, hsqrt, hpn⟩ := hs p hp + split_ifs with h3 + · exact (pow_right_mono₀ hprime.one_lt.le + (Nat.factorization_choose_le_one (Nat.sqrt_lt'.mp hsqrt))).trans (pow_one p).le + · rw [Nat.factorization_centralBinom_of_two_mul_self_lt_three_mul hn hpn (by omega), + pow_zero] + _ = ∏ p ∈ s.filter (· ≤ 2 * n / 3), p := (prod_filter _ _).symm + _ ≤ primorial (2 * n / 3) := by + refine prod_le_prod_of_subset_of_one_le (fun p hp => ?_) + fun p hp _ => (mem_filter.1 hp).2.one_lt.le + obtain ⟨hps, hp3⟩ := mem_filter.1 hp + exact mem_filter.2 ⟨mem_range.2 (by omega), (hs p hps).1⟩ + _ ≤ 4 ^ (2 * n / 3) := primorial_le_four_pow _ + +/-- Erdős's bound, keeping the primes of `(n, 2n]`: `C(2n, n) ≤ (2n)^√(2n) · 4^(2n/3) · (2n)^k`, +where `k` is the number of primes in `(n, 2n]`. Mathlib proves the case `k = 0` on the way to +Bertrand's postulate (`centralBinom_le_of_no_bertrand_prime`). Adapted from Mathlib, +Mathlib/NumberTheory/Bertrand.lean, Apache 2.0. +-/ +private theorem centralBinom_le (n : ℕ) (hn : 2 < n) : + n.centralBinom ≤ + (2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) * (2 * n) ^ (bertrandPrimes n).card := by + -- `C(2n, n)` is the product of its prime powers over the set `T` of the primes up to `2n`. + set T := (range (2 * n + 1)).filter Nat.Prime with hT + have hprod : ∏ p ∈ T, p ^ n.centralBinom.factorization p = n.centralBinom := + (prod_filter_of_ne fun p _ h => by + contrapose h + rw [Nat.factorization_eq_zero_of_not_prime _ h, pow_zero]).trans + n.prod_pow_factorization_centralBinom + -- Split the primes at `n`, and those up to `n` at `√(2n)`. + rw [← hprod, ← prod_filter_mul_prod_filter_not T (· ≤ n), + ← prod_filter_mul_prod_filter_not (T.filter (· ≤ n)) (· ≤ Nat.sqrt (2 * n))] + refine mul_le_mul' (mul_le_mul' ?_ ?_) ?_ + · -- There are at most `√(2n)` primes up to `√(2n)`. + have hsub : (T.filter (· ≤ n)).filter (· ≤ Nat.sqrt (2 * n)) ⊆ Icc 1 (Nat.sqrt (2 * n)) := by + intro p hp + simp only [hT, mem_filter, mem_range] at hp + obtain ⟨⟨⟨-, hprime⟩, -⟩, hsqrt⟩ := hp + exact mem_Icc.mpr ⟨hprime.one_lt.le, hsqrt⟩ + exact (prod_pow_factorization_le (by omega) _).trans (pow_right_mono₀ (by omega) + ((card_le_card hsub).trans_eq (by rw [Nat.card_Icc, Nat.add_sub_cancel]))) + · -- The primes in `(√(2n), n]`. + refine prod_middle_le hn _ fun p hp => ?_ + simp only [hT, mem_filter, mem_range, not_le] at hp + obtain ⟨⟨⟨-, hprime⟩, hpn⟩, hsqrt⟩ := hp + exact ⟨hprime, hsqrt, hpn⟩ + · -- The primes above `n` are those of `(n, 2n]`. + refine (prod_pow_factorization_le (by omega) _).trans + (pow_right_mono₀ (by omega) (card_le_card fun p hp => ?_)) + simp only [hT, mem_filter, mem_range] at hp + obtain ⟨⟨hp2n, hprime⟩, hnp⟩ := hp + exact mem_bertrandPrimes.2 ⟨⟨by omega, by omega⟩, hprime⟩ + +/-! +## 2. The elementary estimate `n^6 · (2n)^(6√(2n)) ≤ 2^n` for `n ≥ 16562` -/ + +/-- For `a ≥ 0` the function `log x + √(2x) · log (2x) - a x` is concave for `x > 1/2`. Adapted from +Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0 (there inside +`Bertrand.real_main_inequality`, for `a = log 4 / 3`). +-/ +private theorem concaveOn_log_add_sqrt_mul_log_sub (a : ℝ) (ha : 0 ≤ a) : + ConcaveOn ℝ (Set.Ioi (1 / 2 : ℝ)) + (fun x : ℝ => Real.log x + √(2 * x) * Real.log (2 * x) - a * x) := by + apply ConcaveOn.sub + · apply ConcaveOn.add + · exact strictConcaveOn_log_Ioi.concaveOn.subset + (Set.Ioi_subset_Ioi (by norm_num)) (convex_Ioi _) + · have h := strictConcaveOn_sqrt_mul_log_Ioi.concaveOn.comp_linearMap + ((2 : ℝ) • (LinearMap.id : ℝ →ₗ[ℝ] ℝ)) + have hset : (⇑((2 : ℝ) • (LinearMap.id : ℝ →ₗ[ℝ] ℝ))) ⁻¹' Set.Ioi 1 = Set.Ioi (1 / 2) := by + ext x + simp only [Set.mem_Ioi, Set.mem_preimage, LinearMap.smul_apply, LinearMap.id_coe, id_eq, + smul_eq_mul] + constructor <;> intro h <;> linarith + rw [hset] at h + exact h + · exact ConvexOn.smul ha (convexOn_id (convex_Ioi _)) + +/-- `log x + √(2x) · log (2x) ≤ (log 2 / 6) · x` for `x ≥ 16562`. The difference of the two sides +is concave, it is nonnegative at `x = 2` and nonpositive at `x = 16562 = 2 · 91²` (where `√(2x) += 182`; the only numerical input is `91^13 ≤ 2^85`), so it is nonpositive from there on. Adapted +from Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0 (the same argument proves +`Bertrand.real_main_inequality` there, with other numbers). +-/ +private theorem log_add_sqrt_mul_log_le {x : ℝ} (hx : 16562 ≤ x) : + Real.log x + √(2 * x) * Real.log (2 * x) ≤ Real.log 2 / 6 * x := by + have hlog2 : 0 < Real.log 2 := Real.log_pos (by norm_num) + have hat2 : (0 : ℝ) ≤ + Real.log 2 + √(2 * 2) * Real.log (2 * 2) - Real.log 2 / 6 * 2 := by + rw [Real.sqrt_mul_self (by norm_num), Real.log_mul (by norm_num) (by norm_num)] + linarith + have hat16562 : Real.log 16562 + √(2 * 16562) * Real.log (2 * 16562) + - Real.log 2 / 6 * 16562 ≤ 0 := by + have hlog91 : 13 * Real.log 91 ≤ 85 * Real.log 2 := by + have h := Real.log_le_log (by positivity) (show (91 : ℝ) ^ 13 ≤ 2 ^ 85 by norm_num) + rw [Real.log_pow, Real.log_pow] at h + exact_mod_cast h + have hlogx : Real.log 16562 = Real.log 2 + 2 * Real.log 91 := by + rw [show (16562 : ℝ) = 2 * 91 ^ 2 by norm_num, Real.log_mul (by norm_num) (by norm_num), + Real.log_pow] + norm_num + have hlog2x : Real.log (182 * 182) = 2 * Real.log 2 + 2 * Real.log 91 := by + rw [show (182 * 182 : ℝ) = 2 ^ 2 * 91 ^ 2 by norm_num, + Real.log_mul (by norm_num) (by norm_num), Real.log_pow, Real.log_pow] + norm_num + rw [show (2 * 16562 : ℝ) = 182 * 182 by norm_num, Real.sqrt_mul_self (by norm_num), hlogx, + hlog2x] + -- `366 log 91 ≤ 366 · (85/13) log 2 < (16562/6 - 365) log 2` + linarith only [hlog91, hlog2] + have hconcave := concaveOn_log_add_sqrt_mul_log_sub (Real.log 2 / 6) (by positivity) + have hatx := hconcave.right_le_of_le_left'' + (x := 2) (y := 16562) (z := x) (by norm_num [Set.mem_Ioi]) (by rw [Set.mem_Ioi]; linarith) + (by norm_num) hx (hat16562.trans hat2) + linarith only [hatx, hat16562] + +/-- The same estimate for natural numbers, without logarithms. -/ +private theorem pow_mul_pow_sqrt_le_two_pow {n : ℕ} (hn : 16562 ≤ n) : + n ^ 6 * (2 * n) ^ (6 * Nat.sqrt (2 * n)) ≤ 2 ^ n := by + have hx : (16562 : ℝ) ≤ n := by exact_mod_cast hn + have hreal := log_add_sqrt_mul_log_le hx + have hsqrt : (Nat.sqrt (2 * n) : ℝ) ≤ √(2 * n) := by + exact_mod_cast Real.nat_sqrt_le_real_sqrt (a := 2 * n) + have hlog : 0 ≤ Real.log (2 * n) := Real.log_nonneg (by linarith) + have hpos : (0 : ℝ) < n := by linarith + rw [← Nat.cast_le (α := ℝ)] + push_cast + rw [← Real.log_le_log_iff (by positivity) (by positivity), + Real.log_mul (by positivity) (by positivity), Real.log_pow, Real.log_pow, Real.log_pow] + push_cast + -- `6 log n + 6 ⌊√(2n)⌋ log(2n) ≤ 6 (log n + √(2n) log(2n)) ≤ n log 2` + linarith [hreal, mul_le_mul_of_nonneg_right hsqrt hlog] + +/-! +## 3. The count for `n ≥ 16562` -/ + +/-- `2^n ≤ (2n)^(2k)` for `n ≥ 16562`. If it failed, the sixth power of `4^n < n · (2n)^√(2n) · +4^(2n/3) · (2n)^k` would give `2^(12n) < (n^6 (2n)^(6√(2n))) · 2^(8n) · 2^(3n) ≤ 2^(12n)`. +-/ +private theorem two_pow_le_of_large {n : ℕ} (hn : 16562 ≤ n) : + 2 ^ n ≤ (2 * n) ^ (2 * (bertrandPrimes n).card) := by + by_contra hcon + have hfew : (2 * n) ^ (2 * (bertrandPrimes n).card) ≤ 2 ^ n := (not_le.mp hcon).le + have herdos : 4 ^ n < n * ((2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) + * (2 * n) ^ (bertrandPrimes n).card) := + (Nat.four_pow_lt_mul_centralBinom n (by omega)).trans_le + (Nat.mul_le_mul_left _ (centralBinom_le n (by omega))) + have hsmall := pow_mul_pow_sqrt_le_two_pow hn + have hmiddle : 4 ^ (6 * (2 * n / 3)) ≤ 4 ^ (4 * n) := + Nat.pow_le_pow_right (by norm_num) (by omega) + refine lt_irrefl ((4 ^ n) ^ 6) ?_ + calc (4 ^ n) ^ 6 + < (n * ((2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) + * (2 * n) ^ (bertrandPrimes n).card)) ^ 6 := Nat.pow_lt_pow_left herdos (by norm_num) + _ = (n ^ 6 * (2 * n) ^ (6 * Nat.sqrt (2 * n))) * 4 ^ (6 * (2 * n / 3)) + * ((2 * n) ^ (2 * (bertrandPrimes n).card)) ^ 3 := by ring + _ ≤ 2 ^ n * 4 ^ (4 * n) * (2 ^ n) ^ 3 := by gcongr + _ = (4 ^ n) ^ 6 := by + rw [mul_comm 4 n, pow_mul 4 n 4, show (4 : ℕ) ^ n = 2 ^ n * 2 ^ n by rw [← mul_pow]; rfl] + ring + +/-- The count of step 3 with natural logarithms: `n log 2 ≤ 2 k log (2n)`. -/ +private theorem mul_log_two_le_card_bertrandPrimes_mul_log {n : ℕ} (hn : 16562 ≤ n) : + (n : ℝ) * Real.log 2 ≤ 2 * (bertrandPrimes n).card * Real.log (2 * n) := by + have hpow : (2 : ℝ) ^ n ≤ (2 * (n : ℝ)) ^ (2 * (bertrandPrimes n).card) := by + exact_mod_cast two_pow_le_of_large hn + have hlog := Real.log_le_log (by positivity) hpow + rw [Real.log_pow, Real.log_pow] at hlog + exact_mod_cast hlog + +/-! +## 4. Half-open windows `[x/2, x)` with real endpoints -/ + +/-- For a real `x ≥ 4` the natural number `n = ⌈x/2⌉ - 1`, written below as `(⌈x⌉ - 1) / 2`, +satisfies `n ≥ 1`, `n ≥ x/4` and `2n < x`, and every natural number in `(n, 2n]` lies in `[x/2, +x)`. +-/ +private theorem exists_nat_window {x : ℝ} (hx : 4 ≤ x) : + ∃ n : ℕ, 1 ≤ n ∧ x / 4 ≤ (n : ℝ) ∧ 2 * (n : ℝ) < x ∧ + ∀ p : ℕ, n < p → p ≤ 2 * n → x / 2 ≤ (p : ℝ) ∧ (p : ℝ) < x := by + have hle : x ≤ (⌈x⌉₊ : ℝ) := Nat.le_ceil x + have hlt : (⌈x⌉₊ : ℝ) < x + 1 := Nat.ceil_lt_add_one (by linarith) + have hceil : 4 ≤ ⌈x⌉₊ := by exact_mod_cast hx.trans hle + obtain ⟨n, hn⟩ : ∃ n, n = (⌈x⌉₊ - 1) / 2 := ⟨_, rfl⟩ + have hlow : (⌈x⌉₊ : ℝ) ≤ 2 * n + 2 := by exact_mod_cast (by omega : ⌈x⌉₊ ≤ 2 * n + 2) + have hhigh : (2 * n + 1 : ℝ) ≤ (⌈x⌉₊ : ℝ) := by exact_mod_cast (by omega : 2 * n + 1 ≤ ⌈x⌉₊) + refine ⟨n, by omega, by linarith, by linarith, fun p hnp hp2n => ?_⟩ + have hnp' : (n : ℝ) + 1 ≤ p := by exact_mod_cast hnp + have hp2n' : (p : ℝ) ≤ 2 * n := by exact_mod_cast hp2n + exact ⟨by linarith, by linarith⟩ + +/-- For every real `x ≥ 4` there is a prime `p` with `x/2 ≤ p < x`, by Bertrand's postulate. -/ +theorem exists_prime_half_le_and_lt (x : ℝ) (hx : 4 ≤ x) : + ∃ p : ℕ, p.Prime ∧ x / 2 ≤ (p : ℝ) ∧ (p : ℝ) < x := by + obtain ⟨n, hn1, -, -, hwindow⟩ := exists_nat_window hx + obtain ⟨p, hprime, hnp, hp2n⟩ := Nat.exists_prime_lt_and_le_two_mul n (by omega) + exact ⟨p, hprime, hwindow p hnp hp2n⟩ + +/-- If `(n, 2n]` lies in the window and `n ≥ x/4` is large, the window has at least `x / (12 log x)` +primes: `(x/4) log 2 ≤ n log 2 ≤ 2 k log (2n) ≤ 2 k log x`, and `log 2 ≥ 2/3`. +-/ +private theorem div_log_le_card_of_large {x : ℝ} {n : ℕ} {S : Finset ℕ} (hx : 4 ≤ x) + (hn : 16562 ≤ n) (hn4 : x / 4 ≤ (n : ℝ)) (h2n : 2 * (n : ℝ) < x) (hsub : bertrandPrimes n ⊆ S) : + x / (12 * Real.log x) ≤ (S.card : ℝ) := by + have hcard : ((bertrandPrimes n).card : ℝ) ≤ S.card := by exact_mod_cast card_le_card hsub + have hn1 : (1 : ℝ) ≤ n := by exact_mod_cast (by omega : 1 ≤ n) + have hlog : Real.log (2 * n) ≤ Real.log x := Real.log_le_log (by linarith) h2n.le + have hlog0 : 0 ≤ Real.log (2 * n) := Real.log_nonneg (by linarith) + have hlog2 : 2 / 3 ≤ Real.log 2 := by linarith [Real.log_two_gt_d9] + rw [div_le_iff₀ (mul_pos (by norm_num) (Real.log_pos (by linarith)))] + calc x = 6 * (x / 4 * (2 / 3)) := by ring + _ ≤ 6 * (n * Real.log 2) := by gcongr + _ ≤ 6 * (2 * (bertrandPrimes n).card * Real.log (2 * n)) := + mul_le_mul_of_nonneg_left (mul_log_two_le_card_bertrandPrimes_mul_log hn) (by norm_num) + _ ≤ 6 * (2 * S.card * Real.log x) := by gcongr + _ = S.card * (12 * Real.log x) := by ring + +/-- There is a `c > 0` such that, for every real `x ≥ 4`, every finite set of natural numbers that +contains all primes `p` with `x/2 ≤ p < x` has at least `c · x / log x` elements. (This form +applies to the set of those primes however it is written down.) +-/ +theorem exists_forall_mul_div_log_le_card_primes : + ∃ c : ℝ, 0 < c ∧ ∀ x : ℝ, 4 ≤ x → ∀ S : Finset ℕ, + (∀ p : ℕ, p.Prime → x / 2 ≤ (p : ℝ) → (p : ℝ) < x → p ∈ S) → + c * (x / Real.log x) ≤ (S.card : ℝ) := by + -- `66248 = 4 · 16562`: for `x` below it one prime is enough + refine ⟨1 / 66248, by norm_num, fun x hx S hS => ?_⟩ + obtain ⟨n, hn1, hn4, h2n, hwindow⟩ := exists_nat_window hx + have hlogx : 1 ≤ Real.log x := one_le_log_of_three_le (by linarith) + rcases le_or_gt 16562 n with hlarge | hsmall + · -- The primes of `(n, 2n]` lie in the window. + have hsub : bertrandPrimes n ⊆ S := fun p hp => by + obtain ⟨⟨hnp, hp2n⟩, hprime⟩ := mem_bertrandPrimes.1 hp + exact hS p hprime (hwindow p hnp hp2n).1 (hwindow p hnp hp2n).2 + calc 1 / 66248 * (x / Real.log x) ≤ 1 / 12 * (x / Real.log x) := by gcongr; norm_num + _ = x / (12 * Real.log x) := by field_simp + _ ≤ (S.card : ℝ) := div_log_le_card_of_large hx hlarge hn4 h2n hsub + · -- Here `x ≤ 4n < 66248`, and one prime is enough. + obtain ⟨p, hprime, hlow, hhigh⟩ := exists_prime_half_le_and_lt x hx + have hone : (1 : ℝ) ≤ S.card := by + exact_mod_cast Finset.card_pos.mpr ⟨p, hS p hprime hlow hhigh⟩ + have hn : (n : ℝ) ≤ 16562 := by exact_mod_cast hsmall.le + calc 1 / 66248 * (x / Real.log x) ≤ 1 / 66248 * (x / 1) := by gcongr + _ ≤ 1 := by linarith + _ ≤ (S.card : ℝ) := hone + +/-! +## 5. The window `[√D/2, √D)` used in the modular reduction -/ + +/-- There is a `c > 0` such that, for every natural number `D ≥ 16`, every finite set of natural +numbers that contains all primes `p` with `√D/2 ≤ p < √D` has at least `c · √D / log D` +elements. +-/ +theorem exists_forall_mul_sqrt_div_log_le_card_primes : + ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → ∀ S : Finset ℕ, + (∀ p : ℕ, p.Prime → √D / 2 ≤ (p : ℝ) → (p : ℝ) < √D → p ∈ S) → + c * (√D / Real.log D) ≤ (S.card : ℝ) := by + obtain ⟨c, hc, hcount⟩ := exists_forall_mul_div_log_le_card_primes + refine ⟨c, hc, fun D hD S hS => ?_⟩ + have hlog : 0 < Real.log D := Real.log_pos (by exact_mod_cast (by omega : 1 < D)) + calc c * (√D / Real.log D) ≤ c * (√D / (Real.log D / 2)) := by + gcongr + linarith + _ = c * (√D / Real.log (√D)) := by rw [Real.log_sqrt (Nat.cast_nonneg D)] + _ ≤ (S.card : ℝ) := hcount _ ((Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD))) S hS + +end Nat diff --git a/Complexity/Foundations/RingTheory/CyclicIntRing.lean b/Complexity/Foundations/RingTheory/CyclicIntRing.lean new file mode 100644 index 0000000000..94ae6d738f --- /dev/null +++ b/Complexity/Foundations/RingTheory/CyclicIntRing.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.RingTheory.AdjoinRoot + +/-! +# Integer polynomials modulo `X^p - 1` + +## Main definitions + +* `CyclicIntRing`: the integer polynomial quotient. +* `CyclicIntRing.coeff`: coefficients in the unique representative of degree less than `p`. + +## Main results + +* `CyclicIntRing.coeff_x_pow`: powers have a single coefficient at the residue of their + exponent. + +## References + +Adapted from [Anthropic's +Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean#L57-L101). +-/ + +@[expose] public section + +namespace Cslib + +open Polynomial in +/-- The quotient ring `ℤ[X]/(X^p - 1)`. -/ +abbrev CyclicIntRing (p : ℕ) : Type := AdjoinRoot ((X : ℤ[X]) ^ p - C 1) + +namespace CyclicIntRing + +open Polynomial + +variable {p : ℕ} + +/-- The element `x` of `ℤ[x]/(x^p − 1)`. -/ +noncomputable def x (p : ℕ) : CyclicIntRing p := AdjoinRoot.root _ + +/-- The coefficient of degree `r` in an element `z` of the quotient, as a linear map. It is the +coefficient of `x^r` in the unique polynomial of degree less than `p` that represents `z` +(Mathlib's `AdjoinRoot.modByMonicHom` gives that polynomial; it asks for the fact that `x^p − 1` +is monic, which holds as `p ≠ 0`). +-/ +noncomputable def coeff (hp : p ≠ 0) (r : ℕ) : CyclicIntRing p →ₗ[ℤ] ℤ := + lcoeff ℤ r ∘ₗ AdjoinRoot.modByMonicHom (monic_X_pow_sub_C (1 : ℤ) hp) + +/-- The coefficient, in terms of the representing polynomial. -/ +theorem coeff_apply (hp : p ≠ 0) (r : ℕ) (z : CyclicIntRing p) : + coeff hp r z = (AdjoinRoot.modByMonicHom (monic_X_pow_sub_C (1 : ℤ) hp) z).coeff r := + rfl + +/-- In `ℤ[x]/(x^p − 1)` we have `x^p = 1`. -/ +private theorem x_pow_self (p : ℕ) : x p ^ p = 1 := by + have h : AdjoinRoot.mk ((X : ℤ[X]) ^ p - C 1) ((X : ℤ[X]) ^ p - C 1) = 0 := AdjoinRoot.mk_self + rw [map_sub, map_pow, AdjoinRoot.mk_X, C_1, map_one] at h + exact sub_eq_zero.1 h + +/-- The coefficient of `x^r` in the power `x^k` of `ℤ[x]/(x^p − 1)` is 1 if `r = k mod p` and 0 +otherwise. +-/ +theorem coeff_x_pow (hp : p ≠ 0) (k r : ℕ) : + coeff hp r (x p ^ k) = if r = k % p then 1 else 0 := by + have hred : x p ^ k = x p ^ (k % p) := by + conv_lhs => rw [← Nat.div_add_mod k p, pow_add, pow_mul, x_pow_self, one_pow, one_mul] + have hdeg : ((X : ℤ[X]) ^ (k % p)).degree < ((X : ℤ[X]) ^ p - C 1).degree := by + rw [degree_X_pow, degree_X_pow_sub_C (Nat.pos_of_ne_zero hp)] + exact_mod_cast Nat.mod_lt k (Nat.pos_of_ne_zero hp) + rw [coeff_apply, hred, x, ← AdjoinRoot.mk_X, ← map_pow, AdjoinRoot.modByMonicHom_mk, + (modByMonic_eq_self_iff (monic_X_pow_sub_C (1 : ℤ) hp)).2 hdeg, coeff_X_pow] + +end CyclicIntRing + + +end Cslib diff --git a/ComplexityTests/ExactTriangleHashing.lean b/ComplexityTests/ExactTriangleHashing.lean new file mode 100644 index 0000000000..41df09a874 --- /dev/null +++ b/ComplexityTests/ExactTriangleHashing.lean @@ -0,0 +1,115 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Hashing + +/-! +# Modular Exact Triangle counting and prime-selection regressions + +Concrete zeros, false positives, coefficient wraparound, minimum selection, the integer window +boundary and an admitted instance of the universal bound. These assert no native running time. +-/ + +namespace ComplexityTests.ExactTriangleHashing + +open Cslib Cslib.Algorithms.ExactTriangle + +private def ab (a _ : Fin 2) : ℤ := if a = 0 then 0 else 2 +private def z (_ _ : Fin 2) : ℤ := 0 + +private theorem window16 : primesInRange 16 = {2, 3} := by + rw [primesInRange_eq_filter] + decide + +-- Both endpoints matter: prime 2 is admitted at the lower endpoint for D=16; +-- prime 5 is excluded at the upper endpoint for D=25, and prime 3 is admitted there. +example : 2 ∈ primesInRange 16 ∧ 5 ∉ primesInRange 25 ∧ 3 ∈ primesInRange 25 := by + simp only [mem_primesInRange_iff_sq] + decide + +example : 2 ∉ primesInRange 17 := by rw [mem_primesInRange_iff_sq]; decide + +example : countZeroMod ab z z 2 = 8 ∧ countZeroMod ab z z 3 = 4 := by + unfold countZeroMod weightSum ab z + decide + +example : zeroCount ab z z = 4 ∧ falsePositiveCount ab z z 2 = 4 ∧ + falsePositiveCount ab z z 3 = 0 := by + unfold zeroCount falsePositiveCount IsFalsePositive weightSum ab z + decide + +example : countZeroMod ab z z 0 = 4 ∧ falsePositiveCount ab z z 0 = 0 := by + unfold countZeroMod falsePositiveCount IsFalsePositive weightSum ab z + decide + +-- Counting distinguishes the two candidate primes; the minimum is 3. +example : IsSelectedPrime ab z z 16 3 ∧ ¬IsSelectedPrime ab z z 16 2 := by + simp only [IsSelectedPrime, window16] + unfold countZeroMod weightSum ab z + decide + +-- A tie permits either minimum prime; the predicate does not impose a tie-break. +example : IsSelectedPrime (fun _ _ : Fin 1 => 0) (fun _ _ => 0) (fun _ _ => 0) 16 2 ∧ + IsSelectedPrime (fun _ _ : Fin 1 => 0) (fun _ _ => 0) (fun _ _ => 0) 16 3 := by + constructor <;> refine ⟨by rw [window16]; decide, ?_⟩ <;> intro q _ <;> + simp [countZeroMod, weightSum] + +example : HasZeroTriangle ab z z := ⟨0, 0, 0, rfl⟩ +example : 0 < zeroCount ab z z := (zeroCount_pos_iff ab z z).mpr ⟨0, 0, 0, rfl⟩ + +private def one (_ _ : Fin 2) : ℤ := 1 + +example : ¬HasZeroTriangle one one one := by + rintro ⟨a, b, c, h⟩ + norm_num [one] at h + +example : zeroCount one one one = 0 ∧ countZeroMod one one one 3 = 8 ∧ + falsePositiveCount one one one 3 = 8 := by + unfold zeroCount countZeroMod falsePositiveCount IsFalsePositive weightSum one + decide + +-- A negative residue and a positive residue wrap to exponent zero modulo 3. +example : CyclicIntRing.coeff (by decide : 3 ≠ 0) 0 + ((matP (fun _ _ : Fin 2 => (-1 : ℤ)) 3 * + matQ (fun _ _ : Fin 2 => (1 : ℤ)) 3) 0 0) = 2 := by + rw [coeff_matP_mul_matQ (fun _ _ : Fin 2 => (1 : ℤ)) + (fun _ _ : Fin 2 => (-1 : ℤ)) (by decide) 0 0 0 (by decide)] + decide + +-- These matrices are asymmetric: transposing either input changes the selected count. +private def ac (a c : Fin 2) : ℤ := if a = 0 then if c = 0 then -1 else 2 else 0 +private def bc (b c : Fin 2) : ℤ := if b = 0 ∧ c = 1 then 1 else 0 + +example : CyclicIntRing.coeff (by decide : 3 ≠ 0) 0 ((matP ac 3 * matQ bc 3) 0 0) = 1 := by + rw [coeff_matP_mul_matQ bc ac (by decide) 0 0 0 (by decide)] + unfold ac bc + decide + +example : CyclicIntRing.coeff (by decide : 2 ≠ 0) 1 (CyclicIntRing.x 2 ^ 5) = 1 := by + rw [CyclicIntRing.coeff_x_pow] + norm_num + +example : CyclicIntRing.coeff (by decide : 2 ≠ 0) 3 (CyclicIntRing.x 2 ^ 5) = 0 := by + rw [CyclicIntRing.coeff_x_pow] + norm_num + +example : zeroCount (fun _ _ : Fin 0 => 0) (fun _ _ => 0) (fun _ _ => 0) = 0 := by + simp [zeroCount] + +example : ¬WeightsBoundedBy (fun _ _ : Fin 1 => (0 : ℤ)) (fun _ _ => 0) (fun _ _ => 0) (-1) := by + norm_num [WeightsBoundedBy] + +-- The full bound's n,D,kappa hypotheses and input bound have a concrete witness. +example : (falsePositiveCount (fun _ _ : Fin 16 => 0) (fun _ _ => 0) (fun _ _ => 0) 2 : ℝ) ≤ + Hashing.falsePositiveConst * (1 * (16 : ℝ) ^ 3 * Real.log 16 / Real.sqrt 16) := by + apply Hashing.falsePositiveCount_le_const_mul (by decide : 16 ≤ 16) (by decide : 16 ≤ 16) + (by norm_num : (1 : ℝ) ≤ 1) + · norm_num [WeightsBoundedBy] + · refine ⟨by rw [window16]; decide, ?_⟩ + intro q _ + simp [countZeroMod, weightSum] + +end ComplexityTests.ExactTriangleHashing diff --git a/NOTICE b/NOTICE index 3ab01a2df3..95b9c8d57a 100644 --- a/NOTICE +++ b/NOTICE @@ -1,10 +1,11 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/{Defs,Lopsided}.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, Complexity/Foundations/Data/Finset/Average.lean, Complexity/Foundations/Data/Matrix/IntegerBits.lean and -Complexity/Foundations/NumberTheory/PrimeCounting.lean are adapted from +Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow}.lean and +Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. @@ -13,3 +14,7 @@ Sparse Lopsided Graphs" Copyright 2026 Anthropic, PBC This product is licensed under the Apache License, Version 2.0 (see LICENSE). + +The prime-window proof also adapts Mathlib/NumberTheory/Bertrand.lean, +Copyright (c) 2020 Patrick Stevens; authors Patrick Stevens, Bolton Bailey, +under the Apache License, Version 2.0. diff --git a/ROADMAP.md b/ROADMAP.md index 5b3b230013..d31b9ede12 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -422,8 +422,9 @@ each statement against the numbered result. Organize modules by subject, not cha has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting - interface ([#1438]) adds padding, detection and signed digit reconstruction; it still needs - modular Exact Triangle hashing/false-positive splitting, tensor/sparse-query programs, native + interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting + and the selected-prime false-positive bound ([#1442]) are proved mathematically; it still needs + residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded @@ -550,4 +551,5 @@ not planned, not as proved. [#1394]: https://github.com/cslib-community/complexitylib/issues/1394 [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 [#1438]: https://github.com/cslib-community/complexitylib/issues/1438 +[#1442]: https://github.com/cslib-community/complexitylib/issues/1442 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index 87d9cb5288..2bc29e783b 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -174,8 +174,16 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he `Set.EqOn`; no additional weighted-triangle predicate or source machine is introduced. This is the mathematical interface milestone [#1438](https://github.com/cslib-community/complexitylib/issues/1438), with no sparsity assumption, - preprocessing/time claim or solver. Modular hashing/false-positive splitting and tensor/sparse - query programs remain in #1394, alongside the native and asymptotic composition below. + preprocessing/time claim or solver. `Algorithms/ExactTriangle/{Weights,Hashing}.lean` now adapts + the modular counting/coefficient identities, minimum-count prime and uniform false-positive + bound from `Sec3/Theorem17/Hashing.lean`, using supplied matrices and the existing decision + predicate. Its integer-square window characterisation supports later native enumeration. + The quotient/coefficient API lives in `Foundations/RingTheory/CyclicIntRing.lean`; the uniform + all-window prime-count bound lives in `Foundations/NumberTheory/PrimeWindow.lean`, retaining + the source credit to Mathlib, Patrick Stevens and Bolton Bailey. These are mathematical + statements, not native instructions or an assumed solver. See + [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, + witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full input/time/width/asymptotic composition remain in From 0d8da3e78edceaa2e11fa3c31a1b44fce10dbdbd Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:09:24 -0400 Subject: [PATCH 03/32] doc(threeSum): cite the lopsided triangle paper and clarify adaptation scope --- Complexity/Algorithms/ExactTriangle/Hashing.lean | 1 + Complexity/Algorithms/ExactTriangle/Lopsided.lean | 1 + UPSTREAM.md | 13 +++++++------ references.bib | 12 ++++++++++++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 6799e79fc7..affaadd2e2 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -39,6 +39,7 @@ asserted. The supplied three matrices retain the existing `HasZeroTriangle` sema ## References +The modular reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Anthropic's Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), with the minimal count/selection definitions extracted from its `PaperStatements.lean`. diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean index d52e779437..84694440fe 100644 --- a/Complexity/Algorithms/ExactTriangle/Lopsided.lean +++ b/Complexity/Algorithms/ExactTriangle/Lopsided.lean @@ -36,6 +36,7 @@ fast solver or subquadratic running time. ## References +The lopsided matrix interface is used in [AlmanWilliams2026, Section 3]. Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean) and the lopsided definitions in [PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L798-L852). The adaptation uses `Set.EqOn`, pointwise signed parts, and direct finite cardinalities. diff --git a/UPSTREAM.md b/UPSTREAM.md index 2bc29e783b..8b49547dae 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -158,6 +158,13 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he Given its arguments in registers, it writes one instance in at most `131*t² + 5` instructions at width `w + 2`, excluding the halt; its theorem identifies the three encoded matrices and preserves the input. Layout, signed index/filler bounds and target address bounds are explicit. + The existing signed and unsigned APIs were reused; no source machine or separate run semantics + were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full + input/time/width/asymptotic composition remain in + [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved + Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution + are retained; the native proofs and representation choices are local. + `Algorithms/ThreeSum/ThreeSets.lean` and `ArrayPacking.lean` provide the generic distinct-position frontend and three-array packing. `Algorithms/ThreeSum/ChanHe/` now adapts the core mathematical reduction: minimum-prime searches, collision/heavy-element bounds, light-element arrays, bounded @@ -184,12 +191,6 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he statements, not native instructions or an assumed solver. See [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. - The existing signed and unsigned APIs were reused; no source machine or separate run semantics - were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full - input/time/width/asymptotic composition remain in - [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved - Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution - are retained; the native proofs and representation choices are local. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. diff --git a/references.bib b/references.bib index 8cf6daceb4..f42e7901a7 100644 --- a/references.bib +++ b/references.bib @@ -103,6 +103,18 @@ @misc{Algolean2026 url = {https://github.com/Shreyas4991/Algolean} } +@misc{AlmanWilliams2026, + author = {Alman, Josh and Vassilevska Williams, Virginia}, + title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in + Sparse Lopsided Graphs}, + year = {2026}, + eprint = {2610.06783}, + archivePrefix = {arXiv}, + primaryClass = {cs.DS}, + doi = {10.48550/arXiv.2610.06783}, + url = {https://arxiv.org/abs/2610.06783v1} +} + @article{AllenderBuhrmanKouckyVanMelkebeekRonneburger2006, author = {Allender, Eric and Buhrman, Harry and Kouck{\'y}, Michal and van Melkebeek, Dieter and Ronneburger, Detlef}, title = {Power from Random Strings}, From 8d80ecce623c8ab4a08742ed6944892ebe9f9b21 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:09:49 -0400 Subject: [PATCH 04/32] doc(bibliography): sort the new paper entry --- references.bib | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/references.bib b/references.bib index f42e7901a7..af7ca6f2c2 100644 --- a/references.bib +++ b/references.bib @@ -103,18 +103,6 @@ @misc{Algolean2026 url = {https://github.com/Shreyas4991/Algolean} } -@misc{AlmanWilliams2026, - author = {Alman, Josh and Vassilevska Williams, Virginia}, - title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in - Sparse Lopsided Graphs}, - year = {2026}, - eprint = {2610.06783}, - archivePrefix = {arXiv}, - primaryClass = {cs.DS}, - doi = {10.48550/arXiv.2610.06783}, - url = {https://arxiv.org/abs/2610.06783v1} -} - @article{AllenderBuhrmanKouckyVanMelkebeekRonneburger2006, author = {Allender, Eric and Buhrman, Harry and Kouck{\'y}, Michal and van Melkebeek, Dieter and Ronneburger, Detlef}, title = {Power from Random Strings}, @@ -137,6 +125,18 @@ @article{AllenderRubinstein1988 doi = {10.1137/0217075} } +@misc{AlmanWilliams2026, + author = {Alman, Josh and Vassilevska Williams, Virginia}, + title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in + Sparse Lopsided Graphs}, + year = {2026}, + eprint = {2610.06783}, + archivePrefix = {arXiv}, + primaryClass = {cs.DS}, + doi = {10.48550/arXiv.2610.06783}, + url = {https://arxiv.org/abs/2610.06783v1} +} + @article{AlonBoppana1987, author = {Alon, Noga and Boppana, Ravi B.}, title = {The Monotone Circuit Complexity of {B}oolean Functions}, From fd69be7bc459d83cab3863f6f6e2f59239b21595 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:02:59 -0400 Subject: [PATCH 05/32] fix(exactTriangle): repair the prime-window header and hashing documentation --- .../Algorithms/ExactTriangle/Hashing.lean | 115 +++++++++++------- .../Foundations/NumberTheory/PrimeWindow.lean | 4 +- .../Foundations/RingTheory/CyclicIntRing.lean | 1 - 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index affaadd2e2..fbb2834b6e 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -43,7 +43,8 @@ The modular reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Anthropic's Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), with the minimal count/selection definitions extracted from its `PaperStatements.lean`. -Source-derived prose below is paraphrased. The source pin is +The declaration docstrings describe the Lean definitions and statements in local notation. +The source pin is `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @@ -52,16 +53,20 @@ Source-derived prose below is paraphrased. The source pin is namespace Cslib.Algorithms.ExactTriangle open Classical in -/-- Primes in the half-open window `[sqrt D / 2, sqrt D)`. -/ +/-- Prime numbers in the half-open real interval `[sqrt D / 2, sqrt D)`. +-/ noncomputable def primesInRange (D : ℕ) : Finset ℕ := (Finset.range D).filter fun p => p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D -/-- Number of triangles whose sum vanishes modulo `p`. -/ +/-- Count vertex triples whose three edge weights have sum congruent to zero modulo `p`. +-/ def countZeroMod {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (p : ℕ) : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≡ 0 [ZMOD (p : ℤ)]).card -/-- A window prime minimises the modular count over all window primes. -/ +/-- A prime in `primesInRange D` whose modular-zero count is minimal among all primes in that +window. Tied minima all satisfy this predicate. +-/ def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ) : Prop := p ∈ primesInRange D ∧ ∀ q ∈ primesInRange D, countZeroMod wAB wBC wAC p ≤ countZeroMod wAB wBC wAC q @@ -81,8 +86,8 @@ private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)) /-! ### The primes in the range -/ -/-- Proof of Theorem 17, a prime p ∈ [√D/2, √D): the primes in the range are the primes `p` with -`√D/2 ≤ p < √D`. (The bound `p < D` in the definition excludes none of them.) +/-- Window membership is equivalent to primality and the two real endpoint inequalities. The +finite enumeration bound in `primesInRange` excludes no member of the interval. -/ theorem mem_primesInRange {D p : ℕ} : p ∈ primesInRange D ↔ p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D := by @@ -93,7 +98,9 @@ theorem mem_primesInRange {D p : ℕ} : have hsq : p ^ 2 < D := by exact_mod_cast (Real.lt_sqrt (Nat.cast_nonneg p)).1 h.2.2 exact (Nat.le_self_pow two_ne_zero p).trans_lt hsq -/-- Window membership is an exact integer-square test, suitable for native enumeration. -/ +/-- Characterize window membership using primality and integer-square inequalities, including the +closed lower and open upper endpoints. +-/ theorem mem_primesInRange_iff_sq {D p : ℕ} : p ∈ primesInRange D ↔ p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by rw [mem_primesInRange] @@ -111,7 +118,8 @@ theorem mem_primesInRange_iff_sq {D p : ℕ} : have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by exact_mod_cast hlo nlinarith -/-- A computable finite enumeration of exactly the same half-open prime window. -/ +/-- Enumerate the real prime window using a decidable filter of the integers below `D`. +-/ theorem primesInRange_eq_filter (D : ℕ) : primesInRange D = (Finset.range D).filter fun p => p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by ext p @@ -137,20 +145,26 @@ section Counting variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) -/-- A false positive of p is a triple (a,b,c) with S(a,b,c) ≠ 0 and p ∣ S(a,b,c). -/ +/-- A vertex triple has a nonzero integer weight sum that is congruent to zero modulo `p`. +-/ abbrev IsFalsePositive (p : ℕ) (t : Fin n × Fin n × Fin n) : Prop := weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0 ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 -/-- let falsePositiveCount(p) denote the number of false positives of p. -/ +/-- Count vertex triples satisfying `IsFalsePositive` for the supplied three weight matrices and +modulus. +-/ def falsePositiveCount (p : ℕ) : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => IsFalsePositive wAB wBC wAC p t).card -/-- zeroCount, the number of zero triangles. -/ +/-- Count vertex triples whose three supplied edge weights sum to zero as an integer. +-/ def zeroCount : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0).card -/-- A positive zero count is exactly the existing Exact Triangle decision predicate. -/ +/-- The zero-triple count is positive exactly when the supplied matrices satisfy +`HasZeroTriangle`. +-/ theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wBC wAC := by classical simp only [zeroCount, Finset.card_pos, HasZeroTriangle] @@ -160,8 +174,8 @@ theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wB · rintro ⟨a, b, c, h⟩ exact ⟨(a, b, c), Finset.mem_filter.mpr ⟨Finset.mem_univ _, h⟩⟩ -/-- This number is the number of false positives falsePositiveCount(p) plus zeroCount, the number of -zero triangles, which does not depend on p. +/-- Partition the modular-zero triples into false positives and exact zero-sum triples. The +exact-zero count is independent of the modulus. -/ theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) := by @@ -173,17 +187,21 @@ theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiv by_cases h0 : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 <;> simp [IsFalsePositive, Int.modEq_zero_iff_dvd, h0] -/-- let P[a,c] := x^{w(a,c) mod p}, a matrix over `ℤ[x]/(x^p − 1)`. `w mod p` is the residue in -`{0, …, p − 1}`. +/-- Encode the A–C weight at `(a,c)` as a power of the cyclic polynomial generator, with exponent +equal to its nonnegative residue modulo `p`. -/ noncomputable def matP (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := Matrix.of fun a c => CyclicIntRing.x p ^ (wAC a c % (p : ℤ)).toNat -/-- and Q[c,b] := x^{w(b,c) mod p}. -/ +/-- Encode the B–C weight at `(b,c)` in matrix position `(c,b)`. This orientation makes the +product sum over the middle index `c`. +-/ noncomputable def matQ (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := Matrix.of fun c b => CyclicIntRing.x p ^ (wBC b c % (p : ℤ)).toNat -/-- the coefficient of x^r in (PQ)[a,b] is the number of c ∈ C with w(a,c) + w(b,c) ≡ r (mod p). -/ +/-- For a nonzero modulus and a residue `r < p`, the coefficient of degree `r` in the cyclic +matrix product counts middle vertices whose two incident weights sum to `r` modulo `p`. +-/ theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr : r < p) : CyclicIntRing.coeff hp r ((matP wAC p * matQ wBC p) a b) = ((Finset.univ.filter fun c : Fin n => @@ -200,8 +218,8 @@ theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr refine Finset.sum_congr rfl fun c _ => ?_ simp only [eq_add_toNat_emod_iff hp hr] -/-- then falsePositiveCount(p) + zeroCount is the sum over the pairs (a,b) ∈ A × B of the -coefficient of x^{−w(a,b) mod p} in (PQ)[a,b]. +/-- Recover the total modular-zero count from the cyclic matrix product by summing, over `(a,b)`, +the coefficient at the residue of `-wAB a b`. -/ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : (((falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) : ℕ) : ℤ) = @@ -235,7 +253,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : end Counting -/-- the fewer than √D primes in the range. -/ +/-- For `D ≥ 16`, the number of primes in `primesInRange D` is strictly less than `sqrt D`. +-/ theorem card_primesInRange_lt (D : ℕ) (hD : 16 ≤ D) : ((primesInRange D).card : ℝ) < Real.sqrt D := by have hpos : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] @@ -259,15 +278,17 @@ section Selection variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) -/-- There is a prime to select. -/ +/-- For every `D ≥ 16` and every three supplied weight matrices, the prime window contains a +minimum of the modular-zero count. +-/ theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by obtain ⟨q, hq⟩ := Nat.exists_prime_half_le_and_lt (Real.sqrt D) (four_le_sqrt hD) exact Finset.exists_min_image (primesInRange D) (countZeroMod wAB wBC wAC) ⟨q, mem_primesInRange.2 hq⟩ -/-- We select the prime with the smallest count, which is also the prime with the fewest false -positives. +/-- A selected minimum of the modular-zero count also minimizes the false-positive count over the +same prime window. -/ theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : IsSelectedPrime wAB wBC wAC D p) : @@ -282,9 +303,8 @@ theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ### The bound on the number of false positives of the selected prime -/ open Classical in -/-- a triple with S(a,b,c) ≠ 0 is a false positive exactly of the primes in the range that divide -S(a,b,c). Since 0 < |S(a,b,c)| ≤ 3n^ν and these primes are at least √D/2, there are at most -log_{√D/2}(3n^ν) of them. +/-- For a fixed nonzero-sum triple and edge weights bounded by `n^κ`, bound the number of window +primes accepting that triple by the base-`sqrt D / 2` logarithm of `3*n^κ`. -/ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (t : Fin n × Fin n × Fin n) (ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0) : @@ -316,8 +336,9 @@ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB rw [Real.le_logb_iff_rpow_le (by linarith) hY, Real.rpow_natCast] exact hpow.trans hprod -/-- Hence the numbers of false positives of all the primes in the range add up to at most n³ -log_{√D/2}(3n^ν). +/-- Sum the per-triple prime-divisibility bound over all `n^3` triples. For `D ≥ 16`, `κ ≥ 1` and +edge weights bounded by `n^κ`, the sum of false-positive counts is at most `n^3 * logb (sqrt D / +2) (3*n^κ)`. -/ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) : @@ -356,11 +377,9 @@ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : Weights end Selection -/-- By the prime number theorem there are Ω(√D/log D) primes in the range. - -NOTE. The modular reduction needs this for every `D ≥ 16`, not only for large `D`, and so it is -stated. It follows from a counting form of Bertrand's postulate; the prime number theorem is -not needed. +/-- There is a universal positive `c` such that every window with `D ≥ 16` contains at least `c * +sqrt D / log D` primes. The proof uses the all-window counting form of Bertrand’s postulate from +`PrimeWindow`, with no prime-number-theorem assumption. -/ theorem exists_le_card_primesInRange : ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → @@ -369,7 +388,9 @@ theorem exists_le_card_primesInRange : exact ⟨c, hc, fun D hD => hcount D hD _ fun _ hp hge hlt => mem_primesInRange.2 ⟨hp, hge, hlt⟩⟩ -/-- falsePositiveCount(p) is at most the average over them, multiplied out. -/ +/-- Multiply the selected prime’s minimum false-positive count by the number of window primes to +obtain a bound by the sum of their false-positive counts. +-/ theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : IsSelectedPrime wAB wBC wAC D p) : @@ -378,7 +399,8 @@ theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wB rw [mul_comm, ← smul_eq_mul] exact Finset.card_nsmul_le_sum _ _ _ hp.falsePositiveCount_le -/-- log D = O(log(√D/2)) for D ≥ 16. -/ +/-- For `D ≥ 16`, compare `log D` with four times `log (sqrt D / 2)`. +-/ theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : Real.log D ≤ 4 * Real.log (Real.sqrt D / 2) := by have hD16 : (16 : ℝ) ≤ D := by exact_mod_cast hD @@ -391,9 +413,9 @@ theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : rw [Real.log_div hsqrt.ne' (by norm_num), Real.log_sqrt (by linarith)] linarith [hlog16] -/-- falsePositiveCount(p) is at most the average over them, so [...] falsePositiveCount(p) = O(n³ -log(3n^ν)/√D): if there are at least `c √D/log D` primes in the range, then -`falsePositiveCount(p) ≤ (4/c) n³ log(3n^κ)/√D`. +/-- For `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by `n^κ`, a positive pointwise prime-count +lower bound `c * sqrt D / log D` gives every selected prime at most `(4/c) * n^3 * log (3*n^κ) / +sqrt D` false positives. -/ theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) (hc : 0 < c) @@ -435,7 +457,9 @@ theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ _ ≤ (n : ℝ) ^ 3 * (4 * Real.log Y / Real.log D) * Real.log D := by gcongr _ = 4 * (n : ℝ) ^ 3 * Real.log Y := by field_simp -/-- falsePositiveCount(p) = O(n³ log(3n^ν)/√D) = O(ν n³ log n/√D). -/ +/-- There is a universal `C ≥ 1` such that, for `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by +`n^κ`, every selected prime has at most `C * κ * n^3 * log n / sqrt D` false positives. +-/ theorem exists_falsePositiveCount_le : ∃ C : ℝ, 1 ≤ C ∧ ∀ {n D p : ℕ} {κ : ℝ}, 16 ≤ D → D ≤ n → 1 ≤ κ → ∀ wAB wBC wAC : Fin n → Fin n → ℤ, @@ -461,17 +485,18 @@ theorem exists_falsePositiveCount_le : gcongr exact le_max_right _ _ -/-- A name for the constant in falsePositiveCount(p) = [...] = O(ν n³ log n/√D), for the bounds on -running times that are built on this one. +/-- A universal real constant witnessing `exists_falsePositiveCount_le`. It depends on none of the +input dimensions, weight matrices, exponent or selected prime, and is at least one. -/ noncomputable def Hashing.falsePositiveConst : ℝ := Classical.choose exists_falsePositiveCount_le -/-- The constant is at least 1. -/ +/-- The universal false-positive constant `Hashing.falsePositiveConst` is at least one. +-/ theorem Hashing.one_le_falsePositiveConst : 1 ≤ Hashing.falsePositiveConst := (Classical.choose_spec exists_falsePositiveCount_le).1 -/-- falsePositiveCount(p) = [...] = O(ν n³ log n/√D), with `Hashing.falsePositiveConst` as the -constant. +/-- For `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by `n^κ`, bound every selected prime’s +false-positive count by `Hashing.falsePositiveConst * κ * n^3 * log n / sqrt D`. -/ theorem Hashing.falsePositiveCount_le_const_mul {n D p : ℕ} {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) (hκ : 1 ≤ κ) (wAB wBC wAC : Fin n → Fin n → ℤ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) diff --git a/Complexity/Foundations/NumberTheory/PrimeWindow.lean b/Complexity/Foundations/NumberTheory/PrimeWindow.lean index dbeb6390e2..9693a53028 100644 --- a/Complexity/Foundations/NumberTheory/PrimeWindow.lean +++ b/Complexity/Foundations/NumberTheory/PrimeWindow.lean @@ -1,6 +1,5 @@ /- -Copyright (c) 2020 Patrick Stevens. All rights reserved. -Copyright (c) 2026 Anthropic, PBC. All rights reserved. +Copyright (c) 2020 Patrick Stevens, 2026 Anthropic, PBC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Patrick Stevens, Bolton Bailey, Anthropic, PBC -/ @@ -49,7 +48,6 @@ namespace Nat private theorem one_le_log_of_three_le {x : ℝ} (hx : 3 ≤ x) : 1 ≤ Real.log x := (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans hx) - /-- The primes `p` with `n < p ≤ 2n`: the primes that Bertrand's postulate is about. -/ private def bertrandPrimes (n : ℕ) : Finset ℕ := (Ioc n (2 * n)).filter Nat.Prime diff --git a/Complexity/Foundations/RingTheory/CyclicIntRing.lean b/Complexity/Foundations/RingTheory/CyclicIntRing.lean index 94ae6d738f..d15f95b193 100644 --- a/Complexity/Foundations/RingTheory/CyclicIntRing.lean +++ b/Complexity/Foundations/RingTheory/CyclicIntRing.lean @@ -80,5 +80,4 @@ theorem coeff_x_pow (hp : p ≠ 0) (k r : ℕ) : end CyclicIntRing - end Cslib From ce646c75bd6af9c6ace7fadc00bd4fde0c673f72 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:13:16 -0400 Subject: [PATCH 06/32] feat(exactTriangle): split modular queries and recover witnesses Adapt the complete finite Theorem 17 splitting and recovery component, using the existing three weight matrices, lopsided graphs, modular counts and EqOn answer interface. Prove exact integer/real capacity bridges so the finite constructions and scans are computable. Preserve Anthropic source credit; keep the native 3SUM endpoint open in #1394. Validation: all six gates in scripts/check.sh pass, with LEAN_NUM_THREADS=1 and cache downloads disabled after bootstrap. Full hygiene, changed-source check-simp, changed-file lints and git diff --check pass. Curated abstracts and rendering are unaffected. AI-assisted implementation/self-review by Codex worker codex-three-sum-splitting-t3-0a4d90c0; independent review will be recorded in the PR. --- Complexity.lean | 7 + .../Algorithms/ExactTriangle/Hashing.lean | 18 +- .../Algorithms/ExactTriangle/Splitting.lean | 534 ++++++++++++++++++ .../Algorithms/ExactTriangle/Witnesses.lean | 398 +++++++++++++ .../Foundations/Data/Finset/Quotient.lean | 44 ++ Complexity/Foundations/Data/Int/Residue.lean | 41 ++ .../Foundations/Data/Matrix/PairChunks.lean | 114 ++++ Complexity/Foundations/Data/Nat/CeilDiv.lean | 55 ++ .../Foundations/Data/Nat/SqrtRatio.lean | 47 ++ ComplexityTests/ExactTriangleSplitting.lean | 158 ++++++ NOTICE | 8 +- 11 files changed, 1408 insertions(+), 16 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Splitting.lean create mode 100644 Complexity/Algorithms/ExactTriangle/Witnesses.lean create mode 100644 Complexity/Foundations/Data/Finset/Quotient.lean create mode 100644 Complexity/Foundations/Data/Int/Residue.lean create mode 100644 Complexity/Foundations/Data/Matrix/PairChunks.lean create mode 100644 Complexity/Foundations/Data/Nat/CeilDiv.lean create mode 100644 Complexity/Foundations/Data/Nat/SqrtRatio.lean create mode 100644 ComplexityTests/ExactTriangleSplitting.lean diff --git a/Complexity.lean b/Complexity.lean index e6ac945545..0f88b5283d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -3,7 +3,9 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs public import Complexity.Algorithms.ExactTriangle.Hashing public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Algorithms.ExactTriangle.Witnesses public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2067,12 +2069,14 @@ public import Complexity.Foundations.Data.Finset.Average public import Complexity.Foundations.Data.Finset.Expect public import Complexity.Foundations.Data.Finset.Markov public import Complexity.Foundations.Data.Finset.Quantile +public import Complexity.Foundations.Data.Finset.Quotient public import Complexity.Foundations.Data.Finset.Sampling public import Complexity.Foundations.Data.Finset.SubsetSum public import Complexity.Foundations.Data.Finset.SymmDiff public import Complexity.Foundations.Data.Hamming public import Complexity.Foundations.Data.Hamming.Expansion public import Complexity.Foundations.Data.Int.Order +public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.Data.List.Append public import Complexity.Foundations.Data.List.BalancedFold public import Complexity.Foundations.Data.List.Basic @@ -2088,6 +2092,7 @@ public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary public import Complexity.Foundations.Data.Matrix.IntegerBits +public import Complexity.Foundations.Data.Matrix.PairChunks public import Complexity.Foundations.Data.Matrix.Permanent public import Complexity.Foundations.Data.Matrix.Reindex public import Complexity.Foundations.Data.Matrix.TotallyRegular @@ -2109,6 +2114,7 @@ public import Complexity.Foundations.Data.Nat.Bitwise.Projection public import Complexity.Foundations.Data.Nat.Bitwise.Sparse public import Complexity.Foundations.Data.Nat.Bitwise.SumTree public import Complexity.Foundations.Data.Nat.Card +public import Complexity.Foundations.Data.Nat.CeilDiv public import Complexity.Foundations.Data.Nat.Choose public import Complexity.Foundations.Data.Nat.Choose.Real public import Complexity.Foundations.Data.Nat.Div @@ -2123,6 +2129,7 @@ public import Complexity.Foundations.Data.Nat.Prime public import Complexity.Foundations.Data.Nat.Primrec public import Complexity.Foundations.Data.Nat.Radix public import Complexity.Foundations.Data.Nat.Radix.Measurement +public import Complexity.Foundations.Data.Nat.SqrtRatio public import Complexity.Foundations.Data.Option public import Complexity.Foundations.Data.OrderedSet public import Complexity.Foundations.Data.OrderedSet.Spec diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index fbb2834b6e..86aba7824b 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.RingTheory.CyclicIntRing public import Mathlib.Data.Matrix.Mul @@ -74,15 +75,6 @@ def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) -private theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : - ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := - Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) - -private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by - have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) - have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) - omega - /-! ### The primes in the range -/ @@ -134,8 +126,8 @@ private theorem eq_add_toNat_emod_iff {p r : ℕ} (hp : p ≠ 0) (hr : r < p) (u rw [← Int.natCast_inj, Int.ModEq, Int.emod_eq_of_lt (Int.natCast_nonneg r) (by exact_mod_cast hr), eq_comm] push_cast - rw [natCast_toNat_emod (Nat.pos_of_ne_zero hp), - natCast_toNat_emod (Nat.pos_of_ne_zero hp), + rw [Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp), + Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp), ← Int.add_emod] /-! @@ -233,8 +225,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : refine Finset.sum_congr rfl fun a _ => ?_ rw [Fintype.sum_prod_type] refine Finset.sum_congr rfl fun b _ => ?_ - have hcast := natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) - rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + have hcast := Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) _), Finset.card_filter] push_cast refine Finset.sum_congr rfl fun c _ => ?_ diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean new file mode 100644 index 0000000000..5015daf5e1 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -0,0 +1,534 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Hashing +public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Foundations.Data.Matrix.PairChunks +public import Complexity.Foundations.Data.Nat.SqrtRatio +import Mathlib.Tactic.GCongr +import Mathlib.Tactic.FieldSimp + +/-! +# Modular splitting of Exact Triangle into finite lopsided queries + +Pieces of the third part and chunks of A–B residue classes define lopsided graphs with a +middle vertex `(c, label)`. The three input matrices retain their original orientations. +All bounds below use `16 ≤ D ≤ n` and `1 ≤ g`; the sharper degree bound also uses `g ≤ sqrt D`. +No native program, fast solver or charged instruction bound is asserted. + +## Main definitions + +* `piece`, `residueClass`, `chunkOf`: vertex blocks and residue query chunks. +* `lopInstance`, `instanceIndices`, `totalChunks`: lopsided graphs and their finite indices. + +## Main results + +* `existsUnique_mem_piece`, `card_piece_le`: unique piece coverage and size. +* `totalChunks_le`, `card_instanceIndices_le`: chunk and instance bounds. +* `card_middle_lopInstance`, `hasCommonNeighbor_lopInstance_iff`: size and modular correctness. +* `ncard_nbr_lopInstance_le`: degree bound. + +## References + +The reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of +[Instances.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Instances.lean), with only required definitions from +`PaperStatements.lean`, [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and [Parameters.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Parameters.lean), at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +Source-derived prose below is paraphrased; quotations used for review are in the PR record. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Cslib.Matrix + +/-- The largest number of query pairs that is "at most n²/√D" (Corollary 15 and Theorem 17; +Theorem 5 has "at most N²/√D"): `⌊n²/√D⌋`. -/ +def queryCap (n D : ℕ) : ℕ := Nat.sqrt ((n ^ 2) ^ 2 / D) + +/-- The integer capacity is exactly the real-valued floor used in the reduction. -/ +theorem queryCap_eq_floor (n D : ℕ) : queryCap n D = ⌊(n : ℝ) ^ 2 / Real.sqrt D⌋₊ := by + rw [queryCap, Nat.sqrt_div_eq_floor_div_sqrt] + norm_cast + +/-- Proof of Theorem 17: "let s := ⌊√D⌋". -/ +def sOf (D : ℕ) : ℕ := Nat.sqrt D + +/-- Proof of Theorem 17: the bound "⌈s/g⌉" on the number of vertices of a piece. -/ +def pieceSize (D g : ℕ) : ℕ := sOf D ⌈/⌉ g + +/-- The integer block size equals the real ceiling, also at the zero-divisor convention. -/ +theorem pieceSize_eq_ceil (D g : ℕ) : pieceSize D g = ⌈(sOf D : ℝ) / (g : ℝ)⌉₊ := by + by_cases hg : g = 0 + · simp [pieceSize, hg] + exact (Nat.ceil_div_eq_ceilDiv _ (Nat.pos_of_ne_zero hg)).symm + +/-- Proof of Theorem 17: "split C into pieces C₁, …, C_h of at most ⌈s/g⌉ vertices each". The paper +does not say how; we take blocks of `⌈s/g⌉` consecutive vertices. `piece n D g k` is the paper's +`C_{k+1}`. -/ +def piece (n D g k : ℕ) : Finset (Fin n) := + Finset.univ.filter fun c : Fin n => c.val / pieceSize D g = k + +/-- Proof of Theorem 17: the number `h` of pieces, `⌈n / ⌈s/g⌉⌉` for the blocks of `piece` +(meaningful when `⌈s/g⌉ ≥ 1`). -/ +def numPieces (n D g : ℕ) : ℕ := n ⌈/⌉ pieceSize D g + +/-- The number of blocks is the ceiling of the real quotient by the block size. -/ +theorem numPieces_eq_ceil (n D g : ℕ) : + numPieces n D g = ⌈(n : ℝ) / (pieceSize D g : ℝ)⌉₊ := by + by_cases h : pieceSize D g = 0 + · simp [numPieces, h] + exact (Nat.ceil_div_eq_ceilDiv _ (Nat.pos_of_ne_zero h)).symm + +/-- The index of an instance built by the reduction of Theorem 17: a residue `ϱ ∈ ℤ_p`, the number +`j` of a chunk of `W_ϱ`, and the number `k` of a piece of `C`. -/ +abbrev InstanceIndex (p : ℕ) : Type := Fin p × ℕ × ℕ + + +/-- Proof of Theorem 17: "For ϱ ∈ ℤ_p let W_ϱ be the set of edges (a,b) ∈ A × B with w(a,b) ≡ ϱ (mod +p)". -/ +def residueClass {n : ℕ} (wAB : Fin n → Fin n → ℤ) (p : ℕ) (ϱ : Fin p) : Finset (Fin n × Fin n) := + Finset.univ.filter fun q : Fin n × Fin n => wAB q.1 q.2 ≡ ((ϱ : ℕ) : ℤ) [ZMOD (p : ℤ)] + +/-- Proof of Theorem 17: "and cut it into chunks of at most n²/√D query pairs". The `j`-th chunk of +`W_ϱ`. -/ +def chunkOf {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D p : ℕ) (ϱ : Fin p) (j : ℕ) : + Finset (Fin n × Fin n) := + chunk (residueClass wAB p ϱ) (queryCap n D) j + +/-- Proof of Theorem 17: "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k form the instance of +Lop-AE-SparseTri(n, D) with query pairs W := 𝒬 and middle part C_k × ℤ_p [...], whose middle +vertices are pairs (vertex, label) [...], with a ∼ (c, σ) ⟺ σ ≡ w(a,c) + ϱ, (c, σ) ∼ b ⟺ σ ≡ +−w(b,c) (mod p)." -/ +def lopInstance {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) : + InstanceIndex p → LopsidedGraph n + | (ϱ, j, k) => + { M := {c : Fin n // c ∈ piece n D g k} × Fin p + adjA := fun a (c, σ) => ((σ : ℕ) : ℤ) ≡ wAC a c + ((ϱ : ℕ) : ℤ) [ZMOD (p : ℤ)] + adjB := fun (c, σ) b => ((σ : ℕ) : ℤ) ≡ -wBC b c [ZMOD (p : ℤ)] + W := chunkOf wAB D p ϱ j } + +/-- Proof of Theorem 17: "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k" there is an instance; these +are the indices of all the instances. This set depends only on the Exact Triangle instance and on +`D`, `g`, `p`, not on any answer of the oracle. -/ +def instanceIndices {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D g p : ℕ) : + Finset (InstanceIndex p) := + Finset.univ.biUnion fun ϱ : Fin p => + ((Finset.range (numChunks (residueClass wAB p ϱ) (queryCap n D))) ×ˢ + (Finset.range (numPieces n D g))).image fun jk => (ϱ, jk) + +/-- Total number of query chunks, summed over residues. -/ +def totalChunks {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D p : ℕ) : ℕ := + ∑ ϱ : Fin p, numChunks (residueClass wAB p ϱ) (queryCap n D) + +variable {n : ℕ} + +/-- `√D ≤ D ≤ n`. -/ +theorem sqrt_le_natCast {D : ℕ} (hD : 1 ≤ D) (hDn : D ≤ n) : Real.sqrt D ≤ n := + calc Real.sqrt D ≤ Real.sqrt D * Real.sqrt D := + le_mul_of_one_le_left (Real.sqrt_nonneg _) (Real.one_le_sqrt.mpr (by exact_mod_cast hD)) + _ = D := Real.mul_self_sqrt (Nat.cast_nonneg D) + _ ≤ n := by exact_mod_cast hDn + +/-- The bound `⌊n²/√D⌋` on the size of a chunk is at least 1, because `√D ≤ n ≤ n²`. -/ +theorem one_le_queryCap {D : ℕ} (hD : 1 ≤ D) (hDn : D ≤ n) : 1 ≤ queryCap n D := by + rw [queryCap_eq_floor] + have hn : (1 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + refine Nat.le_floor ?_ + rw [Nat.cast_one, le_div_iff₀ (Real.sqrt_pos.mpr (by exact_mod_cast hD)), one_mul] + exact (sqrt_le_natCast hD hDn).trans (le_self_pow₀ hn two_ne_zero) + +private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + +variable {n D g p : ℕ} + +/-! ### The pieces -/ + +/-- `s = ⌊√D⌋ ≤ √D`. -/ +theorem sOf_le_sqrt (D : ℕ) : (sOf D : ℝ) ≤ Real.sqrt D := + Real.nat_sqrt_le_real_sqrt + +/-- `√D < s + 1`. -/ +theorem sqrt_lt_sOf_add_one (D : ℕ) : Real.sqrt D < (sOf D : ℝ) + 1 := + Real.real_sqrt_lt_nat_sqrt_succ + +/-- `s > 0` because `D ≥ 16`. -/ +private theorem sOf_pos (hD : 16 ≤ D) : (0 : ℝ) < sOf D := by + linarith [four_le_sqrt hD, sqrt_lt_sOf_add_one D] + +/-- The bound `⌈s/g⌉` on the size of a piece is at least 1. -/ +theorem pieceSize_pos (hD : 16 ≤ D) (hg1 : 1 ≤ g) : 0 < pieceSize D g := by + rw [pieceSize_eq_ceil] + exact Nat.ceil_pos.mpr (div_pos (sOf_pos hD) (by exact_mod_cast hg1)) + +/-- `⌈s/g⌉ ≤ s` because `g ≥ 1`. -/ +theorem pieceSize_le_sOf (hg1 : 1 ≤ g) : pieceSize D g ≤ sOf D := by + rw [pieceSize_eq_ceil] + exact Nat.ceil_le.mpr (div_le_self (Nat.cast_nonneg _) (by exact_mod_cast hg1)) + +/-- A vertex lies in the piece number `k` exactly if its number divided by `⌈s/g⌉` is `k`. -/ +theorem mem_piece {k : ℕ} (c : Fin n) : c ∈ piece n D g k ↔ c.val / pieceSize D g = k := by + simp [piece] + +/-- Proof of Theorem 17: "split C into pieces C₁, …, C_h of at most ⌈s/g⌉ vertices each": every +vertex of `C` lies in exactly one of the `h` pieces. -/ +theorem existsUnique_mem_piece (hD : 16 ≤ D) (hg1 : 1 ≤ g) (c : Fin n) : + ∃! k, k < numPieces n D g ∧ c ∈ piece n D g k := by + refine ⟨c.val / pieceSize D g, ⟨?_, (mem_piece c).mpr rfl⟩, + fun k hk => ((mem_piece c).mp hk.2).symm⟩ + rw [numPieces_eq_ceil] + refine Nat.lt_ceil.mpr (lt_of_le_of_lt Nat.cast_div_le ?_) + exact div_lt_div_of_pos_right (by exact_mod_cast c.isLt) + (by exact_mod_cast pieceSize_pos hD hg1) + +/-- Proof of Theorem 17, "pieces [...] of at most ⌈s/g⌉ vertices each": `|C_k| ≤ ⌈s/g⌉`, because the +numbers of the vertices of the piece number `k` lie in `[k ⌈s/g⌉, (k + 1) ⌈s/g⌉)`. -/ +theorem card_piece_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) (n k : ℕ) : + (piece n D g k).card ≤ pieceSize D g := + Finset.univ.card_filter_div_eq_le (fun _ _ _ _ h => Fin.ext h) (pieceSize_pos hD hg1) k + +/-- Proof of Theorem 17: "so that h ≤ ⌈ng/s⌉". -/ +theorem numPieces_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) : + numPieces n D g ≤ ⌈(n : ℝ) * (g : ℝ) / (sOf D : ℝ)⌉₊ := by + have hsize : (0 : ℝ) < pieceSize D g := by exact_mod_cast pieceSize_pos hD hg1 + have hg : (0 : ℝ) < g := by exact_mod_cast hg1 + -- `s ≤ ⌈s/g⌉ g` + have hs' : (sOf D : ℝ) / (g : ℝ) ≤ pieceSize D g := by + rw [pieceSize_eq_ceil] + exact Nat.le_ceil _ + have hs := (div_le_iff₀ hg).mp hs' + rw [numPieces_eq_ceil] + refine Nat.ceil_mono ?_ + rw [div_le_div_iff₀ hsize (sOf_pos hD)] + calc (n : ℝ) * (sOf D : ℝ) ≤ (n : ℝ) * ((pieceSize D g : ℝ) * (g : ℝ)) := by gcongr + _ = (n : ℝ) * (g : ℝ) * (pieceSize D g : ℝ) := by ring + +/-! ### Residues -/ + +/-- The residue of the integer `x` modulo `p`, as an element of `Fin p`. -/ +def resFin (hp : p ≠ 0) (x : ℤ) : Fin p := + ⟨(x % (p : ℤ)).toNat, Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) x⟩ + +/-- The residue of `x` is congruent to `x`. -/ +theorem resFin_modEq (hp : p ≠ 0) (x : ℤ) : ((resFin hp x : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] := by + rw [resFin, Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] + exact Int.mod_modEq x p + +/-- An element of `Fin p` is congruent to `x` modulo `p` exactly if it is the residue of `x`. -/ +private theorem modEq_iff_eq_resFin (hp : p ≠ 0) (σ : Fin p) (x : ℤ) : + ((σ : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] ↔ σ = resFin hp x := by + refine ⟨fun h => ?_, fun h => h ▸ resFin_modEq hp x⟩ + -- Two congruent numbers in `{0, …, p − 1}` are equal. + have hmod : (σ : ℕ) ≡ (resFin hp x : ℕ) [MOD p] := + (Int.natCast_modEq_iff).mp (h.trans (resFin_modEq hp x).symm) + exact Fin.ext (Nat.ModEq.eq_of_lt_of_lt hmod σ.isLt (resFin hp x).isLt) + +/-! ### The arithmetic behind the two counts -/ + +/-- The number `⌈|S|/cap⌉` of chunks of `S` satisfies `⌈|S|/cap⌉ cap ≤ |S| + cap − 1`. -/ +private theorem numChunks_mul_le (S : Finset (Fin n × Fin n)) (cap : ℕ) (hcap : 1 ≤ cap) : + numChunks S cap * cap + 1 ≤ S.card + cap := by + rw [numChunks] + exact Nat.ceilDiv_mul_lt hcap + +/-- `s + 1` exceeds `√D` by at least `1/(2√D + 1)`: since `(s + 1)²` and `D` are integers, +`1 ≤ (s + 1)² − D = (s + 1 − √D)(s + 1 + √D)`. -/ +private theorem one_le_gap_mul (D : ℕ) : + 1 ≤ ((sOf D : ℝ) + 1 - Real.sqrt D) * (2 * Real.sqrt D + 1) := by + have hsq : Real.sqrt D ^ 2 = D := Real.sq_sqrt (Nat.cast_nonneg D) + have hle := sOf_le_sqrt D + have hlt := sqrt_lt_sOf_add_one D + have hinteger : (D : ℝ) + 1 ≤ ((sOf D : ℝ) + 1) ^ 2 := by + have hreal : (D : ℝ) < ((sOf D : ℝ) + 1) ^ 2 := + hsq ▸ pow_lt_pow_left₀ hlt (Real.sqrt_nonneg _) two_ne_zero + have hnat : D < (sOf D + 1) ^ 2 := by exact_mod_cast hreal + exact_mod_cast hnat + calc (1 : ℝ) ≤ ((sOf D : ℝ) + 1) ^ 2 - Real.sqrt D ^ 2 := by linarith [hinteger, hsq] + _ = ((sOf D : ℝ) + 1 - Real.sqrt D) * ((sOf D : ℝ) + 1 + Real.sqrt D) := by ring + _ ≤ ((sOf D : ℝ) + 1 - Real.sqrt D) * (2 * Real.sqrt D + 1) := + mul_le_mul_of_nonneg_left (by linarith) (by linarith) + +/-- The inequality behind "at most p + √D [...] chunks": `n² < (s + 1) ⌊n²/√D⌋ + p`. + +Write `r = √D`, `x = n²/r` and `δ = s + 1 − r`. Then +`(s + 1)⌊x⌋ > (s + 1)(x − 1) = n² + xδ − (s + 1)`, so it is enough that `xδ ≥ r/2 + 1 ≥ s + 1 − p`. +This holds because `x ≥ r³` (as `D ≤ n`), `δ ≥ 1/(2r + 1)` (as `D` is an integer) and `r ≥ 4`. -/ +private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) + (hp : Real.sqrt D / 2 ≤ (p : ℝ)) : n ^ 2 < (sOf D + 1) * queryCap n D + p := by + have hgap := one_le_gap_mul D + have hs := sOf_le_sqrt D + set r := Real.sqrt D with hr + set δ := (sOf D : ℝ) + 1 - r with hδ + set x := (n : ℝ) ^ 2 / r with hx + have hr4 : 4 ≤ r := four_le_sqrt hD + have hxr : x * r = (n : ℝ) ^ 2 := div_mul_cancel₀ _ (by linarith) + have hx3 : r ^ 3 ≤ x := by + rw [hx, le_div_iff₀ (by linarith)] + calc r ^ 3 * r = (r ^ 2) ^ 2 := by ring + _ = (D : ℝ) ^ 2 := by rw [hr, Real.sq_sqrt (Nat.cast_nonneg D)] + _ ≤ (n : ℝ) ^ 2 := by gcongr + have hxδ : r / 2 + 1 ≤ x * δ := by + refine le_of_mul_le_mul_right ?_ (show 0 < 2 * r + 1 by linarith) + calc (r / 2 + 1) * (2 * r + 1) ≤ r ^ 3 := by + -- `r³ ≥ 4r²` and `r² ≥ 4r` + linarith [mul_le_mul_of_nonneg_right hr4 (sq_nonneg r), + mul_le_mul_of_nonneg_right hr4 (show 0 ≤ r by linarith)] + _ ≤ x * 1 := by rw [mul_one]; exact hx3 + _ ≤ x * (δ * (2 * r + 1)) := + mul_le_mul_of_nonneg_left hgap (le_trans (by positivity) hx3) + _ = x * δ * (2 * r + 1) := by ring + have hfloor : x < (queryCap n D : ℝ) + 1 := by + rw [queryCap_eq_floor] + exact Nat.lt_floor_add_one _ + have hmain : (n : ℝ) ^ 2 < ((sOf D : ℝ) + 1) * (queryCap n D : ℝ) + p := + calc (n : ℝ) ^ 2 = x * r := hxr.symm + _ ≤ x * r + x * δ - ((sOf D : ℝ) + 1) + r / 2 := by + -- `s + 1 ≤ r + 1 ≤ xδ + r/2` + linarith [hxδ, hs] + _ = ((sOf D : ℝ) + 1) * (x - 1) + r / 2 := by rw [hδ]; ring + _ < ((sOf D : ℝ) + 1) * (queryCap n D : ℝ) + p := + add_lt_add_of_lt_of_le (mul_lt_mul_of_pos_left (by linarith [hfloor]) (by positivity)) hp + exact_mod_cast hmain + +/-- Proof of Theorem 17: the two facts used for the count of the instances, "s ≥ √D − 1 ≥ 3√D/4 +because D ≥ 16, and √D ≤ √n ≤ 2n/3 because D ≤ n and we may assume n ≥ 3". -/ +theorem le_sOf_and_sqrt_le (hD : 16 ≤ D) (hDn : D ≤ n) : + 3 * Real.sqrt D / 4 ≤ (sOf D : ℝ) ∧ Real.sqrt D ≤ 2 * (n : ℝ) / 3 := by + have hsqrtD := four_le_sqrt hD + have hsqrtn := four_le_sqrt (hD.trans hDn) + constructor + · calc 3 * Real.sqrt D / 4 ≤ Real.sqrt D - 1 := by linarith + _ ≤ (sOf D : ℝ) := by linarith [sqrt_lt_sOf_add_one D] + · calc Real.sqrt D ≤ Real.sqrt n := Real.sqrt_le_sqrt (by exact_mod_cast hDn) + _ ≤ 2 * (n : ℝ) / 3 := by + -- `n = √n √n ≥ 4√n` + linarith [mul_le_mul_of_nonneg_right hsqrtn (Real.sqrt_nonneg n), + Real.mul_self_sqrt (Nat.cast_nonneg n : (0 : ℝ) ≤ n)] + +section Matrices + +variable (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-! ### The sets `W_ϱ` -/ + +/-- A pair lies in `W_ϱ` exactly if `ϱ` is the residue of its weight. -/ +theorem mem_residueClass (hp : p ≠ 0) (ϱ : Fin p) (q : Fin n × Fin n) : + q ∈ residueClass wAB p ϱ ↔ ϱ = resFin hp (wAB q.1 q.2) := by + rw [← modEq_iff_eq_resFin] + simp only [residueClass, Finset.mem_filter, Finset.mem_univ, true_and] + exact ⟨Int.ModEq.symm, Int.ModEq.symm⟩ + +/-- Proof of Theorem 17, the sets `W_ϱ` for `ϱ ∈ ℤ_p`: every pair `(a,b) ∈ A × B` lies in exactly +one of them. -/ +theorem existsUnique_mem_residueClass (hp : p ≠ 0) (q : Fin n × Fin n) : + ∃! ϱ : Fin p, q ∈ residueClass wAB p ϱ := + ⟨resFin hp (wAB q.1 q.2), (mem_residueClass wAB hp _ q).mpr rfl, + fun ϱ hϱ => (mem_residueClass wAB hp ϱ q).mp hϱ⟩ + +/-- The sets `W_ϱ` have `n²` pairs in all. -/ +private theorem sum_card_residueClass (hp : p ≠ 0) : + ∑ ϱ : Fin p, (residueClass wAB p ϱ).card = n ^ 2 := by + calc ∑ ϱ : Fin p, (residueClass wAB p ϱ).card + = ∑ ϱ : Fin p, ((Finset.univ : Finset (Fin n × Fin n)).filter + fun q => resFin hp (wAB q.1 q.2) = ϱ).card := by + refine Finset.sum_congr rfl fun ϱ _ => congrArg Finset.card ?_ + ext q + rw [mem_residueClass wAB hp] + simp [eq_comm] + _ = (Finset.univ : Finset (Fin n × Fin n)).card := + (Finset.card_eq_sum_card_fiberwise fun _ _ => Finset.mem_coe.mpr (Finset.mem_univ _)).symm + _ = n ^ 2 := by simp [sq] + +/-! ### The chunks -/ + +/-- Proof of Theorem 17: "cut it into chunks of at most n²/√D query pairs". (The chunks of `W_ϱ` +cover it: `biUnion_chunk`.) -/ +theorem card_chunkOf_le (hD : 1 ≤ D) (hDn : D ≤ n) (ϱ : Fin p) (j : ℕ) : + ((chunkOf wAB D p ϱ j).card : ℝ) ≤ (n : ℝ) ^ 2 / Real.sqrt D := + calc ((chunkOf wAB D p ϱ j).card : ℝ) ≤ (queryCap n D : ℝ) := by + exact_mod_cast card_chunk_le _ (one_le_queryCap hD hDn) j + _ ≤ (n : ℝ) ^ 2 / Real.sqrt D := by + rw [queryCap_eq_floor] + exact Nat.floor_le (by positivity) + +/-- There are at most `p + s` chunks in all. Each `W_ϱ` has at most `(|W_ϱ| + cap − 1)/cap` chunks, +where `cap = ⌊n²/√D⌋`, so `p + s + 1` chunks or more would need `n² ≥ (s + 1) cap + p` pairs. -/ +private theorem totalChunks_le_add_sOf (hD : 16 ≤ D) (hDn : D ≤ n) (hp0 : p ≠ 0) + (hp : Real.sqrt D / 2 ≤ (p : ℝ)) : totalChunks wAB D p ≤ p + sOf D := by + have hsum : totalChunks wAB D p * queryCap n D + p ≤ n ^ 2 + p * queryCap n D := by + have h := Finset.sum_le_sum fun ϱ (_ : ϱ ∈ (Finset.univ : Finset (Fin p))) => + numChunks_mul_le (residueClass wAB p ϱ) (queryCap n D) (one_le_queryCap (by omega) hDn) + simp only [Finset.sum_add_distrib, ← Finset.sum_mul, Finset.sum_const, Finset.card_univ, + Fintype.card_fin, smul_eq_mul, mul_one] at h + rwa [sum_card_residueClass wAB hp0] at h + have hpairs := sq_lt_mul_queryCap_add hD hDn hp + by_contra hmany + have hmul : (p + sOf D + 1) * queryCap n D ≤ totalChunks wAB D p * queryCap n D := + Nat.mul_le_mul_right _ (by omega) + rw [show (p + sOf D + 1) * queryCap n D = p * queryCap n D + (sOf D + 1) * queryCap n D by + ring] at hmul + -- `hsum` and `hmul` give `(s + 1) cap + p ≤ n²`, against `hpairs`. + omega + +/-- Proof of Theorem 17: "There are at most p + √D ≤ 2√D chunks in all." + +NOTE. A chunk holds a whole number of pairs, at most `⌊n²/√D⌋`, so the count is at most +`p + (n² − p)/⌊n²/√D⌋`, and the second term can exceed `√D` slightly. The printed bound is still +true, because the count is an integer, `D` is an integer and `D ≤ n` (`sq_lt_mul_queryCap_add`). -/ +theorem totalChunks_le (hD : 16 ≤ D) (hDn : D ≤ n) (hp : p ∈ primesInRange D) : + (totalChunks wAB D p : ℝ) ≤ (p : ℝ) + Real.sqrt D ∧ + (p : ℝ) + Real.sqrt D ≤ 2 * Real.sqrt D := by + obtain ⟨hprime, hge, hlt⟩ := mem_primesInRange.mp hp + have hnat : (totalChunks wAB D p : ℝ) ≤ (p : ℝ) + (sOf D : ℝ) := by + exact_mod_cast totalChunks_le_add_sOf wAB hD hDn hprime.ne_zero hge + exact ⟨by linarith [sOf_le_sqrt D], by linarith⟩ + +/-! ### The instances -/ + +/-- Proof of Theorem 17: "middle part C_k × ℤ_p, of size at most sp ≤ D". So each instance is an +instance of Lop-AE-SparseTri(n, D). -/ +theorem card_middle_lopInstance (hD : 16 ≤ D) (hg1 : 1 ≤ g) (hp : p ∈ primesInRange D) + (ι : InstanceIndex p) : Fintype.card (lopInstance wAB wBC wAC D g p ι).M ≤ D := by + obtain ⟨-, -, hlt⟩ := mem_primesInRange.mp hp + have hsp : (sOf D : ℝ) * (p : ℝ) ≤ D := + calc (sOf D : ℝ) * (p : ℝ) ≤ Real.sqrt D * Real.sqrt D := + mul_le_mul (sOf_le_sqrt D) hlt.le (Nat.cast_nonneg p) (Real.sqrt_nonneg _) + _ = D := Real.mul_self_sqrt (Nat.cast_nonneg D) + calc Fintype.card ({c : Fin n // c ∈ piece n D g ι.2.2} × Fin p) + = (piece n D g ι.2.2).card * p := by + rw [Fintype.card_prod, Fintype.card_coe, Fintype.card_fin] + _ ≤ sOf D * p := Nat.mul_le_mul_right p + ((card_piece_le hD hg1 n ι.2.2).trans (pieceSize_le_sOf hg1)) + _ ≤ D := by exact_mod_cast hsp + +/-- A query pair of the instance with index `(ϱ, j, k)` lies in `W_ϱ`, and `j` is the number of its +chunk. -/ +theorem mem_lopInstance_W {wAB wBC wAC : Fin n → Fin n → ℤ} + {ι : InstanceIndex p} {q : Fin n × Fin n} + (hq : q ∈ (lopInstance wAB wBC wAC D g p ι).W) : + q ∈ residueClass wAB p ι.1 ∧ rankIn (residueClass wAB p ι.1) q / queryCap n D = ι.2.1 := + Finset.mem_filter.mp hq + +/-- Proof of Theorem 17: "Within the chunk, the condition S(a,b,c) ≡ 0 (mod p) has become the +equality w(a,c) + ϱ ≡ −w(b,c) of a label of (a,c) and a label of (b,c)". -/ +theorem weightSum_modEq_zero_iff {p ϱ : ℤ} {a b : Fin n} (hϱ : wAB a b ≡ ϱ [ZMOD p]) (c : Fin n) : + weightSum wAB wBC wAC a b c ≡ 0 [ZMOD p] ↔ wAC a c + ϱ ≡ -wBC b c [ZMOD p] := by + rw [Int.modEq_iff_dvd, Int.modEq_iff_dvd, + show 0 - weightSum wAB wBC wAC a b c = -wBC b c - (wAC a c + ϱ) + (ϱ - wAB a b) by + simp only [weightSum]; ring] + exact dvd_add_left (Int.modEq_iff_dvd.mp hϱ) + +/-- Proof of Theorem 17: "a query pair (a,b) ∈ 𝒬 has a common neighbor if and only if some c ∈ C_k +has S(a,b,c) ≡ 0 (mod p)". -/ +theorem hasCommonNeighbor_lopInstance_iff (hp : p ≠ 0) + {ι : InstanceIndex p} {q : Fin n × Fin n} + (hq : q ∈ (lopInstance wAB wBC wAC D g p ι).W) : + (lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2 ↔ + ∃ c ∈ piece n D g ι.2.2, weightSum wAB wBC wAC q.1 q.2 c ≡ 0 [ZMOD (p : ℤ)] := by + have hϱ : wAB q.1 q.2 ≡ ((ι.1 : ℕ) : ℤ) [ZMOD (p : ℤ)] := + (Finset.mem_filter.mp (mem_lopInstance_W hq).1).2 + constructor + · -- A common neighbor `(c, σ)` carries the label `σ` of `(a,c)` and of `(b,c)`. + rintro ⟨⟨c, σ⟩, hA, hB⟩ + exact ⟨c, c.2, (weightSum_modEq_zero_iff wAB wBC wAC hϱ c).mpr (hA.symm.trans hB)⟩ + · -- If the two labels agree, the vertex `c` with that label is a common neighbor. + rintro ⟨c, hc, hS⟩ + have hB := resFin_modEq hp (-wBC q.2 c) + exact ⟨(⟨c, hc⟩, resFin hp (-wBC q.2 c)), + hB.trans ((weightSum_modEq_zero_iff wAB wBC wAC hϱ c).mp hS).symm, hB⟩ + +/-- Proof of Theorem 17, "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k": `(ϱ, j, k)` is the index of an +instance exactly if `j` is the number of a chunk of `W_ϱ` and `k` the number of a piece. -/ +theorem mem_instanceIndices (ι : InstanceIndex p) : + ι ∈ instanceIndices wAB D g p ↔ + ι.2.1 < numChunks (residueClass wAB p ι.1) (queryCap n D) ∧ ι.2.2 < numPieces n D g := by + obtain ⟨ϱ, j, k⟩ := ι + simp only [instanceIndices, Finset.mem_biUnion, Finset.mem_univ, true_and, + Finset.mem_image, Finset.mem_product, Finset.mem_range, Prod.mk.injEq, Prod.exists] + exact ⟨fun ⟨_, _, _, h, h1, h2, h3⟩ => by subst h1 h2 h3; exact h, + fun h => ⟨ϱ, j, k, h, rfl, rfl, rfl⟩⟩ + +/-- There are as many instances as chunks times pieces. -/ +theorem card_instanceIndices (D g p : ℕ) : + (instanceIndices wAB D g p).card = totalChunks wAB D p * numPieces n D g := by + unfold instanceIndices totalChunks + rw [Finset.card_biUnion, Finset.sum_mul] + · refine Finset.sum_congr rfl fun ϱ _ => ?_ + rw [Finset.card_image_of_injective _ (Prod.mk_right_injective ϱ), Finset.card_product, + Finset.card_range, Finset.card_range] + · intro ϱ _ ϱ' _ hne + rw [Function.onFun, Finset.disjoint_left] + intro x hx hx' + obtain ⟨_, _, rfl⟩ := Finset.mem_image.mp hx + obtain ⟨_, _, h⟩ := Finset.mem_image.mp hx' + exact hne (congrArg Prod.fst h).symm + +/-- Proof of Theorem 17: "There are at most 2√D h ≤ 2√D(ng/s + 1) ≤ 4ng instances". -/ +theorem card_instanceIndices_le (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (hp : p ∈ primesInRange D) : + ((instanceIndices wAB D g p).card : ℝ) ≤ 4 * (n : ℝ) * (g : ℝ) := by + obtain ⟨hs, hsqrt⟩ := le_sOf_and_sqrt_le hD hDn + obtain ⟨hchunks, hchunks'⟩ := totalChunks_le wAB hD hDn hp + have hs0 := sOf_pos hD + have hpieces : (numPieces n D g : ℝ) ≤ (n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1 := + calc (numPieces n D g : ℝ) ≤ (⌈(n : ℝ) * (g : ℝ) / (sOf D : ℝ)⌉₊ : ℝ) := by + exact_mod_cast numPieces_le hD hg1 + _ ≤ (n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1 := (Nat.ceil_lt_add_one (by positivity)).le + -- `2√D · ng/s ≤ 8ng/3` because `s ≥ 3√D/4`, and `2√D ≤ 4n/3 ≤ 4ng/3`. + have hmain : + Real.sqrt D * ((n : ℝ) * (g : ℝ) / (sOf D : ℝ)) ≤ 4 / 3 * ((n : ℝ) * (g : ℝ)) := by + rw [← mul_div_assoc, div_le_iff₀ hs0] + calc Real.sqrt D * ((n : ℝ) * (g : ℝ)) + = 4 / 3 * ((n : ℝ) * (g : ℝ)) * (3 * Real.sqrt D / 4) := by ring + _ ≤ 4 / 3 * ((n : ℝ) * (g : ℝ)) * (sOf D : ℝ) := by gcongr + have hng : (n : ℝ) ≤ (n : ℝ) * (g : ℝ) := + le_mul_of_one_le_right (Nat.cast_nonneg n) (by exact_mod_cast hg1) + calc ((instanceIndices wAB D g p).card : ℝ) + = (totalChunks wAB D p : ℝ) * (numPieces n D g : ℝ) := by + rw [card_instanceIndices, Nat.cast_mul] + _ ≤ 2 * Real.sqrt D * (numPieces n D g : ℝ) := by gcongr; exact hchunks.trans hchunks' + _ ≤ 2 * Real.sqrt D * ((n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1) := by gcongr + _ ≤ 4 * (n : ℝ) * (g : ℝ) := by linarith [hmain, hng, hsqrt] + +end Matrices + +/-! ### The neighborhoods in an instance -/ + +/-- The graph of a function into `Fin p` has as many points as the domain. -/ +private theorem ncard_graph {α : Type} [Fintype α] (f : α → Fin p) : + {v : α × Fin p | v.2 = f v.1}.ncard = Fintype.card α := by + have h : {v : α × Fin p | v.2 = f v.1} = Set.univ.graphOn f := by + ext v + simp [Set.mem_graphOn, eq_comm] + rw [h, Set.ncard_graphOn, Set.ncard_univ, Nat.card_eq_fintype_card] + +/-- Section 3.1, "The reductions below [...] produce at most n²/√D query pairs and sets of at most +√D elements", and Remark 20, "every vertex of A has O(√D/g) neighbors in the middle part": in an +instance of the reduction, a vertex of `A` or of `B` has exactly one neighbor `(c, σ)` for every +`c ∈ C_k`, hence at most `⌈s/g⌉` neighbors. (And `⌈s/g⌉ ≤ s ≤ √D`; for `g ≤ √D` also +`⌈s/g⌉ ≤ 2√D/g`, `pieceSize_le_two_mul_sqrt_div`.) -/ +theorem ncard_nbr_lopInstance_le (wAB wBC wAC : Fin n → Fin n → ℤ) (hD : 16 ≤ D) + (hg1 : 1 ≤ g) (hp : p ≠ 0) (ι : InstanceIndex p) : + (∀ a, {v | (lopInstance wAB wBC wAC D g p ι).adjA a v}.ncard ≤ pieceSize D g) ∧ + (∀ b, {v | (lopInstance wAB wBC wAC D g p ι).adjB v b}.ncard ≤ pieceSize D g) := by + -- The neighbors of a vertex are the graph of the function that sends `c` to its label. + have hgraph : ∀ (f : {c : Fin n // c ∈ piece n D g ι.2.2} → ℤ), + {v : {c : Fin n // c ∈ piece n D g ι.2.2} × Fin p | + ((v.2 : ℕ) : ℤ) ≡ f v.1 [ZMOD (p : ℤ)]}.ncard ≤ pieceSize D g := by + intro f + refine le_trans (le_of_eq ?_) (card_piece_le hD hg1 n ι.2.2) + rw [← Fintype.card_coe, ← ncard_graph fun c => resFin hp (f c)] + congr 1 + ext v + exact modEq_iff_eq_resFin hp v.2 _ + exact ⟨fun a => hgraph fun c => wAC a c + ((ι.1 : ℕ) : ℤ), fun b => hgraph fun c => -wBC b c⟩ + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean new file mode 100644 index 0000000000..0b5e39cb90 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -0,0 +1,398 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Splitting +public import Mathlib.Combinatorics.Enumerative.DoubleCounting +import Mathlib.Analysis.Complex.ExponentialBounds + +/-! +# Finite witness recovery from modular lopsided answers + +Correct finite answers are ordinary `Set.EqOn` premises on the actual lopsided graphs. +The recovery scans each accepted piece until it finds an exact zero. Distinct failed scans +contain distinct modular false positives. No solver, native program or time oracle is assumed. + +## Main definitions + +* `scanPiece`, `runScans`: finite witness search and number of scans. +* `AnswersCorrect`, `IsScanOrder`: correctness on queried pairs and a duplicate-free scan order. + +## Main results + +* `recovery_correct`: sound and complete recovery with at most `falsePositiveCount + 1` scans. +* `card_failedScans_le`: false-positive accounting. +* `falsePositiveCount_add_one_mul_pieceSize_le`: bound on the number of vertices scanned. + +## References + +Adapted from [Witnesses.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Witnesses.lean) and the minimal scan definitions in +`PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +Context: [AlmanWilliams2026, Section 3]. Source-derived prose is paraphrased. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Cslib.Matrix + +/-- A finite search result returns a zero triangle, or certifies that no zero triangle exists. +This specifies mathematical correctness only. -/ +def IsSearchAnswer {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + (r : Option (Fin n × Fin n × Fin n)) : Prop := + (∀ a b c, r = some (a, b, c) → weightSum wAB wBC wAC a b c = 0) ∧ + (r = none → ¬ HasZeroTriangle wAB wBC wAC) + +/-- Proof of Theorem 17: "scan the piece C_k of its instance for a c with S(a,b,c) = 0". The +vertices of the piece are tried in increasing order; the result is the first `c` found, or `none` if +the scan fails. -/ +def scanPiece {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g k : ℕ) (a b : Fin n) : + Option (Fin n) := + ((List.finRange n).filter fun c => c ∈ piece n D g k).find? fun c => + weightSum wAB wBC wAC a b c = 0 + +/-- Proof of Theorem 17: "For every query pair that the oracle accepts". `ans ι` is the oracle's +answer to the instance with index `ι`. The result is the set of all (instance, query pair of that +instance) that the oracle accepts; each of them calls for one scan. -/ +def acceptedPairs {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Finset (InstanceIndex p × (Fin n × Fin n)) := + ((instanceIndices wAB D g p) ×ˢ Finset.univ).filter fun (ι, q) => + q ∈ (lopInstance wAB wBC wAC D g p ι).W ∧ ans ι q = true + +/-- Proof of Theorem 17: the scans, one after the other, over a list of accepted pairs: "We stop as +soon as a zero triangle is found." The result is the triangle found (or `none`) together with the +number of scans that were carried out. -/ +def runScans {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) {p : ℕ} : + List (InstanceIndex p × (Fin n × Fin n)) → Option (Fin n × Fin n × Fin n) × ℕ + | [] => (none, 0) + | ((_, _, k), a, b) :: rest => + match scanPiece wAB wBC wAC D g k a b with + | some c => (some (a, b, c), 1) + | none => ((runScans wAB wBC wAC D g rest).1, (runScans wAB wBC wAC D g rest).2 + 1) + +/-- Proof of Theorem 17: "For every query pair that the oracle accepts, scan [...]". The paper does +not say in which order. `L` is an order of the scans: a list in which every accepted pair occurs +exactly once. -/ +def IsScanOrder {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) (L : List (InstanceIndex p × (Fin n × Fin n))) : + Prop := + L.Nodup ∧ ∀ x, x ∈ L ↔ x ∈ acceptedPairs wAB wBC wAC D g p ans + + +variable {n D g p : ℕ} + +section Matrices + +variable {wAB wBC wAC : Fin n → Fin n → ℤ} + +/-! ### Scans and accepted pairs -/ + +/-- Proof of Theorem 17, "scan the piece C_k of its instance for a c with S(a,b,c) = 0": a `c` that +a scan returns lies in the piece and has `S(a,b,c) = 0`. -/ +theorem scanPiece_eq_some {k : ℕ} {a b c : Fin n} (h : scanPiece wAB wBC wAC D g k a b = some c) : + c ∈ piece n D g k ∧ weightSum wAB wBC wAC a b c = 0 := by + have hmem := List.mem_of_find?_eq_some h + rw [List.mem_filter] at hmem + exact ⟨by simpa using hmem.2, by simpa using List.find?_some h⟩ + +/-- A scan fails exactly if the piece has no `c` with `S(a,b,c) = 0`. -/ +theorem scanPiece_eq_none_iff {k : ℕ} {a b : Fin n} : + scanPiece wAB wBC wAC D g k a b = none ↔ + ∀ c ∈ piece n D g k, weightSum wAB wBC wAC a b c ≠ 0 := by + unfold scanPiece + rw [List.find?_eq_none] + simp [List.mem_filter] + +variable (wAB wBC wAC) (D g) + +/-- The scan that belongs to `x = ((ϱ, j, k), (a, b))`, a query pair `(a, b)` of the instance with +index `(ϱ, j, k)`: the piece `C_k` is scanned for the pair `(a, b)`. -/ +abbrev scanOf (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) + (x : InstanceIndex p × (Fin n × Fin n)) : Option (Fin n) := + scanPiece wAB wBC wAC D g x.1.2.2 x.2.1 x.2.2 + +/-- What the scans over a list of pairs return: a triangle that is returned is a zero triangle; if +none is returned, then every scan failed; and all the scans that are carried out, except possibly +the last one, fail. -/ +private theorem runScans_spec (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) + (l : List (InstanceIndex p × (Fin n × Fin n))) : + (∀ a b c, (runScans wAB wBC wAC D g l).1 = some (a, b, c) → weightSum wAB wBC wAC a b c = 0) ∧ + ((runScans wAB wBC wAC D g l).1 = none → ∀ x ∈ l, scanOf wAB wBC wAC D g x = none) ∧ + (runScans wAB wBC wAC D g l).2 ≤ + (l.filter fun x => decide (scanOf wAB wBC wAC D g x = none)).length + 1 := by + induction l with + | nil => simp [runScans] + | cons x rest ih => + obtain ⟨ihzero, ihnone, ihcount⟩ := ih + cases hscan : scanOf wAB wBC wAC D g x with + | some c => + -- The first scan finds `c`, and the scans stop. + have hrun : runScans wAB wBC wAC D g (x :: rest) = (some (x.2.1, x.2.2, c), 1) := by + simp only [runScans, hscan] + rw [hrun] + refine ⟨fun a b c' h => ?_, fun h => by simp at h, by simp⟩ + simp only [Option.some.injEq, Prod.mk.injEq] at h + obtain ⟨rfl, rfl, rfl⟩ := h + exact (scanPiece_eq_some hscan).2 + | none => + -- The first scan fails, and the scans go on with the rest of the list. + have hrun : runScans wAB wBC wAC D g (x :: rest) + = ((runScans wAB wBC wAC D g rest).1, (runScans wAB wBC wAC D g rest).2 + 1) := by + simp only [runScans, hscan] + rw [hrun, List.filter_cons_of_pos (by simpa using hscan), List.length_cons] + refine ⟨ihzero, fun h y hy => ?_, by omega⟩ + rcases List.mem_cons.mp hy with rfl | hy + · exact hscan + · exact ihnone h y hy + +variable (p) (ans : InstanceIndex p → Fin n × Fin n → Bool) + +/-- The hypothesis on the oracle: `ans ι` is a correct answer to the instance with index `ι`, for +every instance of the reduction. -/ +def AnswersCorrect (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Prop := + ∀ ι ∈ instanceIndices wAB D g p, Set.EqOn (fun q => ans ι q = true) + (fun q => (lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2) + ((lopInstance wAB wBC wAC D g p ι).W : Set _) + +/-- Proof of Theorem 17: a "failed scan, of a piece C_k for a pair (a,b)": the oracle accepted the +pair in the instance, and the piece contains no `c` with `S(a,b,c) = 0`. -/ +def failedScans (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Finset (InstanceIndex p × (Fin n × Fin n)) := + (acceptedPairs wAB wBC wAC D g p ans).filter fun x => scanOf wAB wBC wAC D g x = none + +variable {wAB wBC wAC D g p ans} + +/-- The accepted pairs: an index of an instance, a query pair of that instance, and the answer +"yes". -/ +theorem mem_acceptedPairs {ι : InstanceIndex p} {q : Fin n × Fin n} : + (ι, q) ∈ acceptedPairs wAB wBC wAC D g p ans ↔ + ι ∈ instanceIndices wAB D g p ∧ q ∈ (lopInstance wAB wBC wAC D g p ι).W ∧ ans ι q = true := by + simp [acceptedPairs] + +/-! ### The sentences of the paragraph "Witnesses" -/ + +/-- Proof of Theorem 17: "A zero triangle is always found, since its c lies in some piece and makes +the oracle accept its pair." -/ +theorem exists_mem_acceptedPairs (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) {a b c : Fin n} + (h0 : weightSum wAB wBC wAC a b c = 0) : + ∃ ϱ j k, ((ϱ, j, k), (a, b)) ∈ acceptedPairs wAB wBC wAC D g p ans ∧ c ∈ piece n D g k := by + -- The piece of `c`, the residue class of `(a, b)` and the chunk of `(a, b)` in it. + obtain ⟨k, ⟨hk, hck⟩, -⟩ := existsUnique_mem_piece hD hg1 c + obtain ⟨ϱ, hab, -⟩ := existsUnique_mem_residueClass wAB hp (a, b) + obtain ⟨j, hj, hW⟩ := Finset.mem_biUnion.mp + ((biUnion_chunk (residueClass wAB p ϱ) (one_le_queryCap (by omega) hDn)).symm ▸ hab) + have hι : ((ϱ, j, k) : InstanceIndex p) ∈ instanceIndices wAB D g p := + (mem_instanceIndices wAB _).mpr ⟨Finset.mem_range.mp hj, hk⟩ + have hquery : (a, b) ∈ (lopInstance wAB wBC wAC D g p (ϱ, j, k)).W := hW + refine ⟨ϱ, j, k, mem_acceptedPairs.mpr ⟨hι, hquery, ?_⟩, hck⟩ + -- The vertex `c` makes the oracle accept the pair. + apply (Iff.of_eq (hans _ hι hquery)).mpr + rw [hasCommonNeighbor_lopInstance_iff wAB wBC wAC hp hquery] + exact ⟨c, hck, by rw [show weightSum wAB wBC wAC a b c = 0 from h0]⟩ + +/-- Proof of Theorem 17: "A failed scan, of a piece C_k for a pair (a,b), contains a c ∈ C_k with +S(a,b,c) ≡ 0 (mod p) but S(a,b,c) ≠ 0, that is, a false positive (a,b,c) of p". -/ +theorem exists_isFalsePositive_of_mem_failedScans (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) + {ϱ : Fin p} {j k : ℕ} {a b : Fin n} + (hx : ((ϱ, j, k), (a, b)) ∈ failedScans wAB wBC wAC D g p ans) : + ∃ c ∈ piece n D g k, IsFalsePositive wAB wBC wAC p (a, b, c) := by + obtain ⟨hacc, hnone⟩ := Finset.mem_filter.mp hx + obtain ⟨hι, hquery, htrue⟩ := mem_acceptedPairs.mp hacc + obtain ⟨c, hc, hS⟩ := (hasCommonNeighbor_lopInstance_iff wAB wBC wAC hp hquery).mp + ((Iff.of_eq (hans _ hι hquery)).mp htrue) + exact ⟨c, hc, scanPiece_eq_none_iff.mp hnone c hc, Int.modEq_zero_iff_dvd.mp hS⟩ + +/-- Proof of Theorem 17: "distinct scans contain distinct false positives": a triple `(a,b,c)` +belongs to the scan of at most one accepted pair. -/ +theorem eq_of_mem_acceptedPairs {ϱ ϱ' : Fin p} {j j' k k' : ℕ} {q : Fin n × Fin n} + (hx : ((ϱ, j, k), q) ∈ acceptedPairs wAB wBC wAC D g p ans) + (hy : ((ϱ', j', k'), q) ∈ acceptedPairs wAB wBC wAC D g p ans) {c : Fin n} + (hc : c ∈ piece n D g k) + (hc' : c ∈ piece n D g k') : (ϱ, j, k) = (ϱ', j', k') := by + have hp : p ≠ 0 := by + rintro rfl + exact ϱ.elim0 + obtain ⟨hclass, hchunk⟩ := mem_lopInstance_W (mem_acceptedPairs.mp hx).2.1 + obtain ⟨hclass', hchunk'⟩ := mem_lopInstance_W (mem_acceptedPairs.mp hy).2.1 + -- The pair determines its residue class, then its chunk; the vertex `c` determines the piece. + obtain rfl : ϱ = ϱ' := (existsUnique_mem_residueClass wAB hp q).unique hclass hclass' + obtain rfl : j = j' := hchunk.symm.trans hchunk' + obtain rfl : k = k' := ((mem_piece c).mp hc).symm.trans ((mem_piece c).mp hc') + rfl + +/-- Proof of Theorem 17: "distinct scans contain distinct false positives, so there are at most F(p) +failed scans", for any finite family `s` of scans and any notion "the scan `x` contains the triple +`t`". This applies to the accepted-pair scans in `card_failedScans_le`. -/ +theorem card_le_falsePositiveCount_of_distinct_scans + (wAB wBC wAC : Fin n → Fin n → ℤ) {σ : Type*} (s : Finset σ) + (contains : σ → Fin n × Fin n × Fin n → Prop) + (hfalse : ∀ x ∈ s, ∃ t, contains x t ∧ IsFalsePositive wAB wBC wAC p t) + (hdistinct : ∀ t, ∀ x ∈ s, ∀ y ∈ s, contains x t → contains y t → x = y) : + s.card ≤ falsePositiveCount wAB wBC wAC p := by + classical + refine Finset.card_le_card_of_forall_subsingleton contains (fun x hx => ?_) + fun t _ x hx y hy => hdistinct t x hx.1 y hy.1 hx.2 hy.2 + obtain ⟨t, ht, hfp⟩ := hfalse x hx + exact ⟨t, Finset.mem_filter.mpr ⟨Finset.mem_univ _, hfp⟩, ht⟩ + +/-- Proof of Theorem 17: "so there are at most F(p) failed scans". -/ +theorem card_failedScans_le (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) : + (failedScans wAB wBC wAC D g p ans).card ≤ falsePositiveCount wAB wBC wAC p := by + -- The scan of the piece `C_k` for the pair `(a, b)` contains the triples `(a, b, c)`, `c ∈ C_k`. + refine card_le_falsePositiveCount_of_distinct_scans wAB wBC wAC _ + (fun x t => x.2 = (t.1, t.2.1) ∧ t.2.2 ∈ piece n D g x.1.2.2) ?_ ?_ + · rintro ⟨⟨ϱ, j, k⟩, a, b⟩ hx + obtain ⟨c, hc, hfalse⟩ := exists_isFalsePositive_of_mem_failedScans hp hans hx + exact ⟨(a, b, c), ⟨rfl, hc⟩, hfalse⟩ + · rintro ⟨a, b, c⟩ ⟨⟨ϱ, j, k⟩, q⟩ hx ⟨⟨ϱ', j', k'⟩, q'⟩ hy ⟨rfl, hc⟩ ⟨rfl, hc'⟩ + rw [eq_of_mem_acceptedPairs (Finset.mem_filter.mp hx).1 (Finset.mem_filter.mp hy).1 hc hc'] + +end Matrices + +/-- Proof of Theorem 17: the length of a scan, the `O(√D/g)` in "the scans cost O((F(p) + 1)√D/g)": +a piece has at most `⌈s/g⌉ ≤ 2√D/g` vertices. -/ +theorem pieceSize_le_two_mul_sqrt_div (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) : + (pieceSize D g : ℝ) ≤ 2 * Real.sqrt D / (g : ℝ) := by + have hg0 : (0 : ℝ) < g := by exact_mod_cast hg1 + calc (pieceSize D g : ℝ) ≤ (sOf D : ℝ) / (g : ℝ) + 1 := by + rw [pieceSize_eq_ceil] + exact (Nat.ceil_lt_add_one (by positivity)).le + _ ≤ Real.sqrt D / (g : ℝ) + Real.sqrt D / (g : ℝ) := + add_le_add (div_le_div_of_nonneg_right (sOf_le_sqrt D) hg0.le) + ((one_le_div hg0).mpr hg) + _ = 2 * Real.sqrt D / (g : ℝ) := by ring + +/-- Proof of Theorem 17: "the scans cost O((F(p) + 1)√D/g) = O(ν n³ log n/g)". -/ +theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) + (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) + ≤ (2 * Hashing.falsePositiveConst + 2) * (κ * (n : ℝ) ^ 3 * Real.log n / (g : ℝ)) := by + have hsqrt : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlog : 1 ≤ Real.log n := + (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans (by linarith)) + set B := κ * (n : ℝ) ^ 3 * Real.log n with hB + -- The two terms: `F(p) √D = O(B)` by the bound on `F(p)`, and `√D ≤ n ≤ n³ ≤ B`. + have hfalse : (falsePositiveCount wAB wBC wAC p : ℝ) * Real.sqrt D ≤ + Hashing.falsePositiveConst * B := by + have hF := Hashing.falsePositiveCount_le_const_mul hD hDn hκ wAB wBC wAC hT hp + rwa [← mul_div_assoc, le_div_iff₀ (by linarith)] at hF + have hone : Real.sqrt D ≤ B := + calc Real.sqrt D ≤ n := sqrt_le_natCast (by omega) hDn + _ ≤ (n : ℝ) ^ 3 := le_self_pow₀ (by linarith) (by norm_num) + _ ≤ κ * (n : ℝ) ^ 3 := le_mul_of_one_le_left (by positivity) hκ + _ ≤ B := le_mul_of_one_le_right (by positivity) hlog + calc ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) + ≤ ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (2 * Real.sqrt D / (g : ℝ)) := by + gcongr + exact pieceSize_le_two_mul_sqrt_div hg1 hg + _ = (2 * ((falsePositiveCount wAB wBC wAC p : ℝ) * Real.sqrt D) + + 2 * Real.sqrt D) / (g : ℝ) := by ring + _ ≤ (2 * (Hashing.falsePositiveConst * B) + 2 * B) / (g : ℝ) := by gcongr + _ = (2 * Hashing.falsePositiveConst + 2) * (B / (g : ℝ)) := by ring + +/-! ### The output of the reduction -/ + +/-- **Theorem 17, correctness.** Given any correct answers of an oracle for +Lop-AE-SparseTri to the instances of the reduction, and whatever the order `L` of the scans, the +reduction's output is correct: it is a zero triangle if one exists, and `none` otherwise. It +carries out at most `F(p) + 1` scans ("We stop as soon as a zero triangle is found"). -/ +theorem recovery_correct (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (wAB wBC wAC : Fin n → Fin n → ℤ) (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + IsSearchAnswer wAB wBC wAC (runScans wAB wBC wAC D g L).1 ∧ + (runScans wAB wBC wAC D g L).2 ≤ falsePositiveCount wAB wBC wAC p + 1 := by + have hp0 : p ≠ 0 := (mem_primesInRange.mp hp.1).1.ne_zero + obtain ⟨hzero, hnone, hcount⟩ := runScans_spec wAB wBC wAC D g L + refine ⟨⟨hzero, fun hout ⟨a, b, c, h0⟩ => ?_⟩, ?_⟩ + · -- "A zero triangle is always found": its scan cannot have failed. + obtain ⟨ϱ, j, k, hx, hc⟩ := exists_mem_acceptedPairs hD hDn hg1 hp0 hans h0 + exact scanPiece_eq_none_iff.mp (hnone hout _ ((hL.2 _).mpr hx)) c hc h0 + · -- All the scans except possibly the last one fail, and there are at most `F(p)` failed scans. + have hfailed : (L.filter fun x => decide (scanOf wAB wBC wAC D g x = none)).length + ≤ (failedScans wAB wBC wAC D g p ans).card := by + rw [← List.toFinset_card_of_nodup (hL.1.filter _)] + refine Finset.card_le_card fun x hx => ?_ + obtain ⟨hxL, hxnone⟩ := List.mem_filter.mp (List.mem_toFinset.mp hx) + exact Finset.mem_filter.mpr ⟨(hL.2 x).mp hxL, by simpa using hxnone⟩ + have hF := card_failedScans_le hp0 hans + omega + + +/-- Every finite set of accepted queries admits a duplicate-free scan order. -/ +theorem exists_isScanOrder (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : + ∃ L, IsScanOrder wAB wBC wAC D g p ans L := + ⟨(acceptedPairs wAB wBC wAC D g p ans).toList, + Finset.nodup_toList _, fun _ => Finset.mem_toList⟩ + +/-- Correct finite answer tables exist. This is a finite specification, with no solver or +complexity claim. Values outside the queried edges remain unconstrained. -/ +theorem exists_answersCorrect (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) : + ∃ ans, AnswersCorrect wAB wBC wAC D g p ans := by + classical + refine ⟨fun ι q => decide ((lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2), ?_⟩ + intro ι _ q hq + simp + +/-- Correct finite common-neighbor counts supply the Boolean answers used by recovery. +This connects directly to the lopsided matrix/counting interface. -/ +theorem answersCorrect_of_counting (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (counts : InstanceIndex p → Fin n × Fin n → ℕ) + (hcounts : ∀ ι ∈ instanceIndices wAB D g p, + Set.EqOn (counts ι) (fun q => (lopInstance wAB wBC wAC D g p ι).commonNeighborCount q.1 q.2) + ((lopInstance wAB wBC wAC D g p ι).W : Set _)) : + AnswersCorrect wAB wBC wAC D g p (fun ι q => decide (counts ι q ≠ 0)) := by + intro ι hι q hq + apply propext + simp only [decide_eq_true_eq] + exact Iff.of_eq ((lopInstance wAB wBC wAC D g p ι).detection_eqOn_of_counting + (counts ι) (hcounts ι hι) hq) + +/-- The recovery scan examines at most the uniform false-positive bound times the piece size. +This counts candidate vertices only; it is not a charged native running-time theorem. -/ +theorem recovery_vertexBound {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) + (wAB wBC wAC : Fin n → Fin n → ℤ) + (hw : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + IsSearchAnswer wAB wBC wAC (runScans wAB wBC wAC D g L).1 ∧ + ((runScans wAB wBC wAC D g L).2 : ℝ) * pieceSize D g ≤ + (2 * Hashing.falsePositiveConst + 2) * (κ * (n : ℝ) ^ 3 * Real.log n / (g : ℝ)) := by + obtain ⟨hcorrect, hcount⟩ := recovery_correct hD hDn hg1 wAB wBC wAC hp hans hL + refine ⟨hcorrect, le_trans ?_ (falsePositiveCount_add_one_mul_pieceSize_le + wAB wBC wAC hD hDn hg1 hg hκ hw hp)⟩ + gcongr + exact_mod_cast hcount + +/-- Finite reduction equivalence: scanning all accepted queries returns `none` exactly when the +original three matrices have no zero triangle. Any duplicate-free scan order works. -/ +theorem recovery_eq_none_iff (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (wAB wBC wAC : Fin n → Fin n → ℤ) (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + (runScans wAB wBC wAC D g L).1 = none ↔ ¬ HasZeroTriangle wAB wBC wAC := by + obtain ⟨hsound, hcomplete⟩ := (recovery_correct hD hDn hg1 wAB wBC wAC hp hans hL).1 + refine ⟨hcomplete, fun hn => ?_⟩ + cases h : (runScans wAB wBC wAC D g L).1 with + | none => rfl + | some t => + obtain ⟨a, b, c⟩ := t + exact False.elim (hn ⟨a, b, c, hsound a b c h⟩) + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/Data/Finset/Quotient.lean b/Complexity/Foundations/Data/Finset/Quotient.lean new file mode 100644 index 0000000000..875f7e52d7 --- /dev/null +++ b/Complexity/Foundations/Data/Finset/Quotient.lean @@ -0,0 +1,44 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Order.Interval.Finset.Nat + +/-! +# Cardinality of a quotient fiber + +## Main results + +* `Finset.card_filter_div_eq_le`: an injectively indexed quotient fiber has at most the divisor + many members. + +## References + +Adapted from [Counting.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Counting.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Finset + +/-- At most `c` elements `x` of `s` have `⌊f x / c⌋ = k`, if `f` is injective on `s`: their values +lie in `[kc, kc + c)`. -/ +theorem card_filter_div_eq_le {α : Type*} (s : Finset α) {f : α → ℕ} (hf : Set.InjOn f s) + {c : ℕ} (hc : 0 < c) (k : ℕ) : (s.filter fun x => f x / c = k).card ≤ c := by + have hmaps : ∀ x ∈ s.filter fun x => f x / c = k, f x ∈ Finset.Ico (k * c) (k * c + c) := by + intro x hx + obtain ⟨-, rfl⟩ := Finset.mem_filter.mp hx + rw [Finset.mem_Ico, ← Nat.succ_mul] + exact ⟨Nat.div_mul_le_self _ _, (Nat.div_lt_iff_lt_mul hc).mp (Nat.lt_succ_self _)⟩ + calc (s.filter fun x => f x / c = k).card ≤ (Finset.Ico (k * c) (k * c + c)).card := + Finset.card_le_card_of_injOn f hmaps (hf.mono fun x hx => (Finset.mem_filter.mp hx).1) + _ = c := by rw [Nat.card_Ico, Nat.add_sub_cancel_left] + +end Finset diff --git a/Complexity/Foundations/Data/Int/Residue.lean b/Complexity/Foundations/Data/Int/Residue.lean new file mode 100644 index 0000000000..f6622c6a71 --- /dev/null +++ b/Complexity/Foundations/Data/Int/Residue.lean @@ -0,0 +1,41 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Ring.Int + +/-! +# Integer residues as natural numbers + +## Main results + +* `Int.natCast_toNat_emod`: the nonnegative integer residue survives a round trip through `Nat`. +* `Int.toNat_emod_lt`: the natural residue is below a positive modulus. + +## References + +Adapted from [Index.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Index.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The existing Exact Triangle hashing client now uses these shared facts. +-/ + +@[expose] public section + +namespace Int + +theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : + ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by + have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) + have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) + omega + +end Int diff --git a/Complexity/Foundations/Data/Matrix/PairChunks.lean b/Complexity/Foundations/Data/Matrix/PairChunks.lean new file mode 100644 index 0000000000..3f3c74fee3 --- /dev/null +++ b/Complexity/Foundations/Data/Matrix/PairChunks.lean @@ -0,0 +1,114 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Nat.CeilDiv +public import Complexity.Foundations.Data.Finset.Quotient +public import Mathlib.Logic.Equiv.Fin.Basic +public import Mathlib.Data.Fintype.Prod +import Mathlib.Order.Interval.Finset.Fin + +/-! +# Chunks of matrix index pairs + +## Main definitions + +* `pairIndex`, `rankIn`: row-major index and rank in a finite set. +* `chunk`, `numChunks`: consecutive chunks and their number. + +## Main results + +* `card_chunk_le`: each chunk has at most the capacity many pairs. +* `biUnion_chunk`: all chunks cover the input finite set. + +## References + +Adapted from [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and the minimal definitions in +`PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +There are no graph or weight assumptions. +-/ + +@[expose] public section + +namespace Cslib.Matrix + +/-- The position of the pair `(a, b)` in the row-major order of `A × B`. -/ +def pairIndex {n : ℕ} (q : Fin n × Fin n) : ℕ := q.1.val * n + q.2.val + +/-- The number of pairs of `S` that come before `q` in row-major order. -/ +def rankIn {n : ℕ} (S : Finset (Fin n × Fin n)) (q : Fin n × Fin n) : ℕ := + (S.filter fun r => pairIndex r < pairIndex q).card + +/-- The `j`-th chunk (`j = 0, 1, …`) of `S` when `S` is cut, in row-major order, into chunks of +`cap` pairs each (the last one may be smaller). -/ +def chunk {n : ℕ} (S : Finset (Fin n × Fin n)) (cap j : ℕ) : Finset (Fin n × Fin n) := + S.filter fun q => rankIn S q / cap = j + +/-- The number `⌈|S| / cap⌉` of nonempty chunks of `S` (for `cap ≥ 1`). -/ +def numChunks {n : ℕ} (S : Finset (Fin n × Fin n)) (cap : ℕ) : ℕ := + S.card ⌈/⌉ cap + +variable {n : ℕ} {S : Finset (Fin n × Fin n)} {cap : ℕ} + +/-! ### Ranks -/ + +/-- The position in row-major order determines the pair. -/ +theorem pairIndex_injective : Function.Injective (pairIndex (n := n)) := fun q r h => + finProdFinEquiv.injective (Fin.ext (by simpa [pairIndex, add_comm, mul_comm] using h)) + +/-- Among the pairs of `S`, a pair that comes earlier in row-major order has a smaller rank. -/ +theorem rankIn_lt_rankIn {q r : Fin n × Fin n} (hq : q ∈ S) + (h : pairIndex q < pairIndex r) : rankIn S q < rankIn S r := by + refine Finset.card_lt_card ⟨fun x hx => ?_, fun hsub => ?_⟩ + · rw [Finset.mem_filter] at hx ⊢ + exact ⟨hx.1, hx.2.trans h⟩ + · exact lt_irrefl _ (Finset.mem_filter.mp (hsub (Finset.mem_filter.mpr ⟨hq, h⟩))).2 + +/-- Different pairs of `S` have different ranks. -/ +theorem rankIn_injOn (S : Finset (Fin n × Fin n)) : Set.InjOn (rankIn S) S := by + intro q hq r hr h + rcases lt_trichotomy (pairIndex q) (pairIndex r) with hlt | heq | hgt + · exact absurd h (rankIn_lt_rankIn hq hlt).ne + · exact pairIndex_injective heq + · exact absurd h (rankIn_lt_rankIn hr hgt).ne' + +/-- The rank of a pair of `S` is less than the number of pairs of `S`. -/ +theorem rankIn_lt_card {q : Fin n × Fin n} (hq : q ∈ S) : rankIn S q < S.card := + Finset.card_lt_card ⟨Finset.filter_subset _ _, + fun hsub => lt_irrefl _ (Finset.mem_filter.mp (hsub hq)).2⟩ + +/-- In the full matrix, row-major rank is the row-major index itself. This avoids enumerating +all preceding pairs when applying the chunk API to a full matrix. -/ +theorem rankIn_univ (q : Fin n × Fin n) : + rankIn (Finset.univ : Finset (Fin n × Fin n)) q = pairIndex q := by + calc rankIn (Finset.univ : Finset (Fin n × Fin n)) q + = (Finset.Iio (finProdFinEquiv q)).card := by + apply Finset.card_equiv finProdFinEquiv + intro r + simp [pairIndex, finProdFinEquiv, add_comm, mul_comm, Fin.lt_def] + _ = pairIndex q := by simp [pairIndex, finProdFinEquiv, add_comm, mul_comm] + +/-! ### Chunks -/ + +/-- A chunk has at most `cap` pairs: their ranks are different numbers in `[j cap, (j + 1) cap)`. -/ +theorem card_chunk_le (S : Finset (Fin n × Fin n)) (hcap : 1 ≤ cap) (j : ℕ) : + (chunk S cap j).card ≤ cap := + S.card_filter_div_eq_le (rankIn_injOn S) hcap j + +/-- The first `⌈|S|/cap⌉` chunks cover `S`, because `|S| ≤ ⌈|S|/cap⌉ cap`. -/ +theorem biUnion_chunk (S : Finset (Fin n × Fin n)) (hcap : 1 ≤ cap) : + (Finset.range (numChunks S cap)).biUnion (fun j => chunk S cap j) = S := by + have hcard : S.card ≤ numChunks S cap * cap := by + rw [numChunks] + exact Nat.le_ceilDiv_mul hcap + ext q + simp only [Finset.mem_biUnion, Finset.mem_range, chunk, Finset.mem_filter] + refine ⟨fun ⟨_, _, hq, _⟩ => hq, fun hq => ⟨rankIn S q / cap, ?_, hq, rfl⟩⟩ + exact (Nat.div_lt_iff_lt_mul hcap).mpr ((rankIn_lt_card hq).trans_le hcard) + +end Cslib.Matrix diff --git a/Complexity/Foundations/Data/Nat/CeilDiv.lean b/Complexity/Foundations/Data/Nat/CeilDiv.lean new file mode 100644 index 0000000000..c88317bc1b --- /dev/null +++ b/Complexity/Foundations/Data/Nat/CeilDiv.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Floor.Div +public import Mathlib.Algebra.Order.Archimedean.Real.Basic + +/-! +# Natural ceilings of quotients + +## Main results + +* `Nat.ceil_div_eq_ceilDiv`: real quotient ceilings equal natural ceiling division. +* `Nat.le_ceilDiv_mul`, `Nat.ceilDiv_mul_lt`: covering and overshoot bounds. + +## References + +Adapted from [Ceil.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Ceil.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Nat + +/-! ## The ceiling of a quotient of natural numbers -/ + +/-- `a ⌈/⌉ b ≤ k` says that `k` pieces of size `b` cover `a`. Mathlib's `ceilDiv_le_iff_le_mul` has +`b * k` on the right. -/ +theorem ceilDiv_le_iff {a b k : ℕ} (hb : 0 < b) : a ⌈/⌉ b ≤ k ↔ a ≤ k * b := by + rw [ceilDiv_le_iff_le_mul hb, Nat.mul_comm] + +/-- `a ⌈/⌉ b` pieces of size `b` cover `a`. -/ +theorem le_ceilDiv_mul {a b : ℕ} (hb : 0 < b) : a ≤ a ⌈/⌉ b * b := + (ceilDiv_le_iff hb).1 le_rfl + +/-- `a ⌈/⌉ b` pieces of size `b` overshoot `a` by less than one piece. -/ +theorem ceilDiv_mul_lt {a b : ℕ} (hb : 0 < b) : a ⌈/⌉ b * b < a + b := by + have := Nat.div_mul_le_self (a + b - 1) b + rw [Nat.ceilDiv_eq_add_pred_div] + omega + +/-- The ceiling of the real quotient of two natural numbers, in natural numbers. -/ +theorem ceil_div_eq_ceilDiv (a : ℕ) {b : ℕ} (hb : 0 < b) : ⌈(a : ℝ) / (b : ℝ)⌉₊ = a ⌈/⌉ b := by + refine eq_of_forall_ge_iff fun k => ?_ + rw [Nat.ceil_le, div_le_iff₀ (Nat.cast_pos.2 hb), ceilDiv_le_iff hb] + exact_mod_cast Iff.rfl + +end Nat diff --git a/Complexity/Foundations/Data/Nat/SqrtRatio.lean b/Complexity/Foundations/Data/Nat/SqrtRatio.lean new file mode 100644 index 0000000000..602c737373 --- /dev/null +++ b/Complexity/Foundations/Data/Nat/SqrtRatio.lean @@ -0,0 +1,47 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Analysis.Real.Sqrt +import Mathlib.Tactic.Positivity +import Mathlib.Tactic.NormNum + +/-! +# Computing a floor involving a square root + +## Main results + +* `Nat.sqrt_div_eq_floor_div_sqrt`: the floor of `a / sqrt b` is the natural square root + of `a² / b`, including the zero-denominator convention. + +This elementary arithmetic bridge is original adaptation code. It lets finite algorithms use +integer arithmetic while retaining their real-valued capacity specifications. +-/ + +public section + +namespace Nat + +/-- A computable integer formula for the floor of a quotient by a square root. -/ +theorem sqrt_div_eq_floor_div_sqrt (a b : ℕ) : + Nat.sqrt (a ^ 2 / b) = ⌊(a : ℝ) / Real.sqrt b⌋₊ := by + by_cases hb : b = 0 + · simp [hb] + have hb0 : 0 < b := Nat.pos_of_ne_zero hb + have hs : 0 < Real.sqrt (b : ℝ) := Real.sqrt_pos.mpr (by exact_mod_cast hb0) + apply eq_of_forall_le_iff + intro k + rw [Nat.le_sqrt', Nat.le_div_iff_mul_le hb0, Nat.le_floor_iff (by positivity), + le_div_iff₀ hs] + have hcast : k ^ 2 * b ≤ a ^ 2 ↔ (k : ℝ) ^ 2 * (b : ℝ) ≤ (a : ℝ) ^ 2 := by + norm_cast + rw [hcast, ← sq_le_sq₀ (show (0 : ℝ) ≤ k * Real.sqrt b by positivity) (Nat.cast_nonneg a)] + rw [mul_pow, Real.sq_sqrt (Nat.cast_nonneg b)] + +end Nat diff --git a/ComplexityTests/ExactTriangleSplitting.lean b/ComplexityTests/ExactTriangleSplitting.lean new file mode 100644 index 0000000000..1768d67f64 --- /dev/null +++ b/ComplexityTests/ExactTriangleSplitting.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Witnesses +import Mathlib.Tactic.NormNum.NatSqrt + +open Cslib.Algorithms.ExactTriangle Cslib.Matrix + +-- Row-major chunks, including the short last chunk and the first empty chunk. +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 0 = + {(0, 0), (0, 1), (1, 0)} := by decide +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 1 = {(1, 1)} := by decide +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 2 = ∅ := by decide +example : chunk (∅ : Finset (Fin 2 × Fin 2)) 3 0 = ∅ := by decide +example : numChunks (Finset.univ : Finset (Fin 2 × Fin 2)) 3 = 2 := by + rw [numChunks, Nat.ceilDiv_eq_add_pred_div] + decide + +private theorem pieceSize16 : pieceSize 16 2 = 2 := by + rw [pieceSize, sOf] + norm_num [Nat.ceilDiv_eq_add_pred_div] +private theorem queryCap16 : queryCap 16 16 = 64 := by norm_num [queryCap] +example : queryCap 17 17 = 70 := by norm_num [queryCap] +example : queryCap 0 16 = 0 := by norm_num [queryCap] +example : queryCap 16 0 = 0 := by norm_num [queryCap] + +-- Boundary indices belong to adjacent pieces; out-of-range pieces are empty. +example : (1 : Fin 16) ∈ piece 16 16 2 0 ∧ (2 : Fin 16) ∈ piece 16 16 2 1 := by + simp [piece, pieceSize16] +example : piece 16 16 2 8 = ∅ := by + ext c + simp only [piece, pieceSize16, Finset.mem_filter, Finset.mem_univ, true_and, + Finset.notMem_empty, iff_false] + omega +example : piece 0 16 2 0 = ∅ := by simp [piece] +example : numPieces 16 16 2 = 8 := by + rw [numPieces, pieceSize16, Nat.ceilDiv_eq_add_pred_div] + +private def zeroWeight : Fin 16 → Fin 16 → ℤ := fun _ _ => 0 +private def falseWeight : Fin 16 → Fin 16 → ℤ := fun _ _ => 6 + +-- The prime window admits p = 3 at the smallest supported D. +private theorem prime3 : 3 ∈ primesInRange 16 := by + rw [mem_primesInRange_iff_sq] + decide + +-- All-zero matrices supply an admitted, nonempty splitting with the promised bounds. +example : (totalChunks zeroWeight 16 3 : ℝ) ≤ 3 + Real.sqrt 16 := + (totalChunks_le zeroWeight (by decide) (by decide) prime3).1 +example : ((instanceIndices zeroWeight 16 2 3).card : ℝ) ≤ 4 * 16 * 2 := + card_instanceIndices_le zeroWeight (by decide) (by decide) (by decide) prime3 +example : Fintype.card (lopInstance zeroWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).M ≤ 16 := + card_middle_lopInstance zeroWeight zeroWeight zeroWeight (by decide) (by decide) prime3 _ + +-- Residues include negative weights, and are independent of exact zero sums. +example : (0, 0) ∈ residueClass (fun _ _ : Fin 1 => -1) 3 2 := by decide +example : IsFalsePositive falseWeight zeroWeight zeroWeight 3 (0, 0, 0) := by decide +example : ¬ HasZeroTriangle falseWeight zeroWeight zeroWeight := by + simp [HasZeroTriangle, falseWeight, zeroWeight] +example : HasZeroTriangle zeroWeight zeroWeight zeroWeight := ⟨0, 0, 0, rfl⟩ + +-- Recovery rejects a modular false positive; a true zero returns a witness on its first scan. +example : scanPiece falseWeight zeroWeight zeroWeight 16 2 0 0 0 = none := by + rw [scanPiece_eq_none_iff] + intro c _ + norm_num [weightSum, falseWeight, zeroWeight] +example : scanPiece zeroWeight zeroWeight zeroWeight 16 2 0 0 0 = some 0 := by + simp [scanPiece, piece, pieceSize16, weightSum, zeroWeight, List.finRange_succ] +example : runScans falseWeight zeroWeight zeroWeight 16 2 + [(((0 : Fin 3), 0, 0), (0, 0))] = (none, 1) := by + have hscan : scanPiece falseWeight zeroWeight zeroWeight 16 2 0 0 0 = none := by + rw [scanPiece_eq_none_iff] + intro c _ + norm_num [weightSum, falseWeight, zeroWeight] + simp [runScans, hscan] +example : runScans zeroWeight zeroWeight zeroWeight 16 2 + [(((0 : Fin 3), 0, 0), (0, 0)), ((0, 0, 1), (1, 1))] = (some (0, 0, 0), 1) := by + simp [runScans, scanPiece, piece, pieceSize16, weightSum, zeroWeight, List.finRange_succ] + +-- The query at rank 63 ends chunk 0; rank 64 starts chunk 1. +example : (3, 15) ∈ chunkOf zeroWeight 16 3 0 0 := by + have hclass : residueClass zeroWeight 3 0 = Finset.univ := by + ext q + simp [residueClass, zeroWeight] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] +example : (4, 0) ∈ chunkOf zeroWeight 16 3 0 1 := by + have hclass : residueClass zeroWeight 3 0 = Finset.univ := by + ext q + simp [residueClass, zeroWeight] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] + +private theorem falseQuery : (0, 0) ∈ + (lopInstance falseWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).W := by + change (0, 0) ∈ chunkOf falseWeight 16 3 0 0 + have hclass : residueClass falseWeight 3 0 = Finset.univ := by + ext q + norm_num [residueClass, falseWeight, Int.ModEq] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] + +-- A modular query really is accepted despite the absence of any exact zero triangle. +example : + (lopInstance falseWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).HasCommonNeighbor 0 0 := by + rw [hasCommonNeighbor_lopInstance_iff falseWeight zeroWeight zeroWeight (by decide) falseQuery] + refine ⟨0, ?_, ?_⟩ + · simp [piece, pieceSize16] + · decide + +-- Supply the actual finite modular answers, rather than an abstract unconstrained answer premise. +private def modularAnswers (wAB wBC wAC : Fin 16 → Fin 16 → ℤ) + (ι : InstanceIndex 3) (q : Fin 16 × Fin 16) : Bool := + decide (∃ c ∈ piece 16 16 2 ι.2.2, weightSum wAB wBC wAC q.1 q.2 c ≡ 0 [ZMOD (3 : ℤ)]) + +private theorem modularAnswers_correct (wAB wBC wAC : Fin 16 → Fin 16 → ℤ) : + AnswersCorrect wAB wBC wAC 16 2 3 (modularAnswers wAB wBC wAC) := by + intro ι _ q hq + apply propext + simp only [modularAnswers, decide_eq_true_eq] + exact (hasCommonNeighbor_lopInstance_iff wAB wBC wAC (by decide) hq).symm + +private theorem zeroSelected : IsSelectedPrime zeroWeight zeroWeight zeroWeight 16 3 := by + refine ⟨prime3, fun q _ => ?_⟩ + simp [countZeroMod, weightSum, zeroWeight] + +-- The complete recovery theorem is inhabited at its smallest admitted n and D. +example : ∃ L, IsScanOrder zeroWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers zeroWeight zeroWeight zeroWeight) L ∧ + (runScans zeroWeight zeroWeight zeroWeight 16 2 L).1 ≠ none ∧ + (runScans zeroWeight zeroWeight zeroWeight 16 2 L).2 ≤ 1 := by + obtain ⟨L, hL⟩ := exists_isScanOrder zeroWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers zeroWeight zeroWeight zeroWeight) + have hc := modularAnswers_correct zeroWeight zeroWeight zeroWeight + obtain ⟨hout, hcount⟩ := recovery_correct (by decide) (by decide) (by decide) + zeroWeight zeroWeight zeroWeight zeroSelected hc hL + refine ⟨L, hL, fun hn => hout.2 hn ⟨0, 0, 0, rfl⟩, ?_⟩ + simpa [falsePositiveCount, IsFalsePositive, weightSum, zeroWeight] using hcount + +private theorem falseSelected : IsSelectedPrime falseWeight zeroWeight zeroWeight 16 3 := by + refine ⟨prime3, fun q hq => ?_⟩ + obtain ⟨hprime, -, hsq⟩ := mem_primesInRange_iff_sq.mp hq + have hq : q = 2 ∨ q = 3 := by + have := hprime.two_le + have : q < 4 := by nlinarith + omega + rcases hq with rfl | rfl <;> + norm_num [countZeroMod, weightSum, falseWeight, zeroWeight, Int.ModEq] + +example : ∃ L, IsScanOrder falseWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers falseWeight zeroWeight zeroWeight) L ∧ + (runScans falseWeight zeroWeight zeroWeight 16 2 L).1 = none := by + obtain ⟨L, hL⟩ := exists_isScanOrder falseWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers falseWeight zeroWeight zeroWeight) + refine ⟨L, hL, (recovery_eq_none_iff (by decide) (by decide) (by decide) + falseWeight zeroWeight zeroWeight falseSelected + (modularAnswers_correct falseWeight zeroWeight zeroWeight) hL).mpr ?_⟩ + simp [HasZeroTriangle, falseWeight, zeroWeight] diff --git a/NOTICE b/NOTICE index 95b9c8d57a..e48c2ecb6c 100644 --- a/NOTICE +++ b/NOTICE @@ -1,9 +1,11 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing}.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Foundations/Data/Finset/Average.lean, -Complexity/Foundations/Data/Matrix/IntegerBits.lean and +Complexity/Foundations/Data/Finset/{Average,Quotient}.lean, +Complexity/Foundations/Data/Int/Residue.lean, +Complexity/Foundations/Data/Nat/CeilDiv.lean, +Complexity/Foundations/Data/Matrix/{IntegerBits,PairChunks}.lean and Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow}.lean and Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision From 2cae26c953e11a21b980198ab983f6c52a5a5062 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:21:36 -0400 Subject: [PATCH 07/32] doc(threeSum): record finite splitting and recovery milestone Address the nonblocking review finding on #1455: finite residue/chunk/piece splitting and witness recovery are implemented. Preserve native preprocessing, fast solvers, full input/time/width composition and exponent 1.9992 as open work under #1394. Documentation only. Lean and test files are byte-identical to independently reviewed e41e1dda62a10c8f89917e2ccab54fbbedad1ad4, whose six strict gates pass. Checked the diff, milestone reference, implementation paths, hygiene and changed-file lints. Abstracts and rendering are unaffected. --- ROADMAP.md | 10 ++++++---- UPSTREAM.md | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d31b9ede12..044ec8d475 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -423,10 +423,11 @@ each statement against the numbered result. Organize modules by subject, not cha matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting - and the selected-prime false-positive bound ([#1442]) are proved mathematically; it still needs - residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, native - Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ - asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width + and the selected-prime false-positive bound ([#1442]), finite residue/chunk/piece splitting and + witness recovery ([#1447]) are proved mathematically. It still needs tensor/sparse-query programs, + native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full + input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; + size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded almost-surely terminating structured loops; random-tape equivalence and BPP/RP/ZPP simulation bridges for the randomized word RAM ([#1124]); transfer of cell-probe lower bounds; and the @@ -552,4 +553,5 @@ not planned, not as proved. [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 [#1438]: https://github.com/cslib-community/complexitylib/issues/1438 [#1442]: https://github.com/cslib-community/complexitylib/issues/1442 +[#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index 8b49547dae..abb96bb7ad 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -189,8 +189,12 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he all-window prime-count bound lives in `Foundations/NumberTheory/PrimeWindow.lean`, retaining the source credit to Mathlib, Patrick Stevens and Bolton Bailey. These are mathematical statements, not native instructions or an assumed solver. See - [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, - witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. + [#1442](https://github.com/cslib-community/complexitylib/issues/1442). + `Algorithms/ExactTriangle/{Splitting,Witnesses}.lean` adapts the complete finite splitting and + witness recovery component ([#1447](https://github.com/cslib-community/complexitylib/issues/1447)), + using the existing matrices, lopsided graph and `Set.EqOn` answer API. Computable integer parameters + retain the source's exact real floor/ceiling specifications through checked bridges. Tensor/sparse + query programs, native preprocessing/solvers and full input/time/width composition remain in #1394. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. From dc4206a314c9623894f6db04178ea0ecbb019bdf Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:23:13 -0400 Subject: [PATCH 08/32] refactor(exactTriangle): reuse the natural residue bound and clarify source prose --- Complexity/Algorithms/ExactTriangle/Hashing.lean | 3 ++- Complexity/Algorithms/ExactTriangle/Splitting.lean | 8 +++++--- Complexity/Algorithms/ExactTriangle/Witnesses.lean | 8 ++++++-- Complexity/Foundations/Data/Int/Residue.lean | 10 ++++------ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 86aba7824b..17ee536865 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -226,7 +226,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : rw [Fintype.sum_prod_type] refine Finset.sum_congr rfl fun b _ => ?_ have hcast := Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) - rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + have hr : ((-wAB a b) % (p : ℤ)).toNat < p := Int.natMod_lt (m := -wAB a b) hp + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ hr, Finset.card_filter] push_cast refine Finset.sum_congr rfl fun c _ => ?_ diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean index 5015daf5e1..4f6f435c3a 100644 --- a/Complexity/Algorithms/ExactTriangle/Splitting.lean +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -40,7 +40,8 @@ The reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Instances.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Instances.lean), with only required definitions from `PaperStatements.lean`, [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and [Parameters.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Parameters.lean), at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -Source-derived prose below is paraphrased; quotations used for review are in the PR record. +Quoted docstring fragments reproduce comments in the pinned formal source; other prose describes +the local Lean API. -/ @[expose] public section @@ -215,11 +216,12 @@ theorem numPieces_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) : /-- The residue of the integer `x` modulo `p`, as an element of `Fin p`. -/ def resFin (hp : p ≠ 0) (x : ℤ) : Fin p := - ⟨(x % (p : ℤ)).toNat, Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) x⟩ + ⟨x.natMod p, Int.natMod_lt hp⟩ /-- The residue of `x` is congruent to `x`. -/ theorem resFin_modEq (hp : p ≠ 0) (x : ℤ) : ((resFin hp x : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] := by - rw [resFin, Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] + change ((x % (p : ℤ)).toNat : ℤ) ≡ x [ZMOD (p : ℤ)] + rw [Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] exact Int.mod_modEq x p /-- An element of `Fin p` is congruent to `x` modulo `p` exactly if it is the residue of `x`. -/ diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean index 0b5e39cb90..dfaeebb6aa 100644 --- a/Complexity/Algorithms/ExactTriangle/Witnesses.lean +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -33,7 +33,8 @@ contain distinct modular false positives. No solver, native program or time orac Adapted from [Witnesses.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Witnesses.lean) and the minimal scan definitions in `PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -Context: [AlmanWilliams2026, Section 3]. Source-derived prose is paraphrased. +Context: [AlmanWilliams2026, Section 3]. Quoted docstring fragments reproduce comments in the pinned +formal source; other prose describes the local Lean API. -/ @[expose] public section @@ -272,7 +273,10 @@ theorem pieceSize_le_two_mul_sqrt_div (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.s ((one_le_div hg0).mpr hg) _ = 2 * Real.sqrt D / (g : ℝ) := by ring -/-- Proof of Theorem 17: "the scans cost O((F(p) + 1)√D/g) = O(ν n³ log n/g)". -/ +/-- For `16 ≤ D ≤ n`, `1 ≤ g ≤ sqrt D`, `κ ≥ 1` and edge weights bounded by `n^κ`, bound +`(falsePositiveCount + 1) * pieceSize` at any selected prime by +`(2 * Hashing.falsePositiveConst + 2) * κ * n^3 * log n / g`. +This arithmetic estimate counts candidate vertices rather than native instructions. -/ theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : IsSelectedPrime wAB wBC wAC D p) : diff --git a/Complexity/Foundations/Data/Int/Residue.lean b/Complexity/Foundations/Data/Int/Residue.lean index f6622c6a71..eee6215f92 100644 --- a/Complexity/Foundations/Data/Int/Residue.lean +++ b/Complexity/Foundations/Data/Int/Residue.lean @@ -16,7 +16,8 @@ public import Mathlib.Algebra.Order.Ring.Int ## Main results * `Int.natCast_toNat_emod`: the nonnegative integer residue survives a round trip through `Nat`. -* `Int.toNat_emod_lt`: the natural residue is below a positive modulus. + +Bounds on natural residues use Mathlib's existing `Int.natMod_lt`. ## References @@ -29,13 +30,10 @@ The existing Exact Triangle hashing client now uses these shared facts. namespace Int +/-- Casting the natural Euclidean residue back to `Int` preserves its value for a positive +modulus. -/ theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) -theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by - have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) - have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) - omega - end Int From 2434f4716afc9c69ce742e87bec05d60a91078df Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:49:36 -0400 Subject: [PATCH 09/32] feat(matrix): prove the tensor recursion and pruned evaluation core --- Complexity.lean | 4 + .../Schonhage/Alphabets.lean | 269 +++++++ .../MatrixMultiplication/Schonhage/Full.lean | 374 +++++++++ .../Schonhage/Identity.lean | 201 +++++ .../Schonhage/Pruned.lean | 751 ++++++++++++++++++ ComplexityTests/Schonhage.lean | 100 +++ NOTICE | 3 +- ROADMAP.md | 7 +- UPSTREAM.md | 11 +- 9 files changed, 1715 insertions(+), 5 deletions(-) create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean create mode 100644 ComplexityTests/Schonhage.lean diff --git a/Complexity.lean b/Complexity.lean index 0f88b5283d..58c9b1d11a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -29,6 +29,10 @@ public import Complexity.Algorithms.Lean.VanEmdeBoas.Elements public import Complexity.Algorithms.Lean.VanEmdeBoas.Insert public import Complexity.Algorithms.Lean.VanEmdeBoas.Layout public import Complexity.Algorithms.Lean.VanEmdeBoas.Successor +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned public import Complexity.Algorithms.Reachability.BreadthFirst public import Complexity.Algorithms.Reachability.InductiveCounting public import Complexity.Algorithms.Reachability.InductiveCounting.Bounds diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean new file mode 100644 index 0000000000..d9d6a76f9a --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean @@ -0,0 +1,269 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.LinearAlgebra.Matrix.Notation +public import Mathlib.Tactic.DeriveFintype + +/-! +# The alphabets and coefficient forms of Schönhage's tensor + +Seven left variables, seven right variables, ten output variables and ten multiplication +terms are represented explicitly. Indices start at zero. Linear forms are coefficient +functions over integers; no arithmetic operation is treated as a native machine instruction. + +## Main definitions + +* `LeftVar`, `RightVar`, `OutVar`, `Term`: the four finite alphabets. +* `phi`, `psi`, `chi`: the left, right and output coefficient forms. +* `Term.Contributes`: occurrence in an output form. + +## Main results + +* `card_leftVar`, `card_rightVar`, `card_term`: alphabet sizes. +* `Term.contributes_iff`, `chi_eq_ite`: output incidence and its coefficient. + +## References + +Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine +semantics or running-time statement is imported. Source-derived prose is paraphrased below. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.2: We call x₁, x₂, x₃, p₁₁, p₁₂, p₂₁, p₂₂ the seven left variables. +-/ +inductive LeftVar : Type + | x (i : Fin 3) + | p (i j : Fin 2) + deriving DecidableEq, Fintype + +/-- Section 2.2: and y₁, y₂, y₃, q₁₁, q₁₂, q₂₁, q₂₂ the seven right variables. +-/ +inductive RightVar : Type + | y (j : Fin 3) + | q (i j : Fin 2) + deriving DecidableEq, Fintype + +/-- Section 2.2: We will also use ten output variables z_ij (for 1 ≤ i, j ≤ 3) and z₀. +-/ +inductive OutVar : Type + | z (i j : Fin 3) + | z0 + deriving DecidableEq, Fintype + +/-- Section 2.2: Schönhage's identity has ten terms, which we name P_ij (for 1 ≤ i, j ≤ 3, +corresponding to the entries of p̂ and q̂) and P₀. +-/ +inductive Term : Type + | P (i j : Fin 3) + | P0 + deriving DecidableEq, Fintype + +/-- The linear form consisting of the single left variable `x_i`. +-/ +def xForm (i : Fin 3) : LeftVar → ℤ := Pi.single (LeftVar.x i) 1 +/-- The linear form consisting of the single left variable `p_ij`. +-/ +def pForm (i j : Fin 2) : LeftVar → ℤ := Pi.single (LeftVar.p i j) 1 +/-- The linear form consisting of the single right variable `y_j`. +-/ +def yForm (j : Fin 3) : RightVar → ℤ := Pi.single (RightVar.y j) 1 +/-- The linear form consisting of the single right variable `q_ij`. +-/ +def qForm (i j : Fin 2) : RightVar → ℤ := Pi.single (RightVar.q i j) 1 +/-- The linear form consisting of the single output variable `z_ij`. +-/ +def zForm (i j : Fin 3) : OutVar → ℤ := Pi.single (OutVar.z i j) 1 +/-- The linear form consisting of the single output variable `z₀`. +-/ +def z0Form : OutVar → ℤ := Pi.single OutVar.z0 1 + +/-- Section 2.2: the 3 × 3 matrix `p̂` of linear forms, p̂ = (p₁₁, p₁₂, 0; p₂₁, p₂₂, 0; -p₁₁-p₂₁, +-p₁₂-p₂₂, 0). +-/ +def pHat : Matrix (Fin 3) (Fin 3) (LeftVar → ℤ) := + !![pForm 0 0, pForm 0 1, 0; + pForm 1 0, pForm 1 1, 0; + -pForm 0 0 - pForm 1 0, -pForm 0 1 - pForm 1 1, 0] + +/-- Section 2.2: the 3 × 3 matrix `q̂` of linear forms, q̂ = (q₁₁, q₁₂, -q₁₁-q₁₂; q₂₁, q₂₂, +-q₂₁-q₂₂; 0, 0, 0). +-/ +def qHat : Matrix (Fin 3) (Fin 3) (RightVar → ℤ) := + !![qForm 0 0, qForm 0 1, -qForm 0 0 - qForm 0 1; + qForm 1 0, qForm 1 1, -qForm 1 0 - qForm 1 1; + 0, 0, 0] + +/-- Section 2.2: the linear form `φ_λ` in the left variables, φ_{P_ij} = x_i + p̂_ij and φ_{P₀} = +-(x₁ + x₂ + x₃). Section 2.3.1: let φ_λ(s) ∈ {0, ±1} denote the coefficient of s in φ_λ; this is +`phi lam s`. +-/ +def phi : Term → LeftVar → ℤ + | .P i j => xForm i + pHat i j + | .P0 => -(xForm 0 + xForm 1 + xForm 2) + +/-- Section 2.2: the linear form `ψ_λ` in the right variables, ψ_{P_ij} = y_j + q̂_ij and ψ_{P₀} = +y₁ + y₂ + y₃. Section 2.3.1: `ψ_λ(t)`, the coefficient of `t` in `ψ_λ`, is `psi lam t`. +-/ +def psi : Term → RightVar → ℤ + | .P i j => yForm j + qHat i j + | .P0 => yForm 0 + yForm 1 + yForm 2 + +/-- Section 2.2: the linear form `χ_λ` in the output variables, χ_{P_ij} = z_ij + z₀ and χ_{P₀} = +z₀. +-/ +def chi : Term → OutVar → ℤ + | .P i j => zForm i j + z0Form + | .P0 => z0Form + +/-- Section 2.2: the output variable `z₀` is inner, the `z_ij` are outer. +-/ +def OutVar.IsInner : OutVar → Prop + | .z _ _ => False + | .z0 => True + +instance OutVar.instDecidablePredIsInner : DecidablePred OutVar.IsInner + | .z _ _ => isFalse fun h => h + | .z0 => isTrue trivial + +/-- Section 2.2: We say that a term λ contributes to an output variable z if z appears in χ_λ. +-/ +def Term.Contributes (lam : Term) (z : OutVar) : Prop := chi lam z ≠ 0 + +instance Term.instDecidableContributes (lam : Term) (z : OutVar) : Decidable (lam.Contributes z) := + inferInstanceAs (Decidable (chi lam z ≠ 0)) + +/-- The term that the private leaf has at a level where the output string has the variable `z`: +`P₀` for `z₀` and `P_ij` for `z_ij`. +-/ +def OutVar.privateTerm : OutVar → Term + | .z i j => .P i j + | .z0 => .P0 + +/-- Section 2.3.1: A left string of length L is a string u = u₁ u₂ ⋯ u_L of L left variables, and +we call u_ℓ its variable at level ℓ. +-/ +abbrev LeftStr (L : ℕ) : Type := Fin L → LeftVar +/-- Section 2.3.1: right strings of length `L`. +-/ +abbrev RightStr (L : ℕ) : Type := Fin L → RightVar +/-- Section 2.3.1: output strings of length `L`. +-/ +abbrev OutStr (L : ℕ) : Type := Fin L → OutVar +/-- Section 2.3.1: We refer to the call reached by choosing the terms τ₁, …, τ_k at levels 1, …, k +as the vertex τ₁ ⋯ τ_k, a string of k terms. It is at depth k. +-/ +abbrev Vertex (k : ℕ) : Type := Fin k → Term +/-- Section 2.3.1: the leaves, the vertices τ = τ₁ ⋯ τ_L at depth L. +-/ +abbrev Leaf (L : ℕ) : Type := Vertex L + +/-! ### The alphabets -/ + +/-- The left variables, listed: `x i` is `.inl i`, and `p i j` is `.inr (i, j)`. +-/ +def LeftVar.equiv : LeftVar ≃ Fin 3 ⊕ Fin 2 × Fin 2 where + toFun + | .x i => .inl i + | .p i j => .inr (i, j) + invFun + | .inl i => .x i + | .inr (i, j) => .p i j + left_inv s := by cases s <;> rfl + right_inv s := by rcases s with _ | ⟨_, _⟩ <;> rfl + +/-- The right variables, listed: `y j` is `.inl j`, and `q i j` is `.inr (i, j)`. +-/ +def RightVar.equiv : RightVar ≃ Fin 3 ⊕ Fin 2 × Fin 2 where + toFun + | .y j => .inl j + | .q i j => .inr (i, j) + invFun + | .inl j => .y j + | .inr (i, j) => .q i j + left_inv t := by cases t <;> rfl + right_inv t := by rcases t with _ | ⟨_, _⟩ <;> rfl + +/-- The terms, listed: `P i j` is `some (i, j)`, and `P0` is `none`. +-/ +def Term.equiv : Term ≃ Option (Fin 3 × Fin 3) where + toFun + | .P i j => some (i, j) + | .P0 => none + invFun + | some (i, j) => .P i j + | none => .P0 + left_inv t := by cases t <;> rfl + right_inv t := by rcases t with _ | ⟨_, _⟩ <;> rfl + +/-- Section 2.2: there are seven left variables. +-/ +theorem card_leftVar : Fintype.card LeftVar = 7 := by + simp [Fintype.card_congr LeftVar.equiv] + +/-- Section 2.2: there are seven right variables. +-/ +theorem card_rightVar : Fintype.card RightVar = 7 := by + simp [Fintype.card_congr RightVar.equiv] + +/-- Section 2.2: Schönhage's identity has ten terms. +-/ +theorem card_term : Fintype.card Term = 10 := by + simp [Fintype.card_congr Term.equiv] + +/-! ### Which terms contribute to which output variables -/ + +/-- Section 2.2, second observation: every term contributes to z₀. +-/ +@[simp] +theorem Term.contributes_z0 (lam : Term) : lam.Contributes .z0 := by + cases lam <;> simp [Term.Contributes, chi, zForm, z0Form] + +/-- Section 2.2, second observation: only P_ij contributes to z_ij. +-/ +@[simp] +theorem Term.contributes_z_iff (lam : Term) (i j : Fin 3) : + lam.Contributes (.z i j) ↔ lam = .P i j := by + cases lam <;> simp [Term.Contributes, chi, zForm, z0Form, Pi.single_apply, eq_comm] + +/-- The second observation in one statement: a term contributes to `z` if and only if `z` is +inner, that is `z₀`, or the term is `z.privateTerm`, which is `P_ij` for `z = z_ij`. (This is the +term that the private leaf of Section 2.4.3 has at a level with the variable `z`.) +-/ +theorem Term.contributes_iff (lam : Term) (z : OutVar) : + lam.Contributes z ↔ z.IsInner ∨ lam = z.privateTerm := by + decide +revert + +/-- A sum over the terms contributing to `z` has the one summand `P_ij` for `z = z_ij`, and runs +over all terms for `z = z₀`. This is how step (4) of the recursions assembles the slice at `z`. +-/ +theorem Term.sum_contributes {A : Type*} [AddCommMonoid A] (c : Term → A) (z : OutVar) : + ∑ lam : Term with lam.Contributes z, c lam + = match z with + | .z i j => c (.P i j) + | .z0 => ∑ lam, c lam := by + cases z with + | z i j => simp [filter_eq'] + | z0 => simp + +/-- Section 2.2: the χ_λ forms are all sums of output variables (without any minus signs or other +coefficients). +-/ +theorem chi_eq_ite (lam : Term) (z : OutVar) : + chi lam z = if lam.Contributes z then 1 else 0 := by + decide +revert + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean new file mode 100644 index 0000000000..a1c8d82b68 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean @@ -0,0 +1,374 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets +public import Mathlib.Algebra.BigOperators.Fin +public import Mathlib.Data.Fintype.BigOperators +public import Mathlib.Data.Finset.Pi +import Mathlib.Tactic.Ring + +/-! +# Full tensor recursion and shared encodings + +The recursion works on mathematical arrays indexed by finite strings. Its multiplication +record identifies the two encoded integers at each leaf. The returned array equals the +bilinear tensor expansion. These recursive values and combinatorial counts carry no native +instruction-cost interpretation. + +## Main definitions + +* `Full.run`, `Full.result`, `Full.multipliedAt`: full evaluation and leaf operands. +* `encodeLeft`, `encodeRight`, `encodingL`, `encodingR`: shared leaf encodings. +* `bilinearProduct`, `coefficient`, `leaves`: the tensor expansion and contributing leaves. + +## Main results + +* `full_spec`: full evaluation returns the bilinear product and uses the stated encodings. +* `tensor_expansion`: expansion into products of the one-level tensor coefficients. +* `abs_phi_le_one`, `abs_psi_le_one`: bounded encoding coefficients. + +## References + +Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine +semantics or running-time statement is imported. Source-derived prose is paraphrased below. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.3.1: An array a on the left strings of length L assigns an integer a[u] to each u +that is a left string of length L. So such an array is a function `LeftStr L → ℤ`, and likewise +for right strings, output strings and leaves. + +the slice of a at a left variable s is the array a_s on the strings of length L - 1 given by +a_s[u'] := a[s u']; the same definition is used for every alphabet. +-/ +def sliceAt {α β : Type} {L : ℕ} (a : (Fin (L + 1) → α) → β) (s : α) : (Fin L → α) → β := + fun u' => a (Fin.cons s u') + +/-- Section 2.3.1, step (2) of `Full.result`: A_λ := ∑_s φ_λ(s) a_s. +-/ +def encodeStepL {L : ℕ} (lam : Term) (a : LeftStr (L + 1) → ℤ) : LeftStr L → ℤ := + fun u' => ∑ s, phi lam s * sliceAt a s u' + +/-- Section 2.3.1, step (2) of `Full.result`: B_λ := ∑_t ψ_λ(t) b_t. +-/ +def encodeStepR {L : ℕ} (lam : Term) (b : RightStr (L + 1) → ℤ) : RightStr L → ℤ := + fun v' => ∑ t, psi lam t * sliceAt b t v' + +/-- Section 2.3.1: The recursion Full.result(a, b), for arrays a, b on the left and right strings +of length L, returns an array on the output strings of length L: (1) If L = 0, return the number +ab. (2) For each term λ of Schönhage's identity, form A_λ := ∑_s φ_λ(s) a_s and B_λ := ∑_t ψ_λ(t) +b_t. (3) For each term λ, recursively compute C_λ := Full.result(A_λ, B_λ). (4) Return the array c +whose slices are c_{z_ij} := C_{P_ij} and c_{z₀} := ∑_λ C_λ. + +`Full.run` is this recursion, and it also keeps a record of the multiplications. Its first +component is the array that is returned. Its second component gives, for each leaf `τ`, the two +numbers that step (1) multiplies at `τ`: the leaf `λ τ'` of a call is the leaf `τ'` of its +recursive call for `λ`. + +An array on the strings of length 0 has one entry, at the empty string `Fin.elim0`; that entry is +the number. +-/ +def Full.run : (L : ℕ) → (LeftStr L → ℤ) → (RightStr L → ℤ) → (OutStr L → ℤ) × (Leaf L → ℤ × ℤ) + | 0, a, b => (fun _ => a Fin.elim0 * b Fin.elim0, fun _ => (a Fin.elim0, b Fin.elim0)) + | L + 1, a, b => + let R : Term → (OutStr L → ℤ) × (Leaf L → ℤ × ℤ) := fun lam => + Full.run L (encodeStepL lam a) (encodeStepR lam b) + (fun w => + match w 0 with + | .z i j => (R (.P i j)).1 (Fin.tail w) + | .z0 => ∑ lam, (R lam).1 (Fin.tail w), + fun τ => (R (τ 0)).2 (Fin.tail τ)) + +/-- The array that `Full.result(a, b)` returns. +-/ +def Full.result (L : ℕ) (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : OutStr L → ℤ := + (Full.run L a b).1 + +/-- The two numbers that `Full.result(a, b)` multiplies at the leaf `τ`, the left one first. +-/ +def Full.multipliedAt (L : ℕ) (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (τ : Leaf L) : ℤ × ℤ := + (Full.run L a b).2 τ + +/-! ### 2.3.2 Unraveling the recursion computation -/ + +/-- Section 2.3.2: Φ_τ(a) := ∑_u a[u] ∏_{ℓ=1}^{L} φ_{τ_ℓ}(u_ℓ), the sum over all left strings `u`. +-/ +def encodeLeft {L : ℕ} (τ : Leaf L) (a : LeftStr L → ℤ) : ℤ := ∑ u, a u * ∏ ℓ, phi (τ ℓ) (u ℓ) + +/-- Section 2.3.2: Ψ_τ(b) := ∑_v b[v] ∏_{ℓ=1}^{L} ψ_{τ_ℓ}(v_ℓ), the sum over all right strings +`v`. +-/ +def encodeRight {L : ℕ} (τ : Leaf L) (b : RightStr L → ℤ) : ℤ := ∑ v, b v * ∏ ℓ, psi (τ ℓ) (v ℓ) + +/-- Section 2.3.2: a vertex τ₁ ⋯ τ_k contributes to an output string w if τ_ℓ contributes to w_ℓ +at every level ℓ ≤ k, for a leaf, that is, for `k = L`: the leaf `τ` contributes to `w` if `τ_ℓ` +contributes to `w_ℓ` at every level `ℓ`. +-/ +def Leaf.Contributes {L : ℕ} (τ : Leaf L) (w : OutStr L) : Prop := + ∀ ℓ, (τ ℓ).Contributes (w ℓ) + +instance Leaf.instDecidableContributes {L : ℕ} (τ : Leaf L) (w : OutStr L) : + Decidable (Leaf.Contributes τ w) := + inferInstanceAs (Decidable (∀ ℓ, (τ ℓ).Contributes (w ℓ))) + +/-- Equation (2): bilinearProduct(a, b)[w] := ∑_{τ contributing to w} Φ_τ(a) Ψ_τ(b). +-/ +def bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : OutStr L → ℤ := + fun w => ∑ τ : Leaf L with Leaf.Contributes τ w, encodeLeft τ a * encodeRight τ b + +/-- Equation (3): γ(s, t, z) := ∑_{λ contributing to z} φ_λ(s) ψ_λ(t). +-/ +def coefficient (s : LeftVar) (t : RightVar) (z : OutVar) : ℤ := + ∑ lam : Term with lam.Contributes z, phi lam s * psi lam t + +/-- Section 2.4.1: The encoding of a is the array of the 10^L numbers Φ_τ(a), indexed by the +leaves τ. +-/ +def encodingL {L : ℕ} (a : LeftStr L → ℤ) : Leaf L → ℤ := fun τ => encodeLeft τ a + +/-- Section 2.4.1: The encoding of b, the array of the numbers Ψ_τ(b). +-/ +def encodingR {L : ℕ} (b : RightStr L → ℤ) : Leaf L → ℤ := fun τ => encodeRight τ b + +/-- Section 2.4.2: For a set U of output strings of length L, we write leaves(U) for the set of +leaves that contribute to some output string of U. +-/ +def leaves {L : ℕ} (U : Finset (OutStr L)) : Finset (Leaf L) := + univ.filter fun τ => ∃ w ∈ U, Leaf.Contributes τ w + +/-- Section 2.3.1: an array on the left strings of length `L` has 7^L entries in total. +-/ +theorem card_leftStr (L : ℕ) : Fintype.card (LeftStr L) = 7 ^ L := by + rw [Fintype.card_fun, card_leftVar, Fintype.card_fin] + +/-- Section 2.3.1: an array on the right strings of length `L` has `7^L` entries. +-/ +theorem card_rightStr (L : ℕ) : Fintype.card (RightStr L) = 7 ^ L := by + rw [Fintype.card_fun, card_rightVar, Fintype.card_fin] + +/-- Section 2.3.1: At depth k there are 10^k vertices; in particular there are `10^L` leaves. +-/ +theorem card_vertex (k : ℕ) : Fintype.card (Vertex k) = 10 ^ k := by + rw [Fintype.card_fun, card_term, Fintype.card_fin] + +/-- Section 2.3.1: φ_λ(s) ∈ {0, ±1}. +-/ +theorem abs_phi_le_one (lam : Term) (s : LeftVar) : |phi lam s| ≤ 1 := by + decide +revert + +/-- Section 2.3.1: `ψ_λ(t) ∈ {0, ±1}`. +-/ +theorem abs_psi_le_one (lam : Term) (t : RightVar) : |psi lam t| ≤ 1 := by + decide +revert + +/-- Section 2.3.1: for each term λ, at most three of the seven values φ_λ(s) are nonzero. +-/ +theorem card_support_phi_le (lam : Term) : (univ.filter fun s => phi lam s ≠ 0).card ≤ 3 := by + decide +revert + +/-- Section 2.3.1: and similarly for the seven values ψ_λ(t). +-/ +theorem card_support_psi_le (lam : Term) : (univ.filter fun t => psi lam t ≠ 0).card ≤ 3 := by + decide +revert + +/-! ### Which leaves contribute to an output string -/ + +/-- In the proof of Lemma 7: the leaves contributing to zw' are the λτ' with λ contributing to z +and τ' contributing to w'. Here `λ = τ 0`, `τ' = Fin.tail τ`, `z = w 0` and `w' = Fin.tail w`. +-/ +theorem Leaf.contributes_succ {L : ℕ} {τ : Leaf (L + 1)} {w : OutStr (L + 1)} : + Leaf.Contributes τ w + ↔ (τ 0).Contributes (w 0) ∧ Leaf.Contributes (Fin.tail τ) (Fin.tail w) := by + simp only [Leaf.Contributes, Fin.forall_fin_succ, Fin.tail] + +/-- A sum over the strings of length `L + 1` is a sum over the first variable `s` and the rest +`u'` of the string `s u'`. +-/ +private theorem sum_strings_succ {α : Type*} [Fintype α] {L : ℕ} (f : (Fin (L + 1) → α) → ℤ) : + ∑ u, f u = ∑ s, ∑ u', f (Fin.cons s u') := by + rw [← Fintype.sum_prod_type'] + exact (Fintype.sum_equiv (Fin.consEquiv fun _ => α) _ _ fun _ => rfl).symm + +/-- A sum over the leaves contributing to `w`, split by the first term of the leaf. +-/ +theorem Leaf.sum_contributes_succ {L : ℕ} (w : OutStr (L + 1)) (f : Leaf (L + 1) → ℤ) : + ∑ τ : Leaf (L + 1) with Leaf.Contributes τ w, f τ + = ∑ lam : Term with lam.Contributes (w 0), + ∑ τ' : Leaf L with Leaf.Contributes τ' (Fin.tail w), f (Fin.cons lam τ') := by + simp only [sum_filter, sum_strings_succ, Leaf.contributes_succ, Fin.cons_zero, + Fin.tail_cons] + refine sum_congr rfl fun lam _ => ?_ + by_cases h : lam.Contributes (w 0) <;> simp [h] + +/-- In the proof of Lemma 8: the leaves contributing to `w` are the leaves whose term at level ℓ +contributes to w_ℓ, independently for each ℓ. +-/ +theorem Leaf.filter_contributes_eq_piFinset {L : ℕ} (w : OutStr L) : + (univ.filter fun τ : Leaf L => Leaf.Contributes τ w) + = Fintype.piFinset fun ℓ => univ.filter fun lam : Term => lam.Contributes (w ℓ) := by + ext τ + simp only [mem_filter, mem_univ, true_and, Fintype.mem_piFinset] + rfl + +/-- Membership in `leaves(U)`. +-/ +@[simp] +theorem mem_leaves {L : ℕ} {U : Finset (OutStr L)} {τ : Leaf L} : + τ ∈ leaves U ↔ ∃ w ∈ U, Leaf.Contributes τ w := by + simp [leaves] + +/-! ### The two encodings at once -/ + +section encodeWith + +variable {α : Type} [Fintype α] (c : Term → α → ℤ) {L : ℕ} + +/-- The number `∑_u a[u] ∏_ℓ c_{τ_ℓ}(u_ℓ)`, for an array `a` on the strings over a finite alphabet +and the coefficients `c` of a family of linear forms, one for each term. By definition it is +`Φ_τ(a)` for `c = φ` and `Ψ_τ(b)` for `c = ψ`. +-/ +def encodeWith (τ : Leaf L) (a : (Fin L → α) → ℤ) : ℤ := ∑ u, a u * ∏ ℓ, c (τ ℓ) (u ℓ) + +/-- For `L = 0` the only string is empty, and so are the products over the levels. +-/ +theorem encodeWith_zero (τ : Leaf 0) (a : (Fin 0 → α) → ℤ) : encodeWith c τ a = a Fin.elim0 := by + simp [encodeWith, Subsingleton.elim (default : Fin 0 → α) Fin.elim0] + +/-- The number at the leaf `λτ'` for `a` is the number at the leaf `τ'` for `∑_s c_λ(s) a_s`. +-/ +theorem encodeWith_cons (lam : Term) (τ' : Leaf L) (a : (Fin (L + 1) → α) → ℤ) : + encodeWith c (Fin.cons lam τ' : Leaf (L + 1)) a + = encodeWith c τ' fun u' => ∑ s, c lam s * sliceAt a s u' := by + simp only [encodeWith, sum_strings_succ, Fin.prod_univ_succ, Fin.cons_zero, Fin.cons_succ, + sliceAt, sum_mul] + rw [sum_comm] + exact sum_congr rfl fun u' _ => sum_congr rfl fun s _ => by ring + +end encodeWith + +/-! ### Lemma 7 -/ + +/-- In the proof of Lemma 7: for every leaf λτ' we have Φ_{λτ'}(a) = ∑_s φ_λ(s) Φ_{τ'}(a_s) = +Φ_{τ'}(A_λ). +-/ +theorem encodeLeft_cons {L : ℕ} (lam : Term) (τ' : Leaf L) (a : LeftStr (L + 1) → ℤ) : + encodeLeft (Fin.cons lam τ' : Leaf (L + 1)) a = encodeLeft τ' (encodeStepL lam a) := + encodeWith_cons phi lam τ' a + +/-- In the proof of Lemma 7: and similarly Ψ_{λτ'}(b) = Ψ_{τ'}(B_λ). +-/ +theorem encodeRight_cons {L : ℕ} (lam : Term) (τ' : Leaf L) (b : RightStr (L + 1) → ℤ) : + encodeRight (Fin.cons lam τ' : Leaf (L + 1)) b = encodeRight τ' (encodeStepR lam b) := + encodeWith_cons psi lam τ' b + +/-- For L = 0, the only leaf is the empty string τ, and Φ_τ(a) = a […], as the products over the +levels are empty (proof of Lemma 7). +-/ +private theorem encodeLeft_zero (τ : Leaf 0) (a : LeftStr 0 → ℤ) : encodeLeft τ a = a Fin.elim0 := + encodeWith_zero phi τ a + +/-- and Ψ_τ(b) = b (proof of Lemma 7). +-/ +private theorem encodeRight_zero (τ : Leaf 0) (b : RightStr 0 → ℤ) : + encodeRight τ b = b Fin.elim0 := + encodeWith_zero psi τ b + +/-- **Lemma 7**, first half. At the leaf τ, Full.result(a, b) multiplies Φ_τ(a) by Ψ_τ(b). +-/ +theorem Full.multipliedAt_eq_encoding {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) + (τ : Leaf L) : + Full.multipliedAt L a b τ = (encodeLeft τ a, encodeRight τ b) := by + induction L with + | zero => + rw [encodeLeft_zero, encodeRight_zero] + rfl + | succ L ih => + -- The leaf `τ` is `λ τ'` with `λ = τ 0` and `τ' = Fin.tail τ`. By steps (2) and (3) it is the + -- leaf `τ'` of `Full.result(A_λ, B_λ)`. + have hstep : Full.multipliedAt (L + 1) a b τ + = Full.multipliedAt L (encodeStepL (τ 0) a) (encodeStepR (τ 0) b) (Fin.tail τ) := rfl + rw [hstep, ih, ← encodeLeft_cons, ← encodeRight_cons, Fin.cons_self_tail] + +/-- Step (4) of `Full.result`, as it is read in the proof of Lemma 7: `c[z w'] = ∑_{λ contributing +to z} C_λ[w']`. +-/ +theorem Full.result_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightStr (L + 1) → ℤ) + (w : OutStr (L + 1)) : + Full.result (L + 1) a b w = ∑ lam : Term with lam.Contributes (w 0), + Full.result L (encodeStepL lam a) (encodeStepR lam b) (Fin.tail w) := by + rw [Term.sum_contributes] + rfl + +/-- In the proof of Lemma 7: `bilinearProduct(a, b)[z w'] = ∑_{λ contributing to z} +bilinearProduct(A_λ, B_λ)[w']`. +-/ +theorem bilinearProduct_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightStr (L + 1) → ℤ) + (w : OutStr (L + 1)) : + bilinearProduct a b w = ∑ lam : Term with lam.Contributes (w 0), + bilinearProduct (encodeStepL lam a) (encodeStepR lam b) (Fin.tail w) := by + simp only [bilinearProduct, Leaf.sum_contributes_succ, encodeLeft_cons, encodeRight_cons] + +/-- **Lemma 7**, second half. and it returns bilinearProduct(a, b). +-/ +theorem Full.result_eq_bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : + Full.result L a b = bilinearProduct a b := by + induction L with + | zero => + funext w + simp [Full.result, Full.run, bilinearProduct, Leaf.Contributes, encodeLeft_zero, + encodeRight_zero] + | succ L ih => + funext w + simp only [Full.result_succ, bilinearProduct_succ, ih] + +/-- **Lemma 7**. At the leaf τ, Full.result(a, b) multiplies Φ_τ(a) by Ψ_τ(b), and it returns +bilinearProduct(a, b). +-/ +theorem full_spec {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : + (∀ τ : Leaf L, Full.multipliedAt L a b τ = (encodeLeft τ a, encodeRight τ b)) ∧ + Full.result L a b = bilinearProduct a b := + ⟨Full.multipliedAt_eq_encoding a b, Full.result_eq_bilinearProduct a b⟩ + +/-! ### Lemma 8 -/ + +/-- The display in the proof of Lemma 8: the sum, over the leaves `τ` contributing to `w`, of `∏_ℓ +φ_{τ_ℓ}(u_ℓ) ψ_{τ_ℓ}(v_ℓ)` is the product of sums ∏_ℓ ∑_{τ_ℓ contributing to w_ℓ} φ_{τ_ℓ}(u_ℓ) +ψ_{τ_ℓ}(v_ℓ) = ∏_ℓ γ(u_ℓ, v_ℓ, w_ℓ) by (3). +-/ +theorem prod_gamma_eq_sum_contributes {L : ℕ} (u : LeftStr L) (v : RightStr L) (w : OutStr L) : + ∏ ℓ, coefficient (u ℓ) (v ℓ) (w ℓ) + = ∑ τ : Leaf L with Leaf.Contributes τ w, (∏ ℓ, phi (τ ℓ) (u ℓ)) * ∏ ℓ, psi (τ ℓ) (v ℓ) := by + simp only [coefficient] + rw [prod_univ_sum, Leaf.filter_contributes_eq_piFinset] + exact sum_congr rfl fun τ _ => prod_mul_distrib + +/-- **Lemma 8**. For all arrays a, b and every output string w, bilinearProduct(a, b)[w] = +∑_{u,v} γ(u₁, v₁, w₁) γ(u₂, v₂, w₂) ⋯ γ(u_L, v_L, w_L) a[u] b[v], the sum over all left strings u +and right strings v. +-/ +theorem tensor_expansion {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (w : OutStr L) : + bilinearProduct a b w = ∑ u, ∑ v, (∏ ℓ, coefficient (u ℓ) (v ℓ) (w ℓ)) * a u * b v := by + -- "the coefficient of a[u] b[v] in (2) is ∑_τ ∏_ℓ φ_{τ_ℓ}(u_ℓ) ψ_{τ_ℓ}(v_ℓ), summed over the + -- leaves τ contributing to w": expand `Φ_τ(a) Ψ_τ(b)` and sum over `τ` last. + simp only [bilinearProduct, encodeLeft, encodeRight, sum_mul_sum] + rw [sum_comm] + refine sum_congr rfl fun u _ => ?_ + rw [sum_comm] + refine sum_congr rfl fun v _ => ?_ + rw [prod_gamma_eq_sum_contributes, sum_mul, sum_mul] + exact sum_congr rfl fun τ _ => by ring + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean new file mode 100644 index 0000000000..15b7942f44 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean @@ -0,0 +1,201 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets +public import Mathlib.Algebra.MvPolynomial.CommRing +import Mathlib.LinearAlgebra.Finsupp.LinearCombination +import Mathlib.Tactic.LinearCombination + +/-! +# Schönhage's ten-term polynomial identity + +The ten products of linear forms equal the wanted inner/outer product polynomial plus +an explicit error polynomial. This is an identity over integers in 24 formal variables. +The error term is retained; it cannot be discarded for unrestricted inputs. + +## Main definitions + +* `TriPoly`, `formL`, `formR`, `formO`: the polynomial interpretation of the forms. +* `G`, `E`: the wanted polynomial and the error polynomial. + +## Main results + +* `identity`: the complete ten-term polynomial identity, including its error. + +## References + +Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine +semantics or running-time statement is imported. Source-derived prose is paraphrased below. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- All 24 variables of the identity: the left, the right and the output variables. +-/ +abbrev Var : Type := LeftVar ⊕ RightVar ⊕ OutVar + +/-- The polynomials in which Schönhage's identity is an identity. +-/ +abbrev TriPoly : Type := MvPolynomial Var ℤ + +/-- A left variable, as a polynomial. +-/ +noncomputable def polyL (s : LeftVar) : TriPoly := MvPolynomial.X (Sum.inl s) +/-- A right variable, as a polynomial. +-/ +noncomputable def polyR (t : RightVar) : TriPoly := MvPolynomial.X (Sum.inr (Sum.inl t)) +/-- An output variable, as a polynomial. +-/ +noncomputable def polyO (z : OutVar) : TriPoly := MvPolynomial.X (Sum.inr (Sum.inr z)) + +/-- A linear form in the left variables, as a polynomial. +-/ +noncomputable def formL (f : LeftVar → ℤ) : TriPoly := ∑ s, MvPolynomial.C (f s) * polyL s +/-- A linear form in the right variables, as a polynomial. +-/ +noncomputable def formR (f : RightVar → ℤ) : TriPoly := ∑ t, MvPolynomial.C (f t) * polyR t +/-- A linear form in the output variables, as a polynomial. +-/ +noncomputable def formO (f : OutVar → ℤ) : TriPoly := ∑ z, MvPolynomial.C (f z) * polyO z + +/-- Section 2.2: G := ∑_{i,j=1}^{3} x_i y_j z_ij + (∑_{i,j=1}^{2} p_ij q_ij) z₀. +-/ +noncomputable def G : TriPoly := + (∑ i : Fin 3, ∑ j : Fin 3, polyL (.x i) * polyR (.y j) * polyO (.z i j)) + + (∑ i : Fin 2, ∑ j : Fin 2, polyL (.p i j) * polyR (.q i j)) * polyO .z0 + +/-- Lemma 6: E := ∑_{i,j=1}^{3} (x_i q̂_ij + p̂_ij y_j + p̂_ij q̂_ij) z_ij. +-/ +noncomputable def E : TriPoly := + ∑ i : Fin 3, ∑ j : Fin 3, + (polyL (.x i) * formR (qHat i j) + formL (pHat i j) * polyR (.y j) + + formL (pHat i j) * formR (qHat i j)) * polyO (.z i j) + +/-- A sum over the ten terms, split into the `P_ij` and `P₀`. +-/ +private theorem sum_term {A : Type*} [AddCommMonoid A] (f : Term → A) : + ∑ lam, f lam = ∑ i, ∑ j, f (.P i j) + f .P0 := by + rw [← Term.equiv.symm.sum_comp f, Fintype.sum_option, Fintype.sum_prod_type, add_comm] + rfl + +/-! ### Linear forms as polynomials -/ + +/-- `formL` is the linear map that sends the form consisting of the single variable `s` to the +polynomial `s`. +-/ +theorem formL_eq_linearCombination (f : LeftVar → ℤ) : + formL f = Fintype.linearCombination ℤ polyL f := by + simp only [formL, Fintype.linearCombination_apply, MvPolynomial.smul_eq_C_mul] + +/-- `formR` is the linear map that sends the form consisting of the single variable `t` to the +polynomial `t`. +-/ +theorem formR_eq_linearCombination (f : RightVar → ℤ) : + formR f = Fintype.linearCombination ℤ polyR f := by + simp only [formR, Fintype.linearCombination_apply, MvPolynomial.smul_eq_C_mul] + +/-- `formO` is the linear map that sends the form consisting of the single variable `z` to the +polynomial `z`. +-/ +theorem formO_eq_linearCombination (f : OutVar → ℤ) : + formO f = Fintype.linearCombination ℤ polyO f := by + simp only [formO, Fintype.linearCombination_apply, MvPolynomial.smul_eq_C_mul] + +/-- The form consisting of a single left variable is that variable. +-/ +theorem formL_single (s : LeftVar) : formL (Pi.single s 1) = polyL s := by + simp [formL_eq_linearCombination] + +/-- The form consisting of a single right variable is that variable. +-/ +theorem formR_single (t : RightVar) : formR (Pi.single t 1) = polyR t := by + simp [formR_eq_linearCombination] + +/-- The form consisting of a single output variable is that variable. +-/ +theorem formO_single (z : OutVar) : formO (Pi.single z 1) = polyO z := by + simp [formO_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `φ_{P_ij} = x_i + p̂_ij`. +-/ +theorem formL_phi_P (i j : Fin 3) : formL (phi (.P i j)) = polyL (.x i) + formL (pHat i j) := by + simp [phi, xForm, formL_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `ψ_{P_ij} = y_j + q̂_ij`. +-/ +theorem formR_psi_P (i j : Fin 3) : formR (psi (.P i j)) = polyR (.y j) + formR (qHat i j) := by + simp [psi, yForm, formR_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `χ_{P_ij} = z_ij + z₀`. +-/ +theorem formO_chi_P (i j : Fin 3) : formO (chi (.P i j)) = polyO (.z i j) + polyO .z0 := by + simp [chi, zForm, z0Form, formO_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `φ_{P₀} = -(x₁ + x₂ + x₃)`. +-/ +theorem formL_phi_P0 : formL (phi .P0) = -(polyL (.x 0) + polyL (.x 1) + polyL (.x 2)) := by + simp [phi, xForm, formL_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `ψ_{P₀} = y₁ + y₂ + y₃`. +-/ +theorem formR_psi_P0 : formR (psi .P0) = polyR (.y 0) + polyR (.y 1) + polyR (.y 2) := by + simp [psi, yForm, formR_eq_linearCombination] + +/-- Section 2.2, the display of the linear forms: `χ_{P₀} = z₀`. +-/ +theorem formO_chi_P0 : formO (chi .P0) = polyO .z0 := + formO_single .z0 + +/-! ### Lemma 6 -/ + +/-- Section 2.2: every column of p̂ […] sums to zero. +-/ +theorem pHat_column_sum (j : Fin 3) : ∑ i, pHat i j = 0 := by + fin_cases j <;> simp [Fin.sum_univ_three, pHat] + +/-- Section 2.2: every row of q̂ sums to zero. +-/ +theorem qHat_row_sum (i : Fin 3) : ∑ j, qHat i j = 0 := by + fin_cases i <;> simp [Fin.sum_univ_three, qHat] + +/-- Section 2.2: Notice that ∑_{i,j=1}^{3} p̂_ij q̂_ij = ∑_{i,j=1}^{2} p_ij q_ij is the desired +inner product. +-/ +theorem sum_pHat_mul_qHat : + ∑ i : Fin 3, ∑ j : Fin 3, formL (pHat i j) * formR (qHat i j) + = ∑ i : Fin 2, ∑ j : Fin 2, polyL (.p i j) * polyR (.q i j) := by + simp [Fin.sum_univ_three, Fin.sum_univ_two, pHat, qHat, pForm, qForm, + formL_eq_linearCombination, formR_eq_linearCombination] + +/-- **Lemma 6**. Summing over the ten terms, ∑_λ φ_λ ψ_λ χ_λ = G + E, where E := ∑_{i,j=1}^{3} +(x_i q̂_ij + p̂_ij y_j + p̂_ij q̂_ij) z_ij. An identity of polynomials with integer coefficients +in the 24 variables. +-/ +theorem identity : ∑ lam, formL (phi lam) * formR (psi lam) * formO (chi lam) = G + E := by + -- The column sums of p̂ and the row sums of q̂, as identities of polynomials. + have hcol : ∀ j, ∑ i, formL (pHat i j) = 0 := fun j => by + simp only [formL_eq_linearCombination, ← map_sum, pHat_column_sum, map_zero] + have hrow : ∀ i, ∑ j, formR (qHat i j) = 0 := fun i => by + simp only [formR_eq_linearCombination, ← map_sum, qHat_row_sum, map_zero] + simp only [Fin.sum_univ_three] at hcol hrow + -- Write out the ten terms, and write the inner product in G as ∑ p̂_ij q̂_ij. + simp only [sum_term, formL_phi_P, formR_psi_P, formO_chi_P, formL_phi_P0, formR_psi_P0, + formO_chi_P0, G, E, ← sum_pHat_mul_qHat, Fin.sum_univ_three] + -- The coefficients of the z_ij agree term by term. In the coefficient of z₀ the products x_i y_j + -- cancel, and the cross terms are ∑_i x_i ∑_j q̂_ij + ∑_j y_j ∑_i p̂_ij = 0. + linear_combination polyO .z0 * (polyL (.x 0) * hrow 0 + polyL (.x 1) * hrow 1 + + polyL (.x 2) * hrow 2 + polyR (.y 0) * hcol 0 + polyR (.y 1) * hcol 1 + polyR (.y 2) * hcol 2) + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean new file mode 100644 index 0000000000..30043faf44 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean @@ -0,0 +1,751 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +public import Mathlib.Data.Multiset.Bind +public import Mathlib.Data.Finset.Piecewise + +/-! +# Pruned tensor evaluation and the calls it makes + +The recursion skips children with no requested output strings, reading only precomputed +leaf encodings. Its values agree with the full recursion on the requested set and vanish +elsewhere. A call trace records finite recursive calls and passed sets; it is not a machine +execution or an instruction counter. + +The root is always called. At depth zero with an empty request set it is itself a visited +leaf, although the set of contributing leaves is empty. The leaf characterization therefore +requires positive depth or a nonempty request set. The value and total-set-size statements +hold without that qualification. + +## Main definitions + +* `Pruned.run`, `Pruned.result`, `Pruned.calls`: pruned evaluation and its call trace. +* `Pruned.Visits`, `Pruned.totalSize`: visited leaves and total size of passed sets. +* `computeEncoding`: recursive computation of either input encoding. + +## Main results + +* `Pruned.result_eq_piecewise_full`: agreement on requested strings and zero elsewhere. +* `Pruned.visits_iff_mem_leaves`: exact visited-leaf characterization with the root exception. +* `Pruned.totalSize_le`: passed-set total bounded by depth plus one times leaf count. +* `pruned_spec`: all three properties together for the two input encodings. + +## References + +Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine +semantics or running-time statement is imported. Source-derived prose is paraphrased below. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.4.2: for a set S of output strings and an output variable z, the slice S_z is the +set of output strings w' with z w' ∈ S. +-/ +def sliceSet {n : ℕ} (S : Finset (OutStr (n + 1))) (z : OutVar) : Finset (OutStr n) := + univ.filter fun w' => (Fin.cons z w' : OutStr (n + 1)) ∈ S + +/-- Section 2.4.2, step (2) of `Pruned.result`: let S_λ be the union of the slices S_z over the z +to which λ contributes. +-/ +def childSet {n : ℕ} (S : Finset (OutStr (n + 1))) (lam : Term) : Finset (OutStr n) := + (univ.filter fun z => lam.Contributes z).biUnion fun z => sliceSet S z + +/-- A call of `Pruned.result`. `vertex` is the vertex `τ₁⋯τ_k` at which it is made, as the pair +of its depth `k` and the string of terms. `passed` is the set that is passed to it, as the pair +of the length of its strings and the set. +-/ +structure PrunedCall where + /-- The call depth and the terms identifying its vertex. +-/ + vertex : Σ k, Vertex k + /-- The remaining string length and requested output strings. +-/ + passed : Σ n, Finset (OutStr n) + +/-- The same call, seen from one level higher: the vertex `τ₁⋯τ_k` below the child `λ` is the +vertex `λ τ₁⋯τ_k`. +-/ +def PrunedCall.under (lam : Term) (c : PrunedCall) : PrunedCall where + vertex := ⟨c.vertex.1 + 1, (Fin.cons lam c.vertex.2 : Vertex (c.vertex.1 + 1))⟩ + passed := c.passed + +/-- Section 2.4.2: Pruned_{τ₁⋯τ_k}(S), for a set S of output strings of length L - k, returns the +array returned by the vertex τ₁⋯τ_k of Full.result, restricted to the strings in S: (1) If k = L, +then look up Φ_τ(a) and Ψ_τ(b) from the precomputed encodings (Section 2.4.1) and return Φ_τ(a) +Ψ_τ(b) for the leaf τ = τ₁⋯τ_L. (2) For each term λ of Schönhage's identity, let S_λ be the union +of the slices S_z over the z to which λ contributes, i.e., S_{P_ij} := S_{z_ij} ∪ S_{z₀} and +S_{P₀} := S_{z₀}. (3) For each term λ of Schönhage's identity with S_λ ≠ ∅, compute C_λ := +Pruned_{τ₁⋯τ_k λ}(S_λ), an array on S_λ. (4) Return the array c on S whose slices are c_{z_ij} := +C_{P_ij} and c_{z₀} := ∑_λ C_λ, restricted to S_{z_ij} and to S_{z₀}. + +`Pruned.run` is this recursion, and it also keeps a record of the calls. Its first component is +the array that is returned. Its second component lists all the calls that are made, this one +included, each with the set passed to it. Vertices are named relative to the call we start from, +so for the call at the root they are the vertices of the paper. + +Here `n = L - k`. The vertex `τ₁⋯τ_k` enters only through the look-ups of step (1), so it is +passed as the parts of the two encodings that lie below it: `encA τ'` is the entry of the encoding +of `a` at the leaf `τ₁⋯τ_k τ'`, and likewise `encB`. At the root these are the two encodings +themselves, and the child `λ` receives their slices at `λ`. For a term `λ` with `S_λ = ∅` no call +is made: nothing is recorded, and `C_λ` is written as the zero array; step (4) does not read it, +because then `S_{z₀}`, and `S_{z_ij}` if `λ = P_ij`, are empty. In step (1) the number is +returned as an array on `S`, like every other result; `S` then consists of the empty string, +except when `Pruned.result` is called at the root with `L = 0` and `S = ∅`. +-/ +def Pruned.run : (n : ℕ) → (Leaf n → ℤ) → (Leaf n → ℤ) → Finset (OutStr n) → + (OutStr n → ℤ) × Multiset PrunedCall + | 0, encA, encB, S => + (S.piecewise (fun _ => encA Fin.elim0 * encB Fin.elim0) 0, {⟨⟨0, Fin.elim0⟩, ⟨0, S⟩⟩}) + | n + 1, encA, encB, S => + let R : Term → (OutStr n → ℤ) × Multiset PrunedCall := fun lam => + if (childSet S lam).Nonempty then + Pruned.run n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam) + else (0, 0) + (S.piecewise (fun w => + match w 0 with + | .z i j => (R (.P i j)).1 (Fin.tail w) + | .z0 => ∑ lam, (R lam).1 (Fin.tail w)) 0, + ⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ ::ₘ + univ.val.bind fun lam => ((R lam).2).map (PrunedCall.under lam)) + +/-- The array that `Pruned.result(S)` returns. +-/ +def Pruned.result (n : ℕ) (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : OutStr n → ℤ := + (Pruned.run n encA encB S).1 + +/-- All the calls that `Pruned.result(S)` makes, itself included, each with the set passed to it. +-/ +def Pruned.calls (n : ℕ) (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : Multiset PrunedCall := + (Pruned.run n encA encB S).2 + +/-- The leaves that `Pruned.result(U)`, called at the root, visits: the vertices at depth `L` +among its calls. +-/ +def Pruned.Visits {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (τ : Leaf L) : Prop := + ∃ c ∈ Pruned.calls L encA encB U, c.vertex = ⟨L, τ⟩ + +/-- The total size of the sets passed to the calls that `Pruned.result(U)` makes. +-/ +def Pruned.totalSize {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) : ℕ := + ((Pruned.calls L encA encB U).map fun c => c.passed.2.card).sum + +/-- Section 2.4.3: the leaves contributing to an output string with inner set Q are the 10^m +leaves that choose any term at each of the m levels of Q, but P_ij at every level outside Q where +the output string has z_ij. We call the one that chooses P₀ at every level of Q the private leaf +of the output string. +-/ +def privateLeaf {L : ℕ} (w : OutStr L) : Leaf L := fun ℓ => (w ℓ).privateTerm + +/-! ### Section 2.4.1: the encodings -/ + +/-- Section 2.4.1, the procedure that computes the encoding of `a`: we run Full.result with b left +out and without step (4): […] the left number at the leaf τ is Φ_τ(a), and the leaf stores it. +What is left of `Full.result` is step (2) for `a`, the recursive calls of step (3), and at depth +`L` the number that has been reached. The encoding of b […] is computed in the same way, so the +procedure is written once, for the coefficients `c` of any family of linear forms: `c = φ` for +`a`, and `c = ψ` for `b`. +-/ +def computeEncoding {α : Type} [Fintype α] (c : Term → α → ℤ) : + (L : ℕ) → ((Fin L → α) → ℤ) → Leaf L → ℤ + | 0, a, _ => a Fin.elim0 + | L + 1, a, τ => computeEncoding c L (fun u' => ∑ s, c (τ 0) s * sliceAt a s u') (Fin.tail τ) + +/-- At the leaf `τ` the procedure of Section 2.4.1 stores `∑_u a[u] ∏_ℓ c_{τ_ℓ}(u_ℓ)`. +-/ +theorem computeEncoding_eq_encodeWith {α : Type} [Fintype α] (c : Term → α → ℤ) {L : ℕ} + (a : (Fin L → α) → ℤ) (τ : Leaf L) : computeEncoding c L a τ = encodeWith c τ a := by + induction L with + | zero => rw [computeEncoding, encodeWith_zero] + | succ n ih => rw [computeEncoding, ih, ← encodeWith_cons, Fin.cons_self_tail] + +/-- The procedure of Section 2.4.1 computes the encoding of `a`. +-/ +theorem computeEncoding_phi {L : ℕ} (a : LeftStr L → ℤ) : computeEncoding phi L a = encodingL a := + funext (computeEncoding_eq_encodeWith phi a) + +/-- The procedure of Section 2.4.1 computes the encoding of `b`. +-/ +theorem computeEncoding_psi {L : ℕ} (b : RightStr L → ℤ) : computeEncoding psi L b = encodingR b := + funext (computeEncoding_eq_encodeWith psi b) + +/-! ### Section 2.4.2: the sets `S_λ` of step (2) -/ + +/-- Membership in a slice of a set of output strings. +-/ +@[simp] +theorem mem_sliceSet {n : ℕ} (S : Finset (OutStr (n + 1))) (z : OutVar) (w' : OutStr n) : + w' ∈ sliceSet S z ↔ (Fin.cons z w' : OutStr (n + 1)) ∈ S := by + simp [sliceSet] + +/-- Section 2.4.2, step (2): i.e., S_{P_ij} := S_{z_ij} ∪ S_{z₀}. +-/ +theorem childSet_P {n : ℕ} (S : Finset (OutStr (n + 1))) (i j : Fin 3) : + childSet S (.P i j) = sliceSet S (.z i j) ∪ sliceSet S .z0 := by + have hz : (univ.filter fun z => (Term.P i j).Contributes z) = {.z i j, .z0} := by + decide +revert + rw [childSet, hz, biUnion_insert, singleton_biUnion] + +/-- Section 2.4.2, step (2): and S_{P₀} := S_{z₀}. +-/ +theorem childSet_P0 {n : ℕ} (S : Finset (OutStr (n + 1))) : + childSet S .P0 = sliceSet S .z0 := by + have hz : (univ.filter fun z => Term.P0.Contributes z) = {.z0} := by decide + rw [childSet, hz, singleton_biUnion] + +/-- Section 2.4.2, step (4): These restrictions make sense, since S_{z_ij} ⊆ S_{P_ij} and S_{z₀} ⊆ +S_λ for every λ, by step (2): `S_z ⊆ S_λ` whenever `λ` contributes to `z`. +-/ +theorem sliceSet_subset_childSet {n : ℕ} (S : Finset (OutStr (n + 1))) {lam : Term} {z : OutVar} + (h : lam.Contributes z) : sliceSet S z ⊆ childSet S lam := + subset_biUnion_of_mem (fun z => sliceSet S z) (mem_filter.mpr ⟨mem_univ z, h⟩) + +/-- In the proof of Lemma 10: S_λ consists of the last L-k-1 variables of each string in S such +that λ contributes to the first variable of that string. +-/ +theorem mem_childSet {n : ℕ} {S : Finset (OutStr (n + 1))} {lam : Term} {w' : OutStr n} : + w' ∈ childSet S lam ↔ ∃ w ∈ S, lam.Contributes (w 0) ∧ Fin.tail w = w' := by + simp only [childSet, mem_biUnion, mem_filter, mem_univ, true_and, mem_sliceSet] + constructor + · rintro ⟨z, hz, hw⟩ + exact ⟨_, hw, by simpa using hz, by simp⟩ + · rintro ⟨w, hw, hz, rfl⟩ + exact ⟨w 0, hz, by rwa [Fin.cons_self_tail]⟩ + +/-! ### Lemma 10, first claim: the values -/ + +/-- The array `C_λ` of step (3) of `Pruned.result`; it is the zero array if `S_λ = ∅`. +-/ +def Pruned.childResult {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) + (lam : Term) : OutStr n → ℤ := + if (childSet S lam).Nonempty then + Pruned.result n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam) + else 0 + +/-- Steps (2) to (4) of `Pruned.result`, written out. +-/ +theorem Pruned.result_succ {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) : + Pruned.result (n + 1) encA encB S = S.piecewise (fun w => + match w 0 with + | .z i j => Pruned.childResult encA encB S (.P i j) (Fin.tail w) + | .z0 => ∑ lam, Pruned.childResult encA encB S lam (Fin.tail w)) 0 := by + simp only [Pruned.result, Pruned.run, Pruned.childResult, apply_ite Prod.fst] + +/-- Step (4) of `Pruned.result` at one string: the entry at `w ∈ S` is the sum of the `C_λ`, at +the rest of `w`, over the terms `λ` that contribute to the first variable of `w`. +-/ +theorem Pruned.result_succ_apply {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) : + Pruned.result (n + 1) encA encB S w = if w ∈ S then + ∑ lam : Term with lam.Contributes (w 0), Pruned.childResult encA encB S lam (Fin.tail w) + else 0 := by + rw [Pruned.result_succ, Finset.piecewise, Term.sum_contributes] + rfl + +/-- What `Pruned.result` returns, for arbitrary arrays in place of the two encodings: at each +string of `S`, the sum, over the leaves contributing to it, of the product of the two numbers +looked up at the leaf in step (1). +-/ +theorem Pruned.result_eq_piecewise_sum {n : ℕ} (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : + Pruned.result n encA encB S + = S.piecewise (fun w => ∑ τ : Leaf n with Leaf.Contributes τ w, encA τ * encB τ) 0 := by + induction n with + | zero => + -- The only leaf is the empty string, and it contributes to the empty output string. + have hleaf (w : OutStr 0) : + (univ.filter fun τ : Leaf 0 => Leaf.Contributes τ w) = {Fin.elim0} := + eq_singleton_iff_unique_mem.mpr + ⟨mem_filter.mpr ⟨mem_univ _, fun ℓ => ℓ.elim0⟩, fun _ _ => Subsingleton.elim _ _⟩ + simp only [Pruned.result, Pruned.run, hleaf, sum_singleton] + | succ n ih => + -- The induction hypothesis describes each `C_λ`, also when `S_λ = ∅`. + have hC (lam : Term) : Pruned.childResult encA encB S lam + = (childSet S lam).piecewise (fun w' => + ∑ τ' : Leaf n with Leaf.Contributes τ' w', + encA (Fin.cons lam τ') * encB (Fin.cons lam τ')) 0 := by + unfold Pruned.childResult + split_ifs with hne + · exact ih _ _ _ + · rw [not_nonempty_iff_eq_empty.mp hne, Finset.piecewise_empty] + -- Step (4) reads `C_λ` only at strings of `S_λ`, and a leaf is its first term and the rest. + funext w + rw [Pruned.result_succ_apply, Finset.piecewise, Leaf.sum_contributes_succ] + split_ifs with hw + · refine sum_congr rfl fun lam hlam => ?_ + have hmem : Fin.tail w ∈ childSet S lam := + mem_childSet.mpr ⟨w, hw, (mem_filter.mp hlam).2, rfl⟩ + rw [hC, Finset.piecewise_eq_of_mem _ _ _ hmem] + · rfl + +/-- **Lemma 10**, first claim. Let a and b be input arrays, and let U be a set of output strings +of length L. Called at the root, Pruned.result(U) returns bilinearProduct(a, b) restricted to U. +-/ +theorem Pruned.result_eq_piecewise_bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) + (b : RightStr L → ℤ) (U : Finset (OutStr L)) : + Pruned.result L (encodingL a) (encodingR b) U = U.piecewise (bilinearProduct a b) 0 := + -- Sum the products of the encoded numbers over the contributing leaves. + Pruned.result_eq_piecewise_sum _ _ U + +/-- Section 2.4.2, the specification of `Pruned.result`: it returns the array returned by the +vertex τ₁⋯τ_k of Full.result, restricted to the strings in S. Here `a` and `b` are the input +arrays of that vertex: by `encodeLeft_cons` and `encodeRight_cons`, applied once for each level, +the parts of the two encodings below a vertex are the encodings of its input arrays. +-/ +theorem Pruned.result_eq_piecewise_full {n : ℕ} (a : LeftStr n → ℤ) (b : RightStr n → ℤ) + (S : Finset (OutStr n)) : + Pruned.result n (encodingL a) (encodingR b) S = S.piecewise (Full.result n a b) 0 := by + rw [Pruned.result_eq_piecewise_bilinearProduct, Full.result_eq_bilinearProduct] + +/-! ### Lemma 10, second claim: the calls that are made and the sets passed to them -/ + +/-- Section 2.3.2: a vertex τ₁ ⋯ τ_k contributes to an output string w if τ_ℓ contributes to w_ℓ +at every level ℓ ≤ k. Only `k ≤ L` occurs; the hypothesis `h` is there so that `w_ℓ` makes sense. +-/ +def Vertex.Contributes {k L : ℕ} (τ : Vertex k) (w : OutStr L) : Prop := + ∀ (ℓ : Fin k) (h : (ℓ : ℕ) < L), (τ ℓ).Contributes (w ⟨ℓ, h⟩) + +/-- Section 2.3.2: for a vertex at depth `L`, that is, a leaf, contributes to means that `τ_ℓ` +contributes to `w_ℓ` at every level. +-/ +theorem Vertex.contributes_iff_leaf {L : ℕ} (τ : Leaf L) (w : OutStr L) : + Vertex.Contributes τ w ↔ Leaf.Contributes τ w := + ⟨fun h ℓ => h ℓ ℓ.2, fun h ℓ _ => h ℓ⟩ + +/-- The vertex `λ τ₁⋯τ_k` contributes to `w` if and only if `λ` contributes to the first variable +of `w` and `τ₁⋯τ_k` contributes to the rest of `w`. +-/ +theorem Vertex.contributes_cons {k L : ℕ} {lam : Term} {τ : Vertex k} {w : OutStr (L + 1)} : + Vertex.Contributes (Fin.cons lam τ : Vertex (k + 1)) w + ↔ lam.Contributes (w 0) ∧ Vertex.Contributes τ (Fin.tail w) := by + constructor + · intro h + exact ⟨h 0 (Nat.succ_pos L), fun ℓ hℓ => h ℓ.succ (Nat.succ_lt_succ hℓ)⟩ + · rintro ⟨h0, h1⟩ ℓ h + induction ℓ using Fin.cases with + | zero => exact h0 + | succ i => exact h1 i (Nat.lt_of_succ_lt_succ h) + +/-- The calls made by the child `λ` of `Pruned.result(S)`: none if `S_λ = ∅`. +-/ +def Pruned.childCalls {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) + (lam : Term) : Multiset PrunedCall := + if (childSet S lam).Nonempty then + Pruned.calls n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam) + else 0 + +/-- The calls made by `Pruned.result(S)`: the call itself, and the calls made by the children `λ` +with `S_λ ≠ ∅`. +-/ +theorem Pruned.calls_succ {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) : + Pruned.calls (n + 1) encA encB S = ⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ ::ₘ + univ.val.bind fun lam => (Pruned.childCalls encA encB S lam).map (.under lam) := by + simp only [Pruned.calls, Pruned.run, Pruned.childCalls, apply_ite Prod.snd] + +/-- The calls of `Pruned.result(S)`, one level unfolded (steps (2) and (3)): the call itself, and +for each term `λ` with `S_λ ≠ ∅` the calls of `Pruned_λ(S_λ)`. +-/ +theorem Pruned.mem_calls_succ {n : ℕ} {encA encB : Leaf (n + 1) → ℤ} + {S : Finset (OutStr (n + 1))} {c : PrunedCall} : + c ∈ Pruned.calls (n + 1) encA encB S ↔ + c = ⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ + ∨ ∃ lam : Term, (childSet S lam).Nonempty + ∧ ∃ c' ∈ Pruned.calls n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam), + c = c'.under lam := by + rw [Pruned.calls_succ, Multiset.mem_cons, Multiset.mem_bind] + refine or_congr Iff.rfl ⟨?_, ?_⟩ + · rintro ⟨lam, -, h⟩ + obtain ⟨c', hc', rfl⟩ := Multiset.mem_map.mp h + unfold Pruned.childCalls at hc' + split_ifs at hc' with hne + · exact ⟨lam, hne, c', hc', rfl⟩ + · simp at hc' + · rintro ⟨lam, hne, c', hc', rfl⟩ + refine ⟨lam, mem_univ lam, Multiset.mem_map.mpr ⟨c', ?_, rfl⟩⟩ + rwa [Pruned.childCalls, ite_eq_left hne] + +/-- A call below the child `λ` is at the vertex `τ` of depth `k + 1` exactly if `λ` is the first +term of `τ` and, seen from the child, the call is at the rest of `τ`. +-/ +private theorem PrunedCall.vertex_under_eq_iff {lam : Term} {c : PrunedCall} {k : ℕ} + {τ : Vertex (k + 1)} : + (c.under lam).vertex = ⟨k + 1, τ⟩ ↔ lam = τ 0 ∧ c.vertex = ⟨k, Fin.tail τ⟩ := by + obtain ⟨⟨k', τ'⟩, T⟩ := c + constructor + · intro h + simp only [PrunedCall.under, Sigma.mk.injEq, Nat.add_right_cancel_iff] at h + obtain ⟨rfl, h⟩ := h + obtain rfl := eq_of_heq h + simp + · rintro ⟨rfl, h⟩ + simp only [Sigma.mk.injEq] at h + obtain ⟨rfl, h⟩ := h + obtain rfl := eq_of_heq h + simp [PrunedCall.under] + +/-- `Pruned.result(U)`, called at the root, calls the vertex `τ`. For a leaf this is +`Pruned.Visits`. +-/ +def Pruned.Called {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (τ : Vertex k) : + Prop := + ∃ c ∈ Pruned.calls L encA encB U, c.vertex = ⟨k, τ⟩ + +/-- The root is called. +-/ +theorem Pruned.called_root {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) + (τ : Vertex 0) : Pruned.Called encA encB U τ := by + obtain rfl : τ = Fin.elim0 := Subsingleton.elim _ _ + cases L with + | zero => exact ⟨_, Multiset.mem_singleton_self _, rfl⟩ + | succ n => exact ⟨_, Pruned.mem_calls_succ.mpr (.inl rfl), rfl⟩ + +/-- Step (3): a vertex `λ τ'` below the root is called exactly if `S_λ ≠ ∅` and the child `λ` +calls `τ'`. +-/ +theorem Pruned.called_succ {n k : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) + (τ : Vertex (k + 1)) : + Pruned.Called encA encB S τ ↔ (childSet S (τ 0)).Nonempty ∧ + Pruned.Called (sliceAt encA (τ 0)) (sliceAt encB (τ 0)) (childSet S (τ 0)) (Fin.tail τ) := by + constructor + · rintro ⟨c, hc, hcτ⟩ + rcases Pruned.mem_calls_succ.mp hc with rfl | ⟨lam, hne, c', hc', rfl⟩ + · exact absurd (congrArg Sigma.fst hcτ) (by simp) + · obtain ⟨rfl, hc'τ⟩ := PrunedCall.vertex_under_eq_iff.mp hcτ + exact ⟨hne, c', hc', hc'τ⟩ + · rintro ⟨hne, c', hc', hc'τ⟩ + exact ⟨c'.under (τ 0), Pruned.mem_calls_succ.mpr (.inr ⟨τ 0, hne, c', hc', rfl⟩), + PrunedCall.vertex_under_eq_iff.mpr ⟨rfl, hc'τ⟩⟩ + +/-- The vertex `λ τ'` contributes to a string of `S` exactly if `τ'` contributes to a string of +`S_λ`. +-/ +private theorem exists_contributes_childSet {n k : ℕ} (S : Finset (OutStr (n + 1))) + (τ : Vertex (k + 1)) : + (∃ w' ∈ childSet S (τ 0), Vertex.Contributes (Fin.tail τ) w') + ↔ ∃ w ∈ S, Vertex.Contributes τ w := by + have hcons (w : OutStr (n + 1)) : Vertex.Contributes τ w + ↔ (τ 0).Contributes (w 0) ∧ Vertex.Contributes (Fin.tail τ) (Fin.tail w) := by + rw [← Vertex.contributes_cons, Fin.cons_self_tail] + constructor + · rintro ⟨w', hw', hτ'⟩ + obtain ⟨w, hw, hz, rfl⟩ := mem_childSet.mp hw' + exact ⟨w, hw, (hcons w).mpr ⟨hz, hτ'⟩⟩ + · rintro ⟨w, hw, hτ⟩ + exact ⟨_, mem_childSet.mpr ⟨w, hw, ((hcons w).mp hτ).1, rfl⟩, ((hcons w).mp hτ).2⟩ + +/-- A vertex at depth `k ≤ L` is called by `Pruned.result(U)` if and only if it is the root or it +contributes to some string of `U` (proof of Lemma 10). The induction follows the recursion: the +vertex `λ τ'` is called by `Pruned.result(U)` exactly if `τ'` is called by `Pruned.result(U_λ)`. +-/ +theorem Pruned.called_iff_root_or {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) + (hkL : k ≤ L) (τ : Vertex k) : + Pruned.Called encA encB U τ ↔ k = 0 ∨ ∃ w ∈ U, Vertex.Contributes τ w := by + induction L generalizing k with + | zero => + obtain rfl : k = 0 := by omega + exact iff_of_true (Pruned.called_root encA encB U τ) (.inl rfl) + | succ n ih => + cases k with + | zero => exact iff_of_true (Pruned.called_root encA encB U τ) (.inl rfl) + | succ k => + have ihτ := ih (sliceAt encA (τ 0)) (sliceAt encB (τ 0)) (childSet U (τ 0)) + (Nat.le_of_succ_le_succ hkL) (Fin.tail τ) + rw [Pruned.called_succ, ← exists_contributes_childSet] + constructor + · rintro ⟨⟨w', hw'⟩, hcalled⟩ + rcases ihτ.mp hcalled with rfl | hτ' + · -- The child `λ` itself, as a vertex at depth 0, contributes to every string of `U_λ`. + exact .inr ⟨w', hw', fun ℓ => ℓ.elim0⟩ + · exact .inr hτ' + · rintro (hk | ⟨w', hw', hτ'⟩) + · exact absurd hk (Nat.succ_ne_zero k) + · exact ⟨⟨w', hw'⟩, ihτ.mpr (.inr ⟨w', hw', hτ'⟩)⟩ + +/-- In the proof of Lemma 10: the vertex is called if and only if this set is nonempty, that is, +if and only if the vertex contributes to some string of `U`. + +NOTE. As printed this fails for the root when `U = ∅` (the root is always called). The +hypothesis `hk` excludes only that case. `hkL` says that `τ` is a vertex of the recursion tree, +whose depth is at most `L` (Section 2.3.1); the type `Vertex k` exists for every `k`. +-/ +theorem Pruned.called_iff {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) + (hk : 1 ≤ k ∨ U.Nonempty) (hkL : k ≤ L) (τ : Vertex k) : + Pruned.Called encA encB U τ ↔ ∃ w ∈ U, Vertex.Contributes τ w := by + rw [Pruned.called_iff_root_or _ _ U hkL τ] + constructor + · rintro (rfl | h) + · obtain ⟨w, hw⟩ := hk.resolve_left (by omega) + exact ⟨w, hw, fun ℓ => ℓ.elim0⟩ + · exact h + · exact Or.inr + +/-- A call at depth `k` is passed strings of length `L - k`. +-/ +theorem Pruned.depth_add_length {L : ℕ} {encA encB : Leaf L → ℤ} {U : Finset (OutStr L)} + {c : PrunedCall} (hc : c ∈ Pruned.calls L encA encB U) : c.vertex.1 + c.passed.1 = L := by + induction L generalizing c with + | zero => + obtain rfl := Multiset.mem_singleton.mp hc + rfl + | succ n ih => + rcases Pruned.mem_calls_succ.mp hc with rfl | ⟨lam, -, c', hc', rfl⟩ + · exact Nat.zero_add _ + · have hlen := ih hc' + change c'.vertex.1 + 1 + c'.passed.1 = n + 1 + omega + +/-- `w'` is the suffix of `w` that is left when the first `k` variables are removed. +-/ +def IsSuffixFrom {n L : ℕ} (k : ℕ) (w' : OutStr n) (w : OutStr L) : Prop := + ∀ (i : Fin n) (hi : k + (i : ℕ) < L), w' i = w ⟨k + i, hi⟩ + +/-- Removing no variable leaves the string as it is. +-/ +private theorem isSuffixFrom_zero {n : ℕ} (w' w : OutStr n) : IsSuffixFrom 0 w' w ↔ w' = w := by + have hidx (i : Fin n) (hi : 0 + (i : ℕ) < n) : (⟨0 + i, hi⟩ : Fin n) = i := + Fin.ext (Nat.zero_add _) + constructor + · intro h + funext i + rw [h i (by simp), hidx] + · rintro rfl i hi + rw [hidx] + +/-- Removing `k + 1` variables is removing the first variable and then `k` more. +-/ +private theorem isSuffixFrom_succ {n L k : ℕ} {w' : OutStr n} {w : OutStr (L + 1)} : + IsSuffixFrom (k + 1) w' w ↔ IsSuffixFrom k w' (Fin.tail w) := by + have hidx (i : Fin n) (h₁ : k + 1 + (i : ℕ) < L + 1) (h₂ : k + (i : ℕ) < L) : + w ⟨k + 1 + i, h₁⟩ = Fin.tail w ⟨k + i, h₂⟩ := + congrArg w (Fin.ext (by simp only [Fin.val_succ]; omega)) + exact ⟨fun h i hi => (h i (by omega)).trans (hidx i _ hi), + fun h i hi => (h i (by omega)).trans (hidx i hi _).symm⟩ + +/-- The set passed to the root is `U`, and the root contributes to every string. +-/ +private theorem mem_iff_root {L : ℕ} (U : Finset (OutStr L)) (w' : OutStr L) : + w' ∈ U ↔ ∃ w ∈ U, Vertex.Contributes (Fin.elim0 : Vertex 0) w ∧ IsSuffixFrom 0 w' w := by + simp only [isSuffixFrom_zero] + exact ⟨fun h => ⟨w', h, fun ℓ => ℓ.elim0, rfl⟩, fun ⟨_, hw, _, h⟩ => h ▸ hw⟩ + +/-- Proof of Lemma 10 (announced in Section 2.4.2): the set passed to the vertex τ₁⋯τ_k consists +of the suffixes w_{k+1}⋯w_L of the strings w of U to which the vertex contributes. For a call +`c`, `c.vertex.1` is the depth `k`, `c.vertex.2` the vertex, `c.passed.1` the length `L - k` of +the strings passed, and `c.passed.2` the set passed. +-/ +theorem Pruned.mem_passed_iff {L : ℕ} {encA encB : Leaf L → ℤ} {U : Finset (OutStr L)} + {c : PrunedCall} (hc : c ∈ Pruned.calls L encA encB U) (w' : OutStr c.passed.1) : + w' ∈ c.passed.2 ↔ + ∃ w ∈ U, Vertex.Contributes c.vertex.2 w ∧ IsSuffixFrom c.vertex.1 w' w := by + induction L generalizing c with + | zero => + obtain rfl := Multiset.mem_singleton.mp hc + exact mem_iff_root U w' + | succ n ih => + rcases Pruned.mem_calls_succ.mp hc with rfl | ⟨lam, -, c', hc', rfl⟩ + · exact mem_iff_root U w' + · -- A vertex below the child `λ`: the induction hypothesis for `S_λ`, whose strings are the + -- rests of the strings of `U` to whose first variable `λ` contributes. + refine (ih hc' w').trans ⟨?_, ?_⟩ + · rintro ⟨_, hw₁, hτ, hsuffix⟩ + obtain ⟨w, hw, hz, rfl⟩ := mem_childSet.mp hw₁ + exact ⟨w, hw, Vertex.contributes_cons.mpr ⟨hz, hτ⟩, isSuffixFrom_succ.mpr hsuffix⟩ + · rintro ⟨w, hw, hτ, hsuffix⟩ + obtain ⟨hz, hτ'⟩ := Vertex.contributes_cons.mp hτ + exact ⟨Fin.tail w, mem_childSet.mpr ⟨w, hw, hz, rfl⟩, hτ', isSuffixFrom_succ.mp hsuffix⟩ + +/-- A leaf is visited exactly when it is the zero-depth root or contributes to a request. This +includes the empty request set without an exceptional hypothesis. +-/ +theorem Pruned.visits_iff_root_or_mem_leaves {L : ℕ} (encA encB : Leaf L → ℤ) + (U : Finset (OutStr L)) (τ : Leaf L) : + Pruned.Visits encA encB U τ ↔ L = 0 ∨ τ ∈ leaves U := by + change Pruned.Called encA encB U τ ↔ _ + simpa only [mem_leaves, Vertex.contributes_iff_leaf] using + Pruned.called_iff_root_or encA encB U (le_refl L) τ + +/-- **Lemma 10**, second claim. The leaves it visits are exactly those of leaves(U). The +hypothesis `0 < L ∨ U.Nonempty` is explained at `pruned_spec`. +-/ +theorem Pruned.visits_iff_mem_leaves {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) + (h : 0 < L ∨ U.Nonempty) (τ : Leaf L) : Pruned.Visits encA encB U τ ↔ τ ∈ leaves U := + -- A leaf is a vertex at depth `L`. + (Pruned.called_iff encA encB U h le_rfl τ).trans + (by simp only [mem_leaves, Vertex.contributes_iff_leaf]) + +/-! ### Lemma 10, third claim: the total size of the sets passed -/ + +/-- The leaf `λ τ'` is in `leaves(S)` if and only if `τ'` is in `leaves(S_λ)`. +-/ +theorem mem_leaves_succ {n : ℕ} {S : Finset (OutStr (n + 1))} {τ : Leaf (n + 1)} : + τ ∈ leaves S ↔ Fin.tail τ ∈ leaves (childSet S (τ 0)) := by + rw [mem_leaves, mem_leaves] + constructor + · rintro ⟨w, hw, hcon⟩ + obtain ⟨h0, h1⟩ := Leaf.contributes_succ.mp hcon + exact ⟨Fin.tail w, mem_childSet.mpr ⟨w, hw, h0, rfl⟩, h1⟩ + · rintro ⟨w', hw', h1⟩ + obtain ⟨w, hw, h0, rfl⟩ := mem_childSet.mp hw' + exact ⟨w, hw, Leaf.contributes_succ.mpr ⟨h0, h1⟩⟩ + +/-- `leaves(S)` is the disjoint union, over the terms `λ`, of the leaves `λ τ'` with `τ'` in +`leaves(S_λ)`. +-/ +theorem card_leaves_succ {n : ℕ} (S : Finset (OutStr (n + 1))) : + (leaves S).card = ∑ lam, (leaves (childSet S lam)).card := by + rw [card_eq_sum_card_fiberwise (f := fun τ : Leaf (n + 1) => τ 0) (t := univ) + (fun _ _ => mem_coe.mpr (mem_univ _))] + refine sum_congr rfl fun lam _ => ?_ + rw [← card_image_of_injective (leaves (childSet S lam)) + (Fin.cons_right_injective (α := fun _ : Fin (n + 1) => Term) lam)] + congr 1 + ext τ + simp only [mem_filter, mem_image] + constructor + · rintro ⟨hτ, rfl⟩ + exact ⟨Fin.tail τ, mem_leaves_succ.mp hτ, Fin.cons_self_tail τ⟩ + · rintro ⟨τ', hτ', rfl⟩ + refine ⟨mem_leaves_succ.mpr ?_, Fin.cons_zero _ _⟩ + simpa using hτ' + +/-- Different output strings have different private leaves. +-/ +theorem privateLeaf_injective {L : ℕ} : Function.Injective (privateLeaf (L := L)) := + have hterm : Function.Injective OutVar.privateTerm := by decide + fun _ _ h => funext fun ℓ => hterm (congrFun h ℓ) + +/-- The map of the proof of Lemma 10, at the root: an output string goes to the leaf that picks +`P_ij` where the string has `z_ij` and `P₀` where it has `z₀`. The leaf contributes to the string +and determines it, so a set of output strings is no larger than its set of leaves. +-/ +theorem card_le_card_leaves {n : ℕ} (S : Finset (OutStr n)) : S.card ≤ (leaves S).card := by + refine card_le_card_of_injOn privateLeaf (fun w hw => ?_) privateLeaf_injective.injOn + exact mem_coe.mpr (mem_leaves.mpr + ⟨w, hw, fun _ => (Term.contributes_iff _ _).mpr (.inr rfl)⟩) + +/-- The total size of the sets passed to the calls at depth `k` in a list of calls. +-/ +private def sizeAt (s : Multiset PrunedCall) (k : ℕ) : ℕ := + ((s.filter fun c => c.vertex.1 = k).map fun c => c.passed.2.card).sum + +/-- The size at depth `k` of a list of calls with one more call. +-/ +private theorem sizeAt_cons (c : PrunedCall) (s : Multiset PrunedCall) (k : ℕ) : + sizeAt (c ::ₘ s) k + = (if c.vertex.1 = k then c.passed.2.card else 0) + sizeAt s k := by + unfold sizeAt + rw [Multiset.filter_cons] + split_ifs <;> simp + +/-- The size at depth `k` of the lists of calls of the ten children together. +-/ +private theorem sizeAt_bind (f : Term → Multiset PrunedCall) (k : ℕ) : + sizeAt (univ.val.bind f) k = ∑ lam, sizeAt (f lam) k := by + unfold sizeAt + rw [Multiset.filter_bind, Multiset.map_bind, Multiset.sum_bind] + rfl + +/-- Seen from one level higher, the calls at depth `k` are at depth `k + 1`. +-/ +private theorem sizeAt_under_succ (lam : Term) (s : Multiset PrunedCall) (k : ℕ) : + sizeAt (s.map (PrunedCall.under lam)) (k + 1) = sizeAt s k := by + unfold sizeAt + rw [Multiset.filter_map, Multiset.map_map] + rw [Multiset.filter_congr (q := fun c => c.vertex.1 = k) (by simp [PrunedCall.under])] + rfl + +/-- Seen from one level higher, no call is at depth `0`. +-/ +private theorem sizeAt_under_zero (lam : Term) (s : Multiset PrunedCall) : + sizeAt (s.map (PrunedCall.under lam)) 0 = 0 := by + unfold sizeAt + rw [Multiset.filter_map, Multiset.filter_eq_nil.mpr (by simp [PrunedCall.under])] + rfl + +/-- The total size of the sets passed is the sum, over the depths, of the sizes at each depth. +-/ +private theorem sum_sizeAt (s : Multiset PrunedCall) (N : ℕ) (h : ∀ c ∈ s, c.vertex.1 < N) : + (s.map fun c => c.passed.2.card).sum = ∑ k ∈ range N, sizeAt s k := by + induction s using Multiset.induction_on with + | empty => simp [sizeAt] + | cons c s ih => + simp only [sizeAt_cons, Multiset.map_cons, Multiset.sum_cons, sum_add_distrib] + rw [ih fun c' hc' => h c' (Multiset.mem_cons_of_mem hc'), sum_ite_eq, + ite_eq_left (mem_range.mpr (h c (Multiset.mem_cons_self c s)))] + +/-- End of the proof of Lemma 10: the sets passed to the vertices at each of the L+1 depths have +total size at most |leaves(U)|. +-/ +theorem Pruned.sum_card_passed_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (k : ℕ) : + (((Pruned.calls L encA encB U).filter fun c => c.vertex.1 = k).map fun c => c.passed.2.card).sum + ≤ (leaves U).card := by + -- The paper's injection (vertex, suffix) ↦ leaf, organised as an induction on `L`: the strings + -- passed to the root inject into `leaves(U)`, and the calls at depth `k + 1` are the calls at + -- depth `k` of the children `λ`, whose leaves, with `λ` put in front, are pairwise disjoint parts + -- of `leaves(U)`. + change sizeAt (Pruned.calls L encA encB U) k ≤ _ + induction L generalizing k with + | zero => + have hcalls : Pruned.calls 0 encA encB U = ⟨⟨0, Fin.elim0⟩, ⟨0, U⟩⟩ ::ₘ 0 := rfl + rw [hcalls, sizeAt_cons] + split_ifs + · simpa [sizeAt] using card_le_card_leaves U + · simp [sizeAt] + | succ n ih => + rw [Pruned.calls_succ, sizeAt_cons, sizeAt_bind] + cases k with + | zero => + simp only [sizeAt_under_zero, sum_const_zero, ite_true, add_zero] + exact card_le_card_leaves U + | succ k => + simp only [sizeAt_under_succ] + rw [ite_eq_right (by simp), zero_add, card_leaves_succ] + refine sum_le_sum fun lam _ => ?_ + unfold Pruned.childCalls + split_ifs + · exact ih _ _ _ _ + · simp [sizeAt] + +/-- **Lemma 10**, third claim. the sets passed to the calls it makes have total size at most +(L+1) |leaves(U)|. +-/ +theorem Pruned.totalSize_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) : + Pruned.totalSize encA encB U ≤ (L + 1) * (leaves U).card := by + have hdepth : ∀ c ∈ Pruned.calls L encA encB U, c.vertex.1 < L + 1 := + fun c hc => by have := Pruned.depth_add_length hc; omega + rw [Pruned.totalSize, sum_sizeAt _ (L + 1) hdepth] + calc ∑ k ∈ range (L + 1), sizeAt (Pruned.calls L encA encB U) k + ≤ ∑ _k ∈ range (L + 1), (leaves U).card := + sum_le_sum fun k _ => Pruned.sum_card_passed_le _ _ U k + _ = (L + 1) * (leaves U).card := by simp + +/-- **Lemma 10**, its three claims together. Let a and b be input arrays, and let U be a set of +output strings of length L. Called at the root, Pruned.result(U) returns bilinearProduct(a, b) +restricted to U. The leaves it visits are exactly those of leaves(U), and the sets passed to the +calls it makes have total size at most (L+1) |leaves(U)|. + +NOTE. The hypothesis `0 < L ∨ U.Nonempty` of the second claim is not in the paper. It excludes +only the case `L = 0`, `U = ∅`, in which the root is itself a leaf, so that the call at the root +visits it, while `leaves(∅)` is empty. In the paper `L ≥ m ≥ 1`, and `Pruned.result` is run only +on nonempty sets (Section 2.4.4). +-/ +theorem pruned_spec {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (U : Finset (OutStr L)) : + Pruned.result L (encodingL a) (encodingR b) U = U.piecewise (bilinearProduct a b) 0 + ∧ (0 < L ∨ U.Nonempty → + ∀ τ : Leaf L, Pruned.Visits (encodingL a) (encodingR b) U τ ↔ τ ∈ leaves U) + ∧ Pruned.totalSize (encodingL a) (encodingR b) U ≤ (L + 1) * (leaves U).card := + ⟨Pruned.result_eq_piecewise_bilinearProduct a b U, Pruned.visits_iff_mem_leaves _ _ U, + Pruned.totalSize_le _ _ U⟩ + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/ComplexityTests/Schonhage.lean b/ComplexityTests/Schonhage.lean new file mode 100644 index 0000000000..62f3fb1d85 --- /dev/null +++ b/ComplexityTests/Schonhage.lean @@ -0,0 +1,100 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned + +/-! Concrete tensor evaluations, signed cancellation and pruning boundary cases. -/ + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +namespace SchonhageTests + +private def left : LeftStr 1 → ℤ := fun u => + match u 0 with + | .x i => ![1, 2, 3] i + | .p i j => !![4, 5; 6, 7] i j + +private def right : RightStr 1 → ℤ := fun v => + match v 0 with + | .y j => ![8, 9, 10] j + | .q i j => !![11, 12; 13, 14] i j + +-- The negative tenth term cancels the unwanted outer products in the inner output. +example : Full.multipliedAt 1 left right ![.P0] = (-6, 27) := by decide + +example : Full.result 1 left right ![.z0] = 280 := by decide + +example : Full.result 1 left right ![.z 0 0] = 95 := by decide + +example : Full.result 1 left right ![.z 2 2] = 30 := by decide + +-- Unrestricted outer outputs include error terms: 95 is not the wanted product 1 * 8. +example : Full.result 1 left right ![.z 0 0] ≠ 1 * 8 := by decide + +example : coefficient (.x 0) (.y 0) .z0 = 0 := by decide + +example : coefficient (.p 0 1) (.q 0 1) .z0 = 1 := by decide + +example : coefficient (.p 0 1) (.q 1 0) .z0 = 0 := by decide + +example : coefficient (.x 0) (.q 0 0) (.z 0 0) = 1 := by decide + +private def outerQuery : Finset (OutStr 1) := {![.z 0 0]} + +private def innerQuery : Finset (OutStr 1) := {![.z0]} + +example : Pruned.result 1 (encodingL left) (encodingR right) outerQuery ![.z 0 0] = 95 := by + decide + +example : Pruned.result 1 (encodingL left) (encodingR right) outerQuery ![.z0] = 0 := by + decide + +example : (leaves outerQuery).card = 1 := by decide + +example : (Pruned.calls 1 (encodingL left) (encodingR right) outerQuery).card = 2 := by + decide + +example : Pruned.totalSize (encodingL left) (encodingR right) outerQuery = 2 := by decide + +example : (leaves innerQuery).card = 10 := by decide + +example : Pruned.totalSize (encodingL left) (encodingR right) innerQuery = 11 := by decide + +-- The shared private leaf of these two outputs is included only once. +private def mixedQuery : Finset (OutStr 1) := {![.z 0 0], ![.z0]} + +example : (leaves mixedQuery).card = 10 := by decide + +example : (Pruned.calls 1 (encodingL left) (encodingR right) mixedQuery).card = 11 := by + decide + +example : Pruned.totalSize (encodingL left) (encodingR right) mixedQuery = 12 := by decide + +example : Pruned.result 1 (encodingL left) (encodingR right) ∅ ![.z0] = 0 := by decide + +example : (Pruned.calls 1 (encodingL left) (encodingR right) ∅).card = 1 := by decide + +example : Pruned.totalSize (encodingL left) (encodingR right) ∅ = 0 := by decide + +-- Below a two-level query, the outer level keeps one child and the inner one keeps ten. +example : Pruned.result 2 (fun _ => 3) (fun _ => -4) {![.z 1 2, .z0]} + ![.z 1 2, .z0] = -120 := by decide + +example : Pruned.totalSize (fun _ : Leaf 2 => 3) (fun _ => -4) {![.z 1 2, .z0]} = 12 := by + decide + +example : Pruned.result 0 (fun _ => -3) (fun _ => 7) {Fin.elim0} Fin.elim0 = -21 := by + decide + +-- The unqualified visited-leaf equivalence fails at the empty, zero-depth root. +example : Pruned.Visits (fun _ : Leaf 0 => 1) (fun _ => 1) ∅ Fin.elim0 ∧ + Fin.elim0 ∉ leaves (∅ : Finset (OutStr 0)) := by + constructor + · exact (Pruned.visits_iff_root_or_mem_leaves _ _ ∅ Fin.elim0).mpr (.inl rfl) + · simp [leaves] + +end SchonhageTests diff --git a/NOTICE b/NOTICE index e48c2ecb6c..5fb9b6c145 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,6 @@ -The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in +The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native matrix builder in Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, +Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, Complexity/Foundations/Data/Finset/{Average,Quotient}.lean, diff --git a/ROADMAP.md b/ROADMAP.md index 044ec8d475..999c5e63cc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -424,8 +424,11 @@ each statement against the numbered result. Organize modules by subject, not cha ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting and the selected-prime false-positive bound ([#1442]), finite residue/chunk/piece splitting and - witness recovery ([#1447]) are proved mathematically. It still needs tensor/sparse-query programs, - native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full + witness recovery ([#1447]) are proved mathematically. The tensor identity, full recursion, + shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set + bounds, with the empty zero-depth root handled explicitly. It still needs tiling and sparse + leaf bounds, native tensor/sparse-query programs, native Chan–He preprocessing, the reduction + host, the fast Exact Triangle solver and full input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded diff --git a/UPSTREAM.md b/UPSTREAM.md index abb96bb7ad..bfa84a8699 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -193,8 +193,15 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he `Algorithms/ExactTriangle/{Splitting,Witnesses}.lean` adapts the complete finite splitting and witness recovery component ([#1447](https://github.com/cslib-community/complexitylib/issues/1447)), using the existing matrices, lopsided graph and `Set.EqOn` answer API. Computable integer parameters - retain the source's exact real floor/ceiling specifications through checked bridges. Tensor/sparse - query programs, native preprocessing/solvers and full input/time/width composition remain in #1394. + retain the source's exact real floor/ceiling specifications through checked bridges. + `Algorithms/MatrixMultiplication/Schonhage/` adapts the tensor-recursion core from the same pin's + `Sec2/{Lemma6,Recursion,Lemma7_8,Lemma10}.lean`: the ten-term polynomial identity with explicit + error, full evaluation, shared encodings, pruned values, visited leaves and passed-set totals. + Its pure finite recursion traces are not native machine semantics or charged instruction costs. + The zero-depth empty-query root exception is explicit, with an unconditional root-or-leaf API. + See [#1459](https://github.com/cslib-community/complexitylib/issues/1459). Tiling/sparse leaf bounds, + native tensor/sparse-query programs, native preprocessing/solvers and full input/time/width + composition remain in #1394. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. From 62dc601e6139cc7146373caa7628f16130156a9a Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:55:40 -0400 Subject: [PATCH 10/32] doc(matrix): clarify the overlapping contributing leaf --- ComplexityTests/Schonhage.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ComplexityTests/Schonhage.lean b/ComplexityTests/Schonhage.lean index 62f3fb1d85..a70da4e167 100644 --- a/ComplexityTests/Schonhage.lean +++ b/ComplexityTests/Schonhage.lean @@ -64,7 +64,7 @@ example : (leaves innerQuery).card = 10 := by decide example : Pruned.totalSize (encodingL left) (encodingR right) innerQuery = 11 := by decide --- The shared private leaf of these two outputs is included only once. +-- The shared contributing leaf of these two outputs is included only once. private def mixedQuery : Finset (OutStr 1) := {![.z 0 0], ![.z0]} example : (leaves mixedQuery).card = 10 := by decide From 4f3226cd0db2b993905987a32864fa2c9a48d774 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:49:23 -0400 Subject: [PATCH 11/32] doc(matrix): make tensor and pruning explanations standalone --- .../MatrixMultiplication/Schonhage/Full.lean | 35 ++++------ .../Schonhage/Identity.lean | 5 +- .../Schonhage/Pruned.lean | 64 ++++++------------- 3 files changed, 34 insertions(+), 70 deletions(-) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean index a1c8d82b68..ed13169001 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean @@ -67,19 +67,11 @@ def encodeStepL {L : ℕ} (lam : Term) (a : LeftStr (L + 1) → ℤ) : LeftStr L def encodeStepR {L : ℕ} (lam : Term) (b : RightStr (L + 1) → ℤ) : RightStr L → ℤ := fun v' => ∑ t, psi lam t * sliceAt b t v' -/-- Section 2.3.1: The recursion Full.result(a, b), for arrays a, b on the left and right strings -of length L, returns an array on the output strings of length L: (1) If L = 0, return the number -ab. (2) For each term λ of Schönhage's identity, form A_λ := ∑_s φ_λ(s) a_s and B_λ := ∑_t ψ_λ(t) -b_t. (3) For each term λ, recursively compute C_λ := Full.result(A_λ, B_λ). (4) Return the array c -whose slices are c_{z_ij} := C_{P_ij} and c_{z₀} := ∑_λ C_λ. - -`Full.run` is this recursion, and it also keeps a record of the multiplications. Its first -component is the array that is returned. Its second component gives, for each leaf `τ`, the two -numbers that step (1) multiplies at `τ`: the leaf `λ τ'` of a call is the leaf `τ'` of its -recursive call for `λ`. - -An array on the strings of length 0 has one entry, at the empty string `Fin.elim0`; that entry is -the number. +/-- Evaluate the two arrays and retain the operands used at each leaf. At depth zero, multiply +their unique entries. At positive depth, apply each term’s left and right coefficient forms, +recurse, and decode the child results into an output array. The second component maps each term +string to its pair of encoded input values. This is a finite integer recursion, with no +instruction counter. -/ def Full.run : (L : ℕ) → (LeftStr L → ℤ) → (RightStr L → ℤ) → (OutStr L → ℤ) × (Leaf L → ℤ × ℤ) | 0, a, b => (fun _ => a Fin.elim0 * b Fin.elim0, fun _ => (a Fin.elim0, b Fin.elim0)) @@ -280,7 +272,7 @@ levels are empty (proof of Lemma 7). private theorem encodeLeft_zero (τ : Leaf 0) (a : LeftStr 0 → ℤ) : encodeLeft τ a = a Fin.elim0 := encodeWith_zero phi τ a -/-- and Ψ_τ(b) = b (proof of Lemma 7). +/-- At depth zero, the right encoding equals the input entry at the unique empty string. -/ private theorem encodeRight_zero (τ : Leaf 0) (b : RightStr 0 → ℤ) : encodeRight τ b = b Fin.elim0 := @@ -321,7 +313,7 @@ theorem bilinearProduct_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightS bilinearProduct (encodeStepL lam a) (encodeStepR lam b) (Fin.tail w) := by simp only [bilinearProduct, Leaf.sum_contributes_succ, encodeLeft_cons, encodeRight_cons] -/-- **Lemma 7**, second half. and it returns bilinearProduct(a, b). +/-- Full evaluation computes the bilinear tensor expansion `bilinearProduct`. -/ theorem Full.result_eq_bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : Full.result L a b = bilinearProduct a b := by @@ -334,8 +326,8 @@ theorem Full.result_eq_bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : Ri funext w simp only [Full.result_succ, bilinearProduct_succ, ih] -/-- **Lemma 7**. At the leaf τ, Full.result(a, b) multiplies Φ_τ(a) by Ψ_τ(b), and it returns -bilinearProduct(a, b). +/-- The complete value specification of full evaluation: its result is `bilinearProduct`, and each +leaf’s operands are the corresponding left and right encodings. -/ theorem full_spec {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : (∀ τ : Leaf L, Full.multipliedAt L a b τ = (encodeLeft τ a, encodeRight τ b)) ∧ @@ -355,14 +347,13 @@ theorem prod_gamma_eq_sum_contributes {L : ℕ} (u : LeftStr L) (v : RightStr L) rw [prod_univ_sum, Leaf.filter_contributes_eq_piFinset] exact sum_congr rfl fun τ _ => prod_mul_distrib -/-- **Lemma 8**. For all arrays a, b and every output string w, bilinearProduct(a, b)[w] = -∑_{u,v} γ(u₁, v₁, w₁) γ(u₂, v₂, w₂) ⋯ γ(u_L, v_L, w_L) a[u] b[v], the sum over all left strings u -and right strings v. +/-- Expand `bilinearProduct` as a double sum of input-entry products. The coefficient of each pair +is the product, over string positions, of the one-level coefficients. -/ theorem tensor_expansion {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (w : OutStr L) : bilinearProduct a b w = ∑ u, ∑ v, (∏ ℓ, coefficient (u ℓ) (v ℓ) (w ℓ)) * a u * b v := by - -- "the coefficient of a[u] b[v] in (2) is ∑_τ ∏_ℓ φ_{τ_ℓ}(u_ℓ) ψ_{τ_ℓ}(v_ℓ), summed over the - -- leaves τ contributing to w": expand `Φ_τ(a) Ψ_τ(b)` and sum over `τ` last. + -- Expand the encoded factors and reorder the finite sums. The coefficient of each input + -- product is the product of its one-level coefficients. simp only [bilinearProduct, encodeLeft, encodeRight, sum_mul_sum] rw [sum_comm] refine sum_congr rfl fun u _ => ?_ diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean index 15b7942f44..dea69212a0 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean @@ -179,9 +179,8 @@ theorem sum_pHat_mul_qHat : simp [Fin.sum_univ_three, Fin.sum_univ_two, pHat, qHat, pForm, qForm, formL_eq_linearCombination, formR_eq_linearCombination] -/-- **Lemma 6**. Summing over the ten terms, ∑_λ φ_λ ψ_λ χ_λ = G + E, where E := ∑_{i,j=1}^{3} -(x_i q̂_ij + p̂_ij y_j + p̂_ij q̂_ij) z_ij. An identity of polynomials with integer coefficients -in the 24 variables. +/-- The ten products of the coefficient forms equal the wanted polynomial `G` plus the explicit +error polynomial `E`. -/ theorem identity : ∑ lam, formL (phi lam) * formR (psi lam) * formO (chi lam) = G + E := by -- The column sums of p̂ and the row sums of q̂, as identities of polynomials. diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean index 30043faf44..e581c0e200 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean @@ -82,28 +82,12 @@ def PrunedCall.under (lam : Term) (c : PrunedCall) : PrunedCall where vertex := ⟨c.vertex.1 + 1, (Fin.cons lam c.vertex.2 : Vertex (c.vertex.1 + 1))⟩ passed := c.passed -/-- Section 2.4.2: Pruned_{τ₁⋯τ_k}(S), for a set S of output strings of length L - k, returns the -array returned by the vertex τ₁⋯τ_k of Full.result, restricted to the strings in S: (1) If k = L, -then look up Φ_τ(a) and Ψ_τ(b) from the precomputed encodings (Section 2.4.1) and return Φ_τ(a) -Ψ_τ(b) for the leaf τ = τ₁⋯τ_L. (2) For each term λ of Schönhage's identity, let S_λ be the union -of the slices S_z over the z to which λ contributes, i.e., S_{P_ij} := S_{z_ij} ∪ S_{z₀} and -S_{P₀} := S_{z₀}. (3) For each term λ of Schönhage's identity with S_λ ≠ ∅, compute C_λ := -Pruned_{τ₁⋯τ_k λ}(S_λ), an array on S_λ. (4) Return the array c on S whose slices are c_{z_ij} := -C_{P_ij} and c_{z₀} := ∑_λ C_λ, restricted to S_{z_ij} and to S_{z₀}. - -`Pruned.run` is this recursion, and it also keeps a record of the calls. Its first component is -the array that is returned. Its second component lists all the calls that are made, this one -included, each with the set passed to it. Vertices are named relative to the call we start from, -so for the call at the root they are the vertices of the paper. - -Here `n = L - k`. The vertex `τ₁⋯τ_k` enters only through the look-ups of step (1), so it is -passed as the parts of the two encodings that lie below it: `encA τ'` is the entry of the encoding -of `a` at the leaf `τ₁⋯τ_k τ'`, and likewise `encB`. At the root these are the two encodings -themselves, and the child `λ` receives their slices at `λ`. For a term `λ` with `S_λ = ∅` no call -is made: nothing is recorded, and `C_λ` is written as the zero array; step (4) does not read it, -because then `S_{z₀}`, and `S_{z_ij}` if `λ = P_ij`, are empty. In step (1) the number is -returned as an array on `S`, like every other result; `S` then consists of the empty string, -except when `Pruned.result` is called at the root with `L = 0` and `S = ∅`. +/-- Evaluate requested outputs from the two supplied leaf-encoding arrays. For each term, form its +child request set and recurse only when that set is nonempty. Decode the child values and set +unrequested outputs to zero. The second component records recursive calls and their requested sets +as a multiset. Each child receives the corresponding slices of the encoding arrays. The root call +is always recorded, including at depth zero with an empty request set. These are mathematical +values and call records, without a native execution cost. -/ def Pruned.run : (n : ℕ) → (Leaf n → ℤ) → (Leaf n → ℤ) → Finset (OutStr n) → (OutStr n → ℤ) × Multiset PrunedCall @@ -151,12 +135,9 @@ def privateLeaf {L : ℕ} (w : OutStr L) : Leaf L := fun ℓ => (w ℓ).privateT /-! ### Section 2.4.1: the encodings -/ -/-- Section 2.4.1, the procedure that computes the encoding of `a`: we run Full.result with b left -out and without step (4): […] the left number at the leaf τ is Φ_τ(a), and the leaf stores it. -What is left of `Full.result` is step (2) for `a`, the recursive calls of step (3), and at depth -`L` the number that has been reached. The encoding of b […] is computed in the same way, so the -procedure is written once, for the coefficients `c` of any family of linear forms: `c = φ` for -`a`, and `c = ψ` for `b`. +/-- Recursively apply the coefficient family `c` along a term string. Each level replaces the +input array by a weighted sum of its first-letter slices; depth zero returns its unique entry. +Choosing `phi` or `psi` computes the corresponding shared input encoding. -/ def computeEncoding {α : Type} [Fintype α] (c : Term → α → ℤ) : (L : ℕ) → ((Fin L → α) → ℤ) → Leaf L → ℤ @@ -289,8 +270,8 @@ theorem Pruned.result_eq_piecewise_sum {n : ℕ} (encA encB : Leaf n → ℤ) (S rw [hC, Finset.piecewise_eq_of_mem _ _ _ hmem] · rfl -/-- **Lemma 10**, first claim. Let a and b be input arrays, and let U be a set of output strings -of length L. Called at the root, Pruned.result(U) returns bilinearProduct(a, b) restricted to U. +/-- Pruning the two input encodings yields `bilinearProduct` on the requested strings and zero +elsewhere. -/ theorem Pruned.result_eq_piecewise_bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (U : Finset (OutStr L)) : @@ -298,10 +279,8 @@ theorem Pruned.result_eq_piecewise_bilinearProduct {L : ℕ} (a : LeftStr L → -- Sum the products of the encoded numbers over the contributing leaves. Pruned.result_eq_piecewise_sum _ _ U -/-- Section 2.4.2, the specification of `Pruned.result`: it returns the array returned by the -vertex τ₁⋯τ_k of Full.result, restricted to the strings in S. Here `a` and `b` are the input -arrays of that vertex: by `encodeLeft_cons` and `encodeRight_cons`, applied once for each level, -the parts of the two encodings below a vertex are the encodings of its input arrays. +/-- Pruning precomputed input encodings agrees with full evaluation at every requested string and +is zero elsewhere. -/ theorem Pruned.result_eq_piecewise_full {n : ℕ} (a : LeftStr n → ℤ) (b : RightStr n → ℤ) (S : Finset (OutStr n)) : @@ -717,8 +696,8 @@ theorem Pruned.sum_card_passed_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Fi · exact ih _ _ _ _ · simp [sizeAt] -/-- **Lemma 10**, third claim. the sets passed to the calls it makes have total size at most -(L+1) |leaves(U)|. +/-- The sum of the cardinalities of all passed request sets is at most `(L + 1) * (leaves +U).card`. -/ theorem Pruned.totalSize_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) : Pruned.totalSize encA encB U ≤ (L + 1) * (leaves U).card := by @@ -730,15 +709,10 @@ theorem Pruned.totalSize_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset ( sum_le_sum fun k _ => Pruned.sum_card_passed_le _ _ U k _ = (L + 1) * (leaves U).card := by simp -/-- **Lemma 10**, its three claims together. Let a and b be input arrays, and let U be a set of -output strings of length L. Called at the root, Pruned.result(U) returns bilinearProduct(a, b) -restricted to U. The leaves it visits are exactly those of leaves(U), and the sets passed to the -calls it makes have total size at most (L+1) |leaves(U)|. - -NOTE. The hypothesis `0 < L ∨ U.Nonempty` of the second claim is not in the paper. It excludes -only the case `L = 0`, `U = ∅`, in which the root is itself a leaf, so that the call at the root -visits it, while `leaves(∅)` is empty. In the paper `L ≥ m ≥ 1`, and `Pruned.result` is run only -on nonempty sets (Section 2.4.4). +/-- Combine value correctness, the visited-leaf characterization and the passed-set cardinality +bound for the two input encodings. Values and the set-size bound apply at every depth and request +set. The leaf equivalence excludes the empty zero-depth root; +`Pruned.visits_iff_root_or_mem_leaves` gives the unconditional alternative for that case. -/ theorem pruned_spec {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (U : Finset (OutStr L)) : Pruned.result L (encodingL a) (encodingR b) U = U.piecewise (bilinearProduct a b) 0 From 19ee9423823818c8a69e038755ae99648c9a30dc Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:17:03 -0400 Subject: [PATCH 12/32] =?UTF-8?q?feat(wordram):=20count=20Chan=E2=80=93He?= =?UTF-8?q?=20collisions=20with=20native=20histograms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement fixed native residue, histogram and composed collision-counting programs with explicit frames, local word-fit contracts, exact mathematical collision semantics and charged bound 34n + 5M + 18 (halt excluded). Implementation layer for #1454 under #1446/#1394, not full preprocessing. Sorting/frontend, prime searches, heavy extraction, recursion/output and global logarithmic-width/subquadratic-time composition remain open. Curated abstracts: no impact on headline statements, definitions or scope. Validated on base 8e182b091199b323c7e6271acebbfcee2e4cdd18 plus this exact diff: LEAN_NUM_THREADS=1 COMPLEXITYLIB_CACHE_AUTO=0 scripts/check.sh: all six gates passed, including strict test/global axiom audit. Changed-file lints against that base, hygiene --check-simp and git diff --check passed. Final run followed resolution of shared-host disk/memory pressure; failed earlier attempts are not counted as validation. Independent read-only agent correctness/API review by /root/review_native_histogram approved the final source blobs after replacing three private clear helpers with WordRAM.clear. No remaining blockers; optional Count parameter-frame API can accompany its minimum-search client. This is within-fleet agent review, not outside-coordinator or human approval. Outside coordinator review is required before integration. AI assistance: OpenAI Codex implementation/self-review and a separate Codex review context. Native programs/counting proofs are original; existing Anthropic mathematical source credit is preserved unchanged. --- Complexity.lean | 4 + Complexity/Algorithms/WordRAM/Histogram.lean | 402 ++++++++++++++++++ .../WordRAM/ThreeSum/ChanHe/Collisions.lean | 77 ++++ .../WordRAM/ThreeSum/ChanHe/Count.lean | 206 +++++++++ .../WordRAM/ThreeSum/ChanHe/Residues.lean | 211 +++++++++ ComplexityTests/ThreeSumHistogram.lean | 149 +++++++ 6 files changed, 1049 insertions(+) create mode 100644 Complexity/Algorithms/WordRAM/Histogram.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean create mode 100644 ComplexityTests/ThreeSumHistogram.lean diff --git a/Complexity.lean b/Complexity.lean index e6ac945545..10dee5c44a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -88,6 +88,7 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Query.Traversal public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static +public import Complexity.Algorithms.WordRAM.Histogram public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree @@ -100,6 +101,9 @@ public import Complexity.Algorithms.WordRAM.Quicksort public import Complexity.Algorithms.WordRAM.Stack public import Complexity.Algorithms.WordRAM.Stack.Controller public import Complexity.Algorithms.WordRAM.ThreeSum.Cell +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Memory public import Complexity.Algorithms.WordRAM.ThreeSum.Weight diff --git a/Complexity/Algorithms/WordRAM/Histogram.lean b/Complexity/Algorithms/WordRAM/Histogram.lean new file mode 100644 index 0000000000..da7669c784 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/Histogram.lean @@ -0,0 +1,402 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Copy +public import Mathlib.Algebra.BigOperators.Group.Finset.Basic +public import Mathlib.Data.Finset.Prod +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum + +/-! +# Charged word-RAM histograms + +A fixed program clears a histogram and inserts the keys of a disjoint input array. +An insertion into a bucket of size `c` creates `2c` ordered collisions. Every address +calculation, load, increment, store and loop test is charged. + +## Main definitions + +* `Histogram.count`: bucket sizes in an input prefix. +* `Histogram.pairs`: ordered collisions accumulated by prefix insertion. +* `Histogram.program`: clear the buckets, then insert the input keys. + +## Main results + +* `Histogram.exec`: histogram construction and collision counting with a linear instruction bound. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.Histogram + +open Cslib.WordRAM +open Finset + +/-- Number of occurrences of a key among the first `k` input positions. -/ +def count (keys : ℕ → ℕ) (k b : ℕ) : ℕ := #{i ∈ range k | keys i = b} + +@[simp] theorem count_zero (keys : ℕ → ℕ) (b : ℕ) : count keys 0 b = 0 := by + simp [count] + +/-- Inserting one key changes just its bucket. -/ +theorem count_succ (keys : ℕ → ℕ) (k b : ℕ) : + count keys (k + 1) b = count keys k b + if keys k = b then 1 else 0 := by + by_cases h : keys k = b <;> simp [count, range_add_one, filter_insert, h] + +/-- A bucket cannot contain more positions than the prefix. -/ +theorem count_le (keys : ℕ → ℕ) (k b : ℕ) : count keys k b ≤ k := + (card_filter_le _ _).trans_eq (card_range k) + +/-- Ordered collisions accumulated while inserting successive keys. -/ +def pairs (keys : ℕ → ℕ) : ℕ → ℕ + | 0 => 0 + | k + 1 => pairs keys k + 2 * count keys k (keys k) + +/-- The prefix collision counter is at most the square of the prefix length. -/ +theorem pairs_le_sq (keys : ℕ → ℕ) (k : ℕ) : pairs keys k ≤ k * k := by + induction k with + | zero => simp [pairs] + | succ k ih => + have := count_le keys k (keys k) + dsimp only [pairs] + nlinarith + +/-- The insertion counter counts exactly the ordered pairs of different positions +with the same key, including when equal keys occur at different input positions. -/ +theorem pairs_eq_card (keys : ℕ → ℕ) (k : ℕ) : + pairs keys k = #{p ∈ (range k).offDiag | keys p.1 = keys p.2} := by + induction k with + | zero => simp [pairs] + | succ k ih => + let S := range k + let A := {p ∈ S.offDiag | keys p.1 = keys p.2} + let B := {p ∈ {k} ×ˢ S | keys p.1 = keys p.2} + let C := {p ∈ S ×ˢ {k} | keys p.1 = keys p.2} + have hrow : B = {k} ×ˢ {i ∈ S | keys i = keys k} := by + ext p + simp only [B, mem_filter, mem_product, mem_singleton] + aesop + have hcol : C = {i ∈ S | keys i = keys k} ×ˢ {k} := by + ext p + simp only [C, mem_filter, mem_product, mem_singleton] + aesop + have hAB : Disjoint A B := by + rw [disjoint_left] + intro p hp hq + have hp' := (mem_offDiag.mp (mem_filter.mp hp).1).1 + have hq' := mem_singleton.mp (mem_product.mp (mem_filter.mp hq).1).1 + have : p.1 < k := mem_range.mp hp' + omega + have hAC : Disjoint (A ∪ B) C := by + rw [disjoint_left] + intro p hp hq + have hq' := mem_singleton.mp (mem_product.mp (mem_filter.mp hq).1).2 + rcases mem_union.mp hp with hp | hp + · have hp' := (mem_offDiag.mp (mem_filter.mp hp).1).2.1 + have : p.2 < k := mem_range.mp hp' + omega + · have hp' := (mem_product.mp (mem_filter.mp hp).1).2 + have : p.2 < k := mem_range.mp hp' + omega + have hsplit : {p ∈ (range (k + 1)).offDiag | keys p.1 = keys p.2} = A ∪ B ∪ C := by + rw [range_add_one, offDiag_insert (notMem_range_self : k ∉ range k), + filter_union, filter_union] + rw [hsplit, card_union_of_disjoint hAC, card_union_of_disjoint hAB, hrow, hcol, + card_product, card_product, card_singleton, one_mul, mul_one] + change pairs keys k + 2 * count keys k (keys k) = + #{p ∈ (range k).offDiag | keys p.1 = keys p.2} + count keys k (keys k) + + count keys k (keys k) + rw [ih] + omega + +/-- The original memory, with the histogram block replaced by prefix bucket counts. -/ +def memory (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k : ℕ) : ℕ → ℕ := + fun a => if dst ≤ a ∧ a < dst + bins then count keys k (a - dst) else mem a + +/-- Reading a histogram bucket. -/ +theorem memory_read (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k b : ℕ) + (hb : b < bins) : memory mem keys dst bins k (dst + b) = count keys k b := by + simp [memory, hb] + +/-- Histogram writes preserve all other cells. -/ +theorem memory_outside (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k a : ℕ) + (ha : ¬(dst ≤ a ∧ a < dst + bins)) : memory mem keys dst bins k a = mem a := by + simp [memory, ha] + +/-- One insertion is a single histogram-cell update. -/ +theorem memory_succ (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k : ℕ) + (hk : keys k < bins) : + memory mem keys dst bins (k + 1) = Function.update (memory mem keys dst bins k) + (dst + keys k) (count keys k (keys k) + 1) := by + funext a + by_cases ha : a = dst + keys k + · subst a + simp [memory_read, hk, count_succ] + · rw [Function.update_of_ne ha] + simp only [memory] + split_ifs with h + · have hne : keys k ≠ a - dst := by omega + simp [count_succ, hne] + · rfl + +/-- Input base. -/ +abbrev src : Fin 11 := 0 +/-- Histogram base. -/ +abbrev dst : Fin 11 := 1 +/-- Number of input keys. -/ +abbrev len : Fin 11 := 2 +/-- Number of buckets. -/ +abbrev bins : Fin 11 := 3 +/-- Prefix length. -/ +abbrev idx : Fin 11 := 4 +/-- Temporary address. -/ +abbrev addr : Fin 11 := 5 +/-- Current key. -/ +abbrev key : Fin 11 := 6 +/-- Current bucket count. -/ +abbrev bucketSize : Fin 11 := 7 +/-- Number of ordered collisions. -/ +abbrev total : Fin 11 := 8 +/-- Constant one. -/ +abbrev one : Fin 11 := 9 +/-- Constant zero. -/ +abbrev zero : Fin 11 := 10 + +/-- Clear one histogram cell and advance the cursor. -/ +def clearBody : Code .standard 11 := ram[ + addr ← dst + idx; *addr ← zero; idx ← idx + one ] + +/-- Insert one key and add twice its old bucket count. -/ +def body : Code .standard 11 := ram[ + addr ← src + idx; key ← *addr; addr ← dst + key; + bucketSize ← *addr; total ← total + bucketSize; total ← total + bucketSize; + bucketSize ← bucketSize + one; *addr ← bucketSize; idx ← idx + one ] + +/-- Clear the histogram, retaining the input and all four parameters. -/ +def clear : Code .standard 11 := + .seq (ram[ idx ← #0; one ← #1; zero ← #0 ]) (.loop (.lt idx bins) clearBody) + +/-- Insert all keys into a cleared histogram. -/ +def insert : Code .standard 11 := + .seq (ram[ idx ← #0; total ← #0 ]) (.loop (.lt idx len) body) + +/-- Fixed code, with all bases and lengths supplied in registers. -/ +def program : Code .standard 11 := .seq clear insert + +private theorem exec_clear {w : ℕ} (x : Data 11) (h1 : Fits w 1) + (hd : Fits w (x.regs dst + x.regs bins)) : + ∃ y, ExecWithin w clear x y (5 * x.regs bins + 5) ∧ + y.mem = Cslib.WordRAM.clear x.mem (x.regs dst) (x.regs bins) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 := by + let I (y : Data 11) : Prop := + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs zero = 0 ∧ + y.regs idx ≤ x.regs bins ∧ y.mem = Cslib.WordRAM.clear x.mem (x.regs dst) (y.regs idx) + have hbody (y : Data 11) (hy : I y) (hc : (Cond.lt idx bins).Holds y) : + ∃ z, ExecWithin w clearBody y z 3 ∧ I z ∧ + x.regs bins - z.regs idx < x.regs bins - y.regs idx := by + obtain ⟨hr, ho, hz, hi, hm⟩ := hy + have hb := hr bins (by decide) + have hdst := hr dst (by decide) + have hlt : y.regs idx < x.regs bins := by simpa only [Cond.holds_lt, hb] using hc + have ha : Fits w (y.regs dst + y.regs idx) := hd.of_le (by rw [hdst]; omega) + have hn : Fits w (y.regs idx + 1) := hd.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [clearBody, ha, hn, ho] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hii] using hr i hi + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using hz + · simpa [ho] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem, Data.set_regs, Function.update_apply] + rw [hz, hm, hdst, ← Cslib.WordRAM.clear_succ] + rfl + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := ((x.set idx 0).set one 1).set zero 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hiz : i ≠ zero := by intro h; subst i; norm_num [zero] at hi + have hio : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hiz, Function.update_of_ne hio, + Function.update_of_ne hii] + · simp +decide [x₀, one, zero, idx] + · simp [x₀] + · simp +decide [x₀, one, zero, idx] + · simp +decide [x₀, one, zero, idx, Cslib.WordRAM.clear_zero] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs bins - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hz, hi, hm⟩ := hy + have hb := hr bins (by decide) + have hend : y.regs idx = x.regs bins := by + simp only [Cond.holds_lt, hb] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, hr, ho⟩ + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; one ← #1; zero ← #0 ]) x x₀ 3 := by + ram_eval [x₀, h1] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, one, zero, ↓reduceIte, + Nat.sub_zero] + omega + +private theorem exec_insert {w : ℕ} (x : Data 11) (keys : ℕ → ℕ) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs bins)) + (ht : Fits w (x.regs len * x.regs len + x.regs len + 1)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hkeys : ∀ i < x.regs len, keys i < x.regs bins) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = keys i) + (hzero : ∀ b < x.regs bins, x.mem (x.regs dst + b) = 0) + (ho : x.regs one = 1) : + ∃ y, ExecWithin w insert x y (11 * x.regs len + 4) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (x.regs len) ∧ + y.regs total = pairs keys (x.regs len) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 := by + let I (y : Data 11) : Prop := + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ + y.regs idx ≤ x.regs len ∧ y.regs total = pairs keys (y.regs idx) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (y.regs idx) + have hbody (y : Data 11) (hy : I y) (hc : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 9 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, ho, hi, hp, hm⟩ := hy + have hl := hr len (by decide) + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hc + have hkey := hkeys _ hlt + have hread : y.mem (y.regs src + y.regs idx) = keys (y.regs idx) := by + rw [hm, hsrc, memory_outside] + · exact hinput _ hlt + · omega + have hbucket : y.mem (y.regs dst + keys (y.regs idx)) = + count keys (y.regs idx) (keys (y.regs idx)) := by + rw [hm, hdst, memory_read _ _ _ _ _ _ hkey] + have ha : Fits w (y.regs src + y.regs idx) := hs.of_le (by rw [hsrc]; omega) + have hb : Fits w (y.regs dst + keys (y.regs idx)) := hd.of_le (by rw [hdst]; omega) + have hn : Fits w (y.regs idx + 1) := ht.of_le (by omega) + have hc := count_le keys (y.regs idx) (keys (y.regs idx)) + have hpnext := pairs_le_sq keys (y.regs idx + 1) + have hsquare : (y.regs idx + 1) * (y.regs idx + 1) ≤ x.regs len * x.regs len := + Nat.mul_self_le_mul_self (by omega) + have ht₂ : Fits w (pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx)) + + count keys (y.regs idx) (keys (y.regs idx))) := ht.of_le (by + dsimp only [pairs] at hpnext + omega) + have ht₁ : Fits w (pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx))) := + ht₂.of_le (by omega) + have hcount : Fits w (count keys (y.regs idx) (keys (y.regs idx)) + 1) := + ht.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ha, hb, hn, ho, hread, hbucket, hp, ht₁, ht₂, hcount] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hik : i ≠ key := by intro h; subst i; norm_num [key] at hi + have hic : i ≠ bucketSize := by intro h; subst i; norm_num [bucketSize] at hi + have hit : i ≠ total := by intro h; subst i; norm_num [total] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hik, Function.update_of_ne hic, Function.update_of_ne hit, + Function.update_of_ne hii] using hr i hi + · simpa +decide [Function.update_apply] using ho + · simpa [ho] using Nat.succ_le_of_lt hlt + · change pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx)) + + count keys (y.regs idx) (keys (y.regs idx)) = pairs keys (y.regs idx + 1) + simp only [pairs, two_mul, Nat.add_assoc] + · change Function.update y.mem (y.regs dst + keys (y.regs idx)) + (count keys (y.regs idx) (keys (y.regs idx)) + 1) = + memory x.mem keys (x.regs dst) (x.regs bins) (y.regs idx + 1) + rw [hm, hdst, ← memory_succ _ _ _ _ _ hkey] + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := (x.set idx 0).set total 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hit : i ≠ total := by intro h; subst i; norm_num [total] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hit, Function.update_of_ne hii] + · simpa +decide [x₀, one, total, idx] using ho + · simp +decide [x₀, total, idx] + · simp +decide [x₀, total, idx, pairs] + · change x.mem = memory x.mem keys (x.regs dst) (x.regs bins) 0 + funext a + by_cases h : x.regs dst ≤ a ∧ a < x.regs dst + x.regs bins + · have ha : x.regs dst + (a - x.regs dst) = a := by omega + simpa [memory, h, ha] using hzero (a - x.regs dst) (by omega) + · simp only [memory, h, ↓reduceIte] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hi, hp, hm⟩ := hy + have hl := hr len (by decide) + have hend : y.regs idx = x.regs len := by + simp only [Cond.holds_lt, hl] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, by simpa only [hend] using hp, hr, ho⟩ + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; total ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, total, ↓reduceIte, + Nat.sub_zero] + omega + +/-- Build a histogram and count ordered collisions within `5M + 11n + 9` native instructions. +The input keys lie in `[0,M)`. The input precedes the output block. Both one-past-end +addresses and the quadratic counter bound must fit. These are explicit local contracts; +constructing the keys and deriving a logarithmic width are separate client obligations. -/ +theorem exec {w : ℕ} (x : Data 11) (keys : ℕ → ℕ) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs bins)) + (ht : Fits w (x.regs len * x.regs len + x.regs len + 1)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hkeys : ∀ i < x.regs len, keys i < x.regs bins) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = keys i) : + ∃ y, ExecWithin w program x y (5 * x.regs bins + 11 * x.regs len + 9) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (x.regs len) ∧ + y.regs total = pairs keys (x.regs len) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) := by + obtain ⟨y, he, hm, hr, ho⟩ := exec_clear x h1 hd + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hlen := hr len (by decide) + have hbins := hr bins (by decide) + obtain ⟨z, hz, hzm, hzt, hzr, _⟩ := exec_insert y keys + (by simpa only [hsrc, hlen] using hs) (by simpa only [hdst, hbins] using hd) + (by simpa only [hlen] using ht) (by simpa only [hsrc, hlen, hdst] using hdisj) + (by simpa only [hlen, hbins] using hkeys) (by + intro i hi + rw [hsrc, hm] + have hlt : i < x.regs len := by simpa only [hlen] using hi + simp only [Cslib.WordRAM.clear, show ¬(x.regs dst ≤ x.regs src + i ∧ + x.regs src + i < x.regs dst + x.regs bins) from by omega, ↓reduceIte] + exact hinput i hlt) (by + intro b hb + rw [hm, hdst] + have hb' : b < x.regs bins := by simpa only [hbins] using hb + simp [Cslib.WordRAM.clear, hb']) ho + refine ⟨z, ?_, ?_, by simpa only [hlen] using hzt, fun i hi => (hzr i hi).trans (hr i hi)⟩ + · exact (he.seq hz).mono (by rw [hlen]; omega) + · rw [hzm, hm, hdst, hbins, hlen] + funext a + simp only [memory, Cslib.WordRAM.clear] + split_ifs <;> rfl + +end Cslib.Algorithms.WordRAM.Histogram diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean new file mode 100644 index 0000000000..6b18f5850d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean @@ -0,0 +1,77 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.Histogram +public import Complexity.Algorithms.ThreeSum.ChanHe.Collisions + +/-! +# Histogram collision counts for Chan–He + +The native histogram counts ordered pairs of different input positions. For an injective +array of integers and its Euclidean residue keys, this is exactly the existing Chan–He +collision count. Injectivity is required because the mathematical reduction operates on sets. +Equal input values must first be handled by the distinct-position frontend. + +## Main results + +* `coll_eq_histogramPairs`: the existing `coll` is the counter computed by the histogram. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +open Finset +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- For distinct array values, histogramming Euclidean residues computes exactly the +ordered distinct-element collision count of the existing Chan–He reduction. -/ +theorem coll_eq_histogramPairs (values : ℕ → ℤ) (n M : ℕ) (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) : + coll ((range n).image values) M = + Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) n := by + rw [Histogram.pairs_eq_card] + symm + refine card_bij (fun p _ => (values p.1, values p.2)) ?_ ?_ ?_ + · intro p hp + obtain ⟨hp, heq⟩ := mem_filter.mp hp + obtain ⟨hp₁, hp₂, hne⟩ := mem_offDiag.mp hp + refine mem_filter.mpr ⟨mem_offDiag.mpr ⟨mem_image.mpr ⟨p.1, hp₁, rfl⟩, + mem_image.mpr ⟨p.2, hp₂, rfl⟩, fun h => hne (hinj hp₁ hp₂ h)⟩, ?_⟩ + apply (natCast_dvd_sub_iff_emod_eq M _ _).mpr + change values p.1 % (M : ℤ) = values p.2 % (M : ℤ) + have hpos : (0 : ℤ) < M := by exact_mod_cast hM + have h₁ := Int.emod_nonneg (values p.1) (ne_of_gt hpos) + have h₂ := Int.emod_nonneg (values p.2) (ne_of_gt hpos) + omega + · intro p hp q hq heq + have hp' := mem_offDiag.mp (mem_filter.mp hp).1 + have hq' := mem_offDiag.mp (mem_filter.mp hq).1 + exact Prod.ext (hinj hp'.1 hq'.1 (congrArg Prod.fst heq)) + (hinj hp'.2.1 hq'.2.1 (congrArg Prod.snd heq)) + · intro p hp + obtain ⟨hp, hdvd⟩ := mem_filter.mp hp + obtain ⟨hp₁, hp₂, hne⟩ := mem_offDiag.mp hp + obtain ⟨i, hi, hival⟩ := mem_image.mp hp₁ + obtain ⟨j, hj, hjval⟩ := mem_image.mp hp₂ + refine ⟨(i, j), mem_filter.mpr ⟨mem_offDiag.mpr ⟨hi, hj, ?_⟩, ?_⟩, ?_⟩ + · intro hij + change i = j at hij + apply hne + rw [← hival, ← hjval, hij] + · have heq := (natCast_dvd_sub_iff_emod_eq M _ _).mp hdvd + change (values i % (M : ℤ)).toNat = (values j % (M : ℤ)).toNat + rw [hival, hjval, heq] + · exact Prod.ext hival hjval + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean new file mode 100644 index 0000000000..677b2c2152 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -0,0 +1,206 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues + +/-! +# Native Chan–He collision counting + +This fixed native program first decodes signed Euclidean residue keys, then builds a +histogram and counts ordered collisions. Register setup between the two loops is charged. +For injective input arrays its counter is exactly the existing Chan–He `coll`. + +## Main definitions + +* `Count.program`: the composed native residue/histogram program. +* `Count.memory`: the complete output memory, including the residue scratch array. + +## Main results + +* `Count.exec`: execution in at most `34n + 5M + 18` native instructions. +* `Count.exec_coll`: the counter equals the existing mathematical collision count. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Residue routine registers occupy the first sixteen registers. -/ +def residuesRegisters (i : Fin 16) : Fin 28 := ⟨i.val, by omega⟩ + +/-- Histogram registers occupy registers sixteen through twenty-six. -/ +def histogramRegisters (i : Fin 11) : Fin 28 := ⟨16 + i.val, by omega⟩ + +private theorem residues_injective : Function.Injective residuesRegisters := by + intro i j h + apply Fin.ext + exact congrArg (fun i : Fin 28 => i.val) h + +private theorem histogram_injective : Function.Injective histogramRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp [histogramRegisters] at this + omega + +/-- Encoded integer input base. -/ +abbrev src : Fin 28 := 9 +/-- Residue scratch base. -/ +abbrev scratch : Fin 28 := 10 +/-- Number of input values. -/ +abbrev len : Fin 28 := 11 +/-- Encoded positive modulus. -/ +abbrev modulus : Fin 28 := 12 +/-- Histogram destination base. -/ +abbrev dst : Fin 28 := 27 +/-- Output collision count, an ordinary unsigned word. -/ +abbrev total : Fin 28 := 24 + +/-- Histogram input base register. -/ +abbrev hsrc : Fin 28 := histogramRegisters Histogram.src +/-- Histogram output base register. -/ +abbrev hdst : Fin 28 := histogramRegisters Histogram.dst +/-- Histogram length register. -/ +abbrev hlen : Fin 28 := histogramRegisters Histogram.len +/-- Histogram modulus register. -/ +abbrev hbins : Fin 28 := histogramRegisters Histogram.bins +/-- Histogram constant register, also used to decode the encoded modulus. -/ +abbrev hone : Fin 28 := histogramRegisters Histogram.one + +/-- Set up the histogram parameters, explicitly decoding the positive modulus. -/ +def prepare : Code .standard 28 := ram[ + hsrc ← scratch; hdst ← dst; hlen ← len; hone ← #2; hbins ← modulus / hone ] + +/-- A single finite native program, independent of input length, modulus and word width. -/ +def program : Code .standard 28 := + .seq (Residues.program.rename residuesRegisters) + (.seq prepare (Histogram.program.rename histogramRegisters)) + +/-- The residue scratch array and the complete histogram, framed by the original memory. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst : ℕ) : ℕ → ℕ := + Histogram.memory (Residues.memory mem values M scratch n) + (fun i => (values i % (M : ℤ)).toNat) dst M n + +/-- Complete native collision counting, including residue computation, clearing the histogram +and register setup. Input, scratch and histogram blocks occur in that order and are disjoint. +The final halt is excluded. Fit hypotheses are local; a caller must derive them from its +input and width contract. -/ +theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hk : Fits (w + 2) (x.regs scratch + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + M)) + (ht : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1)) + (hsk : x.regs src + x.regs len ≤ x.regs scratch) + (hkd : x.regs scratch + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : + ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ + y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) := by + obtain ⟨a, ha, ham, har⟩ := Residues.exec (x.comap residuesRegisters) values hM hmod + hmodreg hfit hs hk hsk hinput + let y := a.extend residuesRegisters x + have hy : ExecWithin (w + 2) (Residues.program.rename residuesRegisters) x y + (23 * x.regs len + 4) := ha.rename residues_injective + have hyr (i : Fin 16) : y.regs (residuesRegisters i) = a.regs i := + residues_injective.extend_apply _ _ i + have hys : y.regs scratch = x.regs scratch := + (hyr Residues.dst).trans (har Residues.dst (by decide)) + have hyl : y.regs len = x.regs len := (hyr Residues.len).trans (har Residues.len (by decide)) + have hym : y.regs modulus = encode (M : ℤ) := + (hyr Residues.modulus).trans ((har Residues.modulus (by decide)).trans hmodreg) + have hyd : y.regs dst = x.regs dst := by + change Function.extend residuesRegisters a.regs x.regs dst = x.regs dst + apply Function.extend_apply' + rintro ⟨i, hi⟩ + have := congrArg Fin.val hi + dsimp [residuesRegisters, dst] at this + omega + let q := ((((y.set (histogramRegisters Histogram.src) (x.regs scratch)).set + (histogramRegisters Histogram.dst) (x.regs dst)).set + (histogramRegisters Histogram.len) (x.regs len)).set + (histogramRegisters Histogram.one) 2).set (histogramRegisters Histogram.bins) M + have htwo : Fits (w + 2) 2 := (show Fits 2 2 from by decide).mono (by omega) + have hraw : Fits (w + 2) M := + ((encode_fits hmod).mono (by omega)).of_le (by + simp only [encode, Int.natAbs_natCast] + omega) + have hprep : ExecWithin (w + 2) prepare y q 5 := by + ram_eval [prepare, q, hys, hyl, hym, hyd, htwo, hraw, histogramRegisters, + Histogram.src, Histogram.dst, Histogram.len, Histogram.one, Histogram.bins, + hsrc, hdst, hlen, hbins, hone, scratch, len, modulus, dst, Fin.mk.injEq, + encode_div_two, Int.natAbs_natCast] + have hqs : (q.comap histogramRegisters).regs Histogram.src = x.regs scratch := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hqd : (q.comap histogramRegisters).regs Histogram.dst = x.regs dst := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hql : (q.comap histogramRegisters).regs Histogram.len = x.regs len := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hqb : (q.comap histogramRegisters).regs Histogram.bins = M := by + simp [q] + have hqm : q.mem = Residues.memory x.mem values M (x.regs scratch) (x.regs len) := ham + have h1 : Fits (w + 2) 1 := (show Fits 2 1 from by decide).mono (by omega) + obtain ⟨b, hb, hbm, hbt, _⟩ := Histogram.exec (q.comap histogramRegisters) + (fun i => (values i % (M : ℤ)).toNat) h1 + (by simpa only [hqs, hql] using hk) (by simpa only [hqd, hqb] using hd) + (by simpa only [hql] using ht) (by simpa only [hqs, hql, hqd] using hkd) + (by + intro i hi + rw [hqb] + have hnonneg := Int.emod_nonneg (values i) (show (M : ℤ) ≠ 0 by omega) + have hlt := Int.emod_lt_of_pos (values i) (show (0 : ℤ) < M by omega) + omega) + (by + intro i hi + have hi' : i < x.regs len := by simpa only [hql] using hi + rw [Data.comap_mem, hqm, hqs] + simp [Residues.memory, hi']) + refine ⟨b.extend histogramRegisters q, ?_, ?_, ?_⟩ + · exact (hy.seq (hprep.seq (hb.rename histogram_injective))).mono (by rw [hql, hqb]; omega) + · change b.mem = _ + rw [hbm, Data.comap_mem, hqm, hqd, hqb, hql] + rfl + · change Function.extend histogramRegisters b.regs q.regs + (histogramRegisters Histogram.total) = _ + rw [histogram_injective.extend_apply, hbt, hql] + +/-- For an injective integer array, the composed program returns exactly the collision +count used by the existing Chan–He prime searches. -/ +theorem exec_coll {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hk : Fits (w + 2) (x.regs scratch + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + M)) + (ht : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1)) + (hsk : x.regs src + x.regs len ≤ x.regs scratch) + (hkd : x.regs scratch + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) + (hinj : Set.InjOn values (↑(Finset.range (x.regs len)) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ + y.regs total = Cslib.Algorithms.ThreeSum.ChanHe.coll + ((Finset.range (x.regs len)).image values) M := by + obtain ⟨y, hy, hm, ht⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput + exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean new file mode 100644 index 0000000000..4cdbd3eef3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean @@ -0,0 +1,211 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Signed.Arithmetic +public import Complexity.Computability.WordRAM.Copy +import Mathlib.Tactic.NormNum + +/-! +# Native Euclidean residue arrays + +The input holds signed encodings. The output holds ordinary unsigned residue keys, ready +for histogram construction. In particular a nonnegative encoded integer is explicitly decoded +by division by two before it is used as a histogram index. + +## Main definitions + +* `Residues.program`: a fixed loop producing Euclidean residue keys. + +## Main results + +* `Residues.exec`: charged execution preserving the input and all cells outside the output. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Residues + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Encoded integer input base. -/ +abbrev src : Fin 16 := 9 +/-- Unsigned residue output base. -/ +abbrev dst : Fin 16 := 10 +/-- Input length. -/ +abbrev len : Fin 16 := 11 +/-- Encoded modulus. -/ +abbrev modulus : Fin 16 := 12 +/-- Loop counter. -/ +abbrev idx : Fin 16 := 13 +/-- Address scratch. -/ +abbrev addr : Fin 16 := 14 +/-- Constant one. -/ +abbrev one : Fin 16 := 15 + +/-- The signed remainder block followed by explicit unsigned decoding and a store. -/ +def body : Code .standard 16 := + .seq (ram[ addr ← src + idx; left ← *addr; right ← modulus ]) + (.seq Signed.modulo (ram[ two ← #2; result ← result / two; + addr ← dst + idx; *addr ← result; idx ← idx + one ])) + +/-- All parameters are supplied at runtime; registers and code are independent +of length and width. -/ +def program : Code .standard 16 := + .seq (ram[ idx ← #0; one ← #1 ]) (.loop (.lt idx len) body) + +/-- Memory after writing a prefix of unsigned residue keys. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst k : ℕ) : ℕ → ℕ := + fun a => if dst ≤ a ∧ a < dst + k then (values (a - dst) % (M : ℤ)).toNat else mem a + +@[simp] theorem memory_zero (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst : ℕ) : + memory mem values M dst 0 = mem := by + funext a + simp [memory] + +private theorem memory_succ (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst k : ℕ) : + memory mem values M dst (k + 1) = Function.update (memory mem values M dst k) + (dst + k) (values k % (M : ℤ)).toNat := by + funext a + by_cases ha : a = dst + k + · subst a + simp [memory] + · rw [Function.update_of_ne ha] + simp only [memory] + have h : (dst ≤ a ∧ a < dst + (k + 1)) ↔ (dst ≤ a ∧ a < dst + k) := by omega + simp only [h] + +/-- Convert `n` encoded signed integers to unsigned Euclidean residue keys in at most +`23n + 4` native instructions. The modulus is positive and encoded in its parameter register; +addresses are ordinary unsigned words. The final halt is excluded. -/ +theorem exec {w M : ℕ} (x : Data 16) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + x.regs len)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : + ∃ y, ExecWithin (w + 2) program x y (23 * x.regs len + 4) ∧ + y.mem = memory x.mem values M (x.regs dst) (x.regs len) ∧ + (∀ i, 9 ≤ i.val ∧ i.val ≤ 12 → y.regs i = x.regs i) := by + let I (y : Data 16) : Prop := + (∀ i, 9 ≤ i.val ∧ i.val ≤ 12 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ + y.regs idx ≤ x.regs len ∧ + y.mem = memory x.mem values M (x.regs dst) (y.regs idx) + have hbody (y : Data 16) (hy : I y) (hc : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin (w + 2) body y z 21 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, ho, hi, hm⟩ := hy + have hl := hr len (by decide) + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hmod' := hr modulus (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hc + have hread : y.mem (y.regs src + y.regs idx) = encode (values (y.regs idx)) := by + rw [hm, hsrc] + simp only [memory, show ¬(x.regs dst ≤ x.regs src + y.regs idx ∧ + x.regs src + y.regs idx < x.regs dst + y.regs idx) from by omega, ↓reduceIte] + exact hinput _ hlt + have ha : Fits (w + 2) (y.regs src + y.regs idx) := hs.of_le (by rw [hsrc]; omega) + let p := ((y.set addr (y.regs src + y.regs idx)).set left + (encode (values (y.regs idx)))).set right (encode (M : ℤ)) + have hp : ExecWithin (w + 2) + (ram[ addr ← src + idx; left ← *addr; right ← modulus ]) y p 3 := by + ram_eval [p, ha, hread, hmod', hmodreg, addr, src, idx, left, right, modulus, Fin.mk.injEq] + obtain ⟨z, hz, hzval, hzmem, hzreg⟩ := Signed.exec_modulo (hfit _ hlt) hmod p + (by simp +decide [p, left, right]) (by simp [p]) + have hzr (i : Fin 16) (hi : 9 ≤ i.val) : z.regs i = p.regs i := hzreg i (Or.inr hi) + have hzi : z.regs idx = y.regs idx := by + simpa +decide [p, idx, addr, left, right] using hzr idx (by decide) + have hzo : z.regs one = 1 := by + simpa +decide [p, one, addr, left, right] using (hzr one (by decide)).trans + (show p.regs one = 1 by simpa +decide [p, one, addr, left, right] using ho) + have hzd : z.regs dst = x.regs dst := by + simpa +decide [p, dst, addr, left, right] using (hzr dst (by decide)).trans + (show p.regs dst = x.regs dst by simpa +decide [p, dst, addr, left, right] using hdst) + have hnonneg := Int.emod_nonneg (values (y.regs idx)) (show (M : ℤ) ≠ 0 by omega) + have hdecode : z.regs result / 2 = (values (y.regs idx) % (M : ℤ)).toNat := by + rw [hzval, GenericRAM.Signed.Arithmetic.eval, encode_div_two] + omega + have hb : Fits (w + 2) (z.regs dst + z.regs idx) := hd.of_le (by rw [hzd, hzi]; omega) + have hn : Fits (w + 2) (z.regs idx + 1) := hs.of_le (by rw [hzi]; omega) + have htwo : Fits (w + 2) 2 := (show Fits 2 2 from by decide).mono (by omega) + have hrem : Fits (w + 2) (z.regs result / 2) := by + rw [hdecode] + have hlt' := Int.emod_lt_of_pos (values (y.regs idx)) (show (0 : ℤ) < M by omega) + exact ((encode_fits hmod).mono (by omega)).of_le (by + simp only [encode, Int.natAbs_natCast, show ¬(M : ℤ) < 0 from by omega, ↓reduceIte] + omega) + refine ⟨?q, ?run, ?inv, ?decrease⟩ + case run => + have htail : ExecWithin (w + 2) + (ram[ two ← #2; result ← result / two; addr ← dst + idx; + *addr ← result; idx ← idx + one ]) z ?q 5 := by + ram_eval [htwo, hrem, hb, hn, hzo, two, result, addr, dst, idx, one, Fin.mk.injEq] + exact (hp.seq (hz.seq htail)) + case inv => + refine ⟨?_, ?_, ?_, ?_⟩ + · intro i hi + have hip : p.regs i = y.regs i := by + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hil : i ≠ left := by intro h; subst i; norm_num [left] at hi + have hir : i ≠ right := by intro h; subst i; norm_num [right] at hi + simp only [p, Data.set_regs, Function.update_of_ne hia, Function.update_of_ne hil, + Function.update_of_ne hir] + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + have hit : i ≠ two := by intro h; subst i; norm_num [two] at hi + have hir : i ≠ result := by intro h; subst i; norm_num [result] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hii, Function.update_of_ne hit, Function.update_of_ne hir] + using (hzr i hi.1).trans (hip.trans (hr i hi)) + · simpa +decide [Function.update_apply] using hzo + · simpa +decide [Function.update_apply, hzi, hzo] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem, Data.set_regs, Function.update_apply] + rw [hdecode, hzmem] + change Function.update y.mem (z.regs dst + z.regs idx) + (values (y.regs idx) % (M : ℤ)).toNat = _ + rw [hm, hzd, hzi, ← memory_succ] + rfl + case decrease => + change x.regs len - (z.regs idx + 1) < x.regs len - y.regs idx + rw [hzi] + omega + let x₀ := (x.set idx 0).set one 1 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_⟩ + · intro i hi + have hio : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hio, Function.update_of_ne hii] + · simp [x₀] + · simp +decide [x₀, idx, one] + · simp +decide [x₀, idx, one] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hi, hm⟩ := hy + have hl := hr len (by decide) + have hend : y.regs idx = x.regs len := by + simp only [Cond.holds_lt, hl] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, hr⟩ + have h1 : Fits (w + 2) 1 := (show Fits 2 1 from by decide).mono (by omega) + have hinit : ExecWithin (B := .standard) (w + 2) (ram[ idx ← #0; one ← #1 ]) x x₀ 2 := by + ram_eval [x₀, h1] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, one, ↓reduceIte, + Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Residues diff --git a/ComplexityTests/ThreeSumHistogram.lean b/ComplexityTests/ThreeSumHistogram.lean new file mode 100644 index 0000000000..23081d4413 --- /dev/null +++ b/ComplexityTests/ThreeSumHistogram.lean @@ -0,0 +1,149 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count + +/-! # Executed histogram and residue-array regressions -/ + +namespace ComplexityTests.ThreeSumHistogram + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM + +private def keys (i : ℕ) : ℕ := [0, 1, 0, 0].getD i 0 + +private def input : Data 11 := + (((Data.init [0, 1, 0, 0]).set Histogram.src 1).set Histogram.dst 10).set Histogram.len 4 + |>.set Histogram.bins 3 + +/-- Clearing ignores the old contents, including nonzero words in the output block. -/ +example : (Code.eval 8 4 Histogram.program (input.write 10 77)).map + (fun yn => (yn.1.mem 10, yn.1.mem 11, yn.1.mem 12, yn.1.regs Histogram.total, yn.2)) = + some (3, 1, 0, 6, 68) := by decide + +/-- Read the same result through the generic charged theorem, checking both frames. -/ +example : ∃ y, ExecWithin 8 Histogram.program input y 68 ∧ + y.mem 10 = 3 ∧ y.mem 11 = 1 ∧ y.regs Histogram.total = 6 ∧ + y.mem 0 = 4 ∧ y.mem 13 = 0 := by + obtain ⟨y, hy, hm, ht, _⟩ := Histogram.exec (w := 8) input keys + (by decide) (by decide) (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_, ?_, ?_⟩ + · rw [hm]; decide + · rw [hm]; decide + · rw [ht]; decide + · rw [hm]; decide + · rw [hm]; decide + +/-- Empty inputs and empty histograms pay only setup and two failed loop tests. -/ +example : (Code.eval 8 0 Histogram.program + ((input.set Histogram.len 0).set Histogram.bins 0)).map + (fun yn => (yn.1.regs Histogram.total, yn.2)) = some (0, 9) := by decide + +/-- A singleton has no collision; two equal keys at different positions create two. -/ +example : Histogram.pairs (fun _ => 0) 0 = 0 ∧ + Histogram.pairs (fun _ => 0) 1 = 0 ∧ Histogram.pairs (fun _ => 0) 2 = 2 := by decide + +/-- Different keys create no collision. -/ +example : Histogram.pairs id 4 = 0 := by decide + +private def values (i : ℕ) : ℤ := [-5, -2, 1, 4].getD i 0 + +private def signedInput : Data 16 := + (((Data.init ([-5, -2, 1, 4].map encode)).set ThreeSum.ChanHe.Residues.src 1).set + ThreeSum.ChanHe.Residues.dst 10).set ThreeSum.ChanHe.Residues.len 4 + |>.set ThreeSum.ChanHe.Residues.modulus (encode 3) + +/-- Negative dividends use Euclidean residues, and the outputs are decoded unsigned keys. -/ +example : (Code.eval 8 4 ThreeSum.ChanHe.Residues.program signedInput).map + (fun yn => (List.range 4).map fun i => yn.1.mem (10 + i)) = some [1, 1, 1, 1] := by decide + +/-- The signed-array contract has satisfiable hypotheses on the executed negative input. -/ +example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Residues.program signedInput y 96 ∧ + y.mem 10 = 1 ∧ y.mem 13 = 1 ∧ y.mem 1 = encode (-5) ∧ y.mem 14 = 0 := by + obtain ⟨y, hy, hm, _⟩ := ThreeSum.ChanHe.Residues.exec (w := 6) (M := 3) + signedInput values (by decide) (by decide) rfl + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_, ?_⟩ <;> rw [hm] <;> decide + +/-- The existing mathematical collision count agrees with the histogram's counter. -/ +example : Cslib.Algorithms.ThreeSum.ChanHe.coll {-5, -2, 1, 4} 3 = 12 ∧ + Histogram.pairs (fun i => (values i % 3).toNat) 4 = 12 := by decide + +private def countInput : Data 28 := + (((((Data.init ([-5, -2, 1, 4].map encode)).set ThreeSum.ChanHe.Count.src 1).set + ThreeSum.ChanHe.Count.scratch 10).set ThreeSum.ChanHe.Count.len 4).set + ThreeSum.ChanHe.Count.modulus (encode 3)).set ThreeSum.ChanHe.Count.dst 20 + +-- Evaluating the three renamed loops unfolds over 150 native instructions and their register maps. +set_option maxRecDepth 4096 in +/-- Execute the composed signed-input program and check its unsigned count and both arrays. -/ +example : (Code.eval 8 4 ThreeSum.ChanHe.Count.program countInput).map + (fun yn => [yn.1.mem 1, yn.1.mem 10, yn.1.mem 13, yn.1.mem 20, yn.1.mem 21, + yn.1.mem 22, yn.1.regs ThreeSum.ChanHe.Count.total]) = + some [encode (-5), 1, 1, 0, 4, 0, 12] := by decide + +/-- The full composed contract is admissible for the same negative-input example. -/ +example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ + y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) := by + obtain ⟨y, hy, hm, ht⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) + countInput values (by decide) (by decide) rfl + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_⟩ + · rw [ht]; decide + · rw [hm]; decide + · rw [hm]; decide + +-- The three complete program evaluations unfold the renamed loops and register maps. +set_option maxRecDepth 4096 in +/-- The composed program handles lengths zero, one and two, including an unused modulus block. -/ +example : ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 0)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 0 ∧ + ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 1)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 0 ∧ + ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 2)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 2 := by decide + +/-- Equal integer values violate the set-enumeration premise, even at different positions. -/ +example : ¬Set.InjOn (fun _ : ℕ => (-1 : ℤ)) (↑(Finset.range 2) : Set ℕ) := by + intro h + have := h (by decide : 0 ∈ (↑(Finset.range 2) : Set ℕ)) + (by decide : 1 ∈ (↑(Finset.range 2) : Set ℕ)) rfl + omega + +end ComplexityTests.ThreeSumHistogram From a7122053713cab3e45b505306b5d03bcd01cc737 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:16:00 -0400 Subject: [PATCH 13/32] fix(wordram): preserve collision-counting parameters explicitly --- .../WordRAM/ThreeSum/ChanHe/Count.lean | 26 ++++++++++++++----- ComplexityTests/ThreeSumHistogram.lean | 8 +++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean index 677b2c2152..5a8a697325 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -9,6 +9,8 @@ module public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.NormNum /-! # Native Chan–He collision counting @@ -89,6 +91,12 @@ def program : Code .standard 28 := .seq (Residues.program.rename residuesRegisters) (.seq prepare (Histogram.program.rename histogramRegisters)) +/-- The complete counting program preserves its input base, scratch base, length, +encoded modulus and histogram base registers. -/ +theorem preserves_parameters (i : Fin 28) + (hi : (9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) : program.Preserves i := by + fin_cases i <;> norm_num [dst] at hi <;> decide + /-- The residue scratch array and the complete histogram, framed by the original memory. -/ def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst : ℕ) : ℕ → ℕ := Histogram.memory (Residues.memory mem values M scratch n) @@ -111,7 +119,8 @@ theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ - y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) := by + y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) → y.regs i = x.regs i := by obtain ⟨a, ha, ham, har⟩ := Residues.exec (x.comap residuesRegisters) values hM hmod hmodreg hfit hs hk hsk hinput let y := a.extend residuesRegisters x @@ -173,8 +182,12 @@ theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) have hi' : i < x.regs len := by simpa only [hql] using hi rw [Data.comap_mem, hqm, hqs] simp [Residues.memory, hi']) - refine ⟨b.extend histogramRegisters q, ?_, ?_, ?_⟩ - · exact (hy.seq (hprep.seq (hb.rename histogram_injective))).mono (by rw [hql, hqb]; omega) + have he := (hy.seq (hprep.seq (hb.rename histogram_injective))).mono + (show (23 * x.regs len + 4) + (5 + (5 * (q.comap histogramRegisters).regs Histogram.bins + + 11 * (q.comap histogramRegisters).regs Histogram.len + 9)) ≤ + 34 * x.regs len + 5 * M + 18 by rw [hql, hqb]; omega) + refine ⟨b.extend histogramRegisters q, he, ?_, ?_, fun i hi => + ExecWithin.regs_eq he (preserves_parameters i hi)⟩ · change b.mem = _ rw [hbm, Data.comap_mem, hqm, hqd, hqb, hql] rfl @@ -199,8 +212,9 @@ theorem exec_coll {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ y.regs total = Cslib.Algorithms.ThreeSum.ChanHe.coll - ((Finset.range (x.regs len)).image values) M := by - obtain ⟨y, hy, hm, ht⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput - exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm⟩ + ((Finset.range (x.regs len)).image values) M ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) → y.regs i = x.regs i := by + obtain ⟨y, hy, hm, ht, hr⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput + exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm, hr⟩ end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/ComplexityTests/ThreeSumHistogram.lean b/ComplexityTests/ThreeSumHistogram.lean index 23081d4413..d51a5340f6 100644 --- a/ComplexityTests/ThreeSumHistogram.lean +++ b/ComplexityTests/ThreeSumHistogram.lean @@ -107,8 +107,10 @@ example : (Code.eval 8 4 ThreeSum.ChanHe.Count.program countInput).map /-- The full composed contract is admissible for the same negative-input example. -/ example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ - y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) := by - obtain ⟨y, hy, hm, ht⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) + y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = ThreeSum.ChanHe.Count.dst) → + y.regs i = countInput.regs i := by + obtain ⟨y, hy, hm, ht, hframe⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) countInput values (by decide) (by decide) rfl (by intro i hi @@ -121,7 +123,7 @@ example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ change i < 4 at hi have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega rcases this with rfl | rfl | rfl | rfl <;> decide) - refine ⟨y, hy, ?_, ?_, ?_⟩ + refine ⟨y, hy, ?_, ?_, ?_, hframe⟩ · rw [ht]; decide · rw [hm]; decide · rw [hm]; decide From 844522a3dbb30980410ff9e07272ed0df0b9402c Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:44:21 -0400 Subject: [PATCH 14/32] feat(matrix): prove finite thin-product correctness and sparse bounds Implement the complete finite mathematical milestone of #1463 above #1464: valid layouts, batch-product correctness/error exclusion, arbitrary-N tiling, exact requested signed products, order/cardinality estimates, geometric tails and the derived sparse bounds. At D=4^m, L=19m, D^18<=N and |W|*2^m<=N^2, encoding entries are bounded by4*N^2/D^(1/18), contributing leaves by33 and passed-set cardinalities by(19m+1)*33 times that scale. Add finite magnitude and auxiliary object-count bounds. None are native instruction costs. All six final local gates pass: strict build, aggregate check, strict tests, environment lint, style lint and init-import check. Hygiene --check-simp, changed-file lints (14 library modules,1 new test) and diff --check pass. Tests kernel-check signed results3,20,0,-8, true padding, empty/sparse and negative-order boundaries, and instantiate all aggregate bounds with jointly satisfiable m=1 parameters and symbolic matrices without resource overrides. Read-only math/API review by /root/thin_product_review is clear after fixed doc references/magnitude wording/quotation hygiene. Focused final delta review by /root/thin_product_final_delta_review is clear for tree b021e2d9340b433a9fdb129882351e158e8c289f, reusing the prior mathematics review. These are internal agent reviews, not outside coordinator or human approval. Source: anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5, selective Section2 finite mathematics; Anthropic credit/NOTICE retained. Abstract assessment: no impact on selected headlines, catalogue or rendering. Full native tensor/sparse-product/Exact Triangle code, native Chan-He #1446, whole input/time/width composition and improved1.9992 remain under #1394. AI assistance: implementation and reviews with OpenAI Codex, worker session codex-thin-product-math-20261028. --- Complexity.lean | 14 + .../MatrixMultiplication/Schonhage/Batch.lean | 191 ++++++++++ .../Schonhage/Layout.lean | 150 ++++++++ .../Schonhage/Levels.lean | 334 ++++++++++++++++++ .../Schonhage/Magnitude.lean | 249 +++++++++++++ .../Schonhage/Orders.lean | 320 +++++++++++++++++ .../Schonhage/Parameters.lean | 134 +++++++ .../Schonhage/Sparse.lean | 311 ++++++++++++++++ .../Schonhage/ThinProduct.lean | 196 ++++++++++ .../Schonhage/Tiling.lean | 248 +++++++++++++ .../Foundations/Algebra/FiniteMagnitude.lean | 55 +++ .../Foundations/Algebra/GeometricTail.lean | 42 +++ .../Combinatorics/BinomialUpperBound.lean | 52 +++ .../Combinatorics/FunctionPlaces.lean | 141 ++++++++ .../Foundations/Data/Finset/OrderGlue.lean | 187 ++++++++++ ComplexityTests/SchonhageThinProduct.lean | 165 +++++++++ NOTICE | 4 +- ROADMAP.md | 6 +- UPSTREAM.md | 13 +- 19 files changed, 2806 insertions(+), 6 deletions(-) create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean create mode 100644 Complexity/Foundations/Algebra/FiniteMagnitude.lean create mode 100644 Complexity/Foundations/Algebra/GeometricTail.lean create mode 100644 Complexity/Foundations/Combinatorics/BinomialUpperBound.lean create mode 100644 Complexity/Foundations/Combinatorics/FunctionPlaces.lean create mode 100644 Complexity/Foundations/Data/Finset/OrderGlue.lean create mode 100644 ComplexityTests/SchonhageThinProduct.lean diff --git a/Complexity.lean b/Complexity.lean index 58c9b1d11a..1cdd3b3c05 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -30,9 +30,18 @@ public import Complexity.Algorithms.Lean.VanEmdeBoas.Insert public import Complexity.Algorithms.Lean.VanEmdeBoas.Layout public import Complexity.Algorithms.Lean.VanEmdeBoas.Successor public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Batch public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Layout +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Magnitude +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Orders +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Parameters public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Sparse +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling public import Complexity.Algorithms.Reachability.BreadthFirst public import Complexity.Algorithms.Reachability.InductiveCounting public import Complexity.Algorithms.Reachability.InductiveCounting.Bounds @@ -1862,6 +1871,8 @@ public import Complexity.Crypto.Protocols.SigmaProtocol.Lyubashevsky.NativeExtra public import Complexity.Crypto.Protocols.SigmaProtocol.SchnorrIdentification public import Complexity.Crypto.Protocols.SigmaProtocol.SchnorrSignature public import Complexity.Crypto.Protocols.Signature.RandomOracle +public import Complexity.Foundations.Algebra.FiniteMagnitude +public import Complexity.Foundations.Algebra.GeometricTail public import Complexity.Foundations.Analysis.BernoulliInequality public import Complexity.Foundations.Analysis.HarmonicMean public import Complexity.Foundations.Analysis.InnerProductSpace.Basic @@ -1926,6 +1937,7 @@ public import Complexity.Foundations.Combinatorics.AffineDisperser.IndependentSo public import Complexity.Foundations.Combinatorics.AffineDisperser.Threshold public import Complexity.Foundations.Combinatorics.BalancedSchedules public import Complexity.Foundations.Combinatorics.BinomialTail +public import Complexity.Foundations.Combinatorics.BinomialUpperBound public import Complexity.Foundations.Combinatorics.CentralBinomial public import Complexity.Foundations.Combinatorics.DepthReduction public import Complexity.Foundations.Combinatorics.Expander.AffineFamily @@ -1964,6 +1976,7 @@ public import Complexity.Foundations.Combinatorics.Expander.Walk public import Complexity.Foundations.Combinatorics.Expander.Zigzag public import Complexity.Foundations.Combinatorics.FiniteCover public import Complexity.Foundations.Combinatorics.FiniteCover.Basic +public import Complexity.Foundations.Combinatorics.FunctionPlaces public import Complexity.Foundations.Combinatorics.FunctionalGraph public import Complexity.Foundations.Combinatorics.FunctionalGraph.Active public import Complexity.Foundations.Combinatorics.FunctionalGraph.CheckpointForest @@ -2072,6 +2085,7 @@ public import Complexity.Foundations.Data.Fin.Tuple public import Complexity.Foundations.Data.Finset.Average public import Complexity.Foundations.Data.Finset.Expect public import Complexity.Foundations.Data.Finset.Markov +public import Complexity.Foundations.Data.Finset.OrderGlue public import Complexity.Foundations.Data.Finset.Quantile public import Complexity.Foundations.Data.Finset.Quotient public import Complexity.Foundations.Data.Finset.Sampling diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean new file mode 100644 index 0000000000..9421e25bb7 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean @@ -0,0 +1,191 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels + +/-! +# Batch correctness of the tensor product + +## Main results + +* `bilinearProduct_arrays_eq_mul`: exact integer matrix multiplication at selected strings. +* `batch_product_spec`: arbitrary output strings with `m` inner levels. + +The tensor error has no effect: a nonzero summand must use the same inner levels on +both inputs as the output, and the remaining outer coefficients are Kronecker deltas. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-! ### The sizes of the matrices, and Figure 4 -/ + +/-- Section 2.3.3: there are `N₀` strings of `L - m` outer left (or right) variables. -/ +theorem card_outerStr (L m : ℕ) : Fintype.card (OuterStr L m) = N0 L m := by + simp [N0] + +/-- Section 2.3.3: there are `D` strings of `m` inner left (or right) variables. -/ +theorem card_innerStr (m : ℕ) : Fintype.card (InnerStr m) = D m := by + simp [D] + +/-- Section 2.3.3: there are `K = binom(L, m)` subsets `Q` of `{1, …, L}` of size `m`. -/ +theorem card_subsets_eq_K (L m : ℕ) : + (univ.filter fun Q : Finset (Fin L) => Q.card = m).card = K L m := by + rw [univ_filter_card_eq, card_powersetCard, card_univ, Fintype.card_fin, K] + +/-! ### The input arrays -/ + +/-- The entry of the left input array at the string with inner set `Q`, outer part `r` and inner +part `π` is `X_Q[r, π]`. -/ +@[simp] +theorem arrayL_leftStrOf {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Q : Finset (Fin L)) + (hQ : Q.card = m) (r : OuterStr L m) (π : InnerStr m) : + arrayL m X (leftStrOf Q hQ r π) = X Q r π := by + have h : (innerSetL (leftStrOf Q hQ r π)).card = m := by rw [innerSetL_leftStrOf, hQ] + rw [arrayL, dite_eq_left h, outerPartL_leftStrOf, innerPartL_leftStrOf, innerSetL_leftStrOf] + +/-- The entry of the right input array at the string with inner set `Q`, inner part `π` and outer +part `c` is `Y_Q[π, c]`. -/ +@[simp] +theorem arrayR_rightStrOf {L m : ℕ} (Y : Finset (Fin L) → RightMat L m) (Q : Finset (Fin L)) + (hQ : Q.card = m) (π : InnerStr m) (c : OuterStr L m) : + arrayR m Y (rightStrOf Q hQ π c) = Y Q π c := by + have h : (innerSetR (rightStrOf Q hQ π c)).card = m := by rw [innerSetR_rightStrOf, hQ] + rw [arrayR, dite_eq_left h, innerPartR_rightStrOf, outerPartR_rightStrOf, innerSetR_rightStrOf] + +/-! ### The proof of Lemma 9 -/ + +/-- The inner-output coefficient selects equal inner index pairs (Section 2.3.3). +Checked directly on `coefficient`; no polynomial coefficient bridge is assumed. -/ +theorem coefficient_z0 (s : LeftVar) (t : RightVar) : + coefficient s t .z0 = if ∃ i j, s = .p i j ∧ t = .q i j then 1 else 0 := by + decide +revert + +/-- With two outer inputs, the outer-output coefficient selects exactly the matching row +and column (Section 2.3.3). -/ +theorem coefficient_x_y_z (i' j' i j : Fin 3) : + coefficient (.x i') (.y j') (.z i j) = if i' = i ∧ j' = j then 1 else 0 := by + decide +revert + +/-- The statement on `γ(x_i', y_j', z_ij)` as it is used: if `s` and `t` are outer and +`γ(s, t, z_ij) ≠ 0`, then `(s, t) = (x_i, y_j)`. -/ +private theorem eq_x_y_of_gamma_ne_zero {s : LeftVar} {t : RightVar} {i j : Fin 3} + (hs : ¬ s.IsInner) (ht : ¬ t.IsInner) (h : coefficient s t (.z i j) ≠ 0) : s = .x i ∧ t = .y j + := by + rcases s with i' | _ + · rcases t with j' | _ + · by_contra hne + exact h ((coefficient_x_y_z i' j' i j).trans + (ite_eq_right fun ⟨hi, hj⟩ => hne ⟨hi ▸ rfl, hj ▸ rfl⟩)) + · exact (ht trivial).elim + · exact (hs trivial).elim + +/-- The summands of Lemma 8 that can be nonzero. Let `w` be the output string with inner +set `Q`, row `r` and column `c`. If `γ(u_ℓ, v_ℓ, w_ℓ) ≠ 0` at every level and the inner sets of `u` +and `v` have `m` elements, then `u` has inner set `Q`, outer part `r` and some inner part `π`, and +`v` has inner set `Q`, outer part `c` and the inner part with the same indices. -/ +private theorem exists_of_summand_ne_zero {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) + (r c : OuterStr L m) {u : LeftStr L} {v : RightStr L} + (hγ : ∀ ℓ, coefficient (u ℓ) (v ℓ) (outStrOf Q hQ r c ℓ) ≠ 0) (hu : (innerSetL u).card = m) + (hv : (innerSetR v).card = m) : ∃ π, u = leftStrOf Q hQ r π ∧ v = rightStrOf Q hQ π c := by + simp only [outStrOf, forall_level_iff Q hQ, glue_innerLevel, glue_outerLevel] at hγ + obtain ⟨hγin, hγout⟩ := hγ + -- at a level ℓ ∈ Q we have w_ℓ = z₀ […] So (u_ℓ, v_ℓ) = (p_ij, q_ij) for some i, j. + have hin : ∀ k, ∃ ij : Fin 2 × Fin 2, + u (Finset.orderEmbOfFin Q hQ k) = .p ij.1 ij.2 ∧ v (Finset.orderEmbOfFin Q hQ k) = .q ij.1 + ij.2 := fun k => by + by_contra hne + exact hγin k ((coefficient_z0 _ _).trans (ite_eq_right fun ⟨i, j, h⟩ => hne ⟨(i, j), h⟩)) + choose π hπ using hin + -- Thus u and v have an inner variable at every level of Q, and their inner sets are therefore + -- exactly Q. So u_ℓ and v_ℓ are outer at the levels outside Q. + have huQ : innerSetL u = Q := + filter_eq_of_forall_innerLevel Q hQ (fun k => by rw [(hπ k).1]; trivial) hu + have hvQ : innerSetR v = Q := + filter_eq_of_forall_innerLevel Q hQ (fun k => by rw [(hπ k).2]; trivial) hv + have huout := ((filter_eq_iff_forall_level Q hQ).mp huQ).2 + have hvout := ((filter_eq_iff_forall_level Q hQ).mp hvQ).2 + -- at a level ℓ ∉ Q we have w_ℓ = z_ij for some i, j […] so (u_ℓ, v_ℓ) = (x_i, y_j). + have hout : ∀ k, u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = .x (r k) ∧ v + ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = .y (c k) := + fun k => eq_x_y_of_gamma_ne_zero (huout k) (hvout k) (hγout k) + exact ⟨π, (eq_glue_iff Q hQ).mpr ⟨fun k => (hπ k).1, fun k => (hout k).1⟩, + (eq_glue_iff Q hQ).mpr ⟨fun k => (hπ k).2, fun k => (hout k).2⟩⟩ + +/-- In the proof of Lemma 9: the coefficient of the summand of `π` is 1. -/ +private theorem prod_gamma_eq_one {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) + (r c : OuterStr L m) (π : InnerStr m) : + ∏ ℓ, coefficient (leftStrOf Q hQ r π ℓ) (rightStrOf Q hQ π c ℓ) (outStrOf Q hQ r c ℓ) = 1 := by + have hone : ∀ ℓ, + coefficient (leftStrOf Q hQ r π ℓ) (rightStrOf Q hQ π c ℓ) (outStrOf Q hQ r c ℓ) = 1 := by + simp only [forall_level_iff Q hQ, leftStrOf, rightStrOf, outStrOf, glue_innerLevel, + glue_outerLevel] + exact ⟨fun _ => (coefficient_z0 _ _).trans (ite_eq_left ⟨_, _, rfl, rfl⟩), + fun _ => (coefficient_x_y_z _ _ _ _).trans (ite_eq_left ⟨rfl, rfl⟩)⟩ + exact prod_eq_one fun ℓ _ => hone ℓ + +/-- Lemma 9 for the output string with inner set `Q`, row `r` and column `c`: by Lemma 8 the entry +is a sum over all pairs `(u, v)`, and the summands that remain are those of the matrix product. -/ +theorem bilinearProduct_arrays_eq_mul {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) + (Y : Finset (Fin L) → RightMat L m) (Q : Finset (Fin L)) (hQ : Q.card = m) + (r c : OuterStr L m) : + bilinearProduct (arrayL m X) (arrayR m Y) (outStrOf Q hQ r c) = (X Q * Y Q) r c := by + -- To show: `∑_{(u,v)} ∏_ℓ γ(u_ℓ, v_ℓ, w_ℓ) a[u] b[v] = ∑_π X_Q[r, π] Y_Q[π, c]`. The right-hand + -- side is the part of the left-hand side over the pairs `(u, v)` that belong to some `π`. + rw [tensor_expansion, Matrix.mul_apply, ← Fintype.sum_prod_type'] + symm + refine Fintype.sum_of_injective (fun π => (leftStrOf Q hQ r π, rightStrOf Q hQ π c)) ?_ _ _ ?_ ?_ + · -- There is one such summand for each of the D strings π. + exact fun π π' h => (leftStrOf_inj.mp (congrArg Prod.fst h)).2 + · -- Every other summand is zero. a[u] b[v] ≠ 0 requires the inner sets of u and v to have + -- exactly m elements. + rintro ⟨u, v⟩ hnot + by_contra hne + obtain ⟨⟨hγ, ha⟩, hb⟩ : ((∏ ℓ, coefficient (u ℓ) (v ℓ) (outStrOf Q hQ r c ℓ)) ≠ 0 + ∧ arrayL m X u ≠ 0) ∧ arrayR m Y v ≠ 0 := by + simpa only [mul_ne_zero_iff] using hne + obtain ⟨π, rfl, rfl⟩ := exists_of_summand_ne_zero Q hQ r c + (fun ℓ => prod_ne_zero_iff.mp hγ ℓ (mem_univ ℓ)) + (by_contra fun h => ha (dite_eq_right h)) (by_contra fun h => hb (dite_eq_right h)) + exact hnot ⟨π, rfl⟩ + · -- The summand of `π` is `1 · X_Q[r, π] · Y_Q[π, c]`. + intro π + rw [prod_gamma_eq_one, one_mul, arrayL_leftStrOf, arrayR_rightStrOf] + +/-- **Lemma 9**. For the input arrays a, b above and every output string w whose inner +set Q has exactly m elements, bilinearProduct(a, b)[w] = (X_Q Y_Q)[w]. (Equation (4).) -/ +theorem batch_product_spec {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Y : Finset (Fin L) → + RightMat L m) + (w : OutStr L) (h : (innerSetO w).card = m) : + bilinearProduct (arrayL m X) (arrayR m Y) w + = (X (innerSetO w) * Y (innerSetO w)) (rowO w h) (colO w h) := by + -- `w` is the output string with the inner set, the row and the column of `w`. + conv_lhs => rw [outStr_eq_outStrOf h rfl h] + exact bilinearProduct_arrays_eq_mul X Y (innerSetO w) h (rowO w h) (colO w h) + +/-- Section 2.3.3: one run of Full […] computes all K of the products X_Q Y_Q. -/ +theorem Full.arrays_eq_mul {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) + (Y : Finset (Fin L) → RightMat L m) (Q : Finset (Fin L)) (hQ : Q.card = m) + (r c : OuterStr L m) : + Full.result L (arrayL m X) (arrayR m Y) (outStrOf Q hQ r c) = (X Q * Y Q) r c := by + rw [Full.result_eq_bilinearProduct] + exact bilinearProduct_arrays_eq_mul X Y Q hQ r c + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean new file mode 100644 index 0000000000..354678f42e --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean @@ -0,0 +1,150 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels +public import Mathlib.Data.Nat.Sqrt + +/-! +# Tiling layout data + +## Main definitions + +* `Layout`: ordinary finite indexing choices, with no solver or bound certificate. +* `wantedStrings`, `tiles`: requested outputs grouped by tile. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.3.4: K₀ := ⌊√K⌋. -/ +def K0 (L m : ℕ) : ℕ := Nat.sqrt (K L m) + +/-- `N₀ = 3^{L-m}` is positive. -/ +theorem N0_pos (L m : ℕ) : 0 < N0 L m := Nat.pow_pos (by decide) + +/-- `K₀ = ⌊√binom(L, m)⌋` is positive when `m ≤ L`. -/ +theorem K0_pos {L m : ℕ} (h : m ≤ L) : 0 < K0 L m := Nat.sqrt_pos.mpr (Nat.choose_pos h) + +/-- The choices that the paper makes once and for all without naming them. + +* `hmL`: `m ≤ L`. The paper never states this; it follows from `L = 19m` (Section 2) and from + `L ≥ 10m` (Section 4). It makes `K₀ ≥ 1`. +* Section 2.3.3: we index its N₀ rows by the N₀ strings of L - m outer left variables, and its D + columns by the D strings of m inner left variables, and similarly for a `D × N₀` matrix. + `rowIdx`, `colIdx` and `innerIdx` say which string indexes which row or column, counted from 0. +* Section 2.3.4: We fix K₀² ≤ K distinct subsets of {1, …, L} of size m, one for each block product + of the grid, the same in every tile. `table (g, h)` is the subset of the block product of the + `g`-th row block of a row band and the `h`-th column block of a column band. + +Every statement about the tiling holds for every such choice. -/ +structure Layout (L m : ℕ) where + hmL : m ≤ L + /-- Index a block's rows by outer strings. -/ + rowIdx : Fin (N0 L m) ≃ OuterStr L m + /-- Index a block's columns by outer strings. -/ + colIdx : Fin (N0 L m) ≃ OuterStr L m + /-- Index the thin matrix dimension by strings of inner index pairs. -/ + innerIdx : Fin (D m) ≃ InnerStr m + /-- Assign a distinct size-`m` level set to each block product in a tile. -/ + table : Fin (K0 L m) × Fin (K0 L m) → Finset (Fin L) + table_card : ∀ gh, (table gh).card = m + table_injective : Function.Injective table + +/-- The number `K₀ N₀` of rows of a row band (Section 2.3.4: a band is `K₀` consecutive blocks of +`N₀` consecutive rows), which is also the number of columns of a column band. -/ +def bandSize (L m : ℕ) : ℕ := K0 L m * N0 L m + +/-- The number of row bands (and of column bands) after padding: `N / (K₀ N₀)` rounded up. -/ +def numBands (L m N : ℕ) : ℕ := (N + bandSize L m - 1) / bandSize L m + +/-- Section 2.3.4: we first pad N to a multiple of K₀ N₀. The padded size. -/ +def padN (L m N : ℕ) : ℕ := numBands L m N * bandSize L m + +/-- Section 2.3.4: `X` padded with zero rows. Rows are numbered from 0. -/ +def padRows {N D' : ℕ} (X : Matrix (Fin N) (Fin D') ℤ) (I : ℕ) (k : Fin D') : ℤ := + if h : I < N then X ⟨I, h⟩ k else 0 + +/-- Section 2.3.4: `Y` padded with zero columns. Columns are numbered from 0. -/ +def padCols {N D' : ℕ} (Y : Matrix (Fin D') (Fin N) ℤ) (k : Fin D') (J : ℕ) : ℤ := + if h : J < N then Y k ⟨J, h⟩ else 0 + +/-- Section 2.3.4: Cut X into row blocks of N₀ consecutive rows. The `B`-th row block (counted +from 0) consists of the rows `B N₀, …, B N₀ + N₀ - 1`. -/ +def rowBlock {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (B : ℕ) : + LeftMat L m := + fun r π => padRows X (B * N0 L m + lay.rowIdx.symm r) (lay.innerIdx.symm π) + +/-- Section 2.3.4: and Y into column blocks of N₀ consecutive columns. -/ +def colBlock {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (B : ℕ) : + RightMat L m := + fun π c => padCols Y (lay.innerIdx.symm π) (B * N0 L m + lay.colIdx.symm c) + +/-- Section 2.3.4: In a tile, let X_Q and Y_Q be the row block and the column block of the block +product with subset Q, and let X_Q = Y_Q = 0 for the other subsets Q. The row band `β` (counted +from 0) consists of the row blocks `β K₀, …, β K₀ + K₀ - 1`. The subsets in the table are distinct, +so at most one summand is nonzero. -/ +def bandFamilyL {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) : + Finset (Fin L) → LeftMat L m := + fun Q => ∑ gh, if lay.table gh = Q then rowBlock lay X (β * K0 L m + gh.1) else 0 + +/-- The matrices `Y_Q` of a tile; they depend only on its column band `β`. -/ +def bandFamilyR {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) : + Finset (Fin L) → RightMat L m := + fun Q => ∑ gh, if lay.table gh = Q then colBlock lay Y (β * K0 L m + gh.2) else 0 + +/-- Section 2.3.4: the left input array of a tile depends only on its row band. -/ +def bandArrayL {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) : + LeftStr L → ℤ := + arrayL m (bandFamilyL lay X β) + +/-- Section 2.3.4: and the right one only on its column band. -/ +def bandArrayR {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) : + RightStr L → ℤ := + arrayR m (bandFamilyR lay Y β) + +/-- The band that contains row (or column) `I`. -/ +def bandOf (L m I : ℕ) : ℕ := I / bandSize L m + +/-- The position, within its band, of the block that contains row (or column) `I`. -/ +def blockOf {L m : ℕ} (lay : Layout L m) (I : ℕ) : Fin (K0 L m) := + ⟨I / N0 L m % K0 L m, Nat.mod_lt _ (K0_pos lay.hmL)⟩ + +/-- The position of row (or column) `I` within its block. -/ +def offsetOf (L m I : ℕ) : Fin (N0 L m) := ⟨I % N0 L m, Nat.mod_lt _ (N0_pos L m)⟩ + +/-- Section 2.4.4: Each wanted position (I, J) ∈ W lies in one tile T and is indexed by one output +string of T, the string whose inner set is the subset Q of the block product containing (I, J) and +whose row and column are those of (I, J) within that block product. The tile is the pair +`(bandOf L m I, bandOf L m J)`, and this is the output string. -/ +def outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : OutStr L := + outStrOf (lay.table (blockOf lay I, blockOf lay J)) (lay.table_card _) + (lay.rowIdx (offsetOf L m I)) (lay.colIdx (offsetOf L m J)) + +/-- Section 2.4.4: For each tile T, let W_T be the set of the output strings of the wanted +positions in T. The tile `T` is given by its row band `β` and its column band `β'`. -/ +def wantedStrings {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) (β β' : ℕ) : + Finset (OutStr L) := + (W.filter fun IJ => bandOf L m IJ.1 = β ∧ bandOf L m IJ.2 = β').image + fun IJ => outStrOfPos lay IJ.1 IJ.2 + +/-- All the tiles: pairs of a row band and a column band. -/ +def tiles (L m N : ℕ) : Finset (ℕ × ℕ) := range (numBands L m N) ×ˢ range (numBands L m N) + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean new file mode 100644 index 0000000000..38d7001a93 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean @@ -0,0 +1,334 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +public import Complexity.Foundations.Data.Finset.OrderGlue + +/-! +# Matrix entries indexed by tensor strings + +## Main definitions + +* `arrayL`, `arrayR`: batch arrays with exactly `m` inner levels. +* `leftStrOf`, `rightStrOf`, `outStrOf`: entry strings. + +## Main results + +* `existsUnique_leftStr`, `existsUnique_rightStr`, `existsUnique_outStr`: nonvacuous entry layouts. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.2: We call the variables of the inner product, p, q, and z₀, inner. -/ +def LeftVar.IsInner : LeftVar → Prop + | .x _ => False + | .p _ _ => True +/-- Section 2.2: the right variables `q` are inner, the `y` are outer. -/ +def RightVar.IsInner : RightVar → Prop + | .y _ => False + | .q _ _ => True +instance LeftVar.instDecidablePredIsInner : DecidablePred LeftVar.IsInner + | .x _ => isFalse fun h => h + | .p _ _ => isTrue trivial +instance RightVar.instDecidablePredIsInner : DecidablePred RightVar.IsInner + | .y _ => isFalse fun h => h + | .q _ _ => isTrue trivial +/-- Section 2.3.3: The inner set of a left string is the set of levels at which it has a p (as +opposed to an x). -/ +def innerSetL {L : ℕ} (u : LeftStr L) : Finset (Fin L) := univ.filter fun ℓ => (u ℓ).IsInner +/-- Section 2.3.3: the inner set of a right string is the set of levels at which it has a q (as +opposed to a y). -/ +def innerSetR {L : ℕ} (v : RightStr L) : Finset (Fin L) := univ.filter fun ℓ => (v ℓ).IsInner +/-- Section 2.3.3: the inner set of an output string is the set of levels at which it has z₀ (as +opposed to a z_ij). -/ +def innerSetO {L : ℕ} (w : OutStr L) : Finset (Fin L) := univ.filter fun ℓ => (w ℓ).IsInner + +/-- Section 2.3.3: N₀ := 3^{L-m}. Meant for `m ≤ L`; for `m > L` the subtraction of natural +numbers is cut off at 0 and the value is 1. -/ +def N0 (L m : ℕ) : ℕ := 3 ^ (L - m) +/-- Section 2.3.3: D := 4^m. -/ +def D (m : ℕ) : ℕ := 4 ^ m +/-- Section 2.3.3: K := binom(L, m), the number of subsets of `{1, …, L}` of size `m`. -/ +def K (L m : ℕ) : ℕ := L.choose m + +/-- The index `i` of an outer left variable `x_i` (the value at a `p` is never used). -/ +def LeftVar.outerIndex : LeftVar → Fin 3 + | .x i => i + | .p _ _ => 0 +/-- The index pair `(i, j)` of an inner left variable `p_ij` (the value at an `x` is never used). -/ +def LeftVar.innerIndex : LeftVar → Fin 2 × Fin 2 + | .x _ => (0, 0) + | .p i j => (i, j) +/-- The index `j` of an outer right variable `y_j` (the value at a `q` is never used). -/ +def RightVar.outerIndex : RightVar → Fin 3 + | .y j => j + | .q _ _ => 0 +/-- The index pair `(i, j)` of an inner right variable `q_ij` (the value at a `y` is never used). -/ +def RightVar.innerIndex : RightVar → Fin 2 × Fin 2 + | .y _ => (0, 0) + | .q i j => (i, j) +/-- The first index `i` of an outer output variable `z_ij` (the value at `z₀` is never used). -/ +def OutVar.rowIndex : OutVar → Fin 3 + | .z i _ => i + | .z0 => 0 +/-- The second index `j` of an outer output variable `z_ij` (the value at `z₀` is never used). -/ +def OutVar.colIndex : OutVar → Fin 3 + | .z _ j => j + | .z0 => 0 + +/-- A string of `L - m` outer left variables `x_{i₁} ⋯ x_{i_{L-m}}`, written as the string of its +indices `i₁ ⋯ i_{L-m}`. The same type is used for strings `y_{j₁} ⋯ y_{j_{L-m}}` of outer right +variables. Section 2.3.3: these strings index the `N₀` rows of an `N₀ × D` matrix and the `N₀` +columns of a `D × N₀` matrix. -/ +abbrev OuterStr (L m : ℕ) : Type := Fin (L - m) → Fin 3 + +/-- A string of `m` inner left variables `π = p_{i₁j₁} ⋯ p_{i_m j_m}`, written as the string of its +index pairs. The same type is used for strings `π' = q_{i₁j₁} ⋯ q_{i_m j_m}` of inner right +variables, so that the paper's `π'`, with the same indices as `π`, is the same element of this +type as `π`. Section 2.3.3: these strings index the `D` columns of an `N₀ × D` matrix and the `D` +rows of a `D × N₀` matrix. With this convention the paper's (AB)[r, c] := ∑_π A[r, π] B[π', c] is +the usual matrix product. -/ +abbrev InnerStr (m : ℕ) : Type := Fin m → Fin 2 × Fin 2 + +/-- Section 2.3.3: its outer part is the string of its variables at the other levels, for a left +string whose inner set has `m` elements. -/ +def outerPartL {L m : ℕ} (u : LeftStr L) (h : (innerSetL u).card = m) : OuterStr L m := + fun k => (u (((innerSetL u)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetL u) h)) k)).outerIndex +/-- Section 2.3.3: The inner part of a string is the string of its variables at the levels of its +inner set, in the order of the levels, for a left string whose inner set has `m` elements. -/ +def innerPartL {L m : ℕ} (u : LeftStr L) (h : (innerSetL u).card = m) : InnerStr m := + fun k => (u (Finset.orderEmbOfFin (innerSetL u) h k)).innerIndex +/-- The outer part of a right string whose inner set has `m` elements. -/ +def outerPartR {L m : ℕ} (v : RightStr L) (h : (innerSetR v).card = m) : OuterStr L m := + fun k => (v (((innerSetR v)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetR v) h)) k)).outerIndex +/-- The inner part of a right string whose inner set has `m` elements. -/ +def innerPartR {L m : ℕ} (v : RightStr L) (h : (innerSetR v).card = m) : InnerStr m := + fun k => (v (Finset.orderEmbOfFin (innerSetR v) h k)).innerIndex + +/-- Section 2.3.3: if the variables of w at the levels outside Q are z_{i₁j₁}, …, z_{i_{L-m} +j_{L-m}}, in the order of the levels, then the row of w is the string x_{i₁} ⋯ x_{i_{L-m}}. -/ +def rowO {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) : OuterStr L m := + fun k => (w (((innerSetO w)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetO w) h)) k)).rowIndex +/-- Section 2.3.3: and its column is the string y_{j₁} ⋯ y_{j_{L-m}}. -/ +def colO {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) : OuterStr L m := + fun k => (w (((innerSetO w)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetO w) h)) k)).colIndex + +/-- An `N₀ × D` matrix, its rows and columns indexed by strings as in Section 2.3.3. -/ +abbrev LeftMat (L m : ℕ) : Type := Matrix (OuterStr L m) (InnerStr m) ℤ +/-- A `D × N₀` matrix, its rows and columns indexed by strings as in Section 2.3.3. -/ +abbrev RightMat (L m : ℕ) : Type := Matrix (InnerStr m) (OuterStr L m) ℤ + +/-- Section 2.3.3: a[u] := X_Q[u] […] for all the left strings u […] whose inner set Q has exactly +m elements, and a[u] := 0 […] at every other string, where X_Q[u] is the entry of `X_Q` at the +row and the column of u: the row is the outer part of `u` and the column is its inner part. The +family `X` gives a matrix `X Q` for every set `Q` of levels; only those with `|Q| = m` are used. -/ +def arrayL {L : ℕ} (m : ℕ) (X : Finset (Fin L) → LeftMat L m) : LeftStr L → ℤ := + fun u => + if h : (innerSetL u).card = m then X (innerSetL u) (outerPartL u h) (innerPartL u h) else 0 + +/-- Section 2.3.3: b[v] := Y_Q[v] for the right strings `v` whose inner set `Q` has exactly `m` +elements, the row given by the inner part of v and the column by the outer part, and `b[v] := 0` +at every other string. -/ +def arrayR {L : ℕ} (m : ℕ) (Y : Finset (Fin L) → RightMat L m) : RightStr L → ℤ := + fun v => + if h : (innerSetR v).card = m then Y (innerSetR v) (innerPartR v h) (outerPartR v h) else 0 + +/-! ### 2.3.4 Tiling the N × D × N product by products of shape N₀ × D × N₀: the number `M` -/ + +/-- Section 2.3.4: M := K N₀². -/ +def M (L m : ℕ) : ℕ := K L m * N0 L m ^ 2 + + +variable {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) + +/-! ### Left, right and output strings with a given inner set, outer part and inner part -/ + +/-- The left string with inner set `Q`, outer part `r` and inner part `π`. -/ +def leftStrOf (r : OuterStr L m) (π : InnerStr m) : LeftStr L := + glue Q hQ (fun k => .p (π k).1 (π k).2) (fun k => .x (r k)) + +/-- The right string with inner set `Q`, inner part `π` and outer part `c`. -/ +def rightStrOf (π : InnerStr m) (c : OuterStr L m) : RightStr L := + glue Q hQ (fun k => .q (π k).1 (π k).2) (fun k => .y (c k)) + +/-- The output string with inner set `Q`, row `r` and column `c` (Section 2.3.3: the output strings +with inner set Q index the entries of the product X_Q Y_Q): it has `z₀` at the levels of `Q`, and +at the `k`-th lowest level outside `Q` it has `z_{r_k c_k}`. -/ +def outStrOf (r c : OuterStr L m) : OutStr L := + glue Q hQ (fun _ => .z0) (fun k => .z (r k) (c k)) + +/-- The inner set of `leftStrOf Q hQ r π` is `Q`. -/ +@[simp] +theorem innerSetL_leftStrOf (r : OuterStr L m) (π : InnerStr m) : + innerSetL (leftStrOf Q hQ r π) = Q := + filter_glue Q hQ LeftVar.IsInner (fun _ => trivial) fun _ => id + +/-- The inner set of `rightStrOf Q hQ π c` is `Q`. -/ +@[simp] +theorem innerSetR_rightStrOf (π : InnerStr m) (c : OuterStr L m) : + innerSetR (rightStrOf Q hQ π c) = Q := + filter_glue Q hQ RightVar.IsInner (fun _ => trivial) fun _ => id + +/-- The inner set of `outStrOf Q hQ r c` is `Q`. -/ +@[simp] +theorem innerSetO_outStrOf (r c : OuterStr L m) : innerSetO (outStrOf Q hQ r c) = Q := + filter_glue Q hQ OutVar.IsInner (fun _ => trivial) fun _ => id + +/-- The outer part of `leftStrOf Q hQ r π` is `r`. -/ +@[simp] +theorem outerPartL_leftStrOf (r : OuterStr L m) (π : InnerStr m) + (h : (innerSetL (leftStrOf Q hQ r π)).card = m) : outerPartL (leftStrOf Q hQ r π) h = r := + funext fun k => congrArg LeftVar.outerIndex + (glue_outerLevel_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) + +/-- The inner part of `leftStrOf Q hQ r π` is `π`. -/ +@[simp] +theorem innerPartL_leftStrOf (r : OuterStr L m) (π : InnerStr m) + (h : (innerSetL (leftStrOf Q hQ r π)).card = m) : innerPartL (leftStrOf Q hQ r π) h = π := + funext fun k => congrArg LeftVar.innerIndex + (glue_innerLevel_of_eq Q hQ _ _ (innerSetL_leftStrOf Q hQ r π) h k) + +/-- The inner part of `rightStrOf Q hQ π c` is `π`. -/ +@[simp] +theorem innerPartR_rightStrOf (π : InnerStr m) (c : OuterStr L m) + (h : (innerSetR (rightStrOf Q hQ π c)).card = m) : innerPartR (rightStrOf Q hQ π c) h = π := + funext fun k => congrArg RightVar.innerIndex + (glue_innerLevel_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) + +/-- The outer part of `rightStrOf Q hQ π c` is `c`. -/ +@[simp] +theorem outerPartR_rightStrOf (π : InnerStr m) (c : OuterStr L m) + (h : (innerSetR (rightStrOf Q hQ π c)).card = m) : outerPartR (rightStrOf Q hQ π c) h = c := + funext fun k => congrArg RightVar.outerIndex + (glue_outerLevel_of_eq Q hQ _ _ (innerSetR_rightStrOf Q hQ π c) h k) + +/-- The variable of `outStrOf Q hQ r c` at the `k`-th lowest level outside its inner set. -/ +private theorem outStrOf_outerLevel (r c : OuterStr L m) + (h : (innerSetO (outStrOf Q hQ r c)).card = m) (k : Fin (L - m)) : + outStrOf Q hQ r c (((innerSetO (outStrOf Q hQ r c))ᶜ.orderEmbOfFin (card_compl_of_card_eq + (innerSetO (outStrOf Q hQ r c)) h)) k) = .z (r k) (c k) := by + rw [outerLevel_congr Q hQ (innerSetO_outStrOf Q hQ r c) h, outStrOf, glue_outerLevel] + +/-- The row of `outStrOf Q hQ r c` is `r`. -/ +@[simp] +theorem rowO_outStrOf (r c : OuterStr L m) (h : (innerSetO (outStrOf Q hQ r c)).card = m) : + rowO (outStrOf Q hQ r c) h = r := + funext fun k => congrArg OutVar.rowIndex (outStrOf_outerLevel Q hQ r c h k) + +/-- The column of `outStrOf Q hQ r c` is `c`. -/ +@[simp] +theorem colO_outStrOf (r c : OuterStr L m) (h : (innerSetO (outStrOf Q hQ r c)).card = m) : + colO (outStrOf Q hQ r c) h = c := + funext fun k => congrArg OutVar.colIndex (outStrOf_outerLevel Q hQ r c h k) + +variable {Q} + +/-- A left string with inner set `Q` is the string `leftStrOf` of its outer part and its inner +part. -/ +theorem leftStr_eq_leftStrOf {u : LeftStr L} (hu : innerSetL u = Q) (h : (innerSetL u).card = m) : + u = leftStrOf Q hQ (outerPartL u h) (innerPartL u h) := by + subst hu + refine glue_index _ h LeftVar.IsInner (inner := fun π : Fin 2 × Fin 2 => .p π.1 π.2) ?_ ?_ rfl + · rintro (i | ⟨i, j⟩) hs + exacts [hs.elim, rfl] + · rintro (i | ⟨i, j⟩) hs + exacts [rfl, (hs trivial).elim] + +/-- A right string with inner set `Q` is the string `rightStrOf` of its inner part and its outer +part. -/ +theorem rightStr_eq_rightStrOf {v : RightStr L} (hv : innerSetR v = Q) + (h : (innerSetR v).card = m) : v = rightStrOf Q hQ (innerPartR v h) (outerPartR v h) := by + subst hv + refine glue_index _ h RightVar.IsInner (inner := fun π : Fin 2 × Fin 2 => .q π.1 π.2) ?_ ?_ rfl + · rintro (j | ⟨i, j⟩) ht + exacts [ht.elim, rfl] + · rintro (j | ⟨i, j⟩) ht + exacts [rfl, (ht trivial).elim] + +/-- An output string with inner set `Q` is the string `outStrOf` of its row and its column. -/ +theorem outStr_eq_outStrOf {w : OutStr L} (hw : innerSetO w = Q) (h : (innerSetO w).card = m) : + w = outStrOf Q hQ (rowO w h) (colO w h) := by + subst hw + refine glue_index _ h OutVar.IsInner (inner := fun _ : Unit => .z0) + (outer := fun rc : Fin 3 × Fin 3 => .z rc.1 rc.2) (innerIndex := fun _ => ()) + (outerIndex := fun z => (z.rowIndex, z.colIndex)) ?_ ?_ rfl + · rintro (⟨i, j⟩ | _) hz + exacts [hz.elim, rfl] + · rintro (⟨i, j⟩ | _) hz + exacts [rfl, (hz trivial).elim] + +variable {hQ} + +/-- Left strings with the same inner set and different outer or inner parts are different. -/ +theorem leftStrOf_inj {r r' : OuterStr L m} {π π' : InnerStr m} : + leftStrOf Q hQ r π = leftStrOf Q hQ r' π' ↔ r = r' ∧ π = π' := by + refine ⟨fun h => ?_, by rintro ⟨rfl, rfl⟩; rfl⟩ + obtain ⟨hp, hx⟩ := (glue_inj Q hQ).mp h + exact ⟨funext fun k => LeftVar.x.inj (congrFun hx k), + funext fun k => Prod.ext (LeftVar.p.inj (congrFun hp k)).1 (LeftVar.p.inj (congrFun hp k)).2⟩ + +/-- Output strings with different inner sets, rows or columns are different. -/ +theorem outStrOf_inj {Q' : Finset (Fin L)} {hQ' : Q'.card = m} {r c r' c' : OuterStr L m} : + outStrOf Q hQ r c = outStrOf Q' hQ' r' c' ↔ Q = Q' ∧ r = r' ∧ c = c' := by + refine ⟨fun h => ?_, by rintro ⟨rfl, rfl, rfl⟩; rfl⟩ + obtain rfl : Q = Q' := by rw [← innerSetO_outStrOf Q hQ r c, h, innerSetO_outStrOf] + have hz := fun k => OutVar.z.inj (congrFun ((glue_inj Q hQ).mp h).2 k) + exact ⟨rfl, funext fun k => (hz k).1, funext fun k => (hz k).2⟩ + +variable (Q) (hQ) +include hQ + +/-- Section 2.3.3: A string is determined by its inner set, its outer part, and its inner part, +and the left strings with inner set Q index the entries of such a matrix: for each set `Q` of `m` +levels, each row `r` and each column `π` there is exactly one left string with inner set `Q`, outer +part `r` and inner part `π`. -/ +theorem existsUnique_leftStr (r : OuterStr L m) (π : InnerStr m) : + ∃! u : LeftStr L, innerSetL u = Q + ∧ ∃ h : (innerSetL u).card = m, outerPartL u h = r ∧ innerPartL u h = π := by + have hset := innerSetL_leftStrOf Q hQ r π + refine ⟨leftStrOf Q hQ r π, ⟨hset, hset.symm ▸ hQ, outerPartL_leftStrOf Q hQ r π _, + innerPartL_leftStrOf Q hQ r π _⟩, ?_⟩ + rintro u ⟨hu, h, rfl, rfl⟩ + exact leftStr_eq_leftStrOf hQ hu h + +/-- Section 2.3.3: Similarly, the right strings v with inner set Q index the entries of a D × N₀ +matrix. -/ +theorem existsUnique_rightStr (π : InnerStr m) (c : OuterStr L m) : + ∃! v : RightStr L, innerSetR v = Q + ∧ ∃ h : (innerSetR v).card = m, innerPartR v h = π ∧ outerPartR v h = c := by + have hset := innerSetR_rightStrOf Q hQ π c + refine ⟨rightStrOf Q hQ π c, ⟨hset, hset.symm ▸ hQ, + innerPartR_rightStrOf Q hQ π c _, outerPartR_rightStrOf Q hQ π c _⟩, ?_⟩ + rintro v ⟨hv, h, rfl, rfl⟩ + exact rightStr_eq_rightStrOf hQ hv h + +/-- Section 2.3.3: Finally, the output strings with inner set Q index the entries of the product +X_Q Y_Q. -/ +theorem existsUnique_outStr (r c : OuterStr L m) : + ∃! w : OutStr L, innerSetO w = Q + ∧ ∃ h : (innerSetO w).card = m, rowO w h = r ∧ colO w h = c := by + have hset := innerSetO_outStrOf Q hQ r c + refine ⟨outStrOf Q hQ r c, ⟨hset, hset.symm ▸ hQ, rowO_outStrOf Q hQ r c _, + colO_outStrOf Q hQ r c _⟩, ?_⟩ + rintro w ⟨hw, h, rfl, rfl⟩ + exact outStr_eq_outStrOf hQ hw h + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean new file mode 100644 index 0000000000..71e660aa65 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean @@ -0,0 +1,249 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct +public import Complexity.Foundations.Algebra.FiniteMagnitude + +/-! +# Finite intermediate magnitudes and auxiliary counts + +## Main results + +* `abs_bandArrayL_le`, `abs_bandArrayR_le`: input entries are unchanged or zero. +* `abs_encodingL_le`, `abs_encodingR_le`, `Pruned.abs_result_le`: explicit integer magnitude bounds. +* `minutiae_total_le`: bound on layout subsets, tiles and wanted positions. + +These bounds do not certify a machine word width or native execution. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-! ### The remaining minutiae -/ + +/-- `2^{m/9} ≤ 2^m`. -/ +private theorem two_rpow_ninth_le (m : ℕ) : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (2 : ℝ) ^ m := by + rw [← Real.rpow_natCast] + have hm : (0 : ℝ) ≤ m := Nat.cast_nonneg m + exact Real.rpow_le_rpow_of_exponent_le (by norm_num) (by linarith) + +/-- A number that is at least `2^{m/9}` is at least 1 after multiplication by `2^{-m/9}`. -/ +private theorem one_le_saving_mul {m : ℕ} {x : ℝ} (hx : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ x) : + 1 ≤ saving m * x := by + rw [← saving_mul_two_rpow m] + exact mul_le_mul_of_nonneg_left hx (saving_pos m).le + +/-- Section 2.4.4, in the remaining minutiae: K ≤ N. -/ +theorem K_le_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K (19 * m) m ≤ N := + (Nat.le_mul_of_pos_right _ (N0_pos _ _)).trans (K_mul_N0_le m N hN) + +/-- Section 2.4.4, in the remaining minutiae: there are at most 4N²/N₀ tiles, where `N` is the +padded size. -/ +theorem card_tiles_mul_N0_le {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : + (tiles L m N).card * N0 L m ≤ 4 * padN L m N ^ 2 := by + -- `N₀ ≤ K N₀² = M`, and there are at most `4N²/M` tiles. + refine le_trans (Nat.mul_le_mul_left _ ?_) (card_tiles_mul_M_le L m N) + calc N0 L m = 1 * N0 L m ^ 1 := by ring + _ ≤ K L m * N0 L m ^ 2 := + Nat.mul_le_mul (Nat.choose_pos hmL) (Nat.pow_le_pow_right (N0_pos L m) (by norm_num)) + +/-- Section 2.4.4, in the remaining minutiae: `N ≥ 2^{m/9}`. -/ +theorem two_rpow_le_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : + (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (N : ℝ) := by + -- `2^{m/9} ≤ 2^m ≤ 4^{18m} = D^18 ≤ N`. + have hpow : 2 ^ m ≤ N := + calc 2 ^ m ≤ 4 ^ m := Nat.pow_le_pow_left (by norm_num) m + _ ≤ (4 ^ m) ^ 18 := Nat.le_self_pow (by norm_num) _ + _ ≤ N := hN + exact (two_rpow_ninth_le m).trans (by exact_mod_cast hpow) + +/-- Section 2.4.4, in the remaining minutiae: `N₀ ≥ 2^{m/9}`. -/ +theorem two_rpow_le_N0 (m : ℕ) : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (N0 (19 * m) m : ℝ) := by + -- `2^{m/9} ≤ 2^m ≤ 3^{18m} = N₀`. + have hpow : 2 ^ m ≤ N0 (19 * m) m := + calc 2 ^ m ≤ 3 ^ m := Nat.pow_le_pow_left (by norm_num) m + _ ≤ 3 ^ (19 * m - m) := Nat.pow_le_pow_right (by norm_num) (by omega) + exact (two_rpow_ninth_le m).trans (by exact_mod_cast hpow) + +/-- The three counts of the remaining minutiae (Section 2.4.4), one by one: the subsets in the +table, the tiles, and the positions of `W`. -/ +private theorem minutiae_parts (m N : ℕ) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) + (hW : W.card * 2 ^ m ≤ N ^ 2) : + (K (19 * m) m : ℝ) ≤ saving m * (N : ℝ) ^ 2 + ∧ ((tiles (19 * m) m N).card : ℝ) ≤ 4 * saving m * (padN (19 * m) m N : ℝ) ^ 2 + ∧ (W.card : ℝ) ≤ saving m * (N : ℝ) ^ 2 := by + have hc := saving_pos m + refine ⟨?_, ?_, ?_⟩ + · -- The subsets in the table: `K ≤ N ≤ 2^{-m/9} N²`. + calc (K (19 * m) m : ℝ) ≤ N := by exact_mod_cast K_le_of_D_pow_le m N hN + _ ≤ saving m * N * N := + le_mul_of_one_le_left N.cast_nonneg (one_le_saving_mul (two_rpow_le_of_D_pow_le m N hN)) + _ = saving m * (N : ℝ) ^ 2 := by ring + · -- The tiles: at most `4N²/N₀ ≤ 4 · 2^{-m/9} N²`. + have hT : ((tiles (19 * m) m N).card : ℝ) * N0 (19 * m) m + ≤ 4 * (padN (19 * m) m N : ℝ) ^ 2 := by + exact_mod_cast card_tiles_mul_N0_le (show m ≤ 19 * m by omega) N + calc ((tiles (19 * m) m N).card : ℝ) + ≤ (tiles (19 * m) m N).card * (saving m * N0 (19 * m) m) := + le_mul_of_one_le_right (Nat.cast_nonneg _) (one_le_saving_mul (two_rpow_le_N0 m)) + _ = saving m * ((tiles (19 * m) m N).card * N0 (19 * m) m) := by ring + _ ≤ saving m * (4 * (padN (19 * m) m N : ℝ) ^ 2) := by gcongr + _ = 4 * saving m * (padN (19 * m) m N : ℝ) ^ 2 := by ring + · -- The positions: `|W| ≤ 2^{-m} N² ≤ 2^{-m/9} N²`. + have hW' : (W.card : ℝ) * (2 : ℝ) ^ m ≤ (N : ℝ) ^ 2 := by exact_mod_cast hW + calc (W.card : ℝ) = saving m * (W.card * (2 : ℝ) ^ ((m : ℝ) / 9)) := by + rw [mul_left_comm, saving_mul_two_rpow, mul_one] + _ ≤ saving m * (W.card * (2 : ℝ) ^ m) := by gcongr; exact two_rpow_ninth_le m + _ ≤ saving m * (N : ℝ) ^ 2 := by gcongr + +/-- Section 2.4.4: O(L) operations per subset, per tile, and per position of W, which is +O(L 2^{-m/9} N²) in all: the number of these objects, with the `K₀²` subsets counted as at most +`K`, as the paper does (as K ≤ N). Here `N` is the size of the input. The constant is +`18 = 1 + 16 + 1`, because the padding at most doubles `N`. -/ +theorem minutiae_total_le (m N : ℕ) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) + (hW : W.card * 2 ^ m ≤ N ^ 2) : + ((K (19 * m) m + (tiles (19 * m) m N).card + W.card : ℕ) : ℝ) + ≤ 18 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + obtain ⟨hK, hT, hW'⟩ := minutiae_parts m N W hN hW + have hc := saving_pos m + have hpad : (padN (19 * m) m N : ℝ) ≤ 2 * (N : ℝ) := by + exact_mod_cast padN_le_two_mul_of_D_pow_le m N hN + have hsq : saving m * (padN (19 * m) m N : ℝ) ^ 2 ≤ saving m * (2 * (N : ℝ)) ^ 2 := by gcongr + rw [← saving_eq_D_rpow] + push_cast + -- `K + |tiles| + |W| ≤ c N² + 4 c (2N)² + c N² = 18 c N²` with `c = 2^{-m/9}`. + linarith [hK, hT, hW', hsq] + +/-! ### The word size -/ + +/-- The subsets in the table are distinct (Section 2.3.4), so a sum over the table that selects the +subset `Q` is one of its terms or 0. -/ +private theorem sum_table_eq_zero_or {L m : ℕ} {A : Type*} [AddCommMonoid A] (lay : Layout L m) + (F : Fin (K0 L m) × Fin (K0 L m) → A) (Q : Finset (Fin L)) : + (∑ gh, if lay.table gh = Q then F gh else 0) = 0 + ∨ ∃ gh, (∑ gh', if lay.table gh' = Q then F gh' else 0) = F gh := by + by_cases hQ : ∃ gh, lay.table gh = Q + · obtain ⟨gh, rfl⟩ := hQ + exact .inr ⟨gh, by simp [lay.table_injective.eq_iff]⟩ + · exact .inl (sum_eq_zero fun gh _ => ite_eq_right (not_exists.mp hQ gh)) + +/-- Section 2.4.4, word size: The input entries are of this size, by assumption. Every entry of +the input array of a row band is an entry of `X` or 0. + +NOTE. `0 ≤ A` is needed only when `N = 0`. -/ +theorem abs_bandArrayL_le {L m N : ℕ} (lay : Layout L m) {X : Matrix (Fin N) (Fin (D m)) ℤ} {A : ℤ} + (hA : 0 ≤ A) (hX : ∀ I k, |X I k| ≤ A) (β : ℕ) (u : LeftStr L) : + |bandArrayL lay X β u| ≤ A := by + unfold bandArrayL arrayL bandFamilyL + split_ifs with h + · rcases sum_table_eq_zero_or lay (fun gh => rowBlock lay X (β * K0 L m + gh.1)) (innerSetL u) + with hzero | ⟨gh, hgh⟩ + · rw [hzero] + simpa using hA + · rw [hgh] + unfold rowBlock padRows + split_ifs + · exact hX _ _ + · simpa using hA + · simpa using hA + +/-- Section 2.4.4, word size: The input entries are of this size, by assumption. Every entry of +the input array of a column band is an entry of `Y` or 0. + +NOTE. `0 ≤ B` is needed only when `N = 0`. -/ +theorem abs_bandArrayR_le {L m N : ℕ} (lay : Layout L m) {Y : Matrix (Fin (D m)) (Fin N) ℤ} {B : ℤ} + (hB : 0 ≤ B) (hY : ∀ k J, |Y k J| ≤ B) (β : ℕ) (v : RightStr L) : + |bandArrayR lay Y β v| ≤ B := by + unfold bandArrayR arrayR bandFamilyR + split_ifs with h + · rcases sum_table_eq_zero_or lay (fun gh => colBlock lay Y (β * K0 L m + gh.2)) (innerSetR v) + with hzero | ⟨gh, hgh⟩ + · rw [hzero] + simpa using hB + · rw [hgh] + unfold colBlock padCols + split_ifs + · exact hY _ _ + · simpa using hB + · simpa using hB + +/-- A combination of the entries of an array, with a product of coefficients 0, 1 or -1 for each +entry, is at most the number of entries times the largest absolute value of an entry. -/ +theorem abs_encodeWith_le {α : Type} [Fintype α] {c : Term → α → ℤ} (hc : ∀ lam s, |c lam s| ≤ 1) + {L : ℕ} {a : (Fin L → α) → ℤ} {A : ℤ} (ha : ∀ u, |a u| ≤ A) (τ : Leaf L) : + |encodeWith c τ a| ≤ Fintype.card (Fin L → α) * A := by + have h := abs_sum_mul_le univ (c := fun u => ∏ ℓ, c (τ ℓ) (u ℓ)) + (fun u _ => abs_prod_le_one _ fun ℓ _ => hc _ _) fun u _ => ha u + simpa only [encodeWith, mul_comm, card_univ] using h + +/-- Section 2.4.4, word size: Every number in an encoding […] is a ±1 combination of at most 7^L +input entries, so its absolute value is at most `7^L` times the largest absolute value of an input +entry. For the encoding of `a`. -/ +theorem abs_encodingL_le {L : ℕ} {a : LeftStr L → ℤ} {A : ℤ} (ha : ∀ u, |a u| ≤ A) (τ : Leaf L) : + |encodingL a τ| ≤ 7 ^ L * A := by + have h := abs_encodeWith_le abs_phi_le_one ha τ + rw [card_leftStr] at h + exact_mod_cast h + +/-- Section 2.4.4, word size: Every number in an encoding […] is a ±1 combination of at most 7^L +input entries. For the encoding of `b`. -/ +theorem abs_encodingR_le {L : ℕ} {b : RightStr L → ℤ} {B : ℤ} (hb : ∀ v, |b v| ≤ B) (τ : Leaf L) : + |encodingR b τ| ≤ 7 ^ L * B := by + have h := abs_encodeWith_le abs_psi_le_one hb τ + rw [card_rightStr] at h + exact_mod_cast h + +/-- A returned pruned entry is a sum of at most `10^n` encoded products (Section 2.4.4). +This bounds the mathematical result, not native execution or machine width. -/ +theorem Pruned.abs_result_le {n : ℕ} (encA encB : Leaf n → ℤ) (A B : ℤ) (hA : ∀ τ, |encA τ| ≤ A) + (hB : ∀ τ, |encB τ| ≤ B) (S : Finset (OutStr n)) (w : OutStr n) : + |Pruned.result n encA encB S w| ≤ 10 ^ n * (A * B) := by + have hA0 : 0 ≤ A := (abs_nonneg _).trans (hA fun _ => Term.P0) + have hAB : 0 ≤ A * B := mul_nonneg hA0 ((abs_nonneg _).trans (hB fun _ => Term.P0)) + -- The value is 0, or the sum over some of the `10^n` leaves of the product of the two numbers + -- looked up there. + rw [Pruned.result_eq_piecewise_sum] + simp only [Finset.piecewise] + split_ifs + · calc |∑ τ : Leaf n with Leaf.Contributes τ w, encA τ * encB τ| + ≤ (univ.filter fun τ : Leaf n => Leaf.Contributes τ w).card * (A * B) := + abs_sum_le_card_mul _ fun τ _ => by + rw [abs_mul] + exact mul_le_mul (hA τ) (hB τ) (abs_nonneg _) hA0 + _ ≤ 10 ^ n * (A * B) := by + gcongr + exact_mod_cast (card_le_univ _).trans_eq (card_vertex n) + · simp only [Pi.zero_apply, abs_zero] + positivity + +/-- Section 2.4.4, word size: 10^L ≤ N² by (6), for `L = 19m` and `N ≥ D^18`. -/ +theorem ten_pow_le_sq (m N : ℕ) (hN : D m ^ 18 ≤ N) : 10 ^ (19 * m) ≤ N ^ 2 := by + -- `N₀ ≤ K N₀ ≤ N` and `2^{-m/9} ≤ 1`, so equation (6) gives `10^L ≤ N N₀ ≤ N²`. + have hN0 : (N0 (19 * m) m : ℝ) ≤ N := by + exact_mod_cast (Nat.le_mul_of_pos_left _ (Nat.choose_pos (by omega))).trans + (K_mul_N0_le m N hN) + have hc := saving_le_one m + have hreal : (10 : ℝ) ^ (19 * m) ≤ (N : ℝ) ^ 2 := + calc (10 : ℝ) ^ (19 * m) ≤ saving m * N * N0 (19 * m) m := encoding_size_le m N hN + _ ≤ 1 * N * N := by gcongr + _ = (N : ℝ) ^ 2 := by ring + exact_mod_cast hreal + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean new file mode 100644 index 0000000000..a11ba4e55b --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean @@ -0,0 +1,320 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned +public import Complexity.Foundations.Combinatorics.FunctionPlaces +public import Mathlib.Algebra.Order.Ring.Rat +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring +import Mathlib.Tactic.Positivity + +/-! +# Orders and counts of contributing leaves + +## Main definitions + +* `order`, `alpha`, `beta`: leaf order and its two counting distributions. + +## Main results + +* `card_contributing_of_order`, `card_filter_order_eq`, `card_outStr_of_leaf`: exact counts. +* `two_pow_mul_beta_le`: geometric decay when `L = 19m`. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The set of levels at which a leaf chooses `P₀`. -/ +def P0Levels {L : ℕ} (τ : Leaf L) : Finset (Fin L) := univ.filter fun ℓ => τ ℓ = Term.P0 + +/-- Section 2.4.3: we define the order of a leaf as m minus the number of levels at which it +chooses P₀. The order is an integer; it is negative for a leaf that chooses `P₀` at more than `m` +levels. -/ +def order {L : ℕ} (m : ℕ) (τ : Leaf L) : ℤ := (m : ℤ) - ((P0Levels τ).card : ℤ) + +/-- Section 2.4.3: α_d := binom(m, d) 9^d. -/ +def alpha (m d : ℕ) : ℕ := m.choose d * 9 ^ d + +/-- Section 2.4.3: β_d := binom(L, m-d) 9^{L-m+d}. Meant for `d ≤ m ≤ L`; outside this range the +subtractions of natural numbers are cut off at 0 and the value has no meaning. -/ +def beta (L m d : ℕ) : ℕ := L.choose (m - d) * 9 ^ (L - m + d) + +/-! ### One level: which terms contribute to which variables -/ + +/-- The private term of `z` is `P₀` exactly if `z = z₀`. -/ +theorem OutVar.privateTerm_eq_P0_iff (z : OutVar) : z.privateTerm = Term.P0 ↔ z.IsInner := by + cases z <;> simp [OutVar.IsInner, OutVar.privateTerm] + +/-- One term is `P₀`. -/ +private theorem card_term_P0 : (univ.filter fun lam : Term => lam = Term.P0).card = 1 := by decide + +/-- Nine terms are different from `P₀`. -/ +private theorem card_term_ne_P0 : (univ.filter fun lam : Term => ¬ lam = Term.P0).card = 9 := by + decide + +/-- Among the terms contributing to `z`, the number of those equal to `P₀`: one if `z = z₀`, none if +`z = z_ij`. -/ +private theorem card_contributing_P0 (z : OutVar) : + ((univ.filter fun lam : Term => lam.Contributes z).filter fun lam => lam = Term.P0).card + = if z.IsInner then 1 else 0 := by + decide +revert + +/-- Among the terms contributing to `z`, the number of those different from `P₀`: nine if `z = z₀`, +one if `z = z_ij`. -/ +private theorem card_contributing_ne_P0 (z : OutVar) : + ((univ.filter fun lam : Term => lam.Contributes z).filter fun lam => ¬ lam = Term.P0).card + = if z.IsInner then 9 else 1 := by + decide +revert + +/-- The number of terms contributing to `z`: ten if `z = z₀`, one if `z = z_ij`. -/ +private theorem card_contributing_term (z : OutVar) : + (univ.filter fun lam : Term => lam.Contributes z).card = if z.IsInner then 10 else 1 := by + decide +revert + +/-- Among the output variables to which `lam` contributes, exactly one is inner. -/ +private theorem card_contributed_inner (lam : Term) : + ((univ.filter fun z : OutVar => lam.Contributes z).filter fun z => z.IsInner).card = 1 := by + decide +revert + +/-- Among the output variables to which `lam` contributes, the number of outer ones: none if +`lam = P₀`, one if `lam = P_ij`. -/ +private theorem card_contributed_outer (lam : Term) : + ((univ.filter fun z : OutVar => lam.Contributes z).filter fun z => ¬ z.IsInner).card + = if lam = Term.P0 then 0 else 1 := by + decide +revert + +/-! ### The order of a leaf -/ + +/-- Membership in the set of levels at which a leaf chooses `P₀`. -/ +@[simp] +theorem mem_P0Levels {L : ℕ} {τ : Leaf L} {ℓ : Fin L} : ℓ ∈ P0Levels τ ↔ τ ℓ = Term.P0 := by + simp [P0Levels] + +/-- Membership in the inner set of an output string. -/ +@[simp] +theorem mem_innerSetO {L : ℕ} {η : OutStr L} {ℓ : Fin L} : ℓ ∈ innerSetO η ↔ (η ℓ).IsInner := by + simp [innerSetO] + +/-- Section 2.4.3: A leaf contributing to an output string chooses P₀ only at levels of the output +string's inner set. -/ +theorem P0Levels_subset {L : ℕ} {τ : Leaf L} {w : OutStr L} (h : Leaf.Contributes τ w) : + P0Levels τ ⊆ innerSetO w := by + intro ℓ hℓ + rw [mem_P0Levels] at hℓ + rw [mem_innerSetO] + rcases (Term.contributes_iff _ _).mp (h ℓ) with hinner | hprivate + · exact hinner + · exact (OutVar.privateTerm_eq_P0_iff _).mp (hprivate.symm.trans hℓ) + +/-- Section 2.4.3: so its order is at least 0. -/ +theorem order_nonneg {L m : ℕ} {w : OutStr L} (hw : (innerSetO w).card = m) {τ : Leaf L} + (h : Leaf.Contributes τ w) : 0 ≤ order m τ := by + have hc := card_le_card (P0Levels_subset h) + unfold order + omega + +/-- In the proof of Lemma 11: every leaf of leaves(U) has an order 0 ≤ d ≤ m. The upper +bound holds for every leaf. -/ +theorem order_le {L : ℕ} (m : ℕ) (τ : Leaf L) : order m τ ≤ m := by + unfold order + omega + +/-- A leaf has order `d` exactly if it chooses `P₀` at `m - d` levels. -/ +theorem order_eq_iff {L : ℕ} (m d : ℕ) (τ : Leaf L) : + order m τ = d ↔ (P0Levels τ).card + d = m := by + unfold order + omega + +/-! ### The counts -/ + +/-- Section 2.4.3: A single output string has 10^m […] leaves contributing to it. -/ +theorem card_filter_contributes {L m : ℕ} (w : OutStr L) (hw : (innerSetO w).card = m) : + (univ.filter fun τ : Leaf L => Leaf.Contributes τ w).card = 10 ^ m := by + -- Ten terms contribute to `z₀` and one to `z_ij`. + simp only [Leaf.filter_contributes_eq_piFinset, Fintype.card_piFinset, + card_contributing_term] + rw [prod_ite, prod_const, prod_const_one, mul_one, ← hw] + rfl + +/-- Section 2.4.3: exactly α_d := binom(m, d) 9^d leaves of order d contribute to an output +string. As everywhere in Section 2.4.3, the output strings are those whose inner sets have exactly +m elements. -/ +theorem card_contributing_of_order {L m : ℕ} (w : OutStr L) (hw : (innerSetO w).card = m) + (d : ℕ) : + (univ.filter fun τ : Leaf L => Leaf.Contributes τ w ∧ order m τ = d).card = alpha m d := by + by_cases hd : d ≤ m + · -- Sort the strings of terms that contribute to `w` level by level and have `P₀` at exactly + -- `m - d` levels by the set `S` of these levels. + have hcount := Finset.card_pi_places_card + (fun ℓ => univ.filter fun lam : Term => lam.Contributes (w ℓ)) (fun lam => lam = Term.P0) + (m - d) + simp only [card_contributing_P0, card_contributing_ne_P0] at hcount + -- Given `S`, there is no such string unless `S ⊆ Q`. Then there are nine choices at each of + -- the `|Q| - |S| = d` levels of `Q` outside `S`, and one choice at every other level. + have hgiven : ∀ S ∈ powersetCard (m - d) (univ : Finset (Fin L)), + (∏ ℓ, if ℓ ∈ S then (if (w ℓ).IsInner then 1 else 0) else (if (w ℓ).IsInner then 9 else 1)) + = if S ⊆ innerSetO w then 9 ^ d else 0 := by + intro S hS + have hQS := prod_ite_ite_subset (innerSetO w) S 9 + rw [hw, (mem_powersetCard.mp hS).2, Nat.sub_sub_self hd] at hQS + rw [← hQS] + exact prod_congr rfl fun ℓ _ => by simp + -- There are `binom(m, m - d) = binom(m, d)` sets `S ⊆ Q`. + rw [sum_congr rfl hgiven, sum_powersetCard_ite_subset, hw, Nat.choose_symm hd] at hcount + -- These strings are the leaves of order `d` that contribute to `w`. + rw [alpha, ← hcount] + refine congrArg card (Finset.ext fun τ => ?_) + simp only [mem_filter, mem_univ, true_and, order_eq_iff] + exact and_congr Iff.rfl (by unfold P0Levels; omega) + · -- No leaf has order more than `m`. + rw [alpha, Nat.choose_eq_zero_of_lt (by omega), zero_mul, card_eq_zero] + refine filter_eq_empty_iff.mpr fun τ _ h => ?_ + have hle := order_le m τ + omega + +/-- Section 2.4.3: The total number of leaves of order d in the entire recursion tree is +β_d := binom(L, m-d) 9^{L-m+d}. + +NOTE. `d ≤ m` and `m ≤ L` are left implicit in the paper. -/ +theorem card_filter_order_eq {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd : d ≤ m) : + (univ.filter fun τ : Leaf L => order m τ = d).card = beta L m d := by + -- The leaves of order `d` are the strings of terms with `P₀` at exactly `m - d` levels. + have hsort : (univ.filter fun τ : Leaf L => order m τ = d) + = univ.filter fun τ : Leaf L => (univ.filter fun ℓ => τ ℓ = Term.P0).card = m - d := by + ext τ + simp only [mem_filter, mem_univ, true_and, order_eq_iff] + unfold P0Levels + omega + rw [hsort, Finset.card_places_card (fun lam : Term => lam = Term.P0) (m - d), card_term_P0, + card_term_ne_P0, one_pow, one_mul, Fintype.card_fin, beta, + show L - (m - d) = L - m + d by omega] + +/-- Section 2.4.3: In particular, β₀ = binom(L, m) 9^{L-m}. -/ +theorem beta_zero (L m : ℕ) : beta L m 0 = L.choose m * 9 ^ (L - m) := by + simp [beta] + +/-- Section 2.4.3: β₀ = binom(L, m) 9^{L-m} = M. -/ +theorem beta_zero_eq_M (L m : ℕ) : beta L m 0 = M L m := by + rw [beta_zero, M, K, N0, ← pow_mul, mul_comm (L - m) 2, pow_mul] + norm_num + +/-- Section 2.4.3: A leaf of order d contributes to binom(L-m+d, d) output strings, one for each +inner set of size m that contains the m-d levels at which it chooses P₀. Only the number is +stated. As everywhere in Section 2.4.3, the output strings are those whose inner sets have exactly +m elements. + +NOTE. `m ≤ L` is left implicit in the paper. -/ +theorem card_outStr_of_leaf {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (τ : Leaf L) + (hτ : order m τ = d) : + (univ.filter fun w : OutStr L => (innerSetO w).card = m ∧ Leaf.Contributes τ w).card + = (L - m + d).choose d := by + have hP0 : (P0Levels τ).card + d = m := (order_eq_iff m d τ).mp hτ + -- Sort the output strings to which `τ` contributes level by level, with `z₀` at exactly `m` + -- levels, by the set `S` of these levels, which is their inner set. + have hcount := Finset.card_pi_places_card + (fun ℓ => univ.filter fun z : OutVar => (τ ℓ).Contributes z) (fun z => z.IsInner) m + simp only [card_contributed_inner, card_contributed_outer] at hcount + -- Given `S`, there is one such string if `S` contains the levels at which `τ` chooses `P₀`, and + -- none otherwise. + have hgiven : ∀ S ∈ powersetCard m (univ : Finset (Fin L)), + (∏ ℓ, if ℓ ∈ S then 1 else (if τ ℓ = Term.P0 then 0 else 1)) + = if P0Levels τ ⊆ S then 1 else 0 := by + intro S _ + rw [← prod_ite_ite_superset] + exact prod_congr rfl fun ℓ _ => by simp + -- A set of `m` levels that contains these `m - d` levels is given by the `L - m` levels outside + -- it, among the other `L - m + d` levels. + rw [sum_congr rfl hgiven, ← card_filter, + card_powersetCard_superset _ (by simpa using hmL), Fintype.card_fin, + show L - (P0Levels τ).card = L - m + d by omega, Nat.choose_symm_add] at hcount + rw [← hcount] + refine congrArg card (Finset.ext fun w => ?_) + simp only [mem_filter, mem_univ, true_and] + exact and_comm + +/-- `β_d` is positive when `m ≤ L`. -/ +theorem beta_pos {L m : ℕ} (hmL : m ≤ L) (d : ℕ) : 0 < beta L m d := + Nat.mul_pos (Nat.choose_pos (by omega)) (by positivity) + +/-- The equality in equation (5) without division: `β_d (L-m+d) = 9 (m-d+1) β_{d-1}` for +`1 ≤ d ≤ m ≤ L`. -/ +theorem Equation5.ratio_nat {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : + beta L m d * (L - m + d) = 9 * (m - d + 1) * beta L m (d - 1) := by + -- With `k = m - d` this is `binom(L, k) (L - k) = binom(L, k + 1) (k + 1)`, times a power of 9. + have hchoose : L.choose (m - d + 1) * (m - d + 1) = L.choose (m - d) * (L - m + d) := by + rw [Nat.choose_succ_right_eq, show L - (m - d) = L - m + d by omega] + have hpow : 9 ^ (L - m + d) = 9 * 9 ^ (L - m + (d - 1)) := by + rw [show L - m + d = L - m + (d - 1) + 1 by omega, pow_succ, mul_comm] + rw [beta, beta, show m - (d - 1) = m - d + 1 by omega, hpow] + calc L.choose (m - d) * (9 * 9 ^ (L - m + (d - 1))) * (L - m + d) + = 9 * 9 ^ (L - m + (d - 1)) * (L.choose (m - d) * (L - m + d)) := by ring + _ = 9 * 9 ^ (L - m + (d - 1)) * (L.choose (m - d + 1) * (m - d + 1)) := by rw [hchoose] + _ = 9 * (m - d + 1) * (L.choose (m - d + 1) * 9 ^ (L - m + (d - 1))) := by ring + +/-- Section 2.4.3, the equality in equation (5), for every `L ≥ m` (it is used again as equation (7) +in Section 4.3): β_d / β_{d-1} = 9(m-d+1) / (L-m+d) for `1 ≤ d ≤ m`. + +NOTE. The paper prints the equality under `L = 19m` in (5) and under `L ≥ 10m` in (7); here it is +stated for every `L ≥ m`. -/ +theorem beta_ratio {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : + (beta L m d : ℚ) / (beta L m (d - 1) : ℚ) + = 9 * ((m : ℚ) - (d : ℚ) + 1) / ((L : ℚ) - (m : ℚ) + (d : ℚ)) := by + have hm : (m : ℚ) ≤ L := by exact_mod_cast hmL + have hd0 : (1 : ℚ) ≤ d := by exact_mod_cast hd1 + have hbeta : (0 : ℚ) < beta L m (d - 1) := by exact_mod_cast beta_pos hmL (d - 1) + have hnat := congrArg (Nat.cast : ℕ → ℚ) (Equation5.ratio_nat hmL d hd1 hd) + push_cast [Nat.cast_sub hmL, Nat.cast_sub hd] at hnat + rw [div_eq_div_iff hbeta.ne' (by linarith), hnat] + +/-- Section 2.4.3, the inequalities in equation (5): if L = 19m, then for 1 ≤ d ≤ m we have +β_d / β_{d-1} = 9(m-d+1)/(L-m+d) ≤ 9m/(18m+1) < 1/2. -/ +theorem beta_ratio_lt_half (m d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : + (beta (19 * m) m d : ℚ) / (beta (19 * m) m (d - 1) : ℚ) ≤ 9 * (m : ℚ) / (18 * (m : ℚ) + 1) + ∧ 9 * (m : ℚ) / (18 * (m : ℚ) + 1) < 1 / 2 := by + have hm : (1 : ℚ) ≤ m := by exact_mod_cast hd1.trans hd + have hd0 : (1 : ℚ) ≤ d := by exact_mod_cast hd1 + refine ⟨?_, ?_⟩ + · -- The numerator is at most `9m` and the denominator at least `18m + 1`, because `d ≥ 1`. + rw [beta_ratio (by omega) d hd1 hd] + push_cast + exact div_le_div₀ (by positivity) (by linarith) (by positivity) (by linarith) + · rw [div_lt_div_iff₀ (by positivity) (by norm_num)] + linarith + +/-- **Equation (5)**, conclusion: if `L = 19m` then β_d ≤ 2^{-d} M, for `0 ≤ d ≤ m`. +The inequality is multiplied out. -/ +theorem two_pow_mul_beta_le (m d : ℕ) (hd : d ≤ m) : 2 ^ d * beta (19 * m) m d ≤ M (19 * m) m := by + induction d with + | zero => simp [beta_zero_eq_M] + | succ d ih => + -- By the two inequalities of (5), `β_{d+1} < β_d / 2`. + obtain ⟨hle, hlt⟩ := beta_ratio_lt_half m (d + 1) (by omega) hd + rw [Nat.add_sub_cancel] at hle + have hbeta : (0 : ℚ) < beta (19 * m) m d := by exact_mod_cast beta_pos (by omega) d + have hhalf : 2 * beta (19 * m) m (d + 1) ≤ beta (19 * m) m d := by + have hq := (div_lt_iff₀ hbeta).mp (hle.trans_lt hlt) + exact_mod_cast (by linarith : (2 * beta (19 * m) m (d + 1) : ℚ) ≤ beta (19 * m) m d) + calc 2 ^ (d + 1) * beta (19 * m) m (d + 1) = 2 ^ d * (2 * beta (19 * m) m (d + 1)) := by ring + _ ≤ 2 ^ d * beta (19 * m) m d := Nat.mul_le_mul_left _ hhalf + _ ≤ M (19 * m) m := ih (by omega) + + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean new file mode 100644 index 0000000000..464b2a19cc --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Sparse +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling +public import Complexity.Foundations.Combinatorics.BinomialUpperBound +public import Mathlib.Analysis.Complex.ExponentialBounds + +/-! +# Parameters of finite thin multiplication + +## Main results + +* `K_mul_N0_le_D_pow`: the band fits when `D^18 ≤ N`. +* `encoding_size_le`: the shared encoding length bound. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.4.4: using the general bound binom(L, m) ≤ (eL/m)^m, we have +K = binom(19m, m) ≤ (19e)^m. The general bound is `Nat.choose_le_exp_mul_div_pow`. + +NOTE. The printed bound divides by `m`. In Lean a quotient by 0 is 0 and `0^0 = 1`, so for `m = 0` +both sides are 1 and no hypothesis `m ≥ 1` is needed. -/ +theorem K_le_pow (m : ℕ) : (K (19 * m) m : ℝ) ≤ (19 * Real.exp 1) ^ m := by + rcases Nat.eq_zero_or_pos m with rfl | hm + · simp [K] + · have hm' : (m : ℝ) ≠ 0 := by exact_mod_cast hm.ne' + have hbase : Real.exp 1 * ((19 * m : ℕ) : ℝ) / (m : ℝ) = 19 * Real.exp 1 := by + push_cast + field_simp + exact hbase ▸ Nat.choose_le_exp_mul_div_pow (19 * m) m + +/-- Section 2.4.4: 19e · 3^18 < 4^18. -/ +theorem nineteen_mul_exp_mul_three_pow_lt : 19 * Real.exp 1 * (3 : ℝ) ^ 18 < (4 : ℝ) ^ 18 := by + calc 19 * Real.exp 1 * (3 : ℝ) ^ 18 < 19 * 2.7182818286 * (3 : ℝ) ^ 18 := by + gcongr + exact Real.exp_one_lt_d9 + _ < (4 : ℝ) ^ 18 := by norm_num + +/-- `4^{18m} = D^18`. -/ +private theorem four_pow_eq (m : ℕ) : ((4 : ℝ) ^ 18) ^ m = (D m : ℝ) ^ 18 := by + rw [D, Nat.cast_pow, Nat.cast_ofNat, ← pow_mul, ← pow_mul, mul_comm] + +/-- Section 2.4.4: so K N₀ ≤ (19e · 3^18)^m ≤ 4^{18m}, and `4^{18m} = D^18`. -/ +theorem K_mul_N0_le_D_pow (m : ℕ) : K (19 * m) m * N0 (19 * m) m ≤ D m ^ 18 := by + have hK := K_le_pow m + have key : (K (19 * m) m : ℝ) * (N0 (19 * m) m : ℝ) ≤ (D m : ℝ) ^ 18 := by + calc (K (19 * m) m : ℝ) * (N0 (19 * m) m : ℝ) + ≤ (19 * Real.exp 1) ^ m * ((3 : ℝ) ^ 18) ^ m := by + rw [N0_nineteen_mul m] + push_cast + rw [pow_mul] + exact mul_le_mul_of_nonneg_right hK (by positivity) + _ = (19 * Real.exp 1 * (3 : ℝ) ^ 18) ^ m := (mul_pow _ _ _).symm + _ ≤ ((4 : ℝ) ^ 18) ^ m := + pow_le_pow_left₀ (by positivity) nineteen_mul_exp_mul_three_pow_lt.le m + _ = (D m : ℝ) ^ 18 := four_pow_eq m + exact_mod_cast key + +/-- Section 2.4.4, the first consequence of `N ≥ D^18`: K N₀ ≤ D^18 ≤ N. -/ +theorem K_mul_N0_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K (19 * m) m * N0 (19 * m) m ≤ N := + (K_mul_N0_le_D_pow m).trans hN + +/-- Section 2.4.4, the first consequence of `N ≥ D^18`: and hence K₀ N₀ ≤ K N₀ ≤ D^18 ≤ N. -/ +theorem K0_mul_N0_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K0 (19 * m) m * N0 (19 * m) m ≤ N := + (Nat.mul_le_mul_right _ (Nat.sqrt_le_self _)).trans (K_mul_N0_le m N hN) + +/-- Section 2.4.4: This means the padding at most doubles N. -/ +theorem padN_le_two_mul_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : padN (19 * m) m N ≤ 2 * N := + padN_le_two_mul (19 * m) m N (K0_mul_N0_le m N hN) + +/-- Section 2.4.4: 10^19 · 2^{1/9} / 3^18 < 4^18. -/ +theorem ten_pow_mul_two_rpow_div_three_pow_lt : + (10 : ℝ) ^ 19 * (2 : ℝ) ^ (1 / 9 : ℝ) / (3 : ℝ) ^ 18 < (4 : ℝ) ^ 18 := by + have htwo : (2 : ℝ) ^ (1 / 9 : ℝ) ≤ 2 := by + calc (2 : ℝ) ^ (1 / 9 : ℝ) ≤ (2 : ℝ) ^ (1 : ℝ) := + Real.rpow_le_rpow_of_exponent_le (by norm_num) (by norm_num) + _ = 2 := Real.rpow_one 2 + rw [div_lt_iff₀ (by positivity)] + calc (10 : ℝ) ^ 19 * (2 : ℝ) ^ (1 / 9 : ℝ) ≤ (10 : ℝ) ^ 19 * 2 := + mul_le_mul_of_nonneg_left htwo (by positivity) + _ < (4 : ℝ) ^ 18 * (3 : ℝ) ^ 18 := by norm_num + +/-- Section 2.4.4: so 10^L · 2^{m/9} / N₀ ≤ D^18, for `L = 19m`. -/ +theorem ten_pow_div_N0_le (m : ℕ) : + (10 : ℝ) ^ (19 * m) * (2 : ℝ) ^ ((m : ℝ) / 9) / (N0 (19 * m) m : ℝ) ≤ (D m : ℝ) ^ 18 := by + have hroot : (2 : ℝ) ^ ((m : ℝ) / 9) = ((2 : ℝ) ^ (1 / 9 : ℝ)) ^ m := by + rw [← Real.rpow_natCast _ m, ← Real.rpow_mul (by norm_num)] + congr 1 + ring + calc (10 : ℝ) ^ (19 * m) * (2 : ℝ) ^ ((m : ℝ) / 9) / (N0 (19 * m) m : ℝ) + = ((10 : ℝ) ^ 19 * (2 : ℝ) ^ (1 / 9 : ℝ) / (3 : ℝ) ^ 18) ^ m := by + rw [N0_nineteen_mul m, hroot] + push_cast + rw [pow_mul, pow_mul, div_pow, mul_pow] + _ ≤ ((4 : ℝ) ^ 18) ^ m := pow_le_pow_left₀ (by positivity) + ten_pow_mul_two_rpow_div_three_pow_lt.le m + _ = (D m : ℝ) ^ 18 := four_pow_eq m + +/-- **Equation (6)**, the second consequence of `N ≥ D^18`: 10^L ≤ 2^{-m/9} N N₀, for +`L = 19m`. -/ +theorem encoding_size_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : + (10 : ℝ) ^ (19 * m) ≤ (2 : ℝ) ^ (-(m : ℝ) / 9) * (N : ℝ) * (N0 (19 * m) m : ℝ) := by + have hN0 : (0 : ℝ) < (N0 (19 * m) m : ℝ) := by exact_mod_cast N0_pos _ _ + have hN' : (D m : ℝ) ^ 18 ≤ (N : ℝ) := by exact_mod_cast hN + have h := (ten_pow_div_N0_le m).trans hN' + rw [div_le_iff₀ hN0] at h + calc (10 : ℝ) ^ (19 * m) + = (10 : ℝ) ^ (19 * m) * (2 : ℝ) ^ ((m : ℝ) / 9) * saving m := by + rw [mul_assoc, mul_comm _ (saving m), saving_mul_two_rpow, mul_one] + _ ≤ (N : ℝ) * (N0 (19 * m) m : ℝ) * saving m := + mul_le_mul_of_nonneg_right h (saving_pos m).le + _ = saving m * (N : ℝ) * (N0 (19 * m) m : ℝ) := by ring + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean new file mode 100644 index 0000000000..9cd46e8f7c --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean @@ -0,0 +1,311 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Orders +public import Complexity.Foundations.Algebra.GeometricTail +public import Mathlib.Analysis.SpecialFunctions.Pow.Real + +/-! +# Sparse query bounds + +## Main definitions + +* `saving`: the factor `D^(-1/18)` for `D = 4^m`. + +## Main results + +* `card_leaves_le_sum_min`: sum the two order-wise bounds. +* `card_leaves_le_saving`: explicit sparse leaf bound, with no assumed sparse solver. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-! ### The parameters from Section 2.4.3 on -/ + +/-- Section 2.4.3: From now on, we fix L = 19m, so that N₀ = 3^{18m}. -/ +theorem N0_nineteen_mul (m : ℕ) : N0 (19 * m) m = 3 ^ (18 * m) := by + unfold N0 + congr 1 + omega + +/-- `M` is positive when `m ≤ L`. -/ +theorem M_pos {L m : ℕ} (hmL : m ≤ L) : 0 < M L m := + Nat.mul_pos (Nat.choose_pos hmL) (Nat.pow_pos (Nat.pow_pos (by decide))) + +/-- `D = 4^m = 2^{2m}`. -/ +private theorem D_eq_two_pow (m : ℕ) : (D m : ℝ) = (2 : ℝ) ^ (2 * m) := by + rw [D, pow_mul] + norm_num + +/-- Section 2.4.3: Recall that D = 4^m, so 2^m = √D. -/ +theorem two_pow_eq_sqrt_D (m : ℕ) : (2 : ℝ) ^ m = Real.sqrt (D m) := by + rw [D_eq_two_pow, pow_mul', Real.sqrt_sq (by positivity)] + +/-- The factor `2^{-m/9}` of Sections 2.4.3 and 2.4.4, by which the costs of the algorithm of +Theorem 5 lie below `N²`. -/ +noncomputable def saving (m : ℕ) : ℝ := (2 : ℝ) ^ (-(m : ℝ) / 9) + +/-- `2^{-m/9}` is positive. -/ +theorem saving_pos (m : ℕ) : 0 < saving m := + Real.rpow_pos_of_pos two_pos _ + +/-- `2^{-m/9} ≤ 1`. -/ +theorem saving_le_one (m : ℕ) : saving m ≤ 1 := + Real.rpow_le_one_of_one_le_of_nonpos one_le_two + (div_nonpos_of_nonpos_of_nonneg (neg_nonpos.mpr m.cast_nonneg) (by norm_num)) + +/-- `2^{-m/9} · 2^{m/9} = 1`. -/ +theorem saving_mul_two_rpow (m : ℕ) : saving m * (2 : ℝ) ^ ((m : ℝ) / 9) = 1 := by + rw [saving, ← Real.rpow_add two_pos, show -(m : ℝ) / 9 + (m : ℝ) / 9 = 0 by ring, Real.rpow_zero] + +/-- Section 2.4.3: and 2^{-m/9} = D^{-1/18}. -/ +theorem saving_eq_D_rpow (m : ℕ) : saving m = (D m : ℝ) ^ (-(1 / 18 : ℝ)) := by + rw [saving, D_eq_two_pow, ← Real.rpow_natCast 2 (2 * m), ← Real.rpow_mul two_pos.le] + congr 1 + push_cast + ring + +/-! ### The first inequality -/ + +/-- In the proof of Lemma 11: We bound the number of leaves of order d in leaves(U) in two +ways: by |U| α_d, charging each output string of U for its leaves of order d, and by β_d, the number +of all leaves of order d. Quoted again in Section 4.3: At every order d, the pruned recursion of +Section 2.4 visits at most min{|U| α_d, β_d} leaves. -/ +theorem card_leaves_filter_order_le {L m : ℕ} (U : Finset (OutStr L)) + (hU : ∀ w ∈ U, (innerSetO w).card = m) (d : ℕ) : + ((leaves U).filter fun τ => order m τ = d).card ≤ min (U.card * alpha m d) (beta L m d) := by + refine le_min ?_ ?_ + · -- Charge each output string of U for its leaves of order d. + calc _ ≤ (U.biUnion fun w => + univ.filter fun τ : Leaf L => Leaf.Contributes τ w ∧ order m τ = d).card := by + refine card_le_card fun τ hτ => ?_ + obtain ⟨hτU, hτd⟩ := mem_filter.mp hτ + obtain ⟨w, hw, hc⟩ := mem_leaves.mp hτU + exact mem_biUnion.mpr ⟨w, hw, mem_filter.mpr ⟨mem_univ _, hc, hτd⟩⟩ + _ ≤ U.card * alpha m d := + card_biUnion_le_card_mul _ _ _ fun w hw => + (card_contributing_of_order w (hU w hw) d).le + · -- There are only β_d leaves of order d in all. + rcases U.eq_empty_or_nonempty with rfl | ⟨w₀, hw₀⟩ + · simp [leaves] + have hmL : m ≤ L := by + have h := card_le_univ (innerSetO w₀) + rwa [hU w₀ hw₀, Fintype.card_fin] at h + rcases le_or_gt d m with hd | hd + · rw [← card_filter_order_eq hmL d hd] + exact card_le_card (filter_subset_filter _ (subset_univ _)) + · -- No leaf has an order above m. + have hempty : ((leaves U).filter fun τ => order m τ = d) = ∅ := by + refine filter_false_of_mem fun τ _ h => ?_ + have hle := order_le m τ + omega + rw [hempty] + exact Nat.zero_le _ + +/-- **Lemma 11**, first inequality. For every set U of output strings whose inner sets have +exactly m elements, |leaves(U)| ≤ ∑_{d=0}^{m} min{|U| α_d, β_d}. -/ +theorem card_leaves_le_sum_min {L m : ℕ} (U : Finset (OutStr L)) + (hU : ∀ w ∈ U, (innerSetO w).card = m) : + (leaves U).card ≤ ∑ d ∈ range (m + 1), min (U.card * alpha m d) (beta L m d) := by + -- Every leaf of `leaves(U)` has an order between 0 and m. + have hord : ∀ τ ∈ leaves U, 0 ≤ order m τ := by + intro τ hτ + obtain ⟨w, hw, hc⟩ := mem_leaves.mp hτ + exact order_nonneg (hU w hw) hc + have hmaps : ∀ τ ∈ leaves U, (order m τ).toNat ∈ range (m + 1) := by + intro τ _ + have hle := order_le m τ + rw [mem_range] + omega + -- Sum the smaller of the two bounds over the orders. + rw [card_eq_sum_card_fiberwise hmaps] + refine sum_le_sum fun d _ => le_trans ?_ (card_leaves_filter_order_le U hU d) + refine card_le_card fun τ hτ => ?_ + obtain ⟨hτU, hτd⟩ := mem_filter.mp hτ + have hnonneg := hord τ hτU + exact mem_filter.mpr ⟨hτU, by omega⟩ + +/-! ### The second inequality -/ + +/-- Equation (5) over the reals: `β_d ≤ M 2^{-d}` for `0 ≤ d ≤ m` and `L = 19m`. -/ +private theorem beta_le_M_mul (m d : ℕ) (hd : d ≤ m) : + (beta (19 * m) m d : ℝ) ≤ (M (19 * m) m : ℝ) * (1 / 2 : ℝ) ^ d := by + have h : (2 : ℝ) ^ d * (beta (19 * m) m d : ℝ) ≤ (M (19 * m) m : ℝ) := by + exact_mod_cast two_pow_mul_beta_le m d hd + rw [one_div, inv_pow, ← div_eq_mul_inv, le_div_iff₀ (by positivity)] + linarith + +/-- By (5), the orders `d ≥ a` contribute `∑ β_d ≤ M ∑_{d ≥ a} 2^{-d} < 2 · 2^{-a} M`. -/ +private theorem sum_beta_lt_of_subset (m a : ℕ) (s : Finset ℕ) (hs : s ⊆ Ico a (m + 1)) : + ((∑ d ∈ s, beta (19 * m) m d : ℕ) : ℝ) < 2 * (1 / 2 : ℝ) ^ a * (M (19 * m) m : ℝ) := by + have hM : (0 : ℝ) < (M (19 * m) m : ℝ) := by exact_mod_cast M_pos (show m ≤ 19 * m by omega) + push_cast + calc ∑ d ∈ s, (beta (19 * m) m d : ℝ) + ≤ ∑ d ∈ s, (M (19 * m) m : ℝ) * (1 / 2 : ℝ) ^ d := + sum_le_sum fun d hd => + beta_le_M_mul m d (by have := mem_Ico.mp (hs hd); omega) + _ ≤ ∑ d ∈ Ico a (m + 1), (M (19 * m) m : ℝ) * (1 / 2 : ℝ) ^ d := + sum_le_sum_of_subset_of_nonneg hs fun _ _ _ => by positivity + _ = (M (19 * m) m : ℝ) * ∑ d ∈ Ico a (m + 1), (1 / 2 : ℝ) ^ d := (mul_sum _ _ _).symm + _ < (M (19 * m) m : ℝ) * (2 * (1 / 2 : ℝ) ^ a) := + mul_lt_mul_of_pos_left + ((geom_sum_Ico_lt_of_lt_one (by norm_num) (by norm_num) a _).trans_eq (by ring)) hM + _ = 2 * (1 / 2 : ℝ) ^ a * (M (19 * m) m : ℝ) := by ring + +/-- In the proof of Lemma 11: The orders d > m/9 contribute at most +∑_{d>m/9} β_d ≤ M ∑_{d>m/9} 2^{-d} < 2 · 2^{-m/9} M by (5). -/ +theorem sum_beta_large_orders_lt (m : ℕ) : + ((∑ d ∈ (range (m + 1)).filter (fun d => m < 9 * d), beta (19 * m) m d : ℕ) : ℝ) + < 2 * saving m * (M (19 * m) m : ℝ) := by + -- The orders d > m/9 are the orders d ≥ ⌊m/9⌋ + 1. + have hs : (range (m + 1)).filter (fun d => m < 9 * d) ⊆ Ico (m / 9 + 1) (m + 1) := by + intro d hd + obtain ⟨hdm, hd9⟩ := mem_filter.mp hd + rw [mem_range] at hdm + rw [mem_Ico] + omega + refine (sum_beta_lt_of_subset m (m / 9 + 1) _ hs).trans_le ?_ + have hpow : (1 / 2 : ℝ) ^ (m / 9 + 1) ≤ saving m := by + have hfloor : (m : ℝ) ≤ 9 * ((m / 9 + 1 : ℕ) : ℝ) := by + exact_mod_cast (by omega : m ≤ 9 * (m / 9 + 1)) + rw [show (1 / 2 : ℝ) ^ (m / 9 + 1) = (2 : ℝ) ^ (-((m / 9 + 1 : ℕ) : ℝ)) by + rw [Real.rpow_neg (by norm_num), Real.rpow_natCast, one_div, inv_pow]] + exact Real.rpow_le_rpow_of_exponent_le (by norm_num) (by linarith) + -- Multiply `hpow` by `2` and by `M`. + gcongr + +/-- One term of the display in the proof of Lemma 11: `binom(m, d) 9^d = binom(m, d) 72^d 8^{-d}`, +which is at most `72^{m/9} binom(m, d) 8^{-d}` for `d ≤ m/9`. The factor `1^{m-d}` makes the right +side a term of the expansion of `(1/8 + 1)^m`. -/ +private theorem alpha_le (m d : ℕ) (hd : 9 * d ≤ m) : + (alpha m d : ℝ) + ≤ (72 : ℝ) ^ ((m : ℝ) / 9) * ((1 / 8 : ℝ) ^ d * 1 ^ (m - d) * (m.choose d : ℝ)) := by + have hd' : (9 : ℝ) * d ≤ m := by exact_mod_cast hd + have hpow : (72 : ℝ) ^ d ≤ (72 : ℝ) ^ ((m : ℝ) / 9) := by + rw [← Real.rpow_natCast (72 : ℝ) d] + exact Real.rpow_le_rpow_of_exponent_le (by norm_num) (by linarith) + calc (alpha m d : ℝ) = (72 : ℝ) ^ d * ((1 / 8 : ℝ) ^ d * 1 ^ (m - d) * (m.choose d : ℝ)) := by + rw [alpha, one_pow, mul_one, ← mul_assoc, ← mul_pow] + push_cast + norm_num [mul_comm] + _ ≤ _ := by gcongr + +/-- The first two steps of the display in the proof of Lemma 11: +`∑_{d ≤ m/9} binom(m, d) 9^d ≤ 72^{m/9} ∑_{d=0}^{m} binom(m, d) 8^{-d} = 72^{m/9} (9/8)^m`. -/ +private theorem sum_alpha_small_orders_le_aux (m : ℕ) : + ((∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d : ℕ) : ℝ) + ≤ (72 : ℝ) ^ ((m : ℝ) / 9) * (9 / 8 : ℝ) ^ m := by + push_cast + calc ∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), (alpha m d : ℝ) + ≤ ∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), + (72 : ℝ) ^ ((m : ℝ) / 9) * ((1 / 8 : ℝ) ^ d * 1 ^ (m - d) * (m.choose d : ℝ)) := + sum_le_sum fun d hd => alpha_le m d (mem_filter.mp hd).2 + _ ≤ ∑ d ∈ range (m + 1), + (72 : ℝ) ^ ((m : ℝ) / 9) * ((1 / 8 : ℝ) ^ d * 1 ^ (m - d) * (m.choose d : ℝ)) := + sum_le_sum_of_subset_of_nonneg (filter_subset _ _) fun _ _ _ => by positivity + _ = (72 : ℝ) ^ ((m : ℝ) / 9) * (9 / 8 : ℝ) ^ m := by + rw [← mul_sum, ← add_pow] + norm_num + +/-- The display in the proof of Lemma 11: ∑_{d ≤ m/9} binom(m, d) 9^d ≤ 72^{m/9} +∑_{d=0}^{m} binom(m, d) 8^{-d} = 72^{m/9} (9/8)^m = (72 · (9/8)^9)^{m/9} < 256^{m/9} = 2^m · +2^{-m/9}. + +NOTE. The strict inequality needs `m ≥ 1`, which is fixed in Section 2.3.3; for `m = 0` both ends +are 1. -/ +theorem sum_alpha_small_orders_lt (m : ℕ) (hm : 1 ≤ m) : + ((∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d : ℕ) : ℝ) + < (2 : ℝ) ^ m * saving m := by + have hm9 : (0 : ℝ) < (m : ℝ) / 9 := by + have : (0 : ℝ) < (m : ℝ) := by exact_mod_cast hm + positivity + refine (sum_alpha_small_orders_le_aux m).trans_lt ?_ + -- 72^{m/9} (9/8)^m = (72 · (9/8)^9)^{m/9} + have hleft : (72 : ℝ) ^ ((m : ℝ) / 9) * (9 / 8 : ℝ) ^ m + = (72 * (9 / 8 : ℝ) ^ 9) ^ ((m : ℝ) / 9) := by + rw [Real.mul_rpow (by norm_num) (by positivity), ← Real.rpow_natCast (9 / 8 : ℝ) 9, + ← Real.rpow_mul (by norm_num), ← Real.rpow_natCast (9 / 8 : ℝ) m] + congr 2 + push_cast + ring + -- 256^{m/9} = 2^m · 2^{-m/9} + have hright : (256 : ℝ) ^ ((m : ℝ) / 9) = (2 : ℝ) ^ m * saving m := by + rw [saving, show (256 : ℝ) = (2 : ℝ) ^ ((8 : ℕ) : ℝ) by rw [Real.rpow_natCast]; norm_num, + ← Real.rpow_mul (by norm_num), ← Real.rpow_natCast (2 : ℝ) m, ← Real.rpow_add (by norm_num)] + congr 1 + push_cast + ring + rw [hleft, ← hright] + -- `72 · (9/8)^9 < 208 < 256`. + exact Real.rpow_lt_rpow (by positivity) (by norm_num) hm9 + +/-- The bound of `sum_alpha_small_orders_lt` with `≤`, which also holds for `m = 0`. -/ +private theorem sum_alpha_small_orders_le (m : ℕ) : + ((∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d : ℕ) : ℝ) + ≤ (2 : ℝ) ^ m * saving m := by + rcases Nat.eq_zero_or_pos m with rfl | hm + · simp [alpha, saving] + · exact (sum_alpha_small_orders_lt m hm).le + +/-- In the proof of Lemma 11: We use |U| α_d for the orders d ≤ m/9 and β_d for the orders +d > m/9. -/ +private theorem card_leaves_le_split {L m : ℕ} (U : Finset (OutStr L)) + (hU : ∀ w ∈ U, (innerSetO w).card = m) : + (leaves U).card + ≤ U.card * ∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d + + ∑ d ∈ (range (m + 1)).filter (fun d => m < 9 * d), beta L m d := by + refine (card_leaves_le_sum_min U hU).trans ?_ + rw [← sum_filter_add_sum_filter_not (range (m + 1)) (fun d => 9 * d ≤ m), mul_sum] + refine add_le_add (sum_le_sum fun d _ => min_le_left _ _) ?_ + rw [filter_congr fun d _ => (not_le (a := 9 * d) (b := m))] + exact sum_le_sum fun d _ => min_le_right _ _ + +/-- **Lemma 11**, second inequality. +and if L = 19m, then |leaves(U)| ≤ 2^{-m/9} (2^m |U| + 2M). -/ +theorem card_leaves_le_saving {m : ℕ} (U : Finset (OutStr (19 * m))) + (hU : ∀ w ∈ U, (innerSetO w).card = m) : + ((leaves U).card : ℝ) ≤ saving m * ((2 : ℝ) ^ m * (U.card : ℝ) + 2 * (M (19 * m) m : ℝ)) := by + have hsplit : ((leaves U).card : ℝ) + ≤ (U.card : ℝ) * ((∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d : ℕ) : ℝ) + + ((∑ d ∈ (range (m + 1)).filter (fun d => m < 9 * d), beta (19 * m) m d : ℕ) : ℝ) := by + exact_mod_cast card_leaves_le_split U hU + have hsmall := + mul_le_mul_of_nonneg_left (sum_alpha_small_orders_le m) (Nat.cast_nonneg (α := ℝ) U.card) + have hlarge := (sum_beta_large_orders_lt m).le + calc ((leaves U).card : ℝ) ≤ _ := hsplit + _ ≤ (U.card : ℝ) * ((2 : ℝ) ^ m * saving m) + 2 * saving m * (M (19 * m) m : ℝ) := + add_le_add hsmall hlarge + _ = _ := by ring + +/-- **Lemma 11**, both inequalities together. For every set U of output strings whose inner +sets have exactly m elements, |leaves(U)| ≤ ∑_{d=0}^{m} min{|U| α_d, β_d}, and if L = 19m, +then |leaves(U)| ≤ 2^{-m/9} (2^m |U| + 2M). -/ +theorem sparse_leaf_bounds {L m : ℕ} (U : Finset (OutStr L)) (hU : ∀ w ∈ U, (innerSetO w).card = + m) : + (leaves U).card ≤ ∑ d ∈ range (m + 1), min (U.card * alpha m d) (beta L m d) + ∧ (L = 19 * m → + ((leaves U).card : ℝ) + ≤ (2 : ℝ) ^ (-(m : ℝ) / 9) * ((2 : ℝ) ^ m * (U.card : ℝ) + 2 * (M L m : ℝ))) := by + refine ⟨card_leaves_le_sum_min U hU, ?_⟩ + rintro rfl + exact card_leaves_le_saving U hU + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean new file mode 100644 index 0000000000..49c113b4a5 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean @@ -0,0 +1,196 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Parameters + +/-! +# Complete finite thin-product composition + +## Main results + +* `thin_product_spec`: exact queried entries for arbitrary signed integer matrices. +* `encodings_total_le`, `total_leaves_le`, `total_passed_sets_le`: explicit finite totals. + +For `D = 4^m`, `L = 19m`, `D^18 ≤ N` and `|W| 2^m ≤ N²`, encoding entries +and contributing leaves are bounded by constants times `N² / D^(1/18)`. Passed-set +cardinalities carry the additional factor `L + 1`. These are not instruction counts. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Section 2.4.4: Each wanted position (I, J) ∈ W lies in one tile T: the tile of a position is +one of the tiles. -/ +theorem tile_mem_tiles {L m N : ℕ} (hmL : m ≤ L) (I J : Fin N) : + (bandOf L m I, bandOf L m J) ∈ tiles L m N := + mem_product.mpr ⟨mem_range.mpr (bandOf_lt_numBands hmL N I I.2), + mem_range.mpr (bandOf_lt_numBands hmL N J J.2)⟩ + +/-- Section 2.4.4: a wanted position is indexed by one output string of T: its output string is in +the set `W_T` of its tile. -/ +theorem outStrOfPos_mem_wantedStrings {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) + {I J : Fin N} (hIJ : (I, J) ∈ W) : + outStrOfPos lay I J ∈ wantedStrings lay W (bandOf L m I) (bandOf L m J) := + mem_image.mpr ⟨(I, J), mem_filter.mpr ⟨hIJ, rfl, rfl⟩, rfl⟩ + +/-- Section 2.4.3: the sets passed to `Pruned` consist only of […] output strings of length L whose +inner sets […] have exactly m elements. -/ +theorem card_innerSetO_of_mem_wantedStrings {L m N : ℕ} (lay : Layout L m) + (W : Finset (Fin N × Fin N)) (β β' : ℕ) (w : OutStr L) (hw : w ∈ wantedStrings lay W β β') : + (innerSetO w).card = m := by + obtain ⟨IJ, -, rfl⟩ := mem_image.mp hw + exact card_innerSetO_outStrOfPos lay IJ.1 IJ.2 + +/-- **Proof of Theorem 5**, correctness of the algorithm. for each tile T with W_T ≠ ∅, +we run Pruned(W_T), and report, for each (I, J) ∈ W, the value at its output string. This is correct +by Lemmas 9 and 10. The run on the tile of `(I, J)` reads the encodings of the input arrays of its +row band and its column band. This holds for all `m ≤ L`. -/ +theorem thin_product_spec {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (W : Finset (Fin N × Fin N)) (I J : Fin N) + (hIJ : (I, J) ∈ W) : + Pruned.result L (encodingL (bandArrayL lay X (bandOf L m I))) + (encodingR (bandArrayR lay Y (bandOf L m J))) + (wantedStrings lay W (bandOf L m I) (bandOf L m J)) (outStrOfPos lay I J) + = (X * Y) I J := by + -- Lemma 10, with Lemma 7: `Pruned` returns the output of `Full` restricted to the set passed to + -- it. + rw [Pruned.result_eq_piecewise_full, Finset.piecewise_eq_of_mem _ _ _ + (outStrOfPos_mem_wantedStrings lay W hIJ)] + -- Lemma 9, through the tiling of Section 2.3.4. + exact Full.bandArray_eq_mul lay X Y I J + +/-- Theorem 5 and Section 2.4.4: |W| ≤ N²/√D = 2^{-m} N². In the statements below this +hypothesis is written without a square root, as `|W| · 2^m ≤ N²`. -/ +theorem le_sq_div_sqrt_D_iff (m N w : ℕ) : + (w : ℝ) ≤ (N : ℝ) ^ 2 / Real.sqrt (D m) ↔ w * 2 ^ m ≤ N ^ 2 := by + rw [← two_pow_eq_sqrt_D, le_div_iff₀ (by positivity)] + exact_mod_cast Iff.rfl + +/-- The number of bands times `N₀` is at most the padded size, because `K₀ ≥ 1`. -/ +private theorem numBands_mul_N0_le {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : + numBands L m N * N0 L m ≤ padN L m N := + Nat.mul_le_mul_left _ (Nat.le_mul_of_pos_left _ (K0_pos hmL)) + +/-- Section 2.4.4: By (6), the encodings cost (2N/K₀N₀) · O(10^L) = O(2^{-m/9} N²) operations: the +encodings of the input arrays of all row bands and all column bands have at most `4 N² / D^{1/18}` +entries in all. Here `N` is the size of the input, which the padding at most doubles. -/ +theorem encodings_total_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : + ((2 * numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) + ≤ 4 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + have hbands : (numBands (19 * m) m N : ℝ) * N0 (19 * m) m ≤ 2 * N := by + exact_mod_cast (numBands_mul_N0_le (by omega) N).trans (padN_le_two_mul_of_D_pow_le m N hN) + have heq6 : (10 : ℝ) ^ (19 * m) ≤ saving m * N * N0 (19 * m) m := encoding_size_le m N hN + have hc := saving_pos m + rw [← saving_eq_D_rpow] + push_cast + calc 2 * (numBands (19 * m) m N : ℝ) * 10 ^ (19 * m) + ≤ 2 * numBands (19 * m) m N * (saving m * N * N0 (19 * m) m) := by gcongr + _ = 2 * saving m * (N * (numBands (19 * m) m N * N0 (19 * m) m)) := by ring + _ ≤ 2 * saving m * (N * (2 * N)) := by gcongr + _ = 4 * saving m * (N : ℝ) ^ 2 := by ring + +/-- Section 2.4.4: Each position of W lands in one set W_T, so ∑_T |W_T| ≤ |W|. -/ +theorem sum_card_wantedStrings_le {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) : + ∑ T ∈ tiles L m N, (wantedStrings lay W T.1 T.2).card ≤ W.card := by + have hmap : ∀ IJ ∈ W, (bandOf L m IJ.1, bandOf L m IJ.2) ∈ tiles L m N := fun IJ _ => + tile_mem_tiles lay.hmL IJ.1 IJ.2 + rw [card_eq_sum_card_fiberwise hmap] + refine sum_le_sum fun T _ => ?_ + refine card_image_le.trans (le_of_eq ?_) + congr 1 + +/-- Section 2.4.4: there are at most 4N²/M tiles, so ∑_T M ≤ 4N², where `N` is the padded size. -/ +theorem sum_M_le (L m N : ℕ) : ∑ _T ∈ tiles L m N, M L m ≤ 4 * padN L m N ^ 2 := by + rw [sum_const, smul_eq_mul] + exact card_tiles_mul_M_le L m N + +/-- Section 2.4.4, the display: by Lemma 11 summed over the tiles, +`∑_T |leaves(W_T)| ≤ 2^{-m/9} (2^m |W| + 2 ∑_T M)`. -/ +theorem sum_card_leaves_le {m N : ℕ} (lay : Layout (19 * m) m) (W : Finset (Fin N × Fin N)) : + ((∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card : ℕ) : ℝ) + ≤ saving m * ((2 : ℝ) ^ m * (W.card : ℝ) + + 2 * ((∑ _T ∈ tiles (19 * m) m N, M (19 * m) m : ℕ) : ℝ)) := by + have hc := saving_pos m + have hsum : ((∑ T ∈ tiles (19 * m) m N, (wantedStrings lay W T.1 T.2).card : ℕ) : ℝ) + ≤ (W.card : ℝ) := by + exact_mod_cast sum_card_wantedStrings_le lay W + push_cast at hsum ⊢ + calc ∑ T ∈ tiles (19 * m) m N, ((leaves (wantedStrings lay W T.1 T.2)).card : ℝ) + ≤ ∑ T ∈ tiles (19 * m) m N, saving m + * ((2 : ℝ) ^ m * ((wantedStrings lay W T.1 T.2).card : ℝ) + 2 * (M (19 * m) m : ℝ)) := + sum_le_sum fun T _ => + card_leaves_le_saving _ (card_innerSetO_of_mem_wantedStrings lay W T.1 T.2) + _ = saving m + * ((2 : ℝ) ^ m * ∑ T ∈ tiles (19 * m) m N, ((wantedStrings lay W T.1 T.2).card : ℝ) + + 2 * ∑ _T ∈ tiles (19 * m) m N, (M (19 * m) m : ℝ)) := by + rw [← mul_sum, sum_add_distrib, ← mul_sum, ← mul_sum] + _ ≤ _ := by gcongr + +/-- **Proof of Theorem 5**, the number of leaves in terms of the `N` and `D` of the theorem (`N` is +the size of the input, not the padded size). Since `N ≥ D^18 ≥ K₀ N₀`, the padding at most doubles +`N`, so `2^m |W| + 2 ∑_T M ≤ N² + 32 N²`. -/ +theorem total_leaves_le {m N : ℕ} (lay : Layout (19 * m) m) (W : Finset (Fin N × Fin N)) + (hN : D m ^ 18 ≤ N) (hW : W.card * 2 ^ m ≤ N ^ 2) : + ((∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card : ℕ) : ℝ) + ≤ 33 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + have hc := saving_pos m + have hWN : (2 : ℝ) ^ m * (W.card : ℝ) ≤ (N : ℝ) ^ 2 := by + rw [mul_comm] + exact_mod_cast hW + have hM : ((∑ _T ∈ tiles (19 * m) m N, M (19 * m) m : ℕ) : ℝ) ≤ 16 * (N : ℝ) ^ 2 := by + have hpad := Nat.pow_le_pow_left (padN_le_two_mul_of_D_pow_le m N hN) 2 + exact_mod_cast (sum_M_le (19 * m) m N).trans (by linarith) + refine (sum_card_leaves_le lay W).trans ?_ + rw [← saving_eq_D_rpow] + calc saving m + * ((2 : ℝ) ^ m * (W.card : ℝ) + 2 * ((∑ _T ∈ tiles (19 * m) m N, M (19 * m) m : ℕ) : ℝ)) + ≤ saving m * ((N : ℝ) ^ 2 + 2 * (16 * (N : ℝ) ^ 2)) := by gcongr + _ = 33 * saving m * (N : ℝ) ^ 2 := by ring + +/-- Lemma 10 summed over the tiles: the total size of the sets handled by the pruned recursions is +at most `L + 1` times the total number of leaves. -/ +private theorem sum_totalSize_le {m N : ℕ} (lay : Layout (19 * m) m) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (W : Finset (Fin N × Fin N)) : + ((∑ T ∈ tiles (19 * m) m N, + Pruned.totalSize (encodingL (bandArrayL lay X T.1)) (encodingR (bandArrayR lay Y T.2)) + (wantedStrings lay W T.1 T.2) : ℕ) : ℝ) + ≤ ((19 * m + 1 : ℕ) : ℝ) + * ((∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card : ℕ) : ℝ) := by + have h : ∑ T ∈ tiles (19 * m) m N, + Pruned.totalSize (encodingL (bandArrayL lay X T.1)) (encodingR (bandArrayR lay Y T.2)) + (wantedStrings lay W T.1 T.2) + ≤ (19 * m + 1) * ∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card := by + rw [mul_sum] + exact sum_le_sum fun T _ => Pruned.totalSize_le _ _ _ + exact_mod_cast h + +/-- **Proof of Theorem 5**, the total size of the sets, in the `N` and `D` of the theorem. -/ +theorem total_passed_sets_le {m N : ℕ} (lay : Layout (19 * m) m) (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) + (hW : W.card * 2 ^ m ≤ N ^ 2) : + ((∑ T ∈ tiles (19 * m) m N, + Pruned.totalSize (encodingL (bandArrayL lay X T.1)) (encodingR (bandArrayR lay Y T.2)) + (wantedStrings lay W T.1 T.2) : ℕ) : ℝ) + ≤ ((19 * m + 1 : ℕ) : ℝ) * (33 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2) := + (sum_totalSize_le lay X Y W).trans + (mul_le_mul_of_nonneg_left (total_leaves_le lay W hN hW) (Nat.cast_nonneg _)) + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean new file mode 100644 index 0000000000..dd34af063b --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean @@ -0,0 +1,248 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Batch +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Layout +import Mathlib.Tactic.Ring + +/-! +# Padding and tiling correctness + +## Main results + +* `nonempty_layout`: valid layouts exist whenever `m ≤ L`. +* `Full.bandArray_eq_mul`: tiling computes every original integer product entry. +* `outStrOfPos_injective`: positions in a tile have distinct output strings. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-! ### The numbers -/ + +/-- Section 2.3.4: K₀² ≤ K. -/ +theorem K0_sq_le_K (L m : ℕ) : K0 L m ^ 2 ≤ K L m := + Nat.sqrt_le' _ + +/-- Section 2.3.4: K₀ ≥ √K / 2, squared. -/ +theorem K_le_four_mul_K0_sq (L m : ℕ) : K L m ≤ 4 * K0 L m ^ 2 := by + have hlt : K L m < (K0 L m + 1) ^ 2 := Nat.lt_succ_sqrt' _ + rcases Nat.eq_zero_or_pos (K0 L m) with h0 | h0 + · rw [h0] at hlt ⊢ + omega + · calc K L m ≤ (K0 L m + 1) ^ 2 := hlt.le + _ ≤ (2 * K0 L m) ^ 2 := Nat.pow_le_pow_left (by omega) 2 + _ = 4 * K0 L m ^ 2 := by ring + +/-- A band has at least one row. -/ +theorem bandSize_pos {L m : ℕ} (hmL : m ≤ L) : 0 < bandSize L m := + Nat.mul_pos (K0_pos hmL) (N0_pos L m) + +/-- Section 2.3.4: we first pad N to a multiple of K₀ N₀. -/ +theorem bandSize_dvd_padN (L m N : ℕ) : bandSize L m ∣ padN L m N := + Dvd.intro_left _ rfl + +/-- The padded size is at least `N`. + +NOTE. `m ≤ L` is left implicit in the paper. For `m > L` there is no subset of size `m` and +`K₀ = 0`. -/ +theorem le_padN {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : N ≤ padN L m N := by + have h := Nat.lt_div_mul_add (a := N + bandSize L m - 1) (bandSize_pos hmL) + unfold padN numBands + omega + +/-- The padded size is the least multiple of `K₀ N₀` that is at least `N`: it is less than +`N + K₀ N₀`. + +NOTE. `m ≤ L` is left implicit in the paper. -/ +theorem padN_lt_add_bandSize {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : + padN L m N < N + bandSize L m := by + have h := Nat.div_mul_le_self (N + bandSize L m - 1) (bandSize L m) + have hb := bandSize_pos hmL + unfold padN numBands + omega + +/-- Section 2.3.4: the padding at most doubles N since N ≥ K₀ N₀. -/ +theorem padN_le_two_mul (L m N : ℕ) (hN : K0 L m * N0 L m ≤ N) : padN L m N ≤ 2 * N := by + rcases Nat.lt_or_ge L m with hLm | hmL + · -- for `m > L` there are no subsets, `K₀ = 0`, and the padded size is 0 + have hK : K0 L m = 0 := by simp [K0, K, Nat.choose_eq_zero_of_lt hLm] + simp [padN, bandSize, hK] + · have h := padN_lt_add_bandSize hmL N + have hN' : bandSize L m ≤ N := hN + omega + +/-- The number of tiles is the square of the number of bands. -/ +theorem card_tiles (L m N : ℕ) : (tiles L m N).card = numBands L m N ^ 2 := by + simp [tiles, sq] + +/-- Section 2.3.4: there are (N / K₀N₀)² ≤ 4N²/M tiles, where `N` is the padded size. The +inequality is multiplied out. -/ +theorem card_tiles_mul_M_le (L m N : ℕ) : (tiles L m N).card * M L m ≤ 4 * padN L m N ^ 2 := by + rw [card_tiles] + calc numBands L m N ^ 2 * M L m + = numBands L m N ^ 2 * N0 L m ^ 2 * K L m := by unfold M; ring + _ ≤ numBands L m N ^ 2 * N0 L m ^ 2 * (4 * K0 L m ^ 2) := + Nat.mul_le_mul_left _ (K_le_four_mul_K0_sq L m) + _ = 4 * padN L m N ^ 2 := by unfold padN bandSize; ring + +/-! ### The table of subsets and the matrices of a tile -/ + +/-- Section 2.3.4: We fix K₀² ≤ K distinct subsets of {1, …, L} of size m: this can be done. + +NOTE. `m ≤ L` is left implicit in the paper; it is a field of `Layout`. -/ +theorem nonempty_layout {L m : ℕ} (hmL : m ≤ L) : Nonempty (Layout L m) := by + have hcard : Fintype.card (Fin (K0 L m) × Fin (K0 L m)) + ≤ Fintype.card {Q : Finset (Fin L) // Q.card = m} := by + rw [Fintype.card_prod, Fintype.card_fin, Fintype.card_subtype, card_subsets_eq_K, ← sq] + exact K0_sq_le_K L m + obtain ⟨e⟩ := Function.Embedding.nonempty_of_card_le hcard + exact ⟨{ hmL := hmL + rowIdx := (Fintype.equivFinOfCardEq (card_outerStr L m)).symm + colIdx := (Fintype.equivFinOfCardEq (card_outerStr L m)).symm + innerIdx := (Fintype.equivFinOfCardEq (card_innerStr m)).symm + table := fun gh => (e gh).1 + table_card := fun gh => (e gh).2 + table_injective := fun _ _ h => e.injective (Subtype.ext h) }⟩ + +/-- The subsets in the table are distinct, so a sum over the table that selects the subset of the +block product `gh` has one term. -/ +private theorem sum_table_eq {L m : ℕ} {A : Type*} [AddCommMonoid A] (lay : Layout L m) + (F : Fin (K0 L m) × Fin (K0 L m) → A) (gh : Fin (K0 L m) × Fin (K0 L m)) : + (∑ gh', if lay.table gh' = lay.table gh then F gh' else 0) = F gh := by + simp [lay.table_injective.eq_iff] + +/-- Section 2.3.4: In a tile, let X_Q and Y_Q be the row block and the column block of the block +product with subset Q. For `X_Q`: it is the `g`-th row block of the row band. -/ +@[simp] +theorem bandFamilyL_table {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) + (g h : Fin (K0 L m)) : + bandFamilyL lay X β (lay.table (g, h)) = rowBlock lay X (β * K0 L m + g) := + sum_table_eq lay _ (g, h) + +/-- The matrix `Y_Q` of the block product in position `(g, h)` is the `h`-th column block of the +column band. -/ +@[simp] +theorem bandFamilyR_table {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) + (g h : Fin (K0 L m)) : + bandFamilyR lay Y β (lay.table (g, h)) = colBlock lay Y (β * K0 L m + h) := + sum_table_eq lay _ (g, h) + +/-- Section 2.3.4: and let X_Q = Y_Q = 0 for the other subsets Q. For `X_Q`. -/ +theorem bandFamilyL_eq_zero {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) + (β : ℕ) {Q : Finset (Fin L)} (hQ : ∀ gh, lay.table gh ≠ Q) : bandFamilyL lay X β Q = 0 := + sum_eq_zero fun gh _ => ite_eq_right (hQ gh) + +/-- Section 2.3.4: and let X_Q = Y_Q = 0 for the other subsets Q. For `Y_Q`. -/ +theorem bandFamilyR_eq_zero {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (β : ℕ) {Q : Finset (Fin L)} (hQ : ∀ gh, lay.table gh ≠ Q) : bandFamilyR lay Y β Q = 0 := + sum_eq_zero fun gh _ => ite_eq_right (hQ gh) + +/-! ### One run of `Full` for each tile -/ + +/-- Section 2.3.4: Then one run of Full computes all K₀² block products of the tile (Lemma 9). +The tile is that of the row band `β` and the column band `β'`; the block product in position `(g, +h)` of its grid is the product of the row block `β K₀ + g` by the column block `β' K₀ + h`. -/ +theorem Full.bandArray_eq_block_mul {L m N : ℕ} (lay : Layout L m) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β β' : ℕ) + (g h : Fin (K0 L m)) (r c : OuterStr L m) : + Full.result L (bandArrayL lay X β) (bandArrayR lay Y β') + (outStrOf (lay.table (g, h)) (lay.table_card (g, h)) r c) + = (rowBlock lay X (β * K0 L m + g) * colBlock lay Y (β' * K0 L m + h)) r c := by + rw [bandArrayL, bandArrayR, Full.arrays_eq_mul, bandFamilyL_table, bandFamilyR_table] + +/-- A row (or column) is recovered from its band, the position of its block in the band and its +position in the block. -/ +private theorem position_decomp {L m : ℕ} (lay : Layout L m) (I : ℕ) : + (bandOf L m I * K0 L m + (blockOf lay I : ℕ)) * N0 L m + (offsetOf L m I : ℕ) = I := by + have hband : bandOf L m I = I / N0 L m / K0 L m := by + rw [bandOf, bandSize, Nat.mul_comm, Nat.div_div_eq_div_mul] + have hblock : bandOf L m I * K0 L m + (blockOf lay I : ℕ) = I / N0 L m := by + rw [hband] + exact Nat.div_add_mod' _ _ + rw [hblock] + exact Nat.div_add_mod' _ _ + +/-- Section 2.3.4: and running it on every tile computes all of XY. The entry `(XY)[I, J]` is the +output of the run on the tile of `(I, J)` at the output string of `(I, J)`. -/ +theorem Full.bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (I J : Fin N) : + Full.result L (bandArrayL lay X (bandOf L m I)) (bandArrayR lay Y (bandOf L m J)) (outStrOfPos + lay I J) + = (X * Y) I J := by + unfold outStrOfPos + rw [Full.bandArray_eq_block_mul, Matrix.mul_apply, Matrix.mul_apply, + ← Equiv.sum_comp lay.innerIdx] + -- Both sides are sums over the `D` inner indices; the summands agree because row `I` is recovered + -- from its band, block and place in the block, and likewise column `J`. + refine sum_congr rfl fun k _ => ?_ + simp only [rowBlock, colBlock, Equiv.symm_apply_apply, position_decomp, padRows, padCols, + I.isLt, J.isLt, dite_eq_left, Fin.eta] + +/-- The entry `(XY)[I, J]` as a value of `bilinearProduct`, that is, as a sum over leaves + (equation (2)). This +is the form in which Section 4 uses the tiling (by (2) and Lemma 9, (X_Q Y_Q)[w] is the sum of the +products at the leaves contributing to w). -/ +theorem bilinearProduct_bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin + (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (I J : Fin N) : + bilinearProduct (bandArrayL lay X (bandOf L m I)) (bandArrayR lay Y (bandOf L m J)) + (outStrOfPos lay I J) + = (X * Y) I J := by + rw [← Full.result_eq_bilinearProduct] + exact Full.bandArray_eq_mul lay X Y I J + +/-- Every row and every column lies in one of the bands. + +NOTE. `m ≤ L` is left implicit in the paper. -/ +theorem bandOf_lt_numBands {L m : ℕ} (hmL : m ≤ L) (N I : ℕ) (hI : I < N) : + bandOf L m I < numBands L m N := by + rw [bandOf, Nat.div_lt_iff_lt_mul (bandSize_pos hmL)] + exact hI.trans_le (le_padN hmL N) + +/-! ### The output string of a position -/ + +/-- Section 2.4.4: the inner set of the output string of a position is the subset Q of the block +product containing (I, J). -/ +theorem innerSetO_outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : + innerSetO (outStrOfPos lay I J) = lay.table (blockOf lay I, blockOf lay J) := + innerSetO_outStrOf _ _ _ _ + +/-- The inner set of the output string of a position has exactly `m` elements. -/ +theorem card_innerSetO_outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : + (innerSetO (outStrOfPos lay I J)).card = m := + innerSetO_outStrOfPos lay I J ▸ lay.table_card _ + +/-- Used implicitly in Section 2.4.4 (is indexed by one output string of T, report […] the value +at its output string): different positions of the same tile have different output strings. -/ +theorem outStrOfPos_injective {L m : ℕ} (lay : Layout L m) (I J I' J' : ℕ) + (hI : bandOf L m I = bandOf L m I') (hJ : bandOf L m J = bandOf L m J') + (h : outStrOfPos lay I J = outStrOfPos lay I' J') : I = I' ∧ J = J' := by + -- The two strings have the same inner set, row and column; so the two positions lie in the same + -- block product and at the same place in it. + obtain ⟨hQ, hr, hc⟩ := outStrOf_inj.mp h + obtain ⟨hblockI, hblockJ⟩ := Prod.mk.inj (lay.table_injective hQ) + constructor + · rw [← position_decomp lay I, ← position_decomp lay I', hI, hblockI, + lay.rowIdx.injective hr] + · rw [← position_decomp lay J, ← position_decomp lay J', hJ, hblockJ, + lay.colIdx.injective hc] + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Foundations/Algebra/FiniteMagnitude.lean b/Complexity/Foundations/Algebra/FiniteMagnitude.lean new file mode 100644 index 0000000000..99c8cad79e --- /dev/null +++ b/Complexity/Foundations/Algebra/FiniteMagnitude.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.BigOperators.Ring.Finset + +/-! +# Magnitudes of finite sums and products + +## Main results + +* `Finset.abs_sum_mul_le`: bounded signed linear combinations. +* `Finset.abs_prod_le_one`: products of coefficients bounded by one. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +namespace Finset + +variable {ι R : Type*} + +/-- If `|f i| ≤ A` for all `i ∈ s`, then `|∑ i ∈ s, f i| ≤ s.card * A`. -/ +theorem abs_sum_le_card_mul [Ring R] [LinearOrder R] [IsOrderedRing R] (s : Finset ι) {f : ι → R} + {A : R} (h : ∀ i ∈ s, |f i| ≤ A) : |∑ i ∈ s, f i| ≤ s.card * A := + (abs_sum_le_sum_abs f s).trans ((sum_le_card_nsmul s _ A h).trans_eq (nsmul_eq_mul _ _)) + +/-- If `|c i| ≤ 1` and `|a i| ≤ A` for all `i ∈ s`, then `|∑ i ∈ s, c i * a i| ≤ s.card * A`. -/ +theorem abs_sum_mul_le [Ring R] [LinearOrder R] [IsOrderedRing R] (s : Finset ι) {c a : ι → R} + {A : R} (hc : ∀ i ∈ s, |c i| ≤ 1) (ha : ∀ i ∈ s, |a i| ≤ A) : + |∑ i ∈ s, c i * a i| ≤ s.card * A := + abs_sum_le_card_mul s fun i hi => + (abs_mul (c i) (a i)).trans_le + ((mul_le_of_le_one_left (abs_nonneg _) (hc i hi)).trans (ha i hi)) + +/-- If `|f i| ≤ 1` for all `i ∈ s`, then `|∏ i ∈ s, f i| ≤ 1`. -/ +theorem abs_prod_le_one [CommRing R] [LinearOrder R] [IsStrictOrderedRing R] (s : Finset ι) + {f : ι → R} (h : ∀ i ∈ s, |f i| ≤ 1) : |∏ i ∈ s, f i| ≤ 1 := + (abs_prod s f).trans_le (prod_le_one₀ (fun _ _ => abs_nonneg _) h) + + +end Finset diff --git a/Complexity/Foundations/Algebra/GeometricTail.lean b/Complexity/Foundations/Algebra/GeometricTail.lean new file mode 100644 index 0000000000..fa60e6dd2b --- /dev/null +++ b/Complexity/Foundations/Algebra/GeometricTail.lean @@ -0,0 +1,42 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Field.GeomSum +public import Mathlib.Algebra.Order.BigOperators.Ring.Finset + +/-! +# Strict bounds for finite geometric tails + +## Main results + +* `geom_sum_Ico_lt_of_lt_one`: every finite tail is strictly below the infinite tail. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +/-- For `0 < x < 1`, `∑ i ∈ Ico m n, x ^ i < x ^ m / (1 - x)`. Mathlib's +`geom_sum_Ico_le_of_lt_one` has `≤`, for `0 ≤ x`. -/ +theorem geom_sum_Ico_lt_of_lt_one {K : Type*} [Field K] [LinearOrder K] [IsStrictOrderedRing K] + {x : K} (hx : 0 < x) (hx1 : x < 1) (m n : ℕ) : + ∑ i ∈ Finset.Ico m n, x ^ i < x ^ m / (1 - x) := by + have hpos : 0 < 1 - x := sub_pos.2 hx1 + rcases le_total m n with hmn | hnm + · rw [geom_sum_Ico' hx1.ne hmn] + exact div_lt_div_of_pos_right (sub_lt_self _ (pow_pos hx n)) hpos + · rw [Finset.Ico_eq_empty_of_le hnm, Finset.sum_empty] + exact div_pos (pow_pos hx m) hpos diff --git a/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean b/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean new file mode 100644 index 0000000000..f47c81e9d7 --- /dev/null +++ b/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean @@ -0,0 +1,52 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Analysis.Complex.Exponential +public import Mathlib.Data.Nat.Choose.Bounds + +/-! +# An exponential upper bound on binomial coefficients + +## Main results + +* `Nat.choose_le_exp_mul_div_pow`: the bound `(e n / k)^k`, including `k = 0`. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +namespace Nat + +/-- `binom(n, k) ≤ (e n / k) ^ k`: indeed `binom(n, k) ≤ n ^ k / k!`, and +`k ^ k / k! ≤ e ^ k` is one term of the exponential series. For `k = 0` both sides are `1`. -/ +theorem choose_le_exp_mul_div_pow (n k : ℕ) : + (n.choose k : ℝ) ≤ (Real.exp 1 * (n : ℝ) / (k : ℝ)) ^ k := by + obtain rfl | hk := k.eq_zero_or_pos + · rw [choose_zero_right, pow_zero, cast_one] + have hk' : (0 : ℝ) < k := cast_pos.2 hk + have hfact : (0 : ℝ) < k.factorial := cast_pos.2 k.factorial_pos + have hexp : (k : ℝ) ^ k ≤ Real.exp k * k.factorial := + (div_le_iff₀ hfact).1 (Real.pow_div_factorial_le_exp _ hk'.le k) + rw [div_pow, mul_pow, ← Real.exp_nat_mul, mul_one] + refine (choose_le_pow_div k n).trans ?_ + rw [div_le_div_iff₀ hfact (pow_pos hk' k)] + calc (n : ℝ) ^ k * (k : ℝ) ^ k ≤ (n : ℝ) ^ k * (Real.exp k * k.factorial) := + mul_le_mul_of_nonneg_left hexp (pow_nonneg n.cast_nonneg k) + _ = Real.exp k * (n : ℝ) ^ k * k.factorial := by ring + + +end Nat diff --git a/Complexity/Foundations/Combinatorics/FunctionPlaces.lean b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean new file mode 100644 index 0000000000..05960b70c0 --- /dev/null +++ b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean @@ -0,0 +1,141 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Group.Action.Defs +public import Mathlib.Algebra.BigOperators.GroupWithZero.Finset +public import Mathlib.Data.Finset.Powerset +public import Mathlib.Data.Fintype.BigOperators + +/-! +# Counting functions by constrained positions + +## Main results + +* `Finset.card_pi_places_card`: count functions by the positions with a property. +* `Finset.card_places_card`: binomial count without pointwise constraints. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +namespace Finset + +variable {ι : Type*} [Fintype ι] [DecidableEq ι] + +/-- A product that takes the value `a` on `S` and `b` elsewhere. -/ +theorem prod_ite_mem_const {M : Type*} [CommMonoid M] (S : Finset ι) (a b : M) : + (∏ i, if i ∈ S then a else b) = a ^ #S * b ^ (Fintype.card ι - #S) := by + rw [prod_ite, prod_const, prod_const, filter_mem_eq_inter, univ_inter, filter_not, + filter_mem_eq_inter, univ_inter, ← compl_eq_univ_sdiff, card_compl] + +/-- A product with the factors `1` on `S ∩ Q`, `0` on `S \ Q`, `c` on `Q \ S` and `1` elsewhere: it +is `0` unless `S ⊆ Q`. -/ +theorem prod_ite_ite_subset {M : Type*} [CommMonoidWithZero M] (Q S : Finset ι) (c : M) : + (∏ i, if i ∈ S then (if i ∈ Q then 1 else 0) else (if i ∈ Q then c else 1)) + = if S ⊆ Q then c ^ (#Q - #S) else 0 := by + split_ifs with h + · have hterm : ∀ i, (if i ∈ S then (if i ∈ Q then 1 else 0) else (if i ∈ Q then c else 1)) + = if i ∈ Q \ S then c else 1 := fun i => by + by_cases hS : i ∈ S + · simp [hS, h hS] + · simp [hS] + simp_rw [hterm] + rw [prod_ite_mem_const, one_pow, mul_one, card_sdiff_of_subset h] + · obtain ⟨i, hiS, hiQ⟩ := not_subset.mp h + exact prod_eq_zero (mem_univ i) (by simp [hiS, hiQ]) + +/-- A product with the factor `0` on `T \ S` and `1` elsewhere: it is `0` unless `T ⊆ S`. -/ +theorem prod_ite_ite_superset {M : Type*} [CommMonoidWithZero M] (T S : Finset ι) : + (∏ i, if i ∈ S then 1 else (if i ∈ T then 0 else 1)) = if T ⊆ S then (1 : M) else 0 := by + split_ifs with h + · refine prod_eq_one fun i _ => ?_ + by_cases hS : i ∈ S + · simp [hS] + · have hT : i ∉ T := fun hT => hS (h hT) + simp [hS, hT] + · obtain ⟨i, hiT, hiS⟩ := not_subset.mp h + exact prod_eq_zero (mem_univ i) (by simp [hiS, hiT]) + +/-- The number of sets of `k` elements inside `Q`, each counted `c` times. -/ +theorem sum_powersetCard_ite_subset (Q : Finset ι) (k c : ℕ) : + (∑ S ∈ powersetCard k (univ : Finset ι), if S ⊆ Q then c else 0) = (#Q).choose k * c := by + rw [← sum_filter, sum_const, smul_eq_mul, ← card_powersetCard] + congr 2 + ext S + simp [mem_powersetCard, and_comm] + +/-- The number of sets of `k` elements that contain `T`: choose the complement outside `T`. -/ +theorem card_powersetCard_superset (T : Finset ι) {k : ℕ} (hk : k ≤ Fintype.card ι) : + #{S ∈ powersetCard k (univ : Finset ι) | T ⊆ S} = + (Fintype.card ι - #T).choose (Fintype.card ι - k) := by + rw [← card_compl, ← card_powersetCard] + refine card_bij' (fun S _ => Sᶜ) (fun S _ => Sᶜ) ?_ ?_ (fun _ _ => compl_compl _) + (fun _ _ => compl_compl _) + · intro S hS + simp only [mem_filter, mem_powersetCard, subset_univ, true_and] at hS + simp [mem_powersetCard, hS.1, hS.2, card_compl] + · intro S hS + simp only [mem_powersetCard] at hS + simp only [mem_filter, mem_powersetCard, subset_univ, true_and, card_compl, hS.2] + exact ⟨by omega, by rw [← compl_subset_compl, compl_compl]; exact hS.1⟩ + +/-! ## Functions by the set of places with a property -/ + +variable {α : Type*} [Fintype α] + +/-- The number of functions `f` with `f i ∈ A i` whose set of places with `p` is exactly `S`: choose +a value with `p` at each place of `S` and one without `p` at every other place. -/ +theorem card_pi_places_eq [DecidableEq α] (A : ι → Finset α) (p : α → Prop) [DecidablePred p] + (S : Finset ι) : + #{f : ι → α | (∀ i, f i ∈ A i) ∧ ({i | p (f i)} : Finset ι) = S} = + ∏ i, if i ∈ S then #{a ∈ A i | p a} else #{a ∈ A i | ¬ p a} := by + have hmem (i : ι) (x : α) : (x ∈ if i ∈ S then {a ∈ A i | p a} else {a ∈ A i | ¬ p a}) ↔ + x ∈ A i ∧ (p x ↔ i ∈ S) := by + split_ifs with hi <;> simp [hi] + simp_rw [← apply_ite Finset.card, ← Fintype.card_piFinset] + congr 1 + ext f + simp only [mem_filter, mem_univ, true_and, Fintype.mem_piFinset, hmem, forall_and, + Finset.ext_iff] + +/-- The number of functions `f` with `f i ∈ A i` that have `p` at exactly `k` places, as a sum over +the possible sets of these places. -/ +theorem card_pi_places_card [DecidableEq α] (A : ι → Finset α) (p : α → Prop) [DecidablePred p] + (k : ℕ) : + #{f : ι → α | (∀ i, f i ∈ A i) ∧ #{i | p (f i)} = k} = + ∑ S ∈ powersetCard k (univ : Finset ι), + ∏ i, if i ∈ S then #{a ∈ A i | p a} else #{a ∈ A i | ¬ p a} := by + rw [card_eq_sum_card_fiberwise (f := fun f : ι → α => ({i | p (f i)} : Finset ι)) + (t := powersetCard k (univ : Finset ι)) + fun f hf => mem_coe.2 (mem_powersetCard.2 ⟨subset_univ _, (mem_filter.1 hf).2.2⟩)] + refine sum_congr rfl fun S hS => ?_ + rw [← card_pi_places_eq, filter_filter] + exact congrArg card (filter_congr fun f _ => + ⟨fun h => ⟨h.1.1, h.2⟩, fun h => ⟨⟨h.1, h.2 ▸ (mem_powersetCard.1 hS).2⟩, h.2⟩⟩) + +/-- The number of functions `ι → α` that have `p` at exactly `k` places. -/ +theorem card_places_card (p : α → Prop) [DecidablePred p] (k : ℕ) : + #{f : ι → α | #{i | p (f i)} = k} = + (Fintype.card ι).choose k * (#{a | p a} ^ k * #{a | ¬ p a} ^ (Fintype.card ι - k)) := by + classical + have h := card_pi_places_card (ι := ι) (fun _ => (univ : Finset α)) p k + simp only [mem_univ, implies_true, true_and] at h + rw [h, sum_congr rfl fun S hS => by + rw [prod_ite_mem_const, (mem_powersetCard.1 hS).2], sum_const, card_powersetCard, card_univ, + smul_eq_mul] + +end Finset diff --git a/Complexity/Foundations/Data/Finset/OrderGlue.lean b/Complexity/Foundations/Data/Finset/OrderGlue.lean new file mode 100644 index 0000000000..02be86b3f3 --- /dev/null +++ b/Complexity/Foundations/Data/Finset/OrderGlue.lean @@ -0,0 +1,187 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Data.Finset.Sort + +/-! +# Gluing functions along ordered finite subsets + +## Main definitions + +* `Finset.glue`: join two strings along a subset and its complement. + +## Main results + +* `Finset.eq_glue_iff`, `Finset.glue_inj`: reconstruction and uniqueness. + +## References + +Adapted selectively from [AlmanWilliams2026, Section 2] and +[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. +Source-derived descriptions below are paraphrases. +-/ + +@[expose] public section + +open Finset + +namespace Finset + +/-- The complement of a size-`m` subset of `Fin L` has size `L - m`. -/ +theorem card_compl_of_card_eq {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) : + Qᶜ.card = L - m := by + rw [Finset.card_compl, Fintype.card_fin, hQ] + +variable {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card = m) + +/-! ### The levels of a set and of its complement, in order -/ + +/-- The `k`-th lowest level outside `Q` is not a level of `Q`. -/ +theorem outerLevel_notMem (k : Fin (L - m)) : (Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k ∉ + Q := + mem_compl.mp (Qᶜ.orderEmbOfFin_mem (card_compl_of_card_eq Q hQ) k) + +/-- Every level of `Q` is the `k`-th lowest level of `Q` for some `k`. -/ +theorem exists_innerLevel {ℓ : Fin L} (hℓ : ℓ ∈ Q) : ∃ k, Finset.orderEmbOfFin Q hQ k = ℓ := + (Q.range_orderEmbOfFin hQ).ge hℓ + +/-- Every level outside `Q` is the `k`-th lowest level outside `Q` for some `k`. -/ +theorem exists_outerLevel {ℓ : Fin L} (hℓ : ℓ ∉ Q) : ∃ k, (Qᶜ.orderEmbOfFin (card_compl_of_card_eq + Q hQ)) k = ℓ := + (Qᶜ.range_orderEmbOfFin (card_compl_of_card_eq Q hQ)).ge (mem_coe.mpr (mem_compl.mpr hℓ)) + +/-- The levels of `Q` in order depend only on the set `Q`. -/ +theorem innerLevel_congr {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) : + Finset.orderEmbOfFin Q' hQ' = Finset.orderEmbOfFin Q hQ := by + subst h + rfl + +/-- The levels outside `Q` in order depend only on the set `Q`. -/ +theorem outerLevel_congr {Q' : Finset (Fin L)} (h : Q' = Q) (hQ' : Q'.card = m) : + (Q'ᶜ.orderEmbOfFin (card_compl_of_card_eq Q' hQ')) = (Qᶜ.orderEmbOfFin (card_compl_of_card_eq + Q hQ)) := by + subst h + rfl + +/-- A property holds at every level if and only if it holds at the levels of `Q` and at the levels +outside `Q`. -/ +theorem forall_level_iff {P : Fin L → Prop} : + (∀ ℓ, P ℓ) ↔ (∀ k, P (Finset.orderEmbOfFin Q hQ k)) ∧ ∀ k, P ((Qᶜ.orderEmbOfFin + (card_compl_of_card_eq Q hQ)) k) := by + refine ⟨fun h => ⟨fun _ => h _, fun _ => h _⟩, fun ⟨hin, hout⟩ ℓ => ?_⟩ + by_cases hℓ : ℓ ∈ Q + · obtain ⟨k, rfl⟩ := exists_innerLevel Q hQ hℓ + exact hin k + · obtain ⟨k, rfl⟩ := exists_outerLevel Q hQ hℓ + exact hout k + +/-- A property cuts out the set `Q` if and only if it holds at the levels of `Q` and fails at the +other levels. -/ +theorem filter_eq_iff_forall_level {P : Fin L → Prop} [DecidablePred P] : + univ.filter P = Q ↔ (∀ k, P (Finset.orderEmbOfFin Q hQ k)) ∧ ∀ k, ¬ P ((Qᶜ.orderEmbOfFin + (card_compl_of_card_eq Q hQ)) k) := by + simp only [Finset.ext_iff, forall_level_iff Q hQ, mem_filter, mem_univ, true_and, + Finset.orderEmbOfFin_mem, outerLevel_notMem Q hQ, iff_true, iff_false] + +/-- A property that holds at the `m` levels of `Q`, and at `m` levels in all, cuts out `Q`. -/ +theorem filter_eq_of_forall_innerLevel {P : Fin L → Prop} [DecidablePred P] + (hin : ∀ k, P (Finset.orderEmbOfFin Q hQ k)) (hcard : (univ.filter P).card = m) : univ.filter + P = Q := by + refine (eq_of_subset_of_card_le (fun ℓ hℓ => ?_) (hcard.trans hQ.symm).le).symm + obtain ⟨k, rfl⟩ := exists_innerLevel Q hQ hℓ + exact mem_filter.mpr ⟨mem_univ _, hin k⟩ + +/-! ### Gluing two strings along a set of levels -/ + +variable {α : Type*} + +/-- The string that has the letters of `f` at the levels of `Q` and the letters of `g` at the other +levels, both in the order of the levels. -/ +def glue (f : Fin m → α) (g : Fin (L - m) → α) : Fin L → α := + fun ℓ => + if hℓ : ℓ ∈ Q then f ((Q.orderIsoOfFin hQ).symm ⟨ℓ, hℓ⟩) + else g ((Qᶜ.orderIsoOfFin (card_compl_of_card_eq Q hQ)).symm ⟨ℓ, mem_compl.mpr hℓ⟩) + +/-- At the `k`-th lowest level of `Q`, the glued string has the `k`-th letter of `f`. -/ +@[simp] +theorem glue_innerLevel (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin m) : + glue Q hQ f g (Finset.orderEmbOfFin Q hQ k) = f k := by + rw [glue, dite_eq_left (Finset.orderEmbOfFin_mem Q hQ k)] + exact congrArg f ((OrderIso.symm_apply_eq _).mpr (Subtype.ext rfl)) + +/-- At the `k`-th lowest level outside `Q`, the glued string has the `k`-th letter of `g`. -/ +@[simp] +theorem glue_outerLevel (f : Fin m → α) (g : Fin (L - m) → α) (k : Fin (L - m)) : + glue Q hQ f g ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k) = g k := by + rw [glue, dite_eq_right (outerLevel_notMem Q hQ k)] + exact congrArg g ((OrderIso.symm_apply_eq _).mpr (Subtype.ext rfl)) + +/-- At the `k`-th lowest level of a set `Q'` that is equal to `Q`, the string glued along `Q` has +the `k`-th letter of `f`. -/ +theorem glue_innerLevel_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} + (h : Q' = Q) (hQ' : Q'.card = m) (k : Fin m) : glue Q hQ f g (Finset.orderEmbOfFin Q' hQ' k) = + f k := by + rw [innerLevel_congr Q hQ h, glue_innerLevel] + +/-- At the `k`-th lowest level outside a set `Q'` that is equal to `Q`, the string glued along `Q` +has the `k`-th letter of `g`. -/ +theorem glue_outerLevel_of_eq (f : Fin m → α) (g : Fin (L - m) → α) {Q' : Finset (Fin L)} + (h : Q' = Q) (hQ' : Q'.card = m) (k : Fin (L - m)) : + glue Q hQ f g ((Q'ᶜ.orderEmbOfFin (card_compl_of_card_eq Q' hQ')) k) = g k := by + subst h + apply glue_outerLevel + +variable {f f' : Fin m → α} {g g' : Fin (L - m) → α} + +/-- The glued string is the only string with the letters of `f` at the levels of `Q` and the letters +of `g` at the other levels. -/ +theorem eq_glue_iff {u : Fin L → α} : + u = glue Q hQ f g + ↔ (∀ k, u (Finset.orderEmbOfFin Q hQ k) = f k) ∧ ∀ k, u ((Qᶜ.orderEmbOfFin + (card_compl_of_card_eq Q hQ)) k) = g k := by + simp only [funext_iff, forall_level_iff Q hQ, glue_innerLevel, glue_outerLevel] + +/-- The two strings can be read off the glued string. -/ +theorem glue_inj : glue Q hQ f g = glue Q hQ f' g' ↔ f = f' ∧ g = g' := by + rw [eq_glue_iff] + simp only [glue_innerLevel, glue_outerLevel, funext_iff] + +/-- A map applied to a glued string, letter by letter. -/ +theorem map_glue {β : Type*} (φ : α → β) : + (fun ℓ => φ (glue Q hQ f g ℓ)) = glue Q hQ (fun k => φ (f k)) (fun k => φ (g k)) := by + simp only [eq_glue_iff, glue_innerLevel, glue_outerLevel, implies_true, and_self] + +/-! ### Alphabets with inner and outer letters -/ + +variable (IsInner : α → Prop) [DecidablePred IsInner] + +/-- If the letters of `f` are inner and those of `g` are outer, then the glued string has its inner +letters exactly at the levels of `Q`. -/ +theorem filter_glue (hf : ∀ k, IsInner (f k)) (hg : ∀ k, ¬ IsInner (g k)) : + (univ.filter fun ℓ => IsInner (glue Q hQ f g ℓ)) = Q := by + simpa only [filter_eq_iff_forall_level Q hQ, glue_innerLevel, glue_outerLevel] using + And.intro hf hg + +/-- Let every inner letter `s` be `inner i` for its index `i = innerIndex s`, and every outer letter +be `outer o` for its index `o = outerIndex s`. Then a string whose inner letters are at the levels +of `Q` is rebuilt by gluing the letters `inner i` and `outer o` for the indices of its letters. -/ +theorem glue_index {I O : Type*} {inner : I → α} {outer : O → α} {innerIndex : α → I} + {outerIndex : α → O} (hI : ∀ s, IsInner s → inner (innerIndex s) = s) + (hO : ∀ s, ¬ IsInner s → outer (outerIndex s) = s) {u : Fin L → α} + (hu : (univ.filter fun ℓ => IsInner (u ℓ)) = Q) : + u = glue Q hQ (fun k => inner (innerIndex (u (Finset.orderEmbOfFin Q hQ k)))) + (fun k => outer (outerIndex (u ((Qᶜ.orderEmbOfFin (card_compl_of_card_eq Q hQ)) k)))) := by + obtain ⟨hin, hout⟩ := (filter_eq_iff_forall_level Q hQ).mp hu + exact (eq_glue_iff Q hQ).mpr ⟨fun k => (hI _ (hin k)).symm, fun k => (hO _ (hout k)).symm⟩ + + +end Finset diff --git a/ComplexityTests/SchonhageThinProduct.lean b/ComplexityTests/SchonhageThinProduct.lean new file mode 100644 index 0000000000..3eff3864fc --- /dev/null +++ b/ComplexityTests/SchonhageThinProduct.lean @@ -0,0 +1,165 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Magnitude +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned + +/-! +Regression clients for finite thin multiplication. Small computable layouts exercise actual signed +products and padding. The positive-depth parameter instance uses symbolic matrices so its large +outer dimension is never enumerated. No example claims a native instruction bound. +-/ + +open Finset Cslib.Algorithms.MatrixMultiplication.Schonhage + +namespace SchonhageThinProductTests + +/-- One outer level: blocks have three rows, and the inner dimension is one. -/ +private def outerLayout : Layout 1 0 where + hmL := by decide + rowIdx := (Equiv.funUnique (Fin 1) (Fin 3)).symm + colIdx := (Equiv.funUnique (Fin 1) (Fin 3)).symm + innerIdx := by + change Fin 1 ≃ (Fin 0 → Fin 2 × Fin 2) + exact Equiv.ofUnique _ _ + table := fun _ => ∅ + table_card := fun _ => rfl + table_injective := by + change Function.Injective (fun _ : Fin 1 × Fin 1 => _) + exact fun _ _ _ => Subsingleton.elim _ _ + +/-- One inner level: four inner indices and one row per block. -/ +private def innerLayout : Layout 1 1 where + hmL := by decide + rowIdx := by + change Fin 1 ≃ (Fin 0 → Fin 3) + exact Equiv.ofUnique _ _ + colIdx := by + change Fin 1 ≃ (Fin 0 → Fin 3) + exact Equiv.ofUnique _ _ + innerIdx := (finProdFinEquiv (m := 2) (n := 2)).symm.trans + (Equiv.funUnique (Fin 1) (Fin 2 × Fin 2)).symm + table := fun _ => {0} + table_card := fun _ => by decide + table_injective := by + change Function.Injective (fun _ : Fin 1 × Fin 1 => _) + exact fun _ _ _ => Subsingleton.elim _ _ + +private def X : Matrix (Fin 4) (Fin 1) ℤ := !![-3; 2; 0; 5] +private def Y : Matrix (Fin 1) (Fin 4) ℤ := !![2, -1, 4, 0] +private def W : Finset (Fin 4 × Fin 4) := {(0, 1), (3, 2), (2, 3)} + +-- N is not a multiple of the band size. The last two padded rows and columns are zero. +example : padN 1 0 4 = 6 := by decide +example : numBands 1 0 4 = 2 := by decide +example : padN 1 0 0 = 0 := by decide +kernel +example : padRows X 4 0 = 0 ∧ padCols Y 0 5 = 0 := by decide +kernel +example : padRows X 3 0 = 5 ∧ padCols Y 0 2 = 4 := by decide + +example : outStrOfPos outerLayout 0 1 = ![.z 0 1] := by decide +example : bandArrayL outerLayout X 0 ![.x 0] = -3 := by decide +kernel +-- These compute the finite recursion itself, including encodings and the wanted-output mapping. +example : Pruned.result 1 (encodingL (bandArrayL outerLayout X 0)) + (encodingR (bandArrayR outerLayout Y 0)) (wantedStrings outerLayout W 0 0) + (outStrOfPos outerLayout 0 1) = 3 := by decide +kernel + +example : Pruned.result 1 (encodingL (bandArrayL outerLayout X 1)) + (encodingR (bandArrayR outerLayout Y 0)) (wantedStrings outerLayout W 1 0) + (outStrOfPos outerLayout 3 2) = 20 := by decide +kernel + +example : Pruned.result 1 (encodingL (bandArrayL outerLayout X 0)) + (encodingR (bandArrayR outerLayout Y 1)) (wantedStrings outerLayout W 0 1) + (outStrOfPos outerLayout 2 3) = 0 := by decide +kernel + +-- Every queried product entry, through the general correctness theorem. +example (I J : Fin 4) (h : (I, J) ∈ W) : + Pruned.result 1 (encodingL (bandArrayL outerLayout X (bandOf 1 0 I))) + (encodingR (bandArrayR outerLayout Y (bandOf 1 0 J))) + (wantedStrings outerLayout W (bandOf 1 0 I) (bandOf 1 0 J)) + (outStrOfPos outerLayout I J) = (X * Y) I J := + thin_product_spec outerLayout X Y W I J h + +example : ∑ T ∈ tiles 1 0 4, (wantedStrings outerLayout W T.1 T.2).card = 3 := by decide +kernel +example : ∑ T ∈ tiles 1 0 4, (leaves (wantedStrings outerLayout W T.1 T.2)).card = 3 := by + decide +example : wantedStrings outerLayout W 1 1 = ∅ := by decide +example : (leaves (wantedStrings outerLayout W 1 1)).card = 0 := by decide +kernel + +-- Four signed summands at a genuine inner level; the tensor's error contributes nothing. +private def A : Matrix (Fin 1) (Fin 4) ℤ := !![-3, 2, 0, 5] +private def B : Matrix (Fin 4) (Fin 1) ℤ := !![2; -1; 4; 0] + +example : Full.result 1 (bandArrayL innerLayout A 0) (bandArrayR innerLayout B 0) + (outStrOfPos innerLayout 0 0) = -8 := by decide +kernel + +example : Pruned.result 1 (encodingL (bandArrayL innerLayout A 0)) + (encodingR (bandArrayR innerLayout B 0)) + (wantedStrings innerLayout ({(0, 0)} : Finset (Fin 1 × Fin 1)) 0 0) + (outStrOfPos innerLayout 0 0) = -8 := by decide +kernel + +example : (univ.filter fun τ : Leaf 1 => + Leaf.Contributes τ (fun _ => OutVar.z0) ∧ order 1 τ = 0).card = 1 := by decide +example : (univ.filter fun τ : Leaf 1 => + Leaf.Contributes τ (fun _ => OutVar.z0) ∧ order 1 τ = 1).card = 9 := by decide +example : (leaves ({fun _ => OutVar.z0} : Finset (OutStr 1))).card = 10 := by decide +example : order 0 (fun _ : Fin 1 => Term.P0) = -1 := by decide +example : (leaves (∅ : Finset (OutStr 0))).card = 0 := by decide +kernel + +example (β β' : ℕ) : wantedStrings outerLayout (∅ : Finset (Fin 4 × Fin 4)) β β' = ∅ := by + simp [wantedStrings] + +-- Existence at both the degenerate and positive-depth boundary, without assumptions on layouts. +example : Nonempty (Layout 0 0) := nonempty_layout (by decide) +example : Nonempty (Layout 19 1) := nonempty_layout (by decide) + +private abbrev admittedN : ℕ := 68719476736 +private noncomputable def admittedLayout : Layout 19 1 := + Classical.choice (nonempty_layout (by decide)) +private def singletonQuery : Finset (Fin admittedN × Fin admittedN) := {(0, 0)} + +-- All parameters of the complete composition are simultaneously satisfiable with m > 0. +example : D 1 ^ 18 ≤ admittedN ∧ singletonQuery.card * 2 ^ 1 ≤ admittedN ^ 2 := by + constructor + · rfl + · norm_num [singletonQuery, admittedN] + +example (X' : Matrix (Fin admittedN) (Fin (D 1)) ℤ) + (Y' : Matrix (Fin (D 1)) (Fin admittedN) ℤ) : + Pruned.result 19 (encodingL (bandArrayL admittedLayout X' (bandOf 19 1 0))) + (encodingR (bandArrayR admittedLayout Y' (bandOf 19 1 0))) + (wantedStrings admittedLayout singletonQuery (bandOf 19 1 0) (bandOf 19 1 0)) + (outStrOfPos admittedLayout 0 0) = (X' * Y') 0 0 := + thin_product_spec admittedLayout X' Y' singletonQuery 0 0 (by simp [singletonQuery]) + +example : + ((2 * numBands (19 * 1) 1 admittedN * 10 ^ (19 * 1) : ℕ) : ℝ) ≤ + 4 * (D 1 : ℝ) ^ (-(1 / 18 : ℝ)) * (admittedN : ℝ) ^ 2 := by + have hN : D 1 ^ 18 ≤ admittedN := by norm_num [D, admittedN] + exact encodings_total_le 1 admittedN hN + +example : + ((∑ T ∈ tiles (19 * 1) 1 admittedN, + (leaves (wantedStrings admittedLayout singletonQuery T.1 T.2)).card : ℕ) : ℝ) ≤ + 33 * (D 1 : ℝ) ^ (-(1 / 18 : ℝ)) * (admittedN : ℝ) ^ 2 := by + have hN : D 1 ^ 18 ≤ admittedN := by norm_num [D, admittedN] + have hW : singletonQuery.card * 2 ^ 1 ≤ admittedN ^ 2 := by + norm_num [singletonQuery, admittedN] + exact total_leaves_le (m := 1) (N := admittedN) admittedLayout singletonQuery hN hW + +example (X' : Matrix (Fin admittedN) (Fin (D 1)) ℤ) + (Y' : Matrix (Fin (D 1)) (Fin admittedN) ℤ) : + ((∑ T ∈ tiles (19 * 1) 1 admittedN, + Pruned.totalSize (encodingL (bandArrayL admittedLayout X' T.1)) + (encodingR (bandArrayR admittedLayout Y' T.2)) + (wantedStrings admittedLayout singletonQuery T.1 T.2) : ℕ) : ℝ) ≤ + ((19 * 1 + 1 : ℕ) : ℝ) * (33 * (D 1 : ℝ) ^ (-(1 / 18 : ℝ)) * (admittedN : ℝ) ^ 2) := by + have hN : D 1 ^ 18 ≤ admittedN := by norm_num [D, admittedN] + have hW : singletonQuery.card * 2 ^ 1 ≤ admittedN ^ 2 := by + norm_num [singletonQuery, admittedN] + exact total_passed_sets_le (m := 1) (N := admittedN) admittedLayout X' Y' singletonQuery hN hW + +end SchonhageThinProductTests diff --git a/NOTICE b/NOTICE index 5fb9b6c145..65eb16f1c4 100644 --- a/NOTICE +++ b/NOTICE @@ -3,7 +3,9 @@ Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Wit Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Foundations/Data/Finset/{Average,Quotient}.lean, +Complexity/Foundations/Algebra/{FiniteMagnitude,GeometricTail}.lean, +Complexity/Foundations/Combinatorics/{BinomialUpperBound,FunctionPlaces}.lean, +Complexity/Foundations/Data/Finset/{Average,OrderGlue,Quotient}.lean, Complexity/Foundations/Data/Int/Residue.lean, Complexity/Foundations/Data/Nat/CeilDiv.lean, Complexity/Foundations/Data/Matrix/{IntegerBits,PairChunks}.lean and diff --git a/ROADMAP.md b/ROADMAP.md index 999c5e63cc..5805e12162 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -426,8 +426,10 @@ each statement against the numbered result. Organize modules by subject, not cha and the selected-prime false-positive bound ([#1442]), finite residue/chunk/piece splitting and witness recovery ([#1447]) are proved mathematically. The tensor identity, full recursion, shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set - bounds, with the empty zero-depth root handled explicitly. It still needs tiling and sparse - leaf bounds, native tensor/sparse-query programs, native Chan–He preprocessing, the reduction + bounds, with the empty zero-depth root handled explicitly. The finite thin-product component + ([#1463]) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and + sparse encoding/leaf/passed-set totals. These counts are not native instruction costs. It still + needs native tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width diff --git a/UPSTREAM.md b/UPSTREAM.md index bfa84a8699..dde42fa023 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -199,9 +199,16 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he error, full evaluation, shared encodings, pruned values, visited leaves and passed-set totals. Its pure finite recursion traces are not native machine semantics or charged instruction costs. The zero-depth empty-query root exception is explicit, with an unconditional root-or-leaf API. - See [#1459](https://github.com/cslib-community/complexitylib/issues/1459). Tiling/sparse leaf bounds, - native tensor/sparse-query programs, native preprocessing/solvers and full input/time/width - composition remain in #1394. + See [#1459](https://github.com/cslib-community/complexitylib/issues/1459). The complete finite + thin-product component ([#1463](https://github.com/cslib-community/complexitylib/issues/1463)) + adapts `Sec2/{Levels,Lemma9,Tiling,Orders,Lemma11,Theorem5}` and needed finite magnitude bounds. + Layouts contain only indexing choices, whose existence is proved; sparse bounds are derived + from exact contributing-leaf counts. Generic function-position counts, ordered gluing, binomial + and finite-sum bounds live in small Foundations modules. Mathlib `Finset.orderEmbOfFin` indexes + both a level set and its complement, and `Finset.piecewise` restricts answers. No source machine, + problem-statement universe or solver/time hypothesis is imported. Native tensor/sparse-query + programs, native preprocessing/solvers and full input/time/width composition, including the + improved Section 4 route to exponent 1.9992, remain in #1394. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. From 5cbb93d5ddc946364292740e2d85e334f4bfa649 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:21:35 -0400 Subject: [PATCH 15/32] =?UTF-8?q?feat(wordram):=20compute=20Chan=E2=80=93H?= =?UTF-8?q?e=20parameters=20from=20native=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement fixed 24-register parameter code with charged numeric helpers, exact existing Chan–He outputs, preserved input memory, derived logarithmic width and subquadratic native time. Add executed boundary and input-init contract examples. Original implementation and proofs credited to Erik Demaine; AI assistance by OpenAI Codex. Part of #1446; independent complete parameter subtask #1458. --- Complexity.lean | 4 + Complexity/Algorithms/WordRAM/NatClog.lean | 116 +++ Complexity/Algorithms/WordRAM/NatLog.lean | 111 +++ Complexity/Algorithms/WordRAM/NatSqrt.lean | 129 +++ .../WordRAM/ThreeSum/ChanHe/Parameters.lean | 891 ++++++++++++++++++ ComplexityTests/WordRAMChanHeParameters.lean | 64 ++ 6 files changed, 1315 insertions(+) create mode 100644 Complexity/Algorithms/WordRAM/NatClog.lean create mode 100644 Complexity/Algorithms/WordRAM/NatLog.lean create mode 100644 Complexity/Algorithms/WordRAM/NatSqrt.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean create mode 100644 ComplexityTests/WordRAMChanHeParameters.lean diff --git a/Complexity.lean b/Complexity.lean index 10dee5c44a..af010bc30b 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -89,6 +89,9 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static public import Complexity.Algorithms.WordRAM.Histogram +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree @@ -103,6 +106,7 @@ public import Complexity.Algorithms.WordRAM.Stack.Controller public import Complexity.Algorithms.WordRAM.ThreeSum.Cell public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Memory diff --git a/Complexity/Algorithms/WordRAM/NatClog.lean b/Complexity/Algorithms/WordRAM/NatClog.lean new file mode 100644 index 0000000000..6feabd6b67 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatClog.lean @@ -0,0 +1,116 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Log + +/-! +# A charged native binary logarithm + +## Main definitions + +* `NatClog.program`: successive ceiling division by two. + +## Main results + +* `NatClog.exec`: the ceiling logarithm with a logarithmic charged instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatClog + +open Cslib.WordRAM + +/-- Quotient, initially the input. -/ +abbrev arg : Fin 4 := 0 +/-- Floor logarithm output. -/ +abbrev result : Fin 4 := 1 +/-- Constant one. -/ +abbrev one : Fin 4 := 2 +/-- Constant two. -/ +abbrev two : Fin 4 := 3 + +/-- Divide the quotient by two and increment the answer. -/ +def body : Code .standard 4 := ram[ arg ← arg + one; arg ← arg / two; result ← result + one ] + +/-- Fixed code for `Nat.clog 2`, including its value zero at input zero. -/ +def program : Code .standard 4 := + .seq (ram[ result ← #0; one ← #1; two ← #2 ]) (.loop (.lt one arg) body) + +/-- Compute the ceiling binary logarithm in at most `5 ⌈log₂ n⌉ + 5` instructions. +Memory is untouched and the input quotient is consumed. -/ +theorem exec {w : ℕ} (x : Data 4) (h2 : Fits w 2) (hn : Fits w (x.regs arg + 1)) + (hout : Fits w (Nat.clog 2 (x.regs arg) + 1)) : + ∃ y, ExecWithin w program x y (5 * Nat.clog 2 (x.regs arg) + 5) ∧ + y.regs result = Nat.clog 2 (x.regs arg) ∧ y.mem = x.mem ∧ + y.regs one = 1 ∧ y.regs two = 2 := by + let I (y : Data 4) : Prop := y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs arg ≤ x.regs arg ∧ + y.regs result + Nat.clog 2 (y.regs arg) = Nat.clog 2 (x.regs arg) ∧ y.mem = x.mem + have hbody (y : Data 4) (hy : I y) (hc : (Cond.lt one arg).Holds y) : + ∃ z, ExecWithin w body y z 3 ∧ I z ∧ + Nat.clog 2 (z.regs arg) < Nat.clog 2 (y.regs arg) := by + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hgt : 1 < y.regs arg := by simpa only [Cond.holds_lt, ho] using hc + have hpos : 0 < Nat.clog 2 (y.regs arg) := Nat.clog_pos (by decide) (by omega) + have hadd : Fits w (y.regs arg + 1) := hn.of_le (by omega) + have hdiv : Fits w ((y.regs arg + 1) / 2) := + hn.of_le ((Nat.div_le_self _ _).trans (by omega)) + have hinc : Fits w (y.regs result + 1) := hout.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, ht, hadd, hdiv, hinc, arg, result, one, two, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using ht + · simpa +decide [Function.update_apply] using + (show (y.regs arg + 1) / 2 ≤ x.regs arg from by omega) + · change y.regs result + 1 + Nat.clog 2 ((y.regs arg + 1) / 2) = Nat.clog 2 (x.regs arg) + have h := Nat.clog_of_two_le (b := 2) (by decide) (by omega : 2 ≤ y.regs arg) + simp only [show y.regs arg + 2 - 1 = y.regs arg + 1 by omega] at h + omega + · exact hm + case decrease => + change Nat.clog 2 ((y.regs arg + 1) / 2) < Nat.clog 2 (y.regs arg) + have h := Nat.clog_of_two_le (b := 2) (by decide) (by omega : 2 ≤ y.regs arg) + simp only [show y.regs arg + 2 - 1 = y.regs arg + 1 by omega] at h + omega + let x₀ := ((x.set result 0).set one 1).set two 2 + have hx₀ : I x₀ := by simp +decide [I, x₀, arg, result, one, two] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.clog 2 (y.regs arg)) hbody hx₀ + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hzero : Nat.clog 2 (y.regs arg) = 0 := by + have hle : y.regs arg ≤ 1 := by simpa only [Cond.holds_lt, ho, not_lt] using hc + exact Nat.clog_of_right_le_one hle 2 + refine ⟨y, ?_, by omega, hm, ho, ht⟩ + have h1 : Fits w 1 := h2.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ result ← #0; one ← #1; two ← #2 ]) x x₀ 3 := by + ram_eval [x₀, h1, h2] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, arg, result, one, two, + ↓reduceIte] + omega + +/-- Run the routine on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 4 → Fin r) (hf : f.Injective) (x : Data r) + (h2 : Fits w 2) (hn : Fits w (x.regs (f arg) + 1)) + (hout : Fits w (Nat.clog 2 (x.regs (f arg)) + 1)) : + ∃ y, ExecWithin w (program.rename f) x y (5 * Nat.clog 2 (x.regs (f arg)) + 5) ∧ + y.regs (f result) = Nat.clog 2 (x.regs (f arg)) ∧ y.mem = x.mem ∧ + y.regs (f one) = 1 ∧ y.regs (f two) = 2 := by + obtain ⟨a, ha, hr, hm, ho, ht⟩ := exec (x.comap f) h2 hn hout + refine ⟨a.extend f x, ha.rename hf, ?_, hm, ?_, ?_⟩ + · exact (hf.extend_apply _ _ result).trans hr + · exact (hf.extend_apply _ _ one).trans ho + · exact (hf.extend_apply _ _ two).trans ht + +end Cslib.Algorithms.WordRAM.NatClog diff --git a/Complexity/Algorithms/WordRAM/NatLog.lean b/Complexity/Algorithms/WordRAM/NatLog.lean new file mode 100644 index 0000000000..eb229076e6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatLog.lean @@ -0,0 +1,111 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Log + +/-! +# A charged native binary logarithm + +## Main definitions + +* `NatLog.program`: successive division by two. + +## Main results + +* `NatLog.exec`: the floor logarithm with a logarithmic charged instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatLog + +open Cslib.WordRAM + +/-- Quotient, initially the input. -/ +abbrev arg : Fin 4 := 0 +/-- Floor logarithm output. -/ +abbrev result : Fin 4 := 1 +/-- Constant one. -/ +abbrev one : Fin 4 := 2 +/-- Constant two. -/ +abbrev two : Fin 4 := 3 + +/-- Divide the quotient by two and increment the answer. -/ +def body : Code .standard 4 := ram[ arg ← arg / two; result ← result + one ] + +/-- Fixed code for `Nat.log 2`, including its value zero at input zero. -/ +def program : Code .standard 4 := + .seq (ram[ result ← #0; one ← #1; two ← #2 ]) (.loop (.lt one arg) body) + +/-- Compute the floor binary logarithm in at most `4 log₂ n + 5` instructions. +Memory is untouched and the input quotient is consumed. -/ +theorem exec {w : ℕ} (x : Data 4) (h2 : Fits w 2) (hn : Fits w (x.regs arg)) + (hout : Fits w (Nat.log 2 (x.regs arg) + 1)) : + ∃ y, ExecWithin w program x y (4 * Nat.log 2 (x.regs arg) + 5) ∧ + y.regs result = Nat.log 2 (x.regs arg) ∧ y.mem = x.mem ∧ + y.regs one = 1 ∧ y.regs two = 2 := by + let I (y : Data 4) : Prop := y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs arg ≤ x.regs arg ∧ + y.regs result + Nat.log 2 (y.regs arg) = Nat.log 2 (x.regs arg) ∧ y.mem = x.mem + have hbody (y : Data 4) (hy : I y) (hc : (Cond.lt one arg).Holds y) : + ∃ z, ExecWithin w body y z 2 ∧ I z ∧ + Nat.log 2 (z.regs arg) < Nat.log 2 (y.regs arg) := by + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hgt : 1 < y.regs arg := by simpa only [Cond.holds_lt, ho] using hc + have hpos : 0 < Nat.log 2 (y.regs arg) := Nat.log_pos (by decide) (by omega) + have hdiv : Fits w (y.regs arg / 2) := hn.of_le ((Nat.div_le_self _ _).trans harg) + have hinc : Fits w (y.regs result + 1) := hout.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, ht, hdiv, hinc, arg, result, one, two, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using ht + · simpa +decide [Function.update_apply] using (Nat.div_le_self (y.regs arg) 2).trans harg + · change y.regs result + 1 + Nat.log 2 (y.regs arg / 2) = Nat.log 2 (x.regs arg) + rw [Nat.log_div_base] + omega + · exact hm + case decrease => + change Nat.log 2 (y.regs arg / 2) < Nat.log 2 (y.regs arg) + rw [Nat.log_div_base] + omega + let x₀ := ((x.set result 0).set one 1).set two 2 + have hx₀ : I x₀ := by simp +decide [I, x₀, arg, result, one, two] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.log 2 (y.regs arg)) hbody hx₀ + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hzero : Nat.log 2 (y.regs arg) = 0 := by + have hle : y.regs arg ≤ 1 := by simpa only [Cond.holds_lt, ho, not_lt] using hc + exact Nat.log_eq_zero_iff.mpr (Or.inl (by omega)) + refine ⟨y, ?_, by omega, hm, ho, ht⟩ + have h1 : Fits w 1 := h2.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ result ← #0; one ← #1; two ← #2 ]) x x₀ 3 := by + ram_eval [x₀, h1, h2] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, arg, result, one, two, + ↓reduceIte] + omega + +/-- Run the routine on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 4 → Fin r) (hf : f.Injective) (x : Data r) + (h2 : Fits w 2) (hn : Fits w (x.regs (f arg))) + (hout : Fits w (Nat.log 2 (x.regs (f arg)) + 1)) : + ∃ y, ExecWithin w (program.rename f) x y (4 * Nat.log 2 (x.regs (f arg)) + 5) ∧ + y.regs (f result) = Nat.log 2 (x.regs (f arg)) ∧ y.mem = x.mem ∧ + y.regs (f one) = 1 ∧ y.regs (f two) = 2 := by + obtain ⟨a, ha, hr, hm, ho, ht⟩ := exec (x.comap f) h2 hn hout + refine ⟨a.extend f x, ha.rename hf, ?_, hm, ?_, ?_⟩ + · exact (hf.extend_apply _ _ result).trans hr + · exact (hf.extend_apply _ _ one).trans ho + · exact (hf.extend_apply _ _ two).trans ht + +end Cslib.Algorithms.WordRAM.NatLog diff --git a/Complexity/Algorithms/WordRAM/NatSqrt.lean b/Complexity/Algorithms/WordRAM/NatSqrt.lean new file mode 100644 index 0000000000..98b3ad67ed --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatSqrt.lean @@ -0,0 +1,129 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Sqrt +import Mathlib.Tactic.Linarith + +/-! +# A charged native integer square root + +## Main definitions + +* `NatSqrt.program`: successive squares. + +## Main results + +* `NatSqrt.exec`: integer square root in a square-root number of native instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatSqrt + +open Cslib.WordRAM + +/-- Input integer, preserved. -/ +abbrev arg : Fin 6 := 0 +/-- Current root and output. -/ +abbrev root : Fin 6 := 1 +/-- Successor of the current root. -/ +abbrev next : Fin 6 := 2 +/-- Square of the successor. -/ +abbrev square : Fin 6 := 3 +/-- Constant one. -/ +abbrev one : Fin 6 := 4 +/-- Input plus one. -/ +abbrev bound : Fin 6 := 5 + +/-- Advance the root and compute the next square. -/ +def body : Code .standard 6 := ram[ + root ← next; next ← next + one; square ← next * next ] + +/-- A fixed program for the integer square root, with all square computations charged. -/ +def program : Code .standard 6 := + .seq (ram[ root ← #0; next ← #1; square ← #1; one ← #1; bound ← arg + one ]) + (.loop (.lt square bound) body) + +/-- Compute `Nat.sqrt n` in at most `5 sqrt(n) + 7` instructions, preserving input and memory. +The linear bound `4n + 4` covers the final unsuccessful square as well as all counters. -/ +theorem exec {w : ℕ} (x : Data 6) (hfit : Fits w (4 * x.regs arg + 4)) : + ∃ y, ExecWithin w program x y (5 * Nat.sqrt (x.regs arg) + 7) ∧ + y.regs root = Nat.sqrt (x.regs arg) ∧ y.regs arg = x.regs arg ∧ y.mem = x.mem ∧ + y.regs one = 1 := by + let n := x.regs arg + let I (y : Data 6) : Prop := y.regs arg = n ∧ y.regs one = 1 ∧ + y.regs bound = n + 1 ∧ y.regs next = y.regs root + 1 ∧ + y.regs square = y.regs next * y.regs next ∧ + y.regs root ≤ Nat.sqrt n ∧ y.mem = x.mem + have hbody (y : Data 6) (hy : I y) (hc : (Cond.lt square bound).Holds y) : + ∃ z, ExecWithin w body y z 3 ∧ I z ∧ + Nat.sqrt n - z.regs root < Nat.sqrt n - y.regs root := by + obtain ⟨ha, ho, hb, hn, hsq, hr, hm⟩ := hy + have hsmall : y.regs next * y.regs next ≤ n := by + simp only [Cond.holds_lt, hsq, hb] at hc + omega + have hnext : y.regs next ≤ Nat.sqrt n := Nat.le_sqrt.mpr hsmall + have hsl := Nat.sqrt_le n + have hsle := Nat.sqrt_le_self n + have hstep : Fits w (y.regs next + 1) := hfit.of_le (by dsimp [n] at *; omega) + have hsquare : Fits w ((y.regs next + 1) * (y.regs next + 1)) := hfit.of_le (by + change (y.regs next + 1) * (y.regs next + 1) ≤ 4 * n + 4 + have hnn : y.regs next ≤ n := hnext.trans hsle + nlinarith) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, hstep, hsquare, root, next, square, one, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ha + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using hb + · rfl + · rfl + · exact hnext + · exact hm + case decrease => + change Nat.sqrt n - y.regs next < Nat.sqrt n - y.regs root + omega + let x₀ := ((((x.set root 0).set next 1).set square 1).set one 1).set bound (n + 1) + have hx₀ : I x₀ := by simp +decide [I, x₀, n, root, next, square, one, bound, arg] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.sqrt n - y.regs root) hbody hx₀ + obtain ⟨ha, ho, hb, hn, hsq, hr, hm⟩ := hy + have hlarge : n < (y.regs root + 1) * (y.regs root + 1) := by + simp only [Cond.holds_lt, hsq, hn, hb] at hc + omega + have hout : y.regs root = Nat.sqrt n := by + have h := Nat.sqrt_lt.mpr hlarge + omega + have h1 : Fits w 1 := hfit.of_le (by omega) + have hbfit : Fits w (x.regs arg + 1) := hfit.of_le (by omega) + have hinit : ExecWithin (B := .standard) w + (ram[ root ← #0; next ← #1; square ← #1; one ← #1; bound ← arg + one ]) x x₀ 5 := by + ram_eval [x₀, n, h1, hbfit, root, next, square, one, bound, arg, Fin.mk.injEq] + refine ⟨y, ?_, hout, ha, hm, ho⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, root, next, square, one, + bound, arg, n, ↓reduceIte, Nat.sub_zero] + omega + +/-- Run successive squares on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 6 → Fin r) (hf : f.Injective) (x : Data r) + (hfit : Fits w (4 * x.regs (f arg) + 4)) : + ∃ y, ExecWithin w (program.rename f) x y (5 * Nat.sqrt (x.regs (f arg)) + 7) ∧ + y.regs (f root) = Nat.sqrt (x.regs (f arg)) ∧ + y.regs (f arg) = x.regs (f arg) ∧ y.mem = x.mem ∧ y.regs (f one) = 1 := by + obtain ⟨a, ha, hr, hn, hm, ho⟩ := exec (x.comap f) hfit + refine ⟨a.extend f x, ha.rename hf, ?_, ?_, hm, ?_⟩ + · exact (hf.extend_apply _ _ root).trans hr + · exact (hf.extend_apply _ _ arg).trans hn + · exact (hf.extend_apply _ _ one).trans ho + +end Cslib.Algorithms.WordRAM.NatSqrt diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean new file mode 100644 index 0000000000..3ddc4b7cc4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -0,0 +1,891 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Defs +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatSqrt +public import Complexity.Computability.WordRAM.Compile +public import Complexity.Computability.WordRAM.Signed.Encoding +import Mathlib.Tactic.Linarith + +/-! +# Native parameters for Chan–He preprocessing + +Original native programs and proofs. The parameter meanings are the existing finite mathematical +Chan–He definitions, with magnitude bound `2U` after the distinct-position frontend. +All loops and arithmetic are instructions; the program is fixed for each exponent `κ`. + +## Main definitions + +* `Parameters.program`: read the input header and compute the exact parameters. +* `Parameters.Output`: identify the unsigned output registers. + +## Main results + +* `Parameters.runs_input`: native execution from signed-encoded input with derived fits. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +open Cslib.WordRAM + +namespace Math +open Cslib.Algorithms.ThreeSum + +/-- A bound covering arithmetic, unsuccessful loop tests, and all output words. -/ +def budget (n U : ℕ) : ℕ := 8 * (ChanHe.mPar n (2 * U) + 1) ^ 2 + 8 * U + 4 * n + 64 + +private theorem want_le (n U : ℕ) : ChanHe.wPar n (2 * U) + 1 ≤ ChanHe.mPar n (2 * U) := + Nat.le_mul_of_pos_right _ (by omega) + +private theorem digits_le (n U : ℕ) : 5 * labelBits (2 * U) ≤ ChanHe.wPar n (2 * U) := + Nat.le_mul_of_pos_right _ (Nat.succ_pos _) + +private theorem factor_le (n U : ℕ) : + 2 * Nat.log 2 (ChanHe.wPar n (2 * U) + 1) + 4 ≤ ChanHe.mPar n (2 * U) := + Nat.le_mul_of_pos_left _ (Nat.succ_pos _) + +private theorem height_le (n : ℕ) : ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + +end Math + +/-- Unsigned input length. -/ +abbrev len : Fin 24 := 0 +/-- Original magnitude bound U. -/ +abbrev magnitude : Fin 24 := 1 +/-- Label bits for magnitude bound 2U. -/ +abbrev digits : Fin 24 := 2 +/-- Recursion height. -/ +abbrev height : Fin 24 := 3 +/-- Recursion fuel. -/ +abbrev fuel : Fin 24 := 4 +/-- Number of desired primes. -/ +abbrev want : Fin 24 := 5 +/-- Upper bound on candidate primes. -/ +abbrev primeBound : Fin 24 := 6 +/-- Common packed convolution length, 8m². -/ +abbrev packedLen : Fin 24 := 7 +/-- Magnitude of the padding for the doubled frontend bound, 4U+1. -/ +abbrev padding : Fin 24 := 8 +/-- Constant one. -/ +abbrev one : Fin 24 := 9 +/-- Constant two. -/ +abbrev two : Fin 24 := 10 +/-- Constant three. -/ +abbrev three : Fin 24 := 11 +/-- Constant four. -/ +abbrev four : Fin 24 := 12 +/-- Constant five. -/ +abbrev five : Fin 24 := 13 +/-- Constant eight. -/ +abbrev eight : Fin 24 := 14 +/-- Input length plus two. -/ +abbrev base : Fin 24 := 15 +/-- Consumed logarithm argument. -/ +abbrev quotient : Fin 24 := 16 +/-- Floor logarithm of input length. -/ +abbrev logLen : Fin 24 := 17 +/-- Integer square root of input length. -/ +abbrev root : Fin 24 := 18 +/-- Number of desired primes plus one. -/ +abbrev wantSucc : Fin 24 := 19 +/-- Arithmetic scratch. -/ +abbrev temp : Fin 24 := 20 +/-- Successor square-root counter. -/ +abbrev nextRoot : Fin 24 := 21 +/-- Square of that counter. -/ +abbrev square : Fin 24 := 22 +/-- Square-root test bound. -/ +abbrev rootBound : Fin 24 := 23 + +/-- Exact mathematical outputs; every output is an ordinary unsigned word. -/ +structure Output (n U : ℕ) (x : Data 24) : Prop where + len : x.regs len = n + magnitude : x.regs magnitude = U + digits : x.regs digits = Cslib.Algorithms.ThreeSum.labelBits (2 * U) + height : x.regs height = Cslib.Algorithms.ThreeSum.ChanHe.height n + fuel : x.regs fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + want : x.regs want = Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + primeBound : x.regs primeBound = Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + packedLen : x.regs packedLen = 8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) ^ 2 + padding : x.regs padding = 4 * U + 1 + +/-- One header load, constants, and the polynomial base. -/ +def setup : Code .standard 24 := ram[ + quotient ← #0; len ← *quotient; one ← #1; two ← #2; three ← #3; + four ← #4; five ← #5; eight ← #8; base ← len + two ] + +/-- Unroll only the fixed exponent, never the input length or word width. -/ +def power : ℕ → Code .standard 24 + | 0 => ram[ magnitude ← #1 ] + | κ + 1 => .seq (power κ) (ram[ magnitude ← magnitude * base ]) + +/-- Register selection for a floor/ceiling logarithm with output `dst`. -/ +def logRegisters (dst : Fin 24) : Fin 4 → Fin 24 := ![quotient, dst, one, two] + +/-- Register selection for successive squares. -/ +def sqrtRegisters : Fin 6 → Fin 24 := ![len, root, nextRoot, square, one, rootBound] + +private theorem sqrt_injective : sqrtRegisters.Injective := by decide + +/-- The instruction sequence after the power computation. -/ +def calculate : Code .standard 24 := + .seq (ram[ quotient ← four * magnitude; padding ← quotient + one ]) + (.seq (NatLog.program.rename (logRegisters digits)) + (.seq (ram[ digits ← digits + one ]) + (.seq (NatSqrt.program.rename sqrtRegisters) + (.seq (ram[ temp ← root + one; want ← five * digits; want ← want * temp; + wantSucc ← want + one; quotient ← wantSucc ]) + (.seq (NatLog.program.rename (logRegisters temp)) + (.seq (ram[ temp ← two * temp; temp ← temp + four; primeBound ← wantSucc * temp ]) + (.seq (ram[ quotient ← len ]) + (.seq (NatLog.program.rename (logRegisters logLen)) + (.seq (ram[ quotient ← logLen + two ]) + (.seq (NatClog.program.rename (logRegisters height)) + (ram[ fuel ← three * height; fuel ← fuel - two; packedLen ← primeBound * primeBound; + packedLen ← eight * packedLen ]))))))))))) + +/-- Fixed 24-register native code for each fixed polynomial magnitude exponent. -/ +def program (κ : ℕ) : Code .standard 24 := .seq setup (.seq (power κ) calculate) + +private theorem exec_power {w : ℕ} (κ : ℕ) (x : Data 24) (hb : 1 ≤ x.regs base) + (hfit : Fits w (x.regs base ^ κ)) : + ExecWithin w (power κ) x (x.set magnitude (x.regs base ^ κ)) (κ + 1) := by + induction κ with + | zero => simpa only [power, pow_zero] using ExecWithin.const hfit + | succ κ ih => + have hprev : Fits w (x.regs base ^ κ) := + hfit.of_le (Nat.pow_le_pow_right hb (by omega)) + have he := ih hprev + have hm : ExecWithin w (ram[ magnitude ← magnitude * base ]) + (x.set magnitude (x.regs base ^ κ)) + (x.set magnitude (x.regs base ^ (κ + 1))) 1 := by + have hp : Fits w (x.regs base ^ κ * x.regs base) := by + simpa only [pow_succ] using hfit + apply ExecWithin.of_eval (fuel := 0) (n := 1) ?_ (by decide) + simp +decide [Code.eval, Op.eval_bin, Data.put, + BinOp.eval, hp, magnitude, base, pow_succ, Data.set, Function.update_idem] + exact (he.seq hm).mono (by omega) + +/-- An explicit charged bound, including all header reads, copies and setup. Halt adds one. -/ +def time (κ n U : ℕ) : ℕ := κ + 64 + 5 * Nat.sqrt n + 4 * Nat.log 2 (4 * U) + + 4 * Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) + + 4 * Nat.log 2 n + 5 * Cslib.Algorithms.ThreeSum.ChanHe.height n + +private theorem time_zero (n U : ℕ) : time 0 n U - 20 = 44 + 5 * Nat.sqrt n + + 4 * Nat.log 2 (4 * U) + + 4 * Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) + + 4 * Nat.log 2 n + 5 * Cslib.Algorithms.ThreeSum.ChanHe.height n := by + dsimp [time] + omega + +set_option maxHeartbeats 800000 in +-- Elaborating twelve native segments and their explicit register frames exceeds the default budget. +private theorem exec_calculate {w n U : ℕ} (x : Data 24) + (hlen : x.regs len = n) (hU : x.regs magnitude = U) + (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h3 : x.regs three = 3) + (h4 : x.regs four = 4) (h5 : x.regs five = 5) (h8 : x.regs eight = 8) + (hfit : Fits w (Math.budget n U)) : + ∃ y, ExecWithin w calculate x y (time 0 n U - 20) ∧ Output n U y ∧ y.mem = x.mem := by + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + let h := Cslib.Algorithms.ThreeSum.ChanHe.height n + let wp := Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + have eΛ : Λ = Nat.log 2 (4 * U) + 1 := by + dsimp [Λ, Cslib.Algorithms.ThreeSum.labelBits] + congr 2 + omega + have ew : wp = 5 * Λ * (Nat.sqrt n + 1) := rfl + have em : m = (wp + 1) * (2 * Nat.log 2 (wp + 1) + 4) := rfl + have eh : Cslib.Algorithms.ThreeSum.ChanHe.fuel n = 3 * h - 2 := rfl + have hd : 5 * Λ ≤ wp := Math.digits_le n U + have hw : wp + 1 ≤ m := Math.want_le n U + have hf : 2 * Nat.log 2 (wp + 1) + 4 ≤ m := Math.factor_le n U + have hh : h ≤ Nat.log 2 n + 2 := Math.height_le n + have hl := Nat.log_le_self 2 n + have hroot := Nat.sqrt_le_self n + have hml : Fits w m := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fitm (v : ℕ) (hv : v ≤ m) : Fits w v := hml.of_le hv + have fitu (v : ℕ) (hv : v ≤ 8 * U + 64) : Fits w v := + hfit.of_le (by dsimp [Math.budget]; omega) + have fitn (v : ℕ) (hv : v ≤ 4 * n + 64) : Fits w v := + hfit.of_le (by dsimp [Math.budget]; omega) + have f1 : Fits w 1 := fitu _ (by omega) + have f2 : Fits w 2 := fitu _ (by omega) + have f4U : Fits w (4 * U) := fitu _ (by omega) + have fpad : Fits w (4 * U + 1) := fitu _ (by omega) + have fΛ : Fits w Λ := fitm _ (by omega) + have flogU : Fits w (Nat.log 2 (4 * U) + 1) := + fitu _ (by have := Nat.log_le_self 2 (4 * U); omega) + have fsqrt : Fits w (4 * n + 4) := fitn _ (by omega) + have fr : Fits w (Nat.sqrt n + 1) := fitn _ (by omega) + have f5Λ : Fits w (5 * Λ) := fitm _ (by omega) + have fwp : Fits w wp := fitm _ (by omega) + have fwps : Fits w (wp + 1) := fitm _ hw + have flogwp : Fits w (Nat.log 2 (wp + 1) + 1) := fitm _ (by omega) + have f2log : Fits w (2 * Nat.log 2 (wp + 1)) := fitm _ (by omega) + have ff : Fits w (2 * Nat.log 2 (wp + 1) + 4) := fitm _ hf + have fn : Fits w n := fitn _ (by omega) + have flogn : Fits w (Nat.log 2 n + 1) := fitn _ (by omega) + have flog2 : Fits w (Nat.log 2 n + 2) := fitn _ (by omega) + have flog3 : Fits w (Nat.log 2 n + 2 + 1) := fitn _ (by omega) + have fh : Fits w (h + 1) := fitn _ (by omega) + have f3h : Fits w (3 * h) := fitn _ (by omega) + have ffuel : Fits w (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) := + fitn _ (by dsimp [Cslib.Algorithms.ThreeSum.ChanHe.fuel]; omega) + have fm2 : Fits w (m * m) := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fN : Fits w (8 * m ^ 2) := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fNm : Fits w (8 * (m * m)) := by simpa only [pow_two] using fN + let a := ((x.set quotient (4 * U)).set padding (4 * U + 1)) + have ea : ExecWithin w (ram[ quotient ← four * magnitude; padding ← quotient + one ]) x a 2 := by + ram_eval [a, h4, hU, h1, f4U, fpad, four, magnitude, one, quotient, padding, + Fin.mk.injEq] + have a_len : a.regs len = n := by + simpa +decide [a, quotient, padding, len] using hlen + have a_magnitude : a.regs magnitude = U := by + simpa +decide [a, quotient, padding, magnitude] using hU + have a_one : a.regs one = 1 := by + simpa +decide [a, quotient, padding, one] using h1 + have a_two : a.regs two = 2 := by + simpa +decide [a, quotient, padding, two] using h2 + have a_three : a.regs three = 3 := by + simpa +decide [a, quotient, padding, three] using h3 + have a_four : a.regs four = 4 := by + simpa +decide [a, quotient, padding, four] using h4 + have a_five : a.regs five = 5 := by + simpa +decide [a, quotient, padding, five] using h5 + have a_eight : a.regs eight = 8 := by + simpa +decide [a, quotient, padding, eight] using h8 + have a_quotient : a.regs quotient = 4 * U := by + simp +decide [a, quotient, padding] + have a_padding : a.regs padding = 4 * U + 1 := by + simp +decide [a, quotient, padding] + have a_mem : a.mem = x.mem := rfl + obtain ⟨b, eb, b_digits, b_mem, b_one, b_two⟩ := + NatLog.exec_rename (logRegisters digits) (by decide) a f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, a_quotient] using f4U) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, a_quotient, h] using flogU) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + a_quotient] at eb b_digits b_one b_two + have b_len : b.regs len = n := (eb.regs_eq (by decide)).trans a_len + have b_magnitude : b.regs magnitude = U := (eb.regs_eq (by decide)).trans a_magnitude + have b_three : b.regs three = 3 := (eb.regs_eq (by decide)).trans a_three + have b_four : b.regs four = 4 := (eb.regs_eq (by decide)).trans a_four + have b_five : b.regs five = 5 := (eb.regs_eq (by decide)).trans a_five + have b_eight : b.regs eight = 8 := (eb.regs_eq (by decide)).trans a_eight + have b_padding : b.regs padding = 4 * U + 1 := (eb.regs_eq (by decide)).trans a_padding + have b_memory : b.mem = x.mem := b_mem.trans a_mem + let c := (b.set digits (Λ)) + have ec : ExecWithin w (ram[ digits ← digits + one ]) b c 1 := by + ram_eval [c, b_digits, b_one, ← eΛ, fΛ, digits, one, Fin.mk.injEq] + have c_len : c.regs len = n := by + simpa +decide [c, digits, len] using b_len + have c_magnitude : c.regs magnitude = U := by + simpa +decide [c, digits, magnitude] using b_magnitude + have c_three : c.regs three = 3 := by + simpa +decide [c, digits, three] using b_three + have c_four : c.regs four = 4 := by + simpa +decide [c, digits, four] using b_four + have c_five : c.regs five = 5 := by + simpa +decide [c, digits, five] using b_five + have c_eight : c.regs eight = 8 := by + simpa +decide [c, digits, eight] using b_eight + have c_padding : c.regs padding = 4 * U + 1 := by + simpa +decide [c, digits, padding] using b_padding + have c_digits : c.regs digits = Λ := by + simp +decide [c, digits] + have c_one : c.regs one = 1 := by + simpa +decide [c, digits, one] using b_one + have c_two : c.regs two = 2 := by + simpa +decide [c, digits, two] using b_two + have c_mem : c.mem = x.mem := b_memory + obtain ⟨d, ed, d_root, d_len, d_mem, d_one⟩ := NatSqrt.exec_rename + sqrtRegisters sqrt_injective c (by simpa [sqrtRegisters, NatSqrt.arg, c_len] using fsqrt) + simp only [sqrtRegisters, NatSqrt.arg, NatSqrt.root, NatSqrt.one, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, c_len] at ed d_root d_len d_one + have d_magnitude : d.regs magnitude = U := (ed.regs_eq (by decide)).trans c_magnitude + have d_three : d.regs three = 3 := (ed.regs_eq (by decide)).trans c_three + have d_four : d.regs four = 4 := (ed.regs_eq (by decide)).trans c_four + have d_five : d.regs five = 5 := (ed.regs_eq (by decide)).trans c_five + have d_eight : d.regs eight = 8 := (ed.regs_eq (by decide)).trans c_eight + have d_padding : d.regs padding = 4 * U + 1 := (ed.regs_eq (by decide)).trans c_padding + have d_digits : d.regs digits = Λ := (ed.regs_eq (by decide)).trans c_digits + have d_two : d.regs two = 2 := (ed.regs_eq (by decide)).trans c_two + have d_memory : d.mem = x.mem := d_mem.trans c_mem + let e := (((((d.set temp (Nat.sqrt n + 1)).set want (5 * Λ)).set want (wp)).set wantSucc + (wp + 1)).set quotient (wp + 1)) + have ee : ExecWithin w (ram[ temp ← root + one; want ← five * digits; want ← want * temp; + wantSucc ← want + one; quotient ← wantSucc ]) d e 5 := by + ram_eval [e, d_root, d_one, d_five, d_digits, ← ew, fr, f5Λ, fwp, fwps, temp, root, one, + want, five, digits, wantSucc, quotient, Fin.mk.injEq, Data.set, Function.update_idem] + have e_magnitude : e.regs magnitude = U := by + simpa +decide [e, temp, want, wantSucc, quotient, magnitude] using d_magnitude + have e_three : e.regs three = 3 := by + simpa +decide [e, temp, want, wantSucc, quotient, three] using d_three + have e_four : e.regs four = 4 := by + simpa +decide [e, temp, want, wantSucc, quotient, four] using d_four + have e_five : e.regs five = 5 := by + simpa +decide [e, temp, want, wantSucc, quotient, five] using d_five + have e_eight : e.regs eight = 8 := by + simpa +decide [e, temp, want, wantSucc, quotient, eight] using d_eight + have e_padding : e.regs padding = 4 * U + 1 := by + simpa +decide [e, temp, want, wantSucc, quotient, padding] using d_padding + have e_digits : e.regs digits = Λ := by + simpa +decide [e, temp, want, wantSucc, quotient, digits] using d_digits + have e_two : e.regs two = 2 := by + simpa +decide [e, temp, want, wantSucc, quotient, two] using d_two + have e_root : e.regs root = Nat.sqrt n := by + simpa +decide [e, temp, want, wantSucc, quotient, root] using d_root + have e_one : e.regs one = 1 := by + simpa +decide [e, temp, want, wantSucc, quotient, one] using d_one + have e_len : e.regs len = n := by + simpa +decide [e, temp, want, wantSucc, quotient, len] using d_len + have e_temp : e.regs temp = Nat.sqrt n + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_want : e.regs want = wp := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_wantSucc : e.regs wantSucc = wp + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_quotient : e.regs quotient = wp + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_mem : e.mem = x.mem := d_memory + obtain ⟨f, ef, f_temp, f_mem, f_one, f_two⟩ := + NatLog.exec_rename (logRegisters temp) (by decide) e f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, e_quotient] using fwps) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, e_quotient, h] using flogwp) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, e_quotient] at ef f_temp f_one f_two + have f_magnitude : f.regs magnitude = U := (ef.regs_eq (by decide)).trans e_magnitude + have f_three : f.regs three = 3 := (ef.regs_eq (by decide)).trans e_three + have f_four : f.regs four = 4 := (ef.regs_eq (by decide)).trans e_four + have f_five : f.regs five = 5 := (ef.regs_eq (by decide)).trans e_five + have f_eight : f.regs eight = 8 := (ef.regs_eq (by decide)).trans e_eight + have f_padding : f.regs padding = 4 * U + 1 := (ef.regs_eq (by decide)).trans e_padding + have f_digits : f.regs digits = Λ := (ef.regs_eq (by decide)).trans e_digits + have f_root : f.regs root = Nat.sqrt n := (ef.regs_eq (by decide)).trans e_root + have f_len : f.regs len = n := (ef.regs_eq (by decide)).trans e_len + have f_want : f.regs want = wp := (ef.regs_eq (by decide)).trans e_want + have f_wantSucc : f.regs wantSucc = wp + 1 := (ef.regs_eq (by decide)).trans e_wantSucc + have f_memory : f.mem = x.mem := f_mem.trans e_mem + let g := (((f.set temp (2 * Nat.log 2 (wp + 1))).set temp + (2 * Nat.log 2 (wp + 1) + 4)).set primeBound m) + have eg : ExecWithin w (ram[ temp ← two * temp; temp ← temp + four; + primeBound ← wantSucc * temp ]) f g 3 := by + ram_eval [g, f_two, f_temp, f_four, f_wantSucc, ← em, f2log, ff, hml, temp, two, four, + primeBound, wantSucc, Fin.mk.injEq, Data.set, Function.update_idem] + have g_magnitude : g.regs magnitude = U := by + simpa +decide [g, temp, primeBound, magnitude] using f_magnitude + have g_three : g.regs three = 3 := by + simpa +decide [g, temp, primeBound, three] using f_three + have g_four : g.regs four = 4 := by + simpa +decide [g, temp, primeBound, four] using f_four + have g_five : g.regs five = 5 := by + simpa +decide [g, temp, primeBound, five] using f_five + have g_eight : g.regs eight = 8 := by + simpa +decide [g, temp, primeBound, eight] using f_eight + have g_padding : g.regs padding = 4 * U + 1 := by + simpa +decide [g, temp, primeBound, padding] using f_padding + have g_digits : g.regs digits = Λ := by + simpa +decide [g, temp, primeBound, digits] using f_digits + have g_root : g.regs root = Nat.sqrt n := by + simpa +decide [g, temp, primeBound, root] using f_root + have g_len : g.regs len = n := by + simpa +decide [g, temp, primeBound, len] using f_len + have g_want : g.regs want = wp := by + simpa +decide [g, temp, primeBound, want] using f_want + have g_wantSucc : g.regs wantSucc = wp + 1 := by + simpa +decide [g, temp, primeBound, wantSucc] using f_wantSucc + have g_temp : g.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simp +decide [g, temp, primeBound] + have g_one : g.regs one = 1 := by + simpa +decide [g, temp, primeBound, one] using f_one + have g_two : g.regs two = 2 := by + simpa +decide [g, temp, primeBound, two] using f_two + have g_primeBound : g.regs primeBound = m := by + simp +decide [g, temp, primeBound] + have g_mem : g.mem = x.mem := f_memory + let j := (g.set quotient (n)) + have ej : ExecWithin (B := .standard) w (ram[ quotient ← len ]) g j 1 := by + ram_eval [j, g_len] + have j_magnitude : j.regs magnitude = U := by + simpa +decide [j, quotient, magnitude] using g_magnitude + have j_three : j.regs three = 3 := by + simpa +decide [j, quotient, three] using g_three + have j_four : j.regs four = 4 := by + simpa +decide [j, quotient, four] using g_four + have j_five : j.regs five = 5 := by + simpa +decide [j, quotient, five] using g_five + have j_eight : j.regs eight = 8 := by + simpa +decide [j, quotient, eight] using g_eight + have j_padding : j.regs padding = 4 * U + 1 := by + simpa +decide [j, quotient, padding] using g_padding + have j_digits : j.regs digits = Λ := by + simpa +decide [j, quotient, digits] using g_digits + have j_root : j.regs root = Nat.sqrt n := by + simpa +decide [j, quotient, root] using g_root + have j_len : j.regs len = n := by + simpa +decide [j, quotient, len] using g_len + have j_want : j.regs want = wp := by + simpa +decide [j, quotient, want] using g_want + have j_wantSucc : j.regs wantSucc = wp + 1 := by + simpa +decide [j, quotient, wantSucc] using g_wantSucc + have j_temp : j.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [j, quotient, temp] using g_temp + have j_one : j.regs one = 1 := by + simpa +decide [j, quotient, one] using g_one + have j_two : j.regs two = 2 := by + simpa +decide [j, quotient, two] using g_two + have j_primeBound : j.regs primeBound = m := by + simpa +decide [j, quotient, primeBound] using g_primeBound + have j_quotient : j.regs quotient = n := by + simp +decide [j, quotient] + have j_mem : j.mem = x.mem := g_mem + obtain ⟨k, ek, k_logLen, k_mem, k_one, k_two⟩ := + NatLog.exec_rename (logRegisters logLen) (by decide) j f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, j_quotient] using fn) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, j_quotient, h] using flogn) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + j_quotient] at ek k_logLen k_one k_two + have k_magnitude : k.regs magnitude = U := (ek.regs_eq (by decide)).trans j_magnitude + have k_three : k.regs three = 3 := (ek.regs_eq (by decide)).trans j_three + have k_four : k.regs four = 4 := (ek.regs_eq (by decide)).trans j_four + have k_five : k.regs five = 5 := (ek.regs_eq (by decide)).trans j_five + have k_eight : k.regs eight = 8 := (ek.regs_eq (by decide)).trans j_eight + have k_padding : k.regs padding = 4 * U + 1 := (ek.regs_eq (by decide)).trans j_padding + have k_digits : k.regs digits = Λ := (ek.regs_eq (by decide)).trans j_digits + have k_root : k.regs root = Nat.sqrt n := (ek.regs_eq (by decide)).trans j_root + have k_len : k.regs len = n := (ek.regs_eq (by decide)).trans j_len + have k_want : k.regs want = wp := (ek.regs_eq (by decide)).trans j_want + have k_wantSucc : k.regs wantSucc = wp + 1 := (ek.regs_eq (by decide)).trans j_wantSucc + have k_temp : k.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := (ek.regs_eq (by decide)).trans j_temp + have k_primeBound : k.regs primeBound = m := (ek.regs_eq (by decide)).trans j_primeBound + have k_memory : k.mem = x.mem := k_mem.trans j_mem + let l := (k.set quotient (Nat.log 2 n + 2)) + have el : ExecWithin w (ram[ quotient ← logLen + two ]) k l 1 := by + ram_eval [l, k_logLen, k_two, flog2, quotient, logLen, two, Fin.mk.injEq] + have l_magnitude : l.regs magnitude = U := by + simpa +decide [l, quotient, magnitude] using k_magnitude + have l_three : l.regs three = 3 := by + simpa +decide [l, quotient, three] using k_three + have l_four : l.regs four = 4 := by + simpa +decide [l, quotient, four] using k_four + have l_five : l.regs five = 5 := by + simpa +decide [l, quotient, five] using k_five + have l_eight : l.regs eight = 8 := by + simpa +decide [l, quotient, eight] using k_eight + have l_padding : l.regs padding = 4 * U + 1 := by + simpa +decide [l, quotient, padding] using k_padding + have l_digits : l.regs digits = Λ := by + simpa +decide [l, quotient, digits] using k_digits + have l_root : l.regs root = Nat.sqrt n := by + simpa +decide [l, quotient, root] using k_root + have l_len : l.regs len = n := by + simpa +decide [l, quotient, len] using k_len + have l_want : l.regs want = wp := by + simpa +decide [l, quotient, want] using k_want + have l_wantSucc : l.regs wantSucc = wp + 1 := by + simpa +decide [l, quotient, wantSucc] using k_wantSucc + have l_temp : l.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [l, quotient, temp] using k_temp + have l_primeBound : l.regs primeBound = m := by + simpa +decide [l, quotient, primeBound] using k_primeBound + have l_logLen : l.regs logLen = Nat.log 2 n := by + simpa +decide [l, quotient, logLen] using k_logLen + have l_one : l.regs one = 1 := by + simpa +decide [l, quotient, one] using k_one + have l_two : l.regs two = 2 := by + simpa +decide [l, quotient, two] using k_two + have l_quotient : l.regs quotient = Nat.log 2 n + 2 := by + simp +decide [l, quotient] + have l_mem : l.mem = x.mem := k_memory + obtain ⟨o, eo, o_height, o_mem, o_one, o_two⟩ := + NatClog.exec_rename (logRegisters height) (by decide) l f2 + (by simpa only [logRegisters, NatClog.arg, Matrix.cons_val_zero, l_quotient] using flog3) + (by simpa only [logRegisters, NatClog.arg, Matrix.cons_val_zero, l_quotient, h, + Cslib.Algorithms.ThreeSum.ChanHe.height] using fh) + simp only [logRegisters, NatClog.arg, NatClog.result, NatClog.one, NatClog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + l_quotient] at eo o_height o_one o_two + change o.regs height = h at o_height + change ExecWithin w _ l o (5 * h + 5) at eo + have o_magnitude : o.regs magnitude = U := (eo.regs_eq (by decide)).trans l_magnitude + have o_three : o.regs three = 3 := (eo.regs_eq (by decide)).trans l_three + have o_four : o.regs four = 4 := (eo.regs_eq (by decide)).trans l_four + have o_five : o.regs five = 5 := (eo.regs_eq (by decide)).trans l_five + have o_eight : o.regs eight = 8 := (eo.regs_eq (by decide)).trans l_eight + have o_padding : o.regs padding = 4 * U + 1 := (eo.regs_eq (by decide)).trans l_padding + have o_digits : o.regs digits = Λ := (eo.regs_eq (by decide)).trans l_digits + have o_root : o.regs root = Nat.sqrt n := (eo.regs_eq (by decide)).trans l_root + have o_len : o.regs len = n := (eo.regs_eq (by decide)).trans l_len + have o_want : o.regs want = wp := (eo.regs_eq (by decide)).trans l_want + have o_wantSucc : o.regs wantSucc = wp + 1 := (eo.regs_eq (by decide)).trans l_wantSucc + have o_temp : o.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := (eo.regs_eq (by decide)).trans l_temp + have o_primeBound : o.regs primeBound = m := (eo.regs_eq (by decide)).trans l_primeBound + have o_logLen : o.regs logLen = Nat.log 2 n := (eo.regs_eq (by decide)).trans l_logLen + have o_memory : o.mem = x.mem := o_mem.trans l_mem + let p := ((((o.set fuel (3 * h)).set fuel (Cslib.Algorithms.ThreeSum.ChanHe.fuel n)).set packedLen + (m * m)).set packedLen (8 * m ^ 2)) + have ep : ExecWithin w (ram[ fuel ← three * height; fuel ← fuel - two; + packedLen ← primeBound * primeBound; packedLen ← eight * packedLen ]) o p 4 := by + ram_eval [p, o_three, o_height, o_two, o_primeBound, o_eight, ← eh, f3h, ffuel, fm2, fNm, + fuel, three, height, two, packedLen, primeBound, eight, Fin.mk.injEq, pow_two, Data.set, + Function.update_idem] + have p_magnitude : p.regs magnitude = U := by + simpa +decide [p, fuel, packedLen, magnitude] using o_magnitude + have p_three : p.regs three = 3 := by + simpa +decide [p, fuel, packedLen, three] using o_three + have p_four : p.regs four = 4 := by + simpa +decide [p, fuel, packedLen, four] using o_four + have p_five : p.regs five = 5 := by + simpa +decide [p, fuel, packedLen, five] using o_five + have p_eight : p.regs eight = 8 := by + simpa +decide [p, fuel, packedLen, eight] using o_eight + have p_padding : p.regs padding = 4 * U + 1 := by + simpa +decide [p, fuel, packedLen, padding] using o_padding + have p_digits : p.regs digits = Λ := by + simpa +decide [p, fuel, packedLen, digits] using o_digits + have p_root : p.regs root = Nat.sqrt n := by + simpa +decide [p, fuel, packedLen, root] using o_root + have p_len : p.regs len = n := by + simpa +decide [p, fuel, packedLen, len] using o_len + have p_want : p.regs want = wp := by + simpa +decide [p, fuel, packedLen, want] using o_want + have p_wantSucc : p.regs wantSucc = wp + 1 := by + simpa +decide [p, fuel, packedLen, wantSucc] using o_wantSucc + have p_temp : p.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [p, fuel, packedLen, temp] using o_temp + have p_primeBound : p.regs primeBound = m := by + simpa +decide [p, fuel, packedLen, primeBound] using o_primeBound + have p_logLen : p.regs logLen = Nat.log 2 n := by + simpa +decide [p, fuel, packedLen, logLen] using o_logLen + have p_height : p.regs height = h := by + simpa +decide [p, fuel, packedLen, height] using o_height + have p_one : p.regs one = 1 := by + simpa +decide [p, fuel, packedLen, one] using o_one + have p_two : p.regs two = 2 := by + simpa +decide [p, fuel, packedLen, two] using o_two + have p_fuel : p.regs fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel n := by + simp +decide [p, fuel, packedLen] + have p_packedLen : p.regs packedLen = 8 * m ^ 2 := by + simp +decide [p, fuel, packedLen] + have p_mem : p.mem = x.mem := o_memory + refine ⟨p, ?_, ?_, p_mem⟩ + · exact (ea.seq + (eb.seq + (ec.seq + (ed.seq + (ee.seq + (ef.seq + (eg.seq + (ej.seq + (ek.seq + (el.seq + (eo.seq + (ep)))))))))))).mono (by + rw [time_zero] + dsimp only [wp, h] + exact le_of_eq (by ring)) + · exact ⟨p_len, p_magnitude, p_digits, p_height, p_fuel, p_want, p_primeBound, p_packedLen, + p_padding⟩ + +/-- Read the native unsigned header and compute all parameters, preserving every memory cell. +This local theorem is discharged from a logarithmic width in `runs_input`. -/ +theorem exec {w : ℕ} (κ : ℕ) (x : Data 24) + (hfit : Fits w (Math.budget (x.mem 0) ((x.mem 0 + 2) ^ κ))) : + ∃ y, ExecWithin w (program κ) x y (time κ (x.mem 0) ((x.mem 0 + 2) ^ κ)) ∧ + Output (x.mem 0) ((x.mem 0 + 2) ^ κ) y ∧ y.mem = x.mem := by + let n := x.mem 0 + let U := (n + 2) ^ κ + have fit (v : ℕ) (hv : v ≤ 8 * U + 4 * n + 64) : Fits w v := + hfit.of_le (by change v ≤ Math.budget n U; dsimp [Math.budget]; omega) + have f1 : Fits w 1 := fit _ (by omega) + have f2 : Fits w 2 := fit _ (by omega) + have f3 : Fits w 3 := fit _ (by omega) + have f4 : Fits w 4 := fit _ (by omega) + have f5 : Fits w 5 := fit _ (by omega) + have f8 : Fits w 8 := fit _ (by omega) + have fb : Fits w (n + 2) := fit _ (by omega) + have fU : Fits w U := fit _ (by omega) + let a := (((((((((x.set quotient 0).set len n).set one 1).set two 2).set three 3).set + four 4).set five 5).set eight 8).set base (n + 2)) + have ha : ExecWithin w setup x a 9 := by + ram_eval [setup, a, n, f1, f2, f3, f4, f5, f8, fb, + quotient, len, one, two, three, four, five, eight, base, Fin.mk.injEq] + have hab : a.regs base = n + 2 := by simp [a] + have hp := exec_power κ a (by rw [hab]; omega) (by simpa only [hab] using fU) + let b := a.set magnitude U + have hpb : ExecWithin w (power κ) a b (κ + 1) := by simpa only [hab] using hp + obtain ⟨y, hy, hout, hm⟩ := exec_calculate b + (by simp +decide [b, a, n, magnitude, len, base, one, two, three, four, five, eight, quotient]) + (by simp [b, U, n]) + (by simp +decide [b, a, magnitude, one, base, two, three, four, five, eight]) + (by simp +decide [b, a, magnitude, two, base, three, four, five, eight]) + (by simp +decide [b, a, magnitude, three, base, four, five, eight]) + (by simp +decide [b, a, magnitude, four, base, five, eight]) + (by simp +decide [b, a, magnitude, five, base, eight]) + (by simp +decide [b, a, magnitude, eight, base]) hfit + refine ⟨y, (ha.seq (hpb.seq hy)).mono ?_, hout, hm⟩ + dsimp only [time] + omega + +namespace Math + +open Cslib.Algorithms.ThreeSum + +/-- A polynomial scale for bounding every parameter intermediate. -/ +def scale (n U : ℕ) : ℕ := 16 * (n + 2) * (U + 1) + +private theorem scale_bounds (n U : ℕ) : + 16 ≤ scale n U ∧ n + 2 ≤ scale n U ∧ 4 * U + 2 ≤ scale n U := by + have h := Nat.mul_le_mul_left 16 (Nat.mul_le_mul + (show 1 ≤ n + 2 by omega) (show 1 ≤ U + 1 by omega)) + have hn := Nat.le_mul_of_pos_right (n + 2) (show 0 < 16 * (U + 1) by positivity) + have hU := Nat.le_mul_of_pos_right (U + 1) (show 0 < n + 2 by omega) + dsimp [scale] + have hnu := Nat.zero_le (n * U) + refine ⟨?_, ?_, ?_⟩ <;> nlinarith + +private theorem scale_pow_mono {b : ℕ} (hb : 1 ≤ b) {i j : ℕ} (hij : i ≤ j) : + b ^ i ≤ b ^ j := Nat.pow_le_pow_right hb hij + +/-- A coarse polynomial bound; it also covers final unsuccessful root/log iterations. -/ +theorem budget_le_scale (n U : ℕ) : budget n U ≤ scale n U ^ 20 := by + let b := scale n U + obtain ⟨hb, hn, hU⟩ := scale_bounds n U + change 16 ≤ b at hb + change n + 2 ≤ b at hn + change 4 * U + 2 ≤ b at hU + have hb1 : 1 ≤ b := by omega + have hΛ : labelBits (2 * U) ≤ b := by + have hl := Nat.log_le_self 2 (4 * U) + dsimp [labelBits] + have he : 2 * (2 * U) = 4 * U := by omega + rw [he] + omega + have hr : Nat.sqrt n + 1 ≤ b := by have := Nat.sqrt_le_self n; omega + have hw : ChanHe.wPar n (2 * U) ≤ b ^ 3 := by + calc + _ ≤ b * b * b := by + dsimp [ChanHe.wPar] + exact Nat.mul_le_mul (Nat.mul_le_mul (by omega) hΛ) hr + _ = b ^ 3 := by ring + have hws : ChanHe.wPar n (2 * U) + 1 ≤ b ^ 4 := by + have hp : 1 ≤ b ^ 3 := Nat.one_le_pow _ _ hb1 + calc + _ ≤ 2 * b ^ 3 := by omega + _ ≤ b * b ^ 3 := Nat.mul_le_mul_right _ (by omega) + _ = b ^ 4 := by ring + have hfac : 2 * Nat.log 2 (ChanHe.wPar n (2 * U) + 1) + 4 ≤ b ^ 5 := by + have hl := Nat.log_le_self 2 (ChanHe.wPar n (2 * U) + 1) + have hp : 1 ≤ b ^ 4 := Nat.one_le_pow _ _ hb1 + calc + _ ≤ 6 * b ^ 4 := by omega + _ ≤ b * b ^ 4 := Nat.mul_le_mul_right _ (by omega) + _ = b ^ 5 := by ring + have hm : ChanHe.mPar n (2 * U) ≤ b ^ 9 := by + calc + _ ≤ b ^ 4 * b ^ 5 := Nat.mul_le_mul hws hfac + _ = b ^ 9 := by ring + have hms : ChanHe.mPar n (2 * U) + 1 ≤ 2 * b ^ 9 := by + have hp : 1 ≤ b ^ 9 := Nat.one_le_pow _ _ hb1 + omega + have hsq : (ChanHe.mPar n (2 * U) + 1) ^ 2 ≤ (2 * b ^ 9) ^ 2 := + Nat.pow_le_pow_left hms 2 + have hsmall : 8 * U + 4 * n + 64 ≤ 16 * b := by omega + have hp18 : b ≤ b ^ 18 := by + simpa only [pow_one] using scale_pow_mono hb1 (show 1 ≤ 18 by decide) + have hbudget : budget n U ≤ 48 * b ^ 18 := by + dsimp only [budget] + nlinarith + calc + _ ≤ 48 * b ^ 18 := hbudget + _ ≤ b ^ 2 * b ^ 18 := Nat.mul_le_mul_right _ (by nlinarith) + _ = b ^ 20 := by ring + +/-- A single polynomial bound, with exponent affine in the fixed magnitude exponent. -/ +theorem budget_polynomial (κ n : ℕ) : budget n ((n + 2) ^ κ) ≤ (n + 2) ^ (20 * κ + 120) := by + let a := n + 2 + have ha : 2 ≤ a := by dsimp [a]; omega + have hpow : 1 ≤ a ^ κ := Nat.one_le_pow _ _ (by omega) + have hs : scale n (a ^ κ) ≤ a ^ (κ + 6) := by + calc + _ = 16 * a * (a ^ κ + 1) := rfl + _ ≤ 32 * a * a ^ κ := by nlinarith + _ ≤ a ^ 5 * a * a ^ κ := by + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by + have := Nat.pow_le_pow_left ha 5 + norm_num at this ⊢ + omega)) + _ = a ^ (κ + 6) := by ring + calc + _ ≤ scale n (a ^ κ) ^ 20 := budget_le_scale n (a ^ κ) + _ ≤ (a ^ (κ + 6)) ^ 20 := Nat.pow_le_pow_left hs 20 + _ = a ^ (20 * κ + 120) := by ring + +end Math +/-- Positive logarithmic word-width threshold for the complete parameter computation. -/ +def width (κ n : ℕ) : ℕ := (20 * κ + 121) * Nat.clog 2 (n + 2) + +/-- The width requirement is satisfiable even at empty input and exponent zero. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := Nat.clog_pos (b := 2) (by decide) (show 1 < n + 2 by omega) + dsimp [width] + positivity + +private theorem budget_le_two_pow (κ n : ℕ) : + Math.budget n ((n + 2) ^ κ) ≤ 2 ^ ((20 * κ + 120) * Nat.clog 2 (n + 2)) := by + calc + _ ≤ (n + 2) ^ (20 * κ + 120) := Math.budget_polynomial κ n + _ ≤ (2 ^ Nat.clog 2 (n + 2)) ^ (20 * κ + 120) := + Nat.pow_le_pow_left (Nat.le_pow_clog (by decide) _) _ + _ = _ := by rw [← pow_mul]; congr 1; ring + +/-- Every local fit premise follows from the positive logarithmic width. -/ +theorem budget_fits {κ n w : ℕ} (hw : width κ n ≤ w) : + Fits w (Math.budget n ((n + 2) ^ κ)) := by + have hl := Nat.clog_pos (b := 2) (by decide) (show 1 < n + 2 by omega) + apply Fits.mono (w := width κ n) _ hw + apply (budget_le_two_pow κ n).trans_lt + apply Nat.pow_lt_pow_right (by decide) + dsimp [width] + nlinarith + +/-- Signed-encoded inputs and the unsigned length header fit at the same width. -/ +theorem inputFits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := by + have hf := budget_fits hw + refine ⟨?_, ?_⟩ + · simp only [List.length_map] + apply hf.of_le + dsimp [Math.budget] + omega + · intro v hv + obtain ⟨z, hz, rfl⟩ := List.mem_map.mp hv + have habs : z.natAbs ≤ (input.length + 2) ^ κ := by + have hc : (z.natAbs : ℤ) ≤ ((input.length + 2) ^ κ : ℕ) := by + simpa only [Int.natCast_natAbs] using hvalues z hz + exact_mod_cast hc + apply hf.of_le + dsimp [Math.budget, Signed.encode] + split_ifs <;> omega + +/-- Explicit `Oκ(√n + log(n+2))` instruction bound, valid also at `n = 0,1,2`. -/ +theorem time_bound (κ n : ℕ) : + time κ n ((n + 2) ^ κ) ≤ + 5 * Nat.sqrt n + (400 * κ + 3000) * Nat.clog 2 (n + 2) := by + let U := (n + 2) ^ κ + let L := Nat.clog 2 (n + 2) + let E := 20 * κ + 120 + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hB : Nat.log 2 (Math.budget n U) ≤ E * L := by + calc + _ ≤ Nat.log 2 (2 ^ (E * L)) := Nat.log_mono_right (budget_le_two_pow κ n) + _ = _ := Nat.log_pow (by decide) _ + have hlogU : Nat.log 2 (4 * U) ≤ E * L := + (Nat.log_mono_right (show 4 * U ≤ Math.budget n U by dsimp [Math.budget]; omega)).trans hB + have hw := Math.want_le n U + have hlogw : Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) ≤ E * L := by + apply (Nat.log_mono_right ?_).trans hB + dsimp [Math.budget] + nlinarith + have hlogn : Nat.log 2 n ≤ E * L := + (Nat.log_mono_right (show n ≤ Math.budget n U by dsimp [Math.budget]; omega)).trans hB + have hh := Math.height_le n + change time κ n U ≤ _ + dsimp only [time] + dsimp only [E, L] at * + nlinarith + +/-- Native run from actual signed input initialization. No parameters or arithmetic routines +are supplied as advice. The returned state identifies all outputs and keeps *all* input memory. +The flat program has 24 registers and depends only on `κ`; halting adds one instruction. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ y t, t ≤ 5 * Nat.sqrt input.length + + (400 * κ + 3000) * Nat.clog 2 (input.length + 2) ∧ + Runs w ((program κ).program packedLen) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ t ∧ + Output input.length ((input.length + 2) ^ κ) y ∧ + y.mem = (Data.init (input.map Signed.encode) : Data 24).mem ∧ y.Bounded w := by + have hi := inputFits input hw hvalues + obtain ⟨y, hy, ho, hm⟩ := exec κ (Data.init (input.map Signed.encode)) + (by simpa only [Data.init_mem_zero, List.length_map] using budget_fits hw) + simp only [Data.init_mem_zero, List.length_map] at hy ho + obtain ⟨t, ht, he⟩ := hy + have hr := he.runs (pc := 0) (p := (program κ).program packedLen) + (Program.Contains.self _).append_left + simp only [Nat.zero_add] at hr + refine ⟨y, t, ht.trans (time_bound κ input.length), hr, ho, hm, ?_⟩ + exact hr.bounded_init hi + +/-- The number of native instructions in the finite structured code is affine in `κ`. -/ +theorem size_program (κ : ℕ) : (program κ).size = κ + 71 := by + have hp : (power κ).size = κ + 1 := by + induction κ with + | zero => rfl + | succ κ ih => simp [power, ih] + simp [program, setup, calculate, hp, NatLog.program, NatLog.body, + NatClog.program, NatClog.body, NatSqrt.program, NatSqrt.body] + omega + +/-- The code is the same at every word width. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by + have hs : ¬setup.ReadsWidth := by decide + have hc : ¬calculate.ReadsWidth := by decide + have hp : ¬(power κ).ReadsWidth := by + induction κ with + | zero => decide + | succ κ ih => + change ¬((power κ).ReadsWidth ∨ + (Code.op (Op.bin BinOp.mul magnitude magnitude base) : Code .standard 24).ReadsWidth) + exact not_or.mpr ⟨ih, by decide⟩ + simpa only [program, Code.ReadsWidth, not_or] using And.intro hs (And.intro hp hc) + +/-- Canonical-width executions use only polynomially bounded integers, including memory +addresses, loop counters, and final unsuccessful tests. -/ +theorem intermediates_polynomial {κ : ℕ} (input : List ℤ) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 24} {t : ℕ} + (hr : Runs (width κ input.length) ((program κ).program packedLen) + (State.init (input.map Signed.encode)) s t) : + (∀ i, s.data.regs i < (input.length + 2) ^ (40 * κ + 242)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (40 * κ + 242) := by + let n := input.length + let L := Nat.clog 2 (n + 2) + have hL : 0 < L := Nat.clog_pos (by decide) (by omega) + have hpred : 2 ^ (L - 1) < n + 2 := + Nat.pow_pred_clog_lt_self (b := 2) (x := n + 2) (by decide) (by omega) + have he : 2 ^ L = 2 * 2 ^ (L - 1) := by + conv_lhs => rw [show L = (L - 1) + 1 by omega] + rw [pow_succ, Nat.mul_comm] + have hceil : 2 ^ L ≤ (n + 2) ^ 2 := by rw [he]; nlinarith + have hpoly : 2 ^ width κ n ≤ (n + 2) ^ (40 * κ + 242) := by + calc + _ = (2 ^ L) ^ (20 * κ + 121) := by + dsimp only [width, L] + rw [← pow_mul] + congr 1 + ring + _ ≤ ((n + 2) ^ 2) ^ (20 * κ + 121) := Nat.pow_le_pow_left hceil _ + _ = _ := by rw [← pow_mul]; congr 1; omega + have hb := hr.bounded_init (inputFits input le_rfl hvalues) + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters diff --git a/ComplexityTests/WordRAMChanHeParameters.lean b/ComplexityTests/WordRAMChanHeParameters.lean new file mode 100644 index 0000000000..f76dec9c15 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeParameters.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +/-! +# Executed boundary cases for native Chan–He parameters + +The observations include all nine outputs, the instruction count, and input memory cells. +The generic run clients below discharge the actual input magnitude and width hypotheses. +-/ + +-- This module contains only private execution checks. +set_option linter.privateModule false + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +private def observe (κ : ℕ) (input : List ℤ) : Option (List ℕ × ℕ × List ℕ) := + ((program κ).eval 64 32 (Data.init (input.map Signed.encode))).map fun (x, t) => + ([x.regs len, x.regs magnitude, x.regs digits, x.regs height, x.regs fuel, + x.regs want, x.regs primeBound, x.regs packedLen, x.regs padding], t, + [x.mem 0, x.mem 1, x.mem 2]) + +-- Empty input, including the fixed exponent zero. +example : observe 0 [] = some ([0, 1, 3, 1, 1, 15, 192, 294912, 5], 83, [0, 0, 0]) := by + decide + +-- One position; the signed encoding of -1 remains in memory. +example : observe 0 [-1] = some ([1, 1, 3, 1, 1, 30, 372, 1107072, 5], 88, [1, 3, 0]) := by + decide + +-- Two positions at exponent zero: the ceiling logarithm and fuel increase. +example : observe 0 [-1, 1] = + some ([2, 1, 3, 2, 4, 30, 372, 1107072, 5], 97, [2, 3, 2]) := by + decide + +-- Nonconstant polynomial magnitude, with both signed boundary values. +example : observe 1 [-4, 4] = + some ([2, 4, 5, 2, 4, 50, 714, 4078368, 17], 110, [2, 9, 8]) := by + decide + +-- A native contract client, including all width/magnitude hypotheses and all memory framing. +example : ∃ y t, t ≤ 5 * Nat.sqrt 2 + 3400 * Nat.clog 2 4 ∧ + Runs 512 ((program 1).program packedLen) (State.init ([(-4 : ℤ), 4].map Signed.encode)) + ⟨(program 1).size, y⟩ t ∧ Output 2 4 y ∧ + y.mem = (Data.init ([(-4 : ℤ), 4].map Signed.encode) : Data 24).mem ∧ y.Bounded 512 := by + apply runs_input (κ := 1) [(-4 : ℤ), 4] (by decide) + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false] at hz + rcases hz with rfl | rfl <;> decide + +-- The empty-input width remains positive and the final input-init theorem is satisfiable. +example : 0 < width 0 0 ∧ InputFits 512 (([] : List ℤ).map Signed.encode) := by + constructor + · decide + · apply inputFits (κ := 0) [] (by decide) + simp From b4936ce28d8b498b364a7947d34f728c55f8b226 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 10:47:14 -0400 Subject: [PATCH 16/32] doc(matrix): explain finite thin-product guarantees in the Lean API Restack #1463 above repaired #1464 without changing the reviewed mathematics. Rewrite 149 documentation blocks in 14 modules as standalone descriptions; replace operation-cost and word-size narratives with the exact entry, leaf, passed-set and specified-value quantities proved. Preserve pinned Anthropic credit, source locators, statements, definitions, proof bodies and imports. An independent comparison with original ee288fb16 confirms that all code bytes outside comments are identical, including code whitespace. Validation of this final tree: all six gates pass together (strict build, aggregate check, strict tests, environment lint, style lint, init imports). Citation-aware full hygiene --check-simp, changed-file lints (14 library modules, one test) and git diff --check pass. Lean MCP FiniteMagnitude diagnostics finish without errors or warnings. Serial Smoke/Axioms warmups avoid the shared-host memory failure previously seen when these import-heavy clients run together. Tests and substantive math/API review evidence are retained from the original reviewed tree b021e2d9340b433a9fdb129882351e158e8c289f in ee288fb16. This change is documentation-only; /root/draft_thin_docs authored the scratch map and the continuation worker inspected/applied it. That drafting is not independent review. Earlier math reviews are inside-fleet agent reviews, not outside or human approval. Outside coordinator review will be requested on the final PR. No selected abstract, catalogue or rendering is affected. PR publication remains held while native dependent restacks are repaired, per root sequencing. #1394 remains open: native shared encodings/pruning/Exact Triangle, Chan-He #1446, complete Convolution/ordinary input/time/width composition and the improved Section4 route to exponent1.9992 are not supplied by these finite counts. AI assistance: OpenAI Codex workers codex-thin-product-math-20261028, codex-three-sum-continuation-t3-3dda8289 and /root/draft_thin_docs. --- .../MatrixMultiplication/Schonhage/Batch.lean | 29 ++--- .../Schonhage/Layout.lean | 67 +++++------ .../Schonhage/Levels.lean | 106 ++++++++---------- .../Schonhage/Magnitude.lean | 65 +++++------ .../Schonhage/Orders.lean | 69 +++++------- .../Schonhage/Parameters.lean | 29 +++-- .../Schonhage/Sparse.lean | 50 ++++----- .../Schonhage/ThinProduct.lean | 65 ++++++----- .../Schonhage/Tiling.lean | 69 +++++------- .../Foundations/Algebra/FiniteMagnitude.lean | 1 - .../Foundations/Algebra/GeometricTail.lean | 3 +- .../Combinatorics/BinomialUpperBound.lean | 1 - .../Combinatorics/FunctionPlaces.lean | 10 +- .../Foundations/Data/Finset/OrderGlue.lean | 8 +- 14 files changed, 270 insertions(+), 302 deletions(-) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean index 9421e25bb7..262f3f864e 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Batch.lean @@ -17,8 +17,9 @@ public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels * `bilinearProduct_arrays_eq_mul`: exact integer matrix multiplication at selected strings. * `batch_product_spec`: arbitrary output strings with `m` inner levels. -The tensor error has no effect: a nonzero summand must use the same inner levels on -both inputs as the output, and the remaining outer coefficients are Kronecker deltas. +The batch arrays vanish on strings with the wrong number of inner positions. +The coefficient rules select equal inner indices and the specified outer row and column, +yielding the usual product of integer matrices. ## References @@ -26,7 +27,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -35,17 +35,17 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-! ### The sizes of the matrices, and Figure 4 -/ +/-! ### Cardinalities of index types -/ -/-- Section 2.3.3: there are `N₀` strings of `L - m` outer left (or right) variables. -/ +/-- The outer-index type has `N0 L m` elements. -/ theorem card_outerStr (L m : ℕ) : Fintype.card (OuterStr L m) = N0 L m := by simp [N0] -/-- Section 2.3.3: there are `D` strings of `m` inner left (or right) variables. -/ +/-- The inner-index type has `D m` elements. -/ theorem card_innerStr (m : ℕ) : Fintype.card (InnerStr m) = D m := by simp [D] -/-- Section 2.3.3: there are `K = binom(L, m)` subsets `Q` of `{1, …, L}` of size `m`. -/ +/-- There are `K L m` subsets of `Fin L` with cardinality `m`. -/ theorem card_subsets_eq_K (L m : ℕ) : (univ.filter fun Q : Finset (Fin L) => Q.card = m).card = K L m := by rw [univ_filter_card_eq, card_powersetCard, card_univ, Fintype.card_fin, K] @@ -70,7 +70,7 @@ theorem arrayR_rightStrOf {L m : ℕ} (Y : Finset (Fin L) → RightMat L m) (Q : have h : (innerSetR (rightStrOf Q hQ π c)).card = m := by rw [innerSetR_rightStrOf, hQ] rw [arrayR, dite_eq_left h, innerPartR_rightStrOf, outerPartR_rightStrOf, innerSetR_rightStrOf] -/-! ### The proof of Lemma 9 -/ +/-! ### Coefficients and batch correctness -/ /-- The inner-output coefficient selects equal inner index pairs (Section 2.3.3). Checked directly on `coefficient`; no polynomial coefficient bridge is assumed. -/ @@ -141,8 +141,9 @@ private theorem prod_gamma_eq_one {L m : ℕ} (Q : Finset (Fin L)) (hQ : Q.card fun _ => (coefficient_x_y_z _ _ _ _).trans (ite_eq_left ⟨rfl, rfl⟩)⟩ exact prod_eq_one fun ℓ _ => hone ℓ -/-- Lemma 9 for the output string with inner set `Q`, row `r` and column `c`: by Lemma 8 the entry -is a sum over all pairs `(u, v)`, and the summands that remain are those of the matrix product. -/ +/-- At the output string for a size-`m` set `Q` and indices `r`, `c`, the bilinear product of +the batch arrays equals `(X Q * Y Q) r c`. The matrices have arbitrary integer entries +(cf. [AlmanWilliams2026, Lemma 9]). -/ theorem bilinearProduct_arrays_eq_mul {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Y : Finset (Fin L) → RightMat L m) (Q : Finset (Fin L)) (hQ : Q.card = m) (r c : OuterStr L m) : @@ -169,8 +170,9 @@ theorem bilinearProduct_arrays_eq_mul {L m : ℕ} (X : Finset (Fin L) → LeftMa intro π rw [prod_gamma_eq_one, one_mul, arrayL_leftStrOf, arrayR_rightStrOf] -/-- **Lemma 9**. For the input arrays a, b above and every output string w whose inner -set Q has exactly m elements, bilinearProduct(a, b)[w] = (X_Q Y_Q)[w]. (Equation (4).) -/ +/-- An output string with exactly `m` inner levels selects the product of the two matrices +indexed by its inner set, at its extracted row and column. This is an equality of integer +values for the arrays `arrayL m X` and `arrayR m Y` (cf. [AlmanWilliams2026, Lemma 9]). -/ theorem batch_product_spec {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Y : Finset (Fin L) → RightMat L m) (w : OutStr L) (h : (innerSetO w).card = m) : @@ -180,7 +182,8 @@ theorem batch_product_spec {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Y : conv_lhs => rw [outStr_eq_outStrOf h rfl h] exact bilinearProduct_arrays_eq_mul X Y (innerSetO w) h (rowO w h) (colO w h) -/-- Section 2.3.3: one run of Full […] computes all K of the products X_Q Y_Q. -/ +/-- `Full.result` on the batch arrays agrees with the matrix product for every size-`m` set +`Q` and every row and column index. -/ theorem Full.arrays_eq_mul {L m : ℕ} (X : Finset (Fin L) → LeftMat L m) (Y : Finset (Fin L) → RightMat L m) (Q : Finset (Fin L)) (hQ : Q.card = m) (r c : OuterStr L m) : diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean index 354678f42e..684266bc9a 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Layout.lean @@ -15,7 +15,7 @@ public import Mathlib.Data.Nat.Sqrt ## Main definitions -* `Layout`: ordinary finite indexing choices, with no solver or bound certificate. +* `Layout`: index equivalences and an injective assignment of level sets to block pairs. * `wantedStrings`, `tiles`: requested outputs grouped by tile. ## References @@ -24,7 +24,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -33,7 +32,7 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.3.4: K₀ := ⌊√K⌋. -/ +/-- The natural square root of the number `K L m` of size-`m` level sets. -/ def K0 (L m : ℕ) : ℕ := Nat.sqrt (K L m) /-- `N₀ = 3^{L-m}` is positive. -/ @@ -42,18 +41,13 @@ theorem N0_pos (L m : ℕ) : 0 < N0 L m := Nat.pow_pos (by decide) /-- `K₀ = ⌊√binom(L, m)⌋` is positive when `m ≤ L`. -/ theorem K0_pos {L m : ℕ} (h : m ≤ L) : 0 < K0 L m := Nat.sqrt_pos.mpr (Nat.choose_pos h) -/-- The choices that the paper makes once and for all without naming them. +/-- Indexing data for the block decomposition, under `m ≤ L`. -* `hmL`: `m ≤ L`. The paper never states this; it follows from `L = 19m` (Section 2) and from - `L ≥ 10m` (Section 4). It makes `K₀ ≥ 1`. -* Section 2.3.3: we index its N₀ rows by the N₀ strings of L - m outer left variables, and its D - columns by the D strings of m inner left variables, and similarly for a `D × N₀` matrix. - `rowIdx`, `colIdx` and `innerIdx` say which string indexes which row or column, counted from 0. -* Section 2.3.4: We fix K₀² ≤ K distinct subsets of {1, …, L} of size m, one for each block product - of the grid, the same in every tile. `table (g, h)` is the subset of the block product of the - `g`-th row block of a row band and the `h`-th column block of a column band. - -Every statement about the tiling holds for every such choice. -/ +The three equivalences identify block rows, block columns and the thin dimension with +their string indices. The injective `table` assigns a size-`m` subset of `Fin L` to each +pair of block indices in a tile. All tiles use this same table. The structure contains no +matrix values or solver certificate; the correctness results hold for any such indexing +choices. -/ structure Layout (L m : ℕ) where hmL : m ≤ L /-- Index a block's rows by outer strings. -/ @@ -67,54 +61,56 @@ structure Layout (L m : ℕ) where table_card : ∀ gh, (table gh).card = m table_injective : Function.Injective table -/-- The number `K₀ N₀` of rows of a row band (Section 2.3.4: a band is `K₀` consecutive blocks of -`N₀` consecutive rows), which is also the number of columns of a column band. -/ +/-- The common band dimension `K0 L m * N0 L m`: each band contains `K0 L m` consecutive +blocks of size `N0 L m`. -/ def bandSize (L m : ℕ) : ℕ := K0 L m * N0 L m -/-- The number of row bands (and of column bands) after padding: `N / (K₀ N₀)` rounded up. -/ +/-- The quotient `(N + bandSize L m - 1) / bandSize L m`. When `m ≤ L`, the band size is +positive and this is the number of bands needed to cover `N` positions, including `N = 0`. -/ def numBands (L m N : ℕ) : ℕ := (N + bandSize L m - 1) / bandSize L m -/-- Section 2.3.4: we first pad N to a multiple of K₀ N₀. The padded size. -/ +/-- The dimension covered by `numBands L m N` complete bands. -/ def padN (L m N : ℕ) : ℕ := numBands L m N * bandSize L m -/-- Section 2.3.4: `X` padded with zero rows. Rows are numbered from 0. -/ +/-- Extend `X` to natural-number row indices by returning zero at indices `I ≥ N`. -/ def padRows {N D' : ℕ} (X : Matrix (Fin N) (Fin D') ℤ) (I : ℕ) (k : Fin D') : ℤ := if h : I < N then X ⟨I, h⟩ k else 0 -/-- Section 2.3.4: `Y` padded with zero columns. Columns are numbered from 0. -/ +/-- Extend `Y` to natural-number column indices by returning zero at indices `J ≥ N`. -/ def padCols {N D' : ℕ} (Y : Matrix (Fin D') (Fin N) ℤ) (k : Fin D') (J : ℕ) : ℤ := if h : J < N then Y k ⟨J, h⟩ else 0 -/-- Section 2.3.4: Cut X into row blocks of N₀ consecutive rows. The `B`-th row block (counted -from 0) consists of the rows `B N₀, …, B N₀ + N₀ - 1`. -/ +/-- Extract the `N0 L m` rows starting at `B * N0 L m` from `padRows X`, using `lay.rowIdx` +and `lay.innerIdx` to index the resulting integer matrix by strings. -/ def rowBlock {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (B : ℕ) : LeftMat L m := fun r π => padRows X (B * N0 L m + lay.rowIdx.symm r) (lay.innerIdx.symm π) -/-- Section 2.3.4: and Y into column blocks of N₀ consecutive columns. -/ +/-- Extract the `N0 L m` columns starting at `B * N0 L m` from `padCols Y`, using +`lay.innerIdx` and `lay.colIdx` to index the resulting integer matrix by strings. -/ def colBlock {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (B : ℕ) : RightMat L m := fun π c => padCols Y (lay.innerIdx.symm π) (B * N0 L m + lay.colIdx.symm c) -/-- Section 2.3.4: In a tile, let X_Q and Y_Q be the row block and the column block of the block -product with subset Q, and let X_Q = Y_Q = 0 for the other subsets Q. The row band `β` (counted -from 0) consists of the row blocks `β K₀, …, β K₀ + K₀ - 1`. The subsets in the table are distinct, -so at most one summand is nonzero. -/ +/-- For a table entry `Q = lay.table (g, h)`, select row block `β * K0 L m + g` of `X`; return +the zero matrix for sets outside the table's image. Injectivity of the table ensures that +the defining sum selects at most one matrix. -/ def bandFamilyL {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) : Finset (Fin L) → LeftMat L m := fun Q => ∑ gh, if lay.table gh = Q then rowBlock lay X (β * K0 L m + gh.1) else 0 -/-- The matrices `Y_Q` of a tile; they depend only on its column band `β`. -/ +/-- For a table entry `Q = lay.table (g, h)`, select column block `β * K0 L m + h` of `Y`; +return the zero matrix for sets outside the table's image. -/ def bandFamilyR {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) : Finset (Fin L) → RightMat L m := fun Q => ∑ gh, if lay.table gh = Q then colBlock lay Y (β * K0 L m + gh.2) else 0 -/-- Section 2.3.4: the left input array of a tile depends only on its row band. -/ +/-- The left batch array formed from the block family for row band `β`. -/ def bandArrayL {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) : LeftStr L → ℤ := arrayL m (bandFamilyL lay X β) -/-- Section 2.3.4: and the right one only on its column band. -/ +/-- The right batch array formed from the block family for column band `β`. -/ def bandArrayR {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) : RightStr L → ℤ := arrayR m (bandFamilyR lay Y β) @@ -129,16 +125,15 @@ def blockOf {L m : ℕ} (lay : Layout L m) (I : ℕ) : Fin (K0 L m) := /-- The position of row (or column) `I` within its block. -/ def offsetOf (L m I : ℕ) : Fin (N0 L m) := ⟨I % N0 L m, Nat.mod_lt _ (N0_pos L m)⟩ -/-- Section 2.4.4: Each wanted position (I, J) ∈ W lies in one tile T and is indexed by one output -string of T, the string whose inner set is the subset Q of the block product containing (I, J) and -whose row and column are those of (I, J) within that block product. The tile is the pair -`(bandOf L m I, bandOf L m J)`, and this is the output string. -/ +/-- Encode the within-tile position `(I, J)` as an output string. Its inner set comes from the +table at the two block indices, and its outer indices come from the two offsets within +those blocks. The tile itself is identified separately by `(bandOf L m I, bandOf L m J)`. -/ def outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : OutStr L := outStrOf (lay.table (blockOf lay I, blockOf lay J)) (lay.table_card _) (lay.rowIdx (offsetOf L m I)) (lay.colIdx (offsetOf L m J)) -/-- Section 2.4.4: For each tile T, let W_T be the set of the output strings of the wanted -positions in T. The tile `T` is given by its row band `β` and its column band `β'`. -/ +/-- Output strings for the positions of `W` whose row band is `β` and column band is `β'`. +This is the image of the filtered position set under `outStrOfPos lay`. -/ def wantedStrings {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) (β β' : ℕ) : Finset (OutStr L) := (W.filter fun IJ => bandOf L m IJ.1 = β ∧ bandOf L m IJ.2 = β').image diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean index 38d7001a93..da18d521e0 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Levels.lean @@ -20,7 +20,7 @@ public import Complexity.Foundations.Data.Finset.OrderGlue ## Main results -* `existsUnique_leftStr`, `existsUnique_rightStr`, `existsUnique_outStr`: nonvacuous entry layouts. +* `existsUnique_leftStr`, `existsUnique_rightStr`, `existsUnique_outStr`: unique entry strings. ## References @@ -28,7 +28,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -37,11 +36,11 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.2: We call the variables of the inner product, p, q, and z₀, inner. -/ +/-- Recognize the constructors `.p i j` as inner left variables. -/ def LeftVar.IsInner : LeftVar → Prop | .x _ => False | .p _ _ => True -/-- Section 2.2: the right variables `q` are inner, the `y` are outer. -/ +/-- Recognize the constructors `.q i j` as inner right variables. -/ def RightVar.IsInner : RightVar → Prop | .y _ => False | .q _ _ => True @@ -51,69 +50,61 @@ instance LeftVar.instDecidablePredIsInner : DecidablePred LeftVar.IsInner instance RightVar.instDecidablePredIsInner : DecidablePred RightVar.IsInner | .y _ => isFalse fun h => h | .q _ _ => isTrue trivial -/-- Section 2.3.3: The inner set of a left string is the set of levels at which it has a p (as -opposed to an x). -/ +/-- Positions of the inner left variables in `u`. -/ def innerSetL {L : ℕ} (u : LeftStr L) : Finset (Fin L) := univ.filter fun ℓ => (u ℓ).IsInner -/-- Section 2.3.3: the inner set of a right string is the set of levels at which it has a q (as -opposed to a y). -/ +/-- Positions of the inner right variables in `v`. -/ def innerSetR {L : ℕ} (v : RightStr L) : Finset (Fin L) := univ.filter fun ℓ => (v ℓ).IsInner -/-- Section 2.3.3: the inner set of an output string is the set of levels at which it has z₀ (as -opposed to a z_ij). -/ +/-- Positions at which the output string is `.z0`. -/ def innerSetO {L : ℕ} (w : OutStr L) : Finset (Fin L) := univ.filter fun ℓ => (w ℓ).IsInner -/-- Section 2.3.3: N₀ := 3^{L-m}. Meant for `m ≤ L`; for `m > L` the subtraction of natural -numbers is cut off at 0 and the value is 1. -/ +/-- The outer dimension `3 ^ (L - m)`, with natural-number subtraction. In particular, this +definition gives `1` if `m > L`. -/ def N0 (L m : ℕ) : ℕ := 3 ^ (L - m) -/-- Section 2.3.3: D := 4^m. -/ +/-- The thin dimension `4 ^ m`. -/ def D (m : ℕ) : ℕ := 4 ^ m -/-- Section 2.3.3: K := binom(L, m), the number of subsets of `{1, …, L}` of size `m`. -/ +/-- The binomial coefficient `L.choose m`, counting size-`m` subsets of `Fin L`. -/ def K (L m : ℕ) : ℕ := L.choose m -/-- The index `i` of an outer left variable `x_i` (the value at a `p` is never used). -/ +/-- Extract `i` from `.x i`; return `0` on inner left variables. -/ def LeftVar.outerIndex : LeftVar → Fin 3 | .x i => i | .p _ _ => 0 -/-- The index pair `(i, j)` of an inner left variable `p_ij` (the value at an `x` is never used). -/ +/-- Extract `(i, j)` from `.p i j`; return `(0, 0)` on outer left variables. -/ def LeftVar.innerIndex : LeftVar → Fin 2 × Fin 2 | .x _ => (0, 0) | .p i j => (i, j) -/-- The index `j` of an outer right variable `y_j` (the value at a `q` is never used). -/ +/-- Extract `j` from `.y j`; return `0` on inner right variables. -/ def RightVar.outerIndex : RightVar → Fin 3 | .y j => j | .q _ _ => 0 -/-- The index pair `(i, j)` of an inner right variable `q_ij` (the value at a `y` is never used). -/ +/-- Extract `(i, j)` from `.q i j`; return `(0, 0)` on outer right variables. -/ def RightVar.innerIndex : RightVar → Fin 2 × Fin 2 | .y _ => (0, 0) | .q i j => (i, j) -/-- The first index `i` of an outer output variable `z_ij` (the value at `z₀` is never used). -/ +/-- Extract the first index from `.z i j`; return `0` on `.z0`. -/ def OutVar.rowIndex : OutVar → Fin 3 | .z i _ => i | .z0 => 0 -/-- The second index `j` of an outer output variable `z_ij` (the value at `z₀` is never used). -/ +/-- Extract the second index from `.z i j`; return `0` on `.z0`. -/ def OutVar.colIndex : OutVar → Fin 3 | .z _ j => j | .z0 => 0 -/-- A string of `L - m` outer left variables `x_{i₁} ⋯ x_{i_{L-m}}`, written as the string of its -indices `i₁ ⋯ i_{L-m}`. The same type is used for strings `y_{j₁} ⋯ y_{j_{L-m}}` of outer right -variables. Section 2.3.3: these strings index the `N₀` rows of an `N₀ × D` matrix and the `N₀` -columns of a `D × N₀` matrix. -/ +/-- Functions from `Fin (L - m)` to `Fin 3`, used as outer row and column indices. Each value +specifies the index of an outer left or right variable. -/ abbrev OuterStr (L m : ℕ) : Type := Fin (L - m) → Fin 3 -/-- A string of `m` inner left variables `π = p_{i₁j₁} ⋯ p_{i_m j_m}`, written as the string of its -index pairs. The same type is used for strings `π' = q_{i₁j₁} ⋯ q_{i_m j_m}` of inner right -variables, so that the paper's `π'`, with the same indices as `π`, is the same element of this -type as `π`. Section 2.3.3: these strings index the `D` columns of an `N₀ × D` matrix and the `D` -rows of a `D × N₀` matrix. With this convention the paper's (AB)[r, c] := ∑_π A[r, π] B[π', c] is -the usual matrix product. -/ +/-- Functions from `Fin m` to pairs of `Fin 2` indices, used for the common inner dimension of +the two matrices. Left and right inner variables with the same pair of indices share one +element of this type, so contraction over `InnerStr m` is ordinary matrix multiplication. -/ abbrev InnerStr (m : ℕ) : Type := Fin m → Fin 2 × Fin 2 -/-- Section 2.3.3: its outer part is the string of its variables at the other levels, for a left -string whose inner set has `m` elements. -/ +/-- Extract the outer left indices in increasing order of their positions, assuming `u` has +exactly `m` inner positions. -/ def outerPartL {L m : ℕ} (u : LeftStr L) (h : (innerSetL u).card = m) : OuterStr L m := fun k => (u (((innerSetL u)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetL u) h)) k)).outerIndex -/-- Section 2.3.3: The inner part of a string is the string of its variables at the levels of its -inner set, in the order of the levels, for a left string whose inner set has `m` elements. -/ +/-- Extract the inner left index pairs in increasing order of their positions, assuming `u` +has exactly `m` inner positions. -/ def innerPartL {L m : ℕ} (u : LeftStr L) (h : (innerSetL u).card = m) : InnerStr m := fun k => (u (Finset.orderEmbOfFin (innerSetL u) h k)).innerIndex /-- The outer part of a right string whose inner set has `m` elements. -/ @@ -123,37 +114,37 @@ def outerPartR {L m : ℕ} (v : RightStr L) (h : (innerSetR v).card = m) : Outer def innerPartR {L m : ℕ} (v : RightStr L) (h : (innerSetR v).card = m) : InnerStr m := fun k => (v (Finset.orderEmbOfFin (innerSetR v) h k)).innerIndex -/-- Section 2.3.3: if the variables of w at the levels outside Q are z_{i₁j₁}, …, z_{i_{L-m} -j_{L-m}}, in the order of the levels, then the row of w is the string x_{i₁} ⋯ x_{i_{L-m}}. -/ +/-- Read the first indices of the outer output variables in increasing position order, +assuming `w` has exactly `m` inner positions. -/ def rowO {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) : OuterStr L m := fun k => (w (((innerSetO w)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetO w) h)) k)).rowIndex -/-- Section 2.3.3: and its column is the string y_{j₁} ⋯ y_{j_{L-m}}. -/ +/-- Read the second indices of the outer output variables in increasing position order, +assuming `w` has exactly `m` inner positions. -/ def colO {L m : ℕ} (w : OutStr L) (h : (innerSetO w).card = m) : OuterStr L m := fun k => (w (((innerSetO w)ᶜ.orderEmbOfFin (card_compl_of_card_eq (innerSetO w) h)) k)).colIndex -/-- An `N₀ × D` matrix, its rows and columns indexed by strings as in Section 2.3.3. -/ +/-- Integer matrices with outer row indices and inner column indices. -/ abbrev LeftMat (L m : ℕ) : Type := Matrix (OuterStr L m) (InnerStr m) ℤ -/-- A `D × N₀` matrix, its rows and columns indexed by strings as in Section 2.3.3. -/ +/-- Integer matrices with inner row indices and outer column indices. -/ abbrev RightMat (L m : ℕ) : Type := Matrix (InnerStr m) (OuterStr L m) ℤ -/-- Section 2.3.3: a[u] := X_Q[u] […] for all the left strings u […] whose inner set Q has exactly -m elements, and a[u] := 0 […] at every other string, where X_Q[u] is the entry of `X_Q` at the -row and the column of u: the row is the outer part of `u` and the column is its inner part. The -family `X` gives a matrix `X Q` for every set `Q` of levels; only those with `|Q| = m` are used. -/ +/-- Encode a family of integer matrices as a left array. A string with exactly `m` inner +positions selects `X` at that set of positions, with row `outerPartL` and column +`innerPartL`; every other string has value zero. -/ def arrayL {L : ℕ} (m : ℕ) (X : Finset (Fin L) → LeftMat L m) : LeftStr L → ℤ := fun u => if h : (innerSetL u).card = m then X (innerSetL u) (outerPartL u h) (innerPartL u h) else 0 -/-- Section 2.3.3: b[v] := Y_Q[v] for the right strings `v` whose inner set `Q` has exactly `m` -elements, the row given by the inner part of v and the column by the outer part, and `b[v] := 0` -at every other string. -/ +/-- Encode a family of integer matrices as a right array. A string with exactly `m` inner +positions selects `Y` at that set of positions, with row `innerPartR` and column +`outerPartR`; every other string has value zero. -/ def arrayR {L : ℕ} (m : ℕ) (Y : Finset (Fin L) → RightMat L m) : RightStr L → ℤ := fun v => if h : (innerSetR v).card = m then Y (innerSetR v) (innerPartR v h) (outerPartR v h) else 0 -/-! ### 2.3.4 Tiling the N × D × N product by products of shape N₀ × D × N₀: the number `M` -/ +/-! ### The block-count parameter `M` -/ -/-- Section 2.3.4: M := K N₀². -/ +/-- The quantity `K L m * N0 L m ^ 2` used in the leaf and tile bounds. -/ def M (L m : ℕ) : ℕ := K L m * N0 L m ^ 2 @@ -169,9 +160,8 @@ def leftStrOf (r : OuterStr L m) (π : InnerStr m) : LeftStr L := def rightStrOf (π : InnerStr m) (c : OuterStr L m) : RightStr L := glue Q hQ (fun k => .q (π k).1 (π k).2) (fun k => .y (c k)) -/-- The output string with inner set `Q`, row `r` and column `c` (Section 2.3.3: the output strings -with inner set Q index the entries of the product X_Q Y_Q): it has `z₀` at the levels of `Q`, and -at the `k`-th lowest level outside `Q` it has `z_{r_k c_k}`. -/ +/-- Construct an output string with `.z0` at `Q` and `.z (r k) (c k)` at the `k`-th position +of its complement. Both sets of positions use increasing order. -/ def outStrOf (r c : OuterStr L m) : OutStr L := glue Q hQ (fun _ => .z0) (fun k => .z (r k) (c k)) @@ -296,10 +286,8 @@ theorem outStrOf_inj {Q' : Finset (Fin L)} {hQ' : Q'.card = m} {r c r' c' : Oute variable (Q) (hQ) include hQ -/-- Section 2.3.3: A string is determined by its inner set, its outer part, and its inner part, -and the left strings with inner set Q index the entries of such a matrix: for each set `Q` of `m` -levels, each row `r` and each column `π` there is exactly one left string with inner set `Q`, outer -part `r` and inner part `π`. -/ +/-- For a set `Q` of cardinality `m`, an outer index `r` and an inner index `π` determine a +unique left string with those extracted parts and inner set `Q`. -/ theorem existsUnique_leftStr (r : OuterStr L m) (π : InnerStr m) : ∃! u : LeftStr L, innerSetL u = Q ∧ ∃ h : (innerSetL u).card = m, outerPartL u h = r ∧ innerPartL u h = π := by @@ -309,8 +297,8 @@ theorem existsUnique_leftStr (r : OuterStr L m) (π : InnerStr m) : rintro u ⟨hu, h, rfl, rfl⟩ exact leftStr_eq_leftStrOf hQ hu h -/-- Section 2.3.3: Similarly, the right strings v with inner set Q index the entries of a D × N₀ -matrix. -/ +/-- For a set `Q` of cardinality `m`, an inner index `π` and an outer index `c` determine a +unique right string with those extracted parts and inner set `Q`. -/ theorem existsUnique_rightStr (π : InnerStr m) (c : OuterStr L m) : ∃! v : RightStr L, innerSetR v = Q ∧ ∃ h : (innerSetR v).card = m, innerPartR v h = π ∧ outerPartR v h = c := by @@ -320,8 +308,8 @@ theorem existsUnique_rightStr (π : InnerStr m) (c : OuterStr L m) : rintro v ⟨hv, h, rfl, rfl⟩ exact rightStr_eq_rightStrOf hQ hv h -/-- Section 2.3.3: Finally, the output strings with inner set Q index the entries of the product -X_Q Y_Q. -/ +/-- For a set `Q` of cardinality `m`, a row `r` and a column `c` determine a unique output +string with those extracted indices and inner set `Q`. -/ theorem existsUnique_outStr (r c : OuterStr L m) : ∃! w : OutStr L, innerSetO w = Q ∧ ∃ h : (innerSetO w).card = m, rowO w h = r ∧ colO w h = c := by diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean index 71e660aa65..7a932b6803 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Magnitude.lean @@ -11,7 +11,7 @@ public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct public import Complexity.Foundations.Algebra.FiniteMagnitude /-! -# Finite intermediate magnitudes and auxiliary counts +# Magnitudes of specified integer values and auxiliary counts ## Main results @@ -19,7 +19,9 @@ public import Complexity.Foundations.Algebra.FiniteMagnitude * `abs_encodingL_le`, `abs_encodingR_le`, `Pruned.abs_result_le`: explicit integer magnitude bounds. * `minutiae_total_le`: bound on layout subsets, tiles and wanted positions. -These bounds do not certify a machine word width or native execution. +The magnitude bounds concern band-array values, encoded values and returned pruned +values. They do not bound every intermediate state of a native implementation or certify +a machine word width. ## References @@ -27,7 +29,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -36,7 +37,7 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-! ### The remaining minutiae -/ +/-! ### Auxiliary finite counts -/ /-- `2^{m/9} ≤ 2^m`. -/ private theorem two_rpow_ninth_le (m : ℕ) : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (2 : ℝ) ^ m := by @@ -50,12 +51,12 @@ private theorem one_le_saving_mul {m : ℕ} {x : ℝ} (hx : (2 : ℝ) ^ ((m : rw [← saving_mul_two_rpow m] exact mul_le_mul_of_nonneg_left hx (saving_pos m).le -/-- Section 2.4.4, in the remaining minutiae: K ≤ N. -/ +/-- When `D m ^ 18 ≤ N`, the number `K (19 * m) m` of size-`m` level sets is at most `N`. -/ theorem K_le_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K (19 * m) m ≤ N := (Nat.le_mul_of_pos_right _ (N0_pos _ _)).trans (K_mul_N0_le m N hN) -/-- Section 2.4.4, in the remaining minutiae: there are at most 4N²/N₀ tiles, where `N` is the -padded size. -/ +/-- For `m ≤ L`, the tile count multiplied by `N0 L m` is at most four times the square of the +padded dimension `padN L m N`. -/ theorem card_tiles_mul_N0_le {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : (tiles L m N).card * N0 L m ≤ 4 * padN L m N ^ 2 := by -- `N₀ ≤ K N₀² = M`, and there are at most `4N²/M` tiles. @@ -64,7 +65,7 @@ theorem card_tiles_mul_N0_le {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : _ ≤ K L m * N0 L m ^ 2 := Nat.mul_le_mul (Nat.choose_pos hmL) (Nat.pow_le_pow_right (N0_pos L m) (by norm_num)) -/-- Section 2.4.4, in the remaining minutiae: `N ≥ 2^{m/9}`. -/ +/-- The size condition `D m ^ 18 ≤ N` implies the real bound `2 ^ (m / 9) ≤ N`. -/ theorem two_rpow_le_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (N : ℝ) := by -- `2^{m/9} ≤ 2^m ≤ 4^{18m} = D^18 ≤ N`. @@ -74,7 +75,7 @@ theorem two_rpow_le_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : _ ≤ N := hN exact (two_rpow_ninth_le m).trans (by exact_mod_cast hpow) -/-- Section 2.4.4, in the remaining minutiae: `N₀ ≥ 2^{m/9}`. -/ +/-- At `L = 19 * m`, the outer dimension is at least the real power `2 ^ (m / 9)`. -/ theorem two_rpow_le_N0 (m : ℕ) : (2 : ℝ) ^ ((m : ℝ) / 9) ≤ (N0 (19 * m) m : ℝ) := by -- `2^{m/9} ≤ 2^m ≤ 3^{18m} = N₀`. have hpow : 2 ^ m ≤ N0 (19 * m) m := @@ -113,10 +114,10 @@ private theorem minutiae_parts (m N : ℕ) (W : Finset (Fin N × Fin N)) (hN : D _ ≤ saving m * (W.card * (2 : ℝ) ^ m) := by gcongr; exact two_rpow_ninth_le m _ ≤ saving m * (N : ℝ) ^ 2 := by gcongr -/-- Section 2.4.4: O(L) operations per subset, per tile, and per position of W, which is -O(L 2^{-m/9} N²) in all: the number of these objects, with the `K₀²` subsets counted as at most -`K`, as the paper does (as K ≤ N). Here `N` is the size of the input. The constant is -`18 = 1 + 16 + 1`, because the padding at most doubles `N`. -/ +/-- At `L = 19 * m`, the sum of `K L m`, the tile count and `W.card` is at most `18 * +D^(-1/18) * N²`, where `D = D m` and `N` is the original matrix dimension. The hypotheses +are `D m ^ 18 ≤ N` and `W.card * 2 ^ m ≤ N ^ 2`. This counts finite objects; it does not +count operations on them. -/ theorem minutiae_total_le (m N : ℕ) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) (hW : W.card * 2 ^ m ≤ N ^ 2) : ((K (19 * m) m + (tiles (19 * m) m N).card + W.card : ℕ) : ℝ) @@ -131,7 +132,7 @@ theorem minutiae_total_le (m N : ℕ) (W : Finset (Fin N × Fin N)) (hN : D m ^ -- `K + |tiles| + |W| ≤ c N² + 4 c (2N)² + c N² = 18 c N²` with `c = 2^{-m/9}`. linarith [hK, hT, hW', hsq] -/-! ### The word size -/ +/-! ### Bounds on specified integer values -/ /-- The subsets in the table are distinct (Section 2.3.4), so a sum over the table that selects the subset `Q` is one of its terms or 0. -/ @@ -144,10 +145,9 @@ private theorem sum_table_eq_zero_or {L m : ℕ} {A : Type*} [AddCommMonoid A] ( exact .inr ⟨gh, by simp [lay.table_injective.eq_iff]⟩ · exact .inl (sum_eq_zero fun gh _ => ite_eq_right (not_exists.mp hQ gh)) -/-- Section 2.4.4, word size: The input entries are of this size, by assumption. Every entry of -the input array of a row band is an entry of `X` or 0. - -NOTE. `0 ≤ A` is needed only when `N = 0`. -/ +/-- A nonnegative bound `A` on the absolute values of all entries of `X` also bounds every +value of its left band arrays, for every band index. Zero padding and unused strings are +included; `0 ≤ A` remains required when `N = 0`. -/ theorem abs_bandArrayL_le {L m N : ℕ} (lay : Layout L m) {X : Matrix (Fin N) (Fin (D m)) ℤ} {A : ℤ} (hA : 0 ≤ A) (hX : ∀ I k, |X I k| ≤ A) (β : ℕ) (u : LeftStr L) : |bandArrayL lay X β u| ≤ A := by @@ -164,10 +164,9 @@ theorem abs_bandArrayL_le {L m N : ℕ} (lay : Layout L m) {X : Matrix (Fin N) ( · simpa using hA · simpa using hA -/-- Section 2.4.4, word size: The input entries are of this size, by assumption. Every entry of -the input array of a column band is an entry of `Y` or 0. - -NOTE. `0 ≤ B` is needed only when `N = 0`. -/ +/-- A nonnegative bound `B` on the absolute values of all entries of `Y` also bounds every +value of its right band arrays, for every band index. Zero padding and unused strings are +included; `0 ≤ B` remains required when `N = 0`. -/ theorem abs_bandArrayR_le {L m N : ℕ} (lay : Layout L m) {Y : Matrix (Fin (D m)) (Fin N) ℤ} {B : ℤ} (hB : 0 ≤ B) (hY : ∀ k J, |Y k J| ≤ B) (β : ℕ) (v : RightStr L) : |bandArrayR lay Y β v| ≤ B := by @@ -184,8 +183,9 @@ theorem abs_bandArrayR_le {L m N : ℕ} (lay : Layout L m) {Y : Matrix (Fin (D m · simpa using hB · simpa using hB -/-- A combination of the entries of an array, with a product of coefficients 0, 1 or -1 for each -entry, is at most the number of entries times the largest absolute value of an entry. -/ +/-- If every coefficient has absolute value at most one and every array entry has absolute +value at most `A`, then an encoded value has absolute value at most the array index type's +cardinality times `A`. This bounds the value of the finite sum defining `encodeWith`. -/ theorem abs_encodeWith_le {α : Type} [Fintype α] {c : Term → α → ℤ} (hc : ∀ lam s, |c lam s| ≤ 1) {L : ℕ} {a : (Fin L → α) → ℤ} {A : ℤ} (ha : ∀ u, |a u| ≤ A) (τ : Leaf L) : |encodeWith c τ a| ≤ Fintype.card (Fin L → α) * A := by @@ -193,25 +193,26 @@ theorem abs_encodeWith_le {α : Type} [Fintype α] {c : Term → α → ℤ} (hc (fun u _ => abs_prod_le_one _ fun ℓ _ => hc _ _) fun u _ => ha u simpa only [encodeWith, mul_comm, card_univ] using h -/-- Section 2.4.4, word size: Every number in an encoding […] is a ±1 combination of at most 7^L -input entries, so its absolute value is at most `7^L` times the largest absolute value of an input -entry. For the encoding of `a`. -/ +/-- An absolute bound `A` on the left array entries gives the bound `7 ^ L * A` on each value +of `encodingL`. -/ theorem abs_encodingL_le {L : ℕ} {a : LeftStr L → ℤ} {A : ℤ} (ha : ∀ u, |a u| ≤ A) (τ : Leaf L) : |encodingL a τ| ≤ 7 ^ L * A := by have h := abs_encodeWith_le abs_phi_le_one ha τ rw [card_leftStr] at h exact_mod_cast h -/-- Section 2.4.4, word size: Every number in an encoding […] is a ±1 combination of at most 7^L -input entries. For the encoding of `b`. -/ +/-- An absolute bound `B` on the right array entries gives the bound `7 ^ L * B` on each value +of `encodingR`. -/ theorem abs_encodingR_le {L : ℕ} {b : RightStr L → ℤ} {B : ℤ} (hb : ∀ v, |b v| ≤ B) (τ : Leaf L) : |encodingR b τ| ≤ 7 ^ L * B := by have h := abs_encodeWith_le abs_psi_le_one hb τ rw [card_rightStr] at h exact_mod_cast h -/-- A returned pruned entry is a sum of at most `10^n` encoded products (Section 2.4.4). -This bounds the mathematical result, not native execution or machine width. -/ +/-- If `encA` and `encB` are bounded in absolute value by `A` and `B`, every value returned by +`Pruned.result` is bounded by `10 ^ n * (A * B)`. This holds for every request set `S` and +output string `w`, including unrequested strings, whose returned value is zero. It bounds +the specified mathematical result, not native scratch states or machine word width. -/ theorem Pruned.abs_result_le {n : ℕ} (encA encB : Leaf n → ℤ) (A B : ℤ) (hA : ∀ τ, |encA τ| ≤ A) (hB : ∀ τ, |encB τ| ≤ B) (S : Finset (OutStr n)) (w : OutStr n) : |Pruned.result n encA encB S w| ≤ 10 ^ n * (A * B) := by @@ -233,7 +234,7 @@ theorem Pruned.abs_result_le {n : ℕ} (encA encB : Leaf n → ℤ) (A B : ℤ) · simp only [Pi.zero_apply, abs_zero] positivity -/-- Section 2.4.4, word size: 10^L ≤ N² by (6), for `L = 19m` and `N ≥ D^18`. -/ +/-- Under `D m ^ 18 ≤ N`, the number `10 ^ (19 * m)` of leaf indices is at most `N ^ 2`. -/ theorem ten_pow_le_sq (m N : ℕ) (hN : D m ^ 18 ≤ N) : 10 ^ (19 * m) ≤ N ^ 2 := by -- `N₀ ≤ K N₀ ≤ N` and `2^{-m/9} ≤ 1`, so equation (6) gives `10^L ≤ N N₀ ≤ N²`. have hN0 : (N0 (19 * m) m : ℝ) ≤ N := by diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean index a11ba4e55b..facc0f50c2 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Orders.lean @@ -33,7 +33,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -45,16 +44,17 @@ namespace Cslib.Algorithms.MatrixMultiplication.Schonhage /-- The set of levels at which a leaf chooses `P₀`. -/ def P0Levels {L : ℕ} (τ : Leaf L) : Finset (Fin L) := univ.filter fun ℓ => τ ℓ = Term.P0 -/-- Section 2.4.3: we define the order of a leaf as m minus the number of levels at which it -chooses P₀. The order is an integer; it is negative for a leaf that chooses `P₀` at more than `m` -levels. -/ +/-- The integer difference between `m` and the number of `P0` positions of a leaf. It can be +negative for arbitrary leaves; nonnegativity is proved for leaves contributing to an +output with `m` inner positions. -/ def order {L : ℕ} (m : ℕ) (τ : Leaf L) : ℤ := (m : ℤ) - ((P0Levels τ).card : ℤ) -/-- Section 2.4.3: α_d := binom(m, d) 9^d. -/ +/-- The quantity `m.choose d * 9 ^ d`, counting order-`d` leaves contributing to one output +with `m` inner positions. -/ def alpha (m d : ℕ) : ℕ := m.choose d * 9 ^ d -/-- Section 2.4.3: β_d := binom(L, m-d) 9^{L-m+d}. Meant for `d ≤ m ≤ L`; outside this range the -subtractions of natural numbers are cut off at 0 and the value has no meaning. -/ +/-- The quantity `L.choose (m - d) * 9 ^ (L - m + d)`, using natural-number subtraction. Its +interpretation as the number of order-`d` leaves requires `d ≤ m ≤ L`. -/ def beta (L m d : ℕ) : ℕ := L.choose (m - d) * 9 ^ (L - m + d) /-! ### One level: which terms contribute to which variables -/ @@ -113,8 +113,7 @@ theorem mem_P0Levels {L : ℕ} {τ : Leaf L} {ℓ : Fin L} : ℓ ∈ P0Levels τ theorem mem_innerSetO {L : ℕ} {η : OutStr L} {ℓ : Fin L} : ℓ ∈ innerSetO η ↔ (η ℓ).IsInner := by simp [innerSetO] -/-- Section 2.4.3: A leaf contributing to an output string chooses P₀ only at levels of the output -string's inner set. -/ +/-- A contributing leaf can use `P0` only at an inner position of the output string. -/ theorem P0Levels_subset {L : ℕ} {τ : Leaf L} {w : OutStr L} (h : Leaf.Contributes τ w) : P0Levels τ ⊆ innerSetO w := by intro ℓ hℓ @@ -124,15 +123,16 @@ theorem P0Levels_subset {L : ℕ} {τ : Leaf L} {w : OutStr L} (h : Leaf.Contrib · exact hinner · exact (OutVar.privateTerm_eq_P0_iff _).mp (hprivate.symm.trans hℓ) -/-- Section 2.4.3: so its order is at least 0. -/ +/-- A leaf contributing to a string with exactly `m` inner positions has nonnegative order +relative to `m`. -/ theorem order_nonneg {L m : ℕ} {w : OutStr L} (hw : (innerSetO w).card = m) {τ : Leaf L} (h : Leaf.Contributes τ w) : 0 ≤ order m τ := by have hc := card_le_card (P0Levels_subset h) unfold order omega -/-- In the proof of Lemma 11: every leaf of leaves(U) has an order 0 ≤ d ≤ m. The upper -bound holds for every leaf. -/ +/-- The order relative to `m` of any leaf is at most `m`, regardless of which outputs it +contributes to. -/ theorem order_le {L : ℕ} (m : ℕ) (τ : Leaf L) : order m τ ≤ m := by unfold order omega @@ -145,7 +145,7 @@ theorem order_eq_iff {L : ℕ} (m d : ℕ) (τ : Leaf L) : /-! ### The counts -/ -/-- Section 2.4.3: A single output string has 10^m […] leaves contributing to it. -/ +/-- Exactly `10 ^ m` leaves contribute to an output string with `m` inner positions. -/ theorem card_filter_contributes {L m : ℕ} (w : OutStr L) (hw : (innerSetO w).card = m) : (univ.filter fun τ : Leaf L => Leaf.Contributes τ w).card = 10 ^ m := by -- Ten terms contribute to `z₀` and one to `z_ij`. @@ -154,9 +154,8 @@ theorem card_filter_contributes {L m : ℕ} (w : OutStr L) (hw : (innerSetO w).c rw [prod_ite, prod_const, prod_const_one, mul_one, ← hw] rfl -/-- Section 2.4.3: exactly α_d := binom(m, d) 9^d leaves of order d contribute to an output -string. As everywhere in Section 2.4.3, the output strings are those whose inner sets have exactly -m elements. -/ +/-- For a string with `m` inner positions, `alpha m d` leaves of order `d` contribute to it. +The natural parameter `d` is unrestricted; for `d > m` both sides are zero. -/ theorem card_contributing_of_order {L m : ℕ} (w : OutStr L) (hw : (innerSetO w).card = m) (d : ℕ) : (univ.filter fun τ : Leaf L => Leaf.Contributes τ w ∧ order m τ = d).card = alpha m d := by @@ -190,10 +189,8 @@ theorem card_contributing_of_order {L m : ℕ} (w : OutStr L) (hw : (innerSetO w have hle := order_le m τ omega -/-- Section 2.4.3: The total number of leaves of order d in the entire recursion tree is -β_d := binom(L, m-d) 9^{L-m+d}. - -NOTE. `d ≤ m` and `m ≤ L` are left implicit in the paper. -/ +/-- If `d ≤ m ≤ L`, the full leaf index type contains exactly `beta L m d` leaves of order +`d`. -/ theorem card_filter_order_eq {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd : d ≤ m) : (univ.filter fun τ : Leaf L => order m τ = d).card = beta L m d := by -- The leaves of order `d` are the strings of terms with `P₀` at exactly `m - d` levels. @@ -207,21 +204,17 @@ theorem card_filter_order_eq {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd : d ≤ m card_term_ne_P0, one_pow, one_mul, Fintype.card_fin, beta, show L - (m - d) = L - m + d by omega] -/-- Section 2.4.3: In particular, β₀ = binom(L, m) 9^{L-m}. -/ +/-- Evaluate `beta` at order zero. -/ theorem beta_zero (L m : ℕ) : beta L m 0 = L.choose m * 9 ^ (L - m) := by simp [beta] -/-- Section 2.4.3: β₀ = binom(L, m) 9^{L-m} = M. -/ +/-- The order-zero count formula equals `M L m`. -/ theorem beta_zero_eq_M (L m : ℕ) : beta L m 0 = M L m := by rw [beta_zero, M, K, N0, ← pow_mul, mul_comm (L - m) 2, pow_mul] norm_num -/-- Section 2.4.3: A leaf of order d contributes to binom(L-m+d, d) output strings, one for each -inner set of size m that contains the m-d levels at which it chooses P₀. Only the number is -stated. As everywhere in Section 2.4.3, the output strings are those whose inner sets have exactly -m elements. - -NOTE. `m ≤ L` is left implicit in the paper. -/ +/-- Assuming `m ≤ L`, a leaf of natural order `d` contributes to exactly `(L - m + d).choose +d` output strings with `m` inner positions. The order hypothesis forces `d ≤ m`. -/ theorem card_outStr_of_leaf {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (τ : Leaf L) (hτ : order m τ = d) : (univ.filter fun w : OutStr L => (innerSetO w).card = m ∧ Leaf.Contributes τ w).card @@ -254,8 +247,8 @@ theorem card_outStr_of_leaf {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (τ : Leaf L) theorem beta_pos {L m : ℕ} (hmL : m ≤ L) (d : ℕ) : 0 < beta L m d := Nat.mul_pos (Nat.choose_pos (by omega)) (by positivity) -/-- The equality in equation (5) without division: `β_d (L-m+d) = 9 (m-d+1) β_{d-1}` for -`1 ≤ d ≤ m ≤ L`. -/ +/-- For `1 ≤ d ≤ m ≤ L`, consecutive values of `beta` satisfy the displayed multiplication +identity, without division (cf. [AlmanWilliams2026, equation (5)]). -/ theorem Equation5.ratio_nat {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : beta L m d * (L - m + d) = 9 * (m - d + 1) * beta L m (d - 1) := by -- With `k = m - d` this is `binom(L, k) (L - k) = binom(L, k + 1) (k + 1)`, times a power of 9. @@ -269,11 +262,9 @@ theorem Equation5.ratio_nat {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d _ = 9 * 9 ^ (L - m + (d - 1)) * (L.choose (m - d + 1) * (m - d + 1)) := by rw [hchoose] _ = 9 * (m - d + 1) * (L.choose (m - d + 1) * 9 ^ (L - m + (d - 1))) := by ring -/-- Section 2.4.3, the equality in equation (5), for every `L ≥ m` (it is used again as equation (7) -in Section 4.3): β_d / β_{d-1} = 9(m-d+1) / (L-m+d) for `1 ≤ d ≤ m`. - -NOTE. The paper prints the equality under `L = 19m` in (5) and under `L ≥ 10m` in (7); here it is -stated for every `L ≥ m`. -/ +/-- For `1 ≤ d ≤ m ≤ L`, the ratio of consecutive `beta` values is `9 * (m - d + 1) / (L - m + +d)`, with the ratio and subtractions interpreted in `ℚ`. This extends the parameter ranges +used in [AlmanWilliams2026, equations (5) and (7)]. -/ theorem beta_ratio {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : (beta L m d : ℚ) / (beta L m (d - 1) : ℚ) = 9 * ((m : ℚ) - (d : ℚ) + 1) / ((L : ℚ) - (m : ℚ) + (d : ℚ)) := by @@ -284,8 +275,8 @@ theorem beta_ratio {L m : ℕ} (hmL : m ≤ L) (d : ℕ) (hd1 : 1 ≤ d) (hd : d push_cast [Nat.cast_sub hmL, Nat.cast_sub hd] at hnat rw [div_eq_div_iff hbeta.ne' (by linarith), hnat] -/-- Section 2.4.3, the inequalities in equation (5): if L = 19m, then for 1 ≤ d ≤ m we have -β_d / β_{d-1} = 9(m-d+1)/(L-m+d) ≤ 9m/(18m+1) < 1/2. -/ +/-- At `L = 19 * m` and `1 ≤ d ≤ m`, the ratio of consecutive `beta` values is bounded above +by `9 * m / (18 * m + 1)`, which is strictly below `1 / 2`. All divisions here are in `ℚ`. -/ theorem beta_ratio_lt_half (m d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : (beta (19 * m) m d : ℚ) / (beta (19 * m) m (d - 1) : ℚ) ≤ 9 * (m : ℚ) / (18 * (m : ℚ) + 1) ∧ 9 * (m : ℚ) / (18 * (m : ℚ) + 1) < 1 / 2 := by @@ -299,8 +290,8 @@ theorem beta_ratio_lt_half (m d : ℕ) (hd1 : 1 ≤ d) (hd : d ≤ m) : · rw [div_lt_div_iff₀ (by positivity) (by norm_num)] linarith -/-- **Equation (5)**, conclusion: if `L = 19m` then β_d ≤ 2^{-d} M, for `0 ≤ d ≤ m`. -The inequality is multiplied out. -/ +/-- For `d ≤ m` and `L = 19 * m`, the order count satisfies `2 ^ d * beta L m d ≤ M L m`. This +is the division-free geometric bound from [AlmanWilliams2026, equation (5)]. -/ theorem two_pow_mul_beta_le (m d : ℕ) (hd : d ≤ m) : 2 ^ d * beta (19 * m) m d ≤ M (19 * m) m := by induction d with | zero => simp [beta_zero_eq_M] diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean index 464b2a19cc..10b5d1679e 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean @@ -17,7 +17,7 @@ public import Mathlib.Analysis.Complex.ExponentialBounds ## Main results -* `K_mul_N0_le_D_pow`: the band fits when `D^18 ≤ N`. +* `K_mul_N0_le_D_pow`: bound `K L m * N0 L m` by `D m ^ 18` at `L = 19 * m`. * `encoding_size_le`: the shared encoding length bound. ## References @@ -26,7 +26,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -35,11 +34,8 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.4.4: using the general bound binom(L, m) ≤ (eL/m)^m, we have -K = binom(19m, m) ≤ (19e)^m. The general bound is `Nat.choose_le_exp_mul_div_pow`. - -NOTE. The printed bound divides by `m`. In Lean a quotient by 0 is 0 and `0^0 = 1`, so for `m = 0` -both sides are 1 and no hypothesis `m ≥ 1` is needed. -/ +/-- The binomial count at `L = 19 * m` is at most `(19 * Real.exp 1) ^ m`. This uses +`Nat.choose_le_exp_mul_div_pow` and includes `m = 0`, when both sides are one. -/ theorem K_le_pow (m : ℕ) : (K (19 * m) m : ℝ) ≤ (19 * Real.exp 1) ^ m := by rcases Nat.eq_zero_or_pos m with rfl | hm · simp [K] @@ -49,7 +45,7 @@ theorem K_le_pow (m : ℕ) : (K (19 * m) m : ℝ) ≤ (19 * Real.exp 1) ^ m := b field_simp exact hbase ▸ Nat.choose_le_exp_mul_div_pow (19 * m) m -/-- Section 2.4.4: 19e · 3^18 < 4^18. -/ +/-- The real constant `19 * exp 1 * 3 ^ 18` is strictly less than `4 ^ 18`. -/ theorem nineteen_mul_exp_mul_three_pow_lt : 19 * Real.exp 1 * (3 : ℝ) ^ 18 < (4 : ℝ) ^ 18 := by calc 19 * Real.exp 1 * (3 : ℝ) ^ 18 < 19 * 2.7182818286 * (3 : ℝ) ^ 18 := by gcongr @@ -60,7 +56,7 @@ theorem nineteen_mul_exp_mul_three_pow_lt : 19 * Real.exp 1 * (3 : ℝ) ^ 18 < ( private theorem four_pow_eq (m : ℕ) : ((4 : ℝ) ^ 18) ^ m = (D m : ℝ) ^ 18 := by rw [D, Nat.cast_pow, Nat.cast_ofNat, ← pow_mul, ← pow_mul, mul_comm] -/-- Section 2.4.4: so K N₀ ≤ (19e · 3^18)^m ≤ 4^{18m}, and `4^{18m} = D^18`. -/ +/-- At `L = 19 * m`, the product `K L m * N0 L m` is at most `D m ^ 18`. -/ theorem K_mul_N0_le_D_pow (m : ℕ) : K (19 * m) m * N0 (19 * m) m ≤ D m ^ 18 := by have hK := K_le_pow m have key : (K (19 * m) m : ℝ) * (N0 (19 * m) m : ℝ) ≤ (D m : ℝ) ^ 18 := by @@ -76,19 +72,20 @@ theorem K_mul_N0_le_D_pow (m : ℕ) : K (19 * m) m * N0 (19 * m) m ≤ D m ^ 18 _ = (D m : ℝ) ^ 18 := four_pow_eq m exact_mod_cast key -/-- Section 2.4.4, the first consequence of `N ≥ D^18`: K N₀ ≤ D^18 ≤ N. -/ +/-- The size condition `D m ^ 18 ≤ N` bounds `K (19 * m) m * N0 (19 * m) m` by `N`. -/ theorem K_mul_N0_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K (19 * m) m * N0 (19 * m) m ≤ N := (K_mul_N0_le_D_pow m).trans hN -/-- Section 2.4.4, the first consequence of `N ≥ D^18`: and hence K₀ N₀ ≤ K N₀ ≤ D^18 ≤ N. -/ +/-- The size condition `D m ^ 18 ≤ N` bounds the band dimension at `L = 19 * m` by `N`. -/ theorem K0_mul_N0_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : K0 (19 * m) m * N0 (19 * m) m ≤ N := (Nat.mul_le_mul_right _ (Nat.sqrt_le_self _)).trans (K_mul_N0_le m N hN) -/-- Section 2.4.4: This means the padding at most doubles N. -/ +/-- Under `D m ^ 18 ≤ N`, padding to complete bands at `L = 19 * m` gives a dimension at most +`2 * N`. -/ theorem padN_le_two_mul_of_D_pow_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : padN (19 * m) m N ≤ 2 * N := padN_le_two_mul (19 * m) m N (K0_mul_N0_le m N hN) -/-- Section 2.4.4: 10^19 · 2^{1/9} / 3^18 < 4^18. -/ +/-- The real constant `10 ^ 19 * 2 ^ (1 / 9) / 3 ^ 18` is strictly less than `4 ^ 18`. -/ theorem ten_pow_mul_two_rpow_div_three_pow_lt : (10 : ℝ) ^ 19 * (2 : ℝ) ^ (1 / 9 : ℝ) / (3 : ℝ) ^ 18 < (4 : ℝ) ^ 18 := by have htwo : (2 : ℝ) ^ (1 / 9 : ℝ) ≤ 2 := by @@ -100,7 +97,7 @@ theorem ten_pow_mul_two_rpow_div_three_pow_lt : mul_le_mul_of_nonneg_left htwo (by positivity) _ < (4 : ℝ) ^ 18 * (3 : ℝ) ^ 18 := by norm_num -/-- Section 2.4.4: so 10^L · 2^{m/9} / N₀ ≤ D^18, for `L = 19m`. -/ +/-- At `L = 19 * m`, the real quotient `10 ^ L * 2 ^ (m / 9) / N0 L m` is at most `D m ^ 18`. -/ theorem ten_pow_div_N0_le (m : ℕ) : (10 : ℝ) ^ (19 * m) * (2 : ℝ) ^ ((m : ℝ) / 9) / (N0 (19 * m) m : ℝ) ≤ (D m : ℝ) ^ 18 := by have hroot : (2 : ℝ) ^ ((m : ℝ) / 9) = ((2 : ℝ) ^ (1 / 9 : ℝ)) ^ m := by @@ -116,8 +113,8 @@ theorem ten_pow_div_N0_le (m : ℕ) : ten_pow_mul_two_rpow_div_three_pow_lt.le m _ = (D m : ℝ) ^ 18 := four_pow_eq m -/-- **Equation (6)**, the second consequence of `N ≥ D^18`: 10^L ≤ 2^{-m/9} N N₀, for -`L = 19m`. -/ +/-- For `L = 19 * m` and `D m ^ 18 ≤ N`, the encoding index count `10 ^ L` is at most `2 ^ (-m +/ 9) * N * N0 L m`, as a real inequality (cf. [AlmanWilliams2026, equation (6)]). -/ theorem encoding_size_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : (10 : ℝ) ^ (19 * m) ≤ (2 : ℝ) ^ (-(m : ℝ) / 9) * (N : ℝ) * (N0 (19 * m) m : ℝ) := by have hN0 : (0 : ℝ) < (N0 (19 * m) m : ℝ) := by exact_mod_cast N0_pos _ _ diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean index 9cd46e8f7c..b18c209f3c 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean @@ -29,7 +29,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -38,9 +37,9 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-! ### The parameters from Section 2.4.3 on -/ +/-! ### Specialization to `L = 19 * m` -/ -/-- Section 2.4.3: From now on, we fix L = 19m, so that N₀ = 3^{18m}. -/ +/-- At `L = 19 * m`, the outer dimension is `3 ^ (18 * m)`. -/ theorem N0_nineteen_mul (m : ℕ) : N0 (19 * m) m = 3 ^ (18 * m) := by unfold N0 congr 1 @@ -55,12 +54,12 @@ private theorem D_eq_two_pow (m : ℕ) : (D m : ℝ) = (2 : ℝ) ^ (2 * m) := by rw [D, pow_mul] norm_num -/-- Section 2.4.3: Recall that D = 4^m, so 2^m = √D. -/ +/-- The real square root of `D m` is `2 ^ m`. -/ theorem two_pow_eq_sqrt_D (m : ℕ) : (2 : ℝ) ^ m = Real.sqrt (D m) := by rw [D_eq_two_pow, pow_mul', Real.sqrt_sq (by positivity)] -/-- The factor `2^{-m/9}` of Sections 2.4.3 and 2.4.4, by which the costs of the algorithm of -Theorem 5 lie below `N²`. -/ +/-- The positive real factor `2 ^ (-m / 9)`, used in the finite leaf and encoding count +bounds. -/ noncomputable def saving (m : ℕ) : ℝ := (2 : ℝ) ^ (-(m : ℝ) / 9) /-- `2^{-m/9}` is positive. -/ @@ -76,7 +75,7 @@ theorem saving_le_one (m : ℕ) : saving m ≤ 1 := theorem saving_mul_two_rpow (m : ℕ) : saving m * (2 : ℝ) ^ ((m : ℝ) / 9) = 1 := by rw [saving, ← Real.rpow_add two_pos, show -(m : ℝ) / 9 + (m : ℝ) / 9 = 0 by ring, Real.rpow_zero] -/-- Section 2.4.3: and 2^{-m/9} = D^{-1/18}. -/ +/-- Express `saving m` as the real power `D^(-1/18)` for `D = D m`. -/ theorem saving_eq_D_rpow (m : ℕ) : saving m = (D m : ℝ) ^ (-(1 / 18 : ℝ)) := by rw [saving, D_eq_two_pow, ← Real.rpow_natCast 2 (2 * m), ← Real.rpow_mul two_pos.le] congr 1 @@ -85,10 +84,9 @@ theorem saving_eq_D_rpow (m : ℕ) : saving m = (D m : ℝ) ^ (-(1 / 18 : ℝ)) /-! ### The first inequality -/ -/-- In the proof of Lemma 11: We bound the number of leaves of order d in leaves(U) in two -ways: by |U| α_d, charging each output string of U for its leaves of order d, and by β_d, the number -of all leaves of order d. Quoted again in Section 4.3: At every order d, the pruned recursion of -Section 2.4 visits at most min{|U| α_d, β_d} leaves. -/ +/-- If every string in `U` has `m` inner positions, its contributing leaves of natural order +`d` are bounded both by `U.card * alpha m d` and by `beta L m d`. The minimum combines +these bounds without counting a shared leaf more than once. -/ theorem card_leaves_filter_order_le {L m : ℕ} (U : Finset (OutStr L)) (hU : ∀ w ∈ U, (innerSetO w).card = m) (d : ℕ) : ((leaves U).filter fun τ => order m τ = d).card ≤ min (U.card * alpha m d) (beta L m d) := by @@ -120,8 +118,9 @@ theorem card_leaves_filter_order_le {L m : ℕ} (U : Finset (OutStr L)) rw [hempty] exact Nat.zero_le _ -/-- **Lemma 11**, first inequality. For every set U of output strings whose inner sets have -exactly m elements, |leaves(U)| ≤ ∑_{d=0}^{m} min{|U| α_d, β_d}. -/ +/-- Bound the cardinality of `leaves U` by summing the smaller of the per-output bound and the +full leaf count over natural orders `0` through `m`. Every string in `U` must have exactly +`m` inner positions; the empty set is permitted (cf. [AlmanWilliams2026, Lemma 11]). -/ theorem card_leaves_le_sum_min {L m : ℕ} (U : Finset (OutStr L)) (hU : ∀ w ∈ U, (innerSetO w).card = m) : (leaves U).card ≤ ∑ d ∈ range (m + 1), min (U.card * alpha m d) (beta L m d) := by @@ -170,8 +169,8 @@ private theorem sum_beta_lt_of_subset (m a : ℕ) (s : Finset ℕ) (hs : s ⊆ I ((geom_sum_Ico_lt_of_lt_one (by norm_num) (by norm_num) a _).trans_eq (by ring)) hM _ = 2 * (1 / 2 : ℝ) ^ a * (M (19 * m) m : ℝ) := by ring -/-- In the proof of Lemma 11: The orders d > m/9 contribute at most -∑_{d>m/9} β_d ≤ M ∑_{d>m/9} 2^{-d} < 2 · 2^{-m/9} M by (5). -/ +/-- At `L = 19 * m`, the sum of `beta L m d` over `0 ≤ d ≤ m` with `m < 9 * d` is strictly +below `2 * saving m * M L m`. This includes `m = 0`, when the sum is empty. -/ theorem sum_beta_large_orders_lt (m : ℕ) : ((∑ d ∈ (range (m + 1)).filter (fun d => m < 9 * d), beta (19 * m) m d : ℕ) : ℝ) < 2 * saving m * (M (19 * m) m : ℝ) := by @@ -225,12 +224,9 @@ private theorem sum_alpha_small_orders_le_aux (m : ℕ) : rw [← mul_sum, ← add_pow] norm_num -/-- The display in the proof of Lemma 11: ∑_{d ≤ m/9} binom(m, d) 9^d ≤ 72^{m/9} -∑_{d=0}^{m} binom(m, d) 8^{-d} = 72^{m/9} (9/8)^m = (72 · (9/8)^9)^{m/9} < 256^{m/9} = 2^m · -2^{-m/9}. - -NOTE. The strict inequality needs `m ≥ 1`, which is fixed in Section 2.3.3; for `m = 0` both ends -are 1. -/ +/-- For `m ≥ 1`, the sum of `alpha m d` over `0 ≤ d ≤ m` with `9 * d ≤ m` is strictly below `2 +^ m * saving m`. For `m = 0`, the two quantities are equal, so the strict inequality needs +the stated positivity hypothesis. -/ theorem sum_alpha_small_orders_lt (m : ℕ) (hm : 1 ≤ m) : ((∑ d ∈ (range (m + 1)).filter (fun d => 9 * d ≤ m), alpha m d : ℕ) : ℝ) < (2 : ℝ) ^ m * saving m := by @@ -278,8 +274,10 @@ private theorem card_leaves_le_split {L m : ℕ} (U : Finset (OutStr L)) rw [filter_congr fun d _ => (not_le (a := 9 * d) (b := m))] exact sum_le_sum fun d _ => min_le_right _ _ -/-- **Lemma 11**, second inequality. -and if L = 19m, then |leaves(U)| ≤ 2^{-m/9} (2^m |U| + 2M). -/ +/-- For strings of length `19 * m`, each with `m` inner positions, the contributing leaf set +has cardinality at most `saving m * (2 ^ m * U.card + 2 * M (19 * m) m)`. This real bound +permits `m = 0` and `U = ∅`; it makes no execution-cost claim (cf. [AlmanWilliams2026, +Lemma 11]). -/ theorem card_leaves_le_saving {m : ℕ} (U : Finset (OutStr (19 * m))) (hU : ∀ w ∈ U, (innerSetO w).card = m) : ((leaves U).card : ℝ) ≤ saving m * ((2 : ℝ) ^ m * (U.card : ℝ) + 2 * (M (19 * m) m : ℝ)) := by @@ -295,9 +293,9 @@ theorem card_leaves_le_saving {m : ℕ} (U : Finset (OutStr (19 * m))) add_le_add hsmall hlarge _ = _ := by ring -/-- **Lemma 11**, both inequalities together. For every set U of output strings whose inner -sets have exactly m elements, |leaves(U)| ≤ ∑_{d=0}^{m} min{|U| α_d, β_d}, and if L = 19m, -then |leaves(U)| ≤ 2^{-m/9} (2^m |U| + 2M). -/ +/-- Combine the order-wise minimum bound on `leaves U` with the explicit real bound available +when `L = 19 * m`. Both conclusions assume only that each requested string has `m` inner +positions; the specialization is conditional on the displayed equality of parameters. -/ theorem sparse_leaf_bounds {L m : ℕ} (U : Finset (OutStr L)) (hU : ∀ w ∈ U, (innerSetO w).card = m) : (leaves U).card ≤ ∑ d ∈ range (m + 1), min (U.card * alpha m d) (beta L m d) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean index 49c113b4a5..cfe5b34c17 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/ThinProduct.lean @@ -10,16 +10,20 @@ module public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Parameters /-! -# Complete finite thin-product composition +# Finite thin-product correctness and cardinality bounds ## Main results * `thin_product_spec`: exact queried entries for arbitrary signed integer matrices. * `encodings_total_le`, `total_leaves_le`, `total_passed_sets_le`: explicit finite totals. -For `D = 4^m`, `L = 19m`, `D^18 ≤ N` and `|W| 2^m ≤ N²`, encoding entries -and contributing leaves are bounded by constants times `N² / D^(1/18)`. Passed-set -cardinalities carry the additional factor `L + 1`. These are not instruction counts. +Correctness holds for arbitrary signed integer matrices and any layout. For `D = 4^m`, +`L = 19m`, `D^18 ≤ N` and `W.card * 2^m ≤ N²`, the encoding entry count is at most +`4 * D^(-1/18) * N²`, the sum of contributing-leaf cardinalities is at most +`33 * D^(-1/18) * N²`, and the sum of passed request-set cardinalities is at most +`(19m + 1) * 33 * D^(-1/18) * N²`. Here `N` is the original matrix dimension. +The definitions permit zero parameters and empty request sets; the bounds retain their +stated size and sparsity hypotheses. No native program or instruction bound is proved. ## References @@ -27,7 +31,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -36,32 +39,31 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.4.4: Each wanted position (I, J) ∈ W lies in one tile T: the tile of a position is -one of the tiles. -/ +/-- For `m ≤ L`, the band pair of any position `(I, J) : Fin N × Fin N` belongs to the finite +tile set. -/ theorem tile_mem_tiles {L m N : ℕ} (hmL : m ≤ L) (I J : Fin N) : (bandOf L m I, bandOf L m J) ∈ tiles L m N := mem_product.mpr ⟨mem_range.mpr (bandOf_lt_numBands hmL N I I.2), mem_range.mpr (bandOf_lt_numBands hmL N J J.2)⟩ -/-- Section 2.4.4: a wanted position is indexed by one output string of T: its output string is in -the set `W_T` of its tile. -/ +/-- A requested position contributes its output string to `wantedStrings` for its band pair. -/ theorem outStrOfPos_mem_wantedStrings {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) {I J : Fin N} (hIJ : (I, J) ∈ W) : outStrOfPos lay I J ∈ wantedStrings lay W (bandOf L m I) (bandOf L m J) := mem_image.mpr ⟨(I, J), mem_filter.mpr ⟨hIJ, rfl, rfl⟩, rfl⟩ -/-- Section 2.4.3: the sets passed to `Pruned` consist only of […] output strings of length L whose -inner sets […] have exactly m elements. -/ +/-- Every string requested in a tile has exactly `m` inner positions. -/ theorem card_innerSetO_of_mem_wantedStrings {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) (β β' : ℕ) (w : OutStr L) (hw : w ∈ wantedStrings lay W β β') : (innerSetO w).card = m := by obtain ⟨IJ, -, rfl⟩ := mem_image.mp hw exact card_innerSetO_outStrOfPos lay IJ.1 IJ.2 -/-- **Proof of Theorem 5**, correctness of the algorithm. for each tile T with W_T ≠ ∅, -we run Pruned(W_T), and report, for each (I, J) ∈ W, the value at its output string. This is correct -by Lemmas 9 and 10. The run on the tile of `(I, J)` reads the encodings of the input arrays of its -row band and its column band. This holds for all `m ≤ L`. -/ +/-- For arbitrary signed integer matrices of shapes `N × D m` and `D m × N`, the pruned result +at any position in `W` equals the corresponding entry of `X * Y`. The encodings and +request set are those for that position's row and column bands. This holds for every +`Layout L m`, which includes `m ≤ L`, without the size or sparsity conditions used in the +counting bounds (cf. [AlmanWilliams2026, Theorem 5]). -/ theorem thin_product_spec {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (W : Finset (Fin N × Fin N)) (I J : Fin N) (hIJ : (I, J) ∈ W) : @@ -76,8 +78,8 @@ theorem thin_product_spec {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) ( -- Lemma 9, through the tiling of Section 2.3.4. exact Full.bandArray_eq_mul lay X Y I J -/-- Theorem 5 and Section 2.4.4: |W| ≤ N²/√D = 2^{-m} N². In the statements below this -hypothesis is written without a square root, as `|W| · 2^m ≤ N²`. -/ +/-- The real inequality `w ≤ N² / sqrt (D m)` is equivalent to the natural-number inequality +`w * 2 ^ m ≤ N ^ 2`, including `m = 0`. -/ theorem le_sq_div_sqrt_D_iff (m N w : ℕ) : (w : ℝ) ≤ (N : ℝ) ^ 2 / Real.sqrt (D m) ↔ w * 2 ^ m ≤ N ^ 2 := by rw [← two_pow_eq_sqrt_D, le_div_iff₀ (by positivity)] @@ -88,9 +90,10 @@ private theorem numBands_mul_N0_le {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : numBands L m N * N0 L m ≤ padN L m N := Nat.mul_le_mul_left _ (Nat.le_mul_of_pos_left _ (K0_pos hmL)) -/-- Section 2.4.4: By (6), the encodings cost (2N/K₀N₀) · O(10^L) = O(2^{-m/9} N²) operations: the -encodings of the input arrays of all row bands and all column bands have at most `4 N² / D^{1/18}` -entries in all. Here `N` is the size of the input, which the padding at most doubles. -/ +/-- At `L = 19 * m` with `D m ^ 18 ≤ N`, the encodings of all row-band and column-band arrays +have at most `4 * D^(-1/18) * N²` entries in total, where `D = D m`. The counted quantity +is `2 * numBands L m N * 10 ^ L`, and `N` is the original dimension. This is an entry +count, not a bound on instructions or encoding time. -/ theorem encodings_total_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : ((2 * numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) ≤ 4 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by @@ -106,7 +109,8 @@ theorem encodings_total_le (m N : ℕ) (hN : D m ^ 18 ≤ N) : _ ≤ 2 * saving m * (N * (2 * N)) := by gcongr _ = 4 * saving m * (N : ℝ) ^ 2 := by ring -/-- Section 2.4.4: Each position of W lands in one set W_T, so ∑_T |W_T| ≤ |W|. -/ +/-- Summing the cardinalities of the requested-string sets over all tiles gives at most +`W.card`. -/ theorem sum_card_wantedStrings_le {L m N : ℕ} (lay : Layout L m) (W : Finset (Fin N × Fin N)) : ∑ T ∈ tiles L m N, (wantedStrings lay W T.1 T.2).card ≤ W.card := by have hmap : ∀ IJ ∈ W, (bandOf L m IJ.1, bandOf L m IJ.2) ∈ tiles L m N := fun IJ _ => @@ -116,13 +120,14 @@ theorem sum_card_wantedStrings_le {L m N : ℕ} (lay : Layout L m) (W : Finset ( refine card_image_le.trans (le_of_eq ?_) congr 1 -/-- Section 2.4.4: there are at most 4N²/M tiles, so ∑_T M ≤ 4N², where `N` is the padded size. -/ +/-- The sum of `M L m` over the finite tile set is at most `4 * padN L m N ^ 2`. -/ theorem sum_M_le (L m N : ℕ) : ∑ _T ∈ tiles L m N, M L m ≤ 4 * padN L m N ^ 2 := by rw [sum_const, smul_eq_mul] exact card_tiles_mul_M_le L m N -/-- Section 2.4.4, the display: by Lemma 11 summed over the tiles, -`∑_T |leaves(W_T)| ≤ 2^{-m/9} (2^m |W| + 2 ∑_T M)`. -/ +/-- At `L = 19 * m`, bound the sum of contributing-leaf cardinalities for the tile request +sets by `saving m * (2 ^ m * W.card + 2 * ∑ T ∈ tiles L m N, M L m)`. A leaf used in +distinct tiles is counted once for each such tile. -/ theorem sum_card_leaves_le {m N : ℕ} (lay : Layout (19 * m) m) (W : Finset (Fin N × Fin N)) : ((∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card : ℕ) : ℝ) ≤ saving m * ((2 : ℝ) ^ m * (W.card : ℝ) @@ -143,9 +148,10 @@ theorem sum_card_leaves_le {m N : ℕ} (lay : Layout (19 * m) m) (W : Finset (Fi rw [← mul_sum, sum_add_distrib, ← mul_sum, ← mul_sum] _ ≤ _ := by gcongr -/-- **Proof of Theorem 5**, the number of leaves in terms of the `N` and `D` of the theorem (`N` is -the size of the input, not the padded size). Since `N ≥ D^18 ≥ K₀ N₀`, the padding at most doubles -`N`, so `2^m |W| + 2 ∑_T M ≤ N² + 32 N²`. -/ +/-- At `L = 19 * m`, assume `D m ^ 18 ≤ N` and `W.card * 2 ^ m ≤ N ^ 2`. The sum of +contributing-leaf cardinalities over all tiles is then at most `33 * D^(-1/18) * N²`, +where `D = D m` and `N` is the original dimension. This counts leaves with their tile +multiplicities, not executed instructions. -/ theorem total_leaves_le {m N : ℕ} (lay : Layout (19 * m) m) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) (hW : W.card * 2 ^ m ≤ N ^ 2) : ((∑ T ∈ tiles (19 * m) m N, (leaves (wantedStrings lay W T.1 T.2)).card : ℕ) : ℝ) @@ -182,7 +188,10 @@ private theorem sum_totalSize_le {m N : ℕ} (lay : Layout (19 * m) m) exact sum_le_sum fun T _ => Pruned.totalSize_le _ _ _ exact_mod_cast h -/-- **Proof of Theorem 5**, the total size of the sets, in the `N` and `D` of the theorem. -/ +/-- At `L = 19 * m`, assume `D m ^ 18 ≤ N` and `W.card * 2 ^ m ≤ N ^ 2`. Summing +`Pruned.totalSize` over all tiles gives at most `(19 * m + 1) * (33 * D^(-1/18) * N²)`, +where `D = D m` and `N` is the original dimension. The quantity sums request-set +cardinalities through the pruned recursions; it is not an instruction count. -/ theorem total_passed_sets_le {m N : ℕ} (lay : Layout (19 * m) m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (W : Finset (Fin N × Fin N)) (hN : D m ^ 18 ≤ N) (hW : W.card * 2 ^ m ≤ N ^ 2) : diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean index dd34af063b..980e8f750d 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Tiling.lean @@ -17,7 +17,7 @@ import Mathlib.Tactic.Ring ## Main results * `nonempty_layout`: valid layouts exist whenever `m ≤ L`. -* `Full.bandArray_eq_mul`: tiling computes every original integer product entry. +* `Full.bandArray_eq_mul`: product entries as `Full.result` values of band arrays. * `outStrOfPos_injective`: positions in a tile have distinct output strings. ## References @@ -26,7 +26,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -37,11 +36,11 @@ namespace Cslib.Algorithms.MatrixMultiplication.Schonhage /-! ### The numbers -/ -/-- Section 2.3.4: K₀² ≤ K. -/ +/-- The square of the natural square root `K0 L m` is at most `K L m`. -/ theorem K0_sq_le_K (L m : ℕ) : K0 L m ^ 2 ≤ K L m := Nat.sqrt_le' _ -/-- Section 2.3.4: K₀ ≥ √K / 2, squared. -/ +/-- The binomial count `K L m` is at most four times `K0 L m ^ 2`. -/ theorem K_le_four_mul_K0_sq (L m : ℕ) : K L m ≤ 4 * K0 L m ^ 2 := by have hlt : K L m < (K0 L m + 1) ^ 2 := Nat.lt_succ_sqrt' _ rcases Nat.eq_zero_or_pos (K0 L m) with h0 | h0 @@ -55,23 +54,19 @@ theorem K_le_four_mul_K0_sq (L m : ℕ) : K L m ≤ 4 * K0 L m ^ 2 := by theorem bandSize_pos {L m : ℕ} (hmL : m ≤ L) : 0 < bandSize L m := Nat.mul_pos (K0_pos hmL) (N0_pos L m) -/-- Section 2.3.4: we first pad N to a multiple of K₀ N₀. -/ +/-- The padded dimension is a multiple of the band size. -/ theorem bandSize_dvd_padN (L m N : ℕ) : bandSize L m ∣ padN L m N := Dvd.intro_left _ rfl -/-- The padded size is at least `N`. - -NOTE. `m ≤ L` is left implicit in the paper. For `m > L` there is no subset of size `m` and -`K₀ = 0`. -/ +/-- For `m ≤ L`, padding covers all `N` original positions. The parameter condition ensures a +positive band size. -/ theorem le_padN {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : N ≤ padN L m N := by have h := Nat.lt_div_mul_add (a := N + bandSize L m - 1) (bandSize_pos hmL) unfold padN numBands omega -/-- The padded size is the least multiple of `K₀ N₀` that is at least `N`: it is less than -`N + K₀ N₀`. - -NOTE. `m ≤ L` is left implicit in the paper. -/ +/-- For `m ≤ L`, the padded dimension is strictly less than `N + bandSize L m`, including `N = +0`. -/ theorem padN_lt_add_bandSize {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : padN L m N < N + bandSize L m := by have h := Nat.div_mul_le_self (N + bandSize L m - 1) (bandSize L m) @@ -79,7 +74,7 @@ theorem padN_lt_add_bandSize {L m : ℕ} (hmL : m ≤ L) (N : ℕ) : unfold padN numBands omega -/-- Section 2.3.4: the padding at most doubles N since N ≥ K₀ N₀. -/ +/-- If the band size is at most `N`, the padded dimension is at most `2 * N`. -/ theorem padN_le_two_mul (L m N : ℕ) (hN : K0 L m * N0 L m ≤ N) : padN L m N ≤ 2 * N := by rcases Nat.lt_or_ge L m with hLm | hmL · -- for `m > L` there are no subsets, `K₀ = 0`, and the padded size is 0 @@ -93,8 +88,8 @@ theorem padN_le_two_mul (L m N : ℕ) (hN : K0 L m * N0 L m ≤ N) : padN L m N theorem card_tiles (L m N : ℕ) : (tiles L m N).card = numBands L m N ^ 2 := by simp [tiles, sq] -/-- Section 2.3.4: there are (N / K₀N₀)² ≤ 4N²/M tiles, where `N` is the padded size. The -inequality is multiplied out. -/ +/-- The tile count multiplied by `M L m` is at most `4 * padN L m N ^ 2`. The bound is stated +without division and requires no positivity hypothesis. -/ theorem card_tiles_mul_M_le (L m N : ℕ) : (tiles L m N).card * M L m ≤ 4 * padN L m N ^ 2 := by rw [card_tiles] calc numBands L m N ^ 2 * M L m @@ -105,9 +100,7 @@ theorem card_tiles_mul_M_le (L m N : ℕ) : (tiles L m N).card * M L m ≤ 4 * p /-! ### The table of subsets and the matrices of a tile -/ -/-- Section 2.3.4: We fix K₀² ≤ K distinct subsets of {1, …, L} of size m: this can be done. - -NOTE. `m ≤ L` is left implicit in the paper; it is a field of `Layout`. -/ +/-- Indexing data `Layout L m` exist for every `m ≤ L`, including `m = 0`. -/ theorem nonempty_layout {L m : ℕ} (hmL : m ≤ L) : Nonempty (Layout L m) := by have hcard : Fintype.card (Fin (K0 L m) × Fin (K0 L m)) ≤ Fintype.card {Q : Finset (Fin L) // Q.card = m} := by @@ -129,8 +122,7 @@ private theorem sum_table_eq {L m : ℕ} {A : Type*} [AddCommMonoid A] (lay : La (∑ gh', if lay.table gh' = lay.table gh then F gh' else 0) = F gh := by simp [lay.table_injective.eq_iff] -/-- Section 2.3.4: In a tile, let X_Q and Y_Q be the row block and the column block of the block -product with subset Q. For `X_Q`: it is the `g`-th row block of the row band. -/ +/-- At the table entry `(g, h)`, the left family selects row block `β * K0 L m + g`. -/ @[simp] theorem bandFamilyL_table {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) (g h : Fin (K0 L m)) : @@ -145,21 +137,21 @@ theorem bandFamilyR_table {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m bandFamilyR lay Y β (lay.table (g, h)) = colBlock lay Y (β * K0 L m + h) := sum_table_eq lay _ (g, h) -/-- Section 2.3.4: and let X_Q = Y_Q = 0 for the other subsets Q. For `X_Q`. -/ +/-- The left family is zero at any level set outside the table image. -/ theorem bandFamilyL_eq_zero {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (β : ℕ) {Q : Finset (Fin L)} (hQ : ∀ gh, lay.table gh ≠ Q) : bandFamilyL lay X β Q = 0 := sum_eq_zero fun gh _ => ite_eq_right (hQ gh) -/-- Section 2.3.4: and let X_Q = Y_Q = 0 for the other subsets Q. For `Y_Q`. -/ +/-- The right family is zero at any level set outside the table image. -/ theorem bandFamilyR_eq_zero {L m N : ℕ} (lay : Layout L m) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β : ℕ) {Q : Finset (Fin L)} (hQ : ∀ gh, lay.table gh ≠ Q) : bandFamilyR lay Y β Q = 0 := sum_eq_zero fun gh _ => ite_eq_right (hQ gh) -/-! ### One run of `Full` for each tile -/ +/-! ### Product identities for tile arrays -/ -/-- Section 2.3.4: Then one run of Full computes all K₀² block products of the tile (Lemma 9). -The tile is that of the row band `β` and the column band `β'`; the block product in position `(g, -h)` of its grid is the product of the row block `β K₀ + g` by the column block `β' K₀ + h`. -/ +/-- For the band arrays of tile `(β, β')`, `Full.result` at the output string for table entry +`(g, h)` equals the product entry of row block `β * K0 L m + g` and column block `β' * K0 +L m + h`. -/ theorem Full.bandArray_eq_block_mul {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (β β' : ℕ) (g h : Fin (K0 L m)) (r c : OuterStr L m) : @@ -180,8 +172,9 @@ private theorem position_decomp {L m : ℕ} (lay : Layout L m) (I : ℕ) : rw [hblock] exact Nat.div_add_mod' _ _ -/-- Section 2.3.4: and running it on every tile computes all of XY. The entry `(XY)[I, J]` is the -output of the run on the tile of `(I, J)` at the output string of `(I, J)`. -/ +/-- The band arrays for the tile containing an original position `(I, J)` give `Full.result` +equal to `(X * Y) I J` at `outStrOfPos lay I J`. The matrices may have arbitrary signed +integer entries. -/ theorem Full.bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (I J : Fin N) : Full.result L (bandArrayL lay X (bandOf L m I)) (bandArrayR lay Y (bandOf L m J)) (outStrOfPos @@ -196,10 +189,8 @@ theorem Full.bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) simp only [rowBlock, colBlock, Equiv.symm_apply_apply, position_decomp, padRows, padCols, I.isLt, J.isLt, dite_eq_left, Fin.eta] -/-- The entry `(XY)[I, J]` as a value of `bilinearProduct`, that is, as a sum over leaves - (equation (2)). This -is the form in which Section 4 uses the tiling (by (2) and Lemma 9, (X_Q Y_Q)[w] is the sum of the -products at the leaves contributing to w). -/ +/-- At the output string of an original position `(I, J)`, the bilinear product of its two +band arrays equals `(X * Y) I J`. -/ theorem bilinearProduct_bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (I J : Fin N) : @@ -209,9 +200,7 @@ theorem bilinearProduct_bandArray_eq_mul {L m N : ℕ} (lay : Layout L m) (X : M rw [← Full.result_eq_bilinearProduct] exact Full.bandArray_eq_mul lay X Y I J -/-- Every row and every column lies in one of the bands. - -NOTE. `m ≤ L` is left implicit in the paper. -/ +/-- For `m ≤ L`, any index `I < N` has band index below `numBands L m N`. -/ theorem bandOf_lt_numBands {L m : ℕ} (hmL : m ≤ L) (N I : ℕ) (hI : I < N) : bandOf L m I < numBands L m N := by rw [bandOf, Nat.div_lt_iff_lt_mul (bandSize_pos hmL)] @@ -219,8 +208,8 @@ theorem bandOf_lt_numBands {L m : ℕ} (hmL : m ≤ L) (N I : ℕ) (hI : I < N) /-! ### The output string of a position -/ -/-- Section 2.4.4: the inner set of the output string of a position is the subset Q of the block -product containing (I, J). -/ +/-- The inner set of a position's output string is the table entry indexed by its two +within-band block indices. -/ theorem innerSetO_outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : innerSetO (outStrOfPos lay I J) = lay.table (blockOf lay I, blockOf lay J) := innerSetO_outStrOf _ _ _ _ @@ -230,8 +219,8 @@ theorem card_innerSetO_outStrOfPos {L m : ℕ} (lay : Layout L m) (I J : ℕ) : (innerSetO (outStrOfPos lay I J)).card = m := innerSetO_outStrOfPos lay I J ▸ lay.table_card _ -/-- Used implicitly in Section 2.4.4 (is indexed by one output string of T, report […] the value -at its output string): different positions of the same tile have different output strings. -/ +/-- Within a fixed pair of row and column bands, equality of output strings implies equality +of both natural-number position indices. -/ theorem outStrOfPos_injective {L m : ℕ} (lay : Layout L m) (I J I' J' : ℕ) (hI : bandOf L m I = bandOf L m I') (hJ : bandOf L m J = bandOf L m J') (h : outStrOfPos lay I J = outStrOfPos lay I' J') : I = I' ∧ J = J' := by diff --git a/Complexity/Foundations/Algebra/FiniteMagnitude.lean b/Complexity/Foundations/Algebra/FiniteMagnitude.lean index 99c8cad79e..e18125684f 100644 --- a/Complexity/Foundations/Algebra/FiniteMagnitude.lean +++ b/Complexity/Foundations/Algebra/FiniteMagnitude.lean @@ -24,7 +24,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section diff --git a/Complexity/Foundations/Algebra/GeometricTail.lean b/Complexity/Foundations/Algebra/GeometricTail.lean index fa60e6dd2b..c9b5095dc8 100644 --- a/Complexity/Foundations/Algebra/GeometricTail.lean +++ b/Complexity/Foundations/Algebra/GeometricTail.lean @@ -16,7 +16,7 @@ public import Mathlib.Algebra.Order.BigOperators.Ring.Finset ## Main results -* `geom_sum_Ico_lt_of_lt_one`: every finite tail is strictly below the infinite tail. +* `geom_sum_Ico_lt_of_lt_one`: the strict bound for `∑ i ∈ Ico m n, x ^ i` when `0 < x < 1`. ## References @@ -24,7 +24,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section diff --git a/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean b/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean index f47c81e9d7..6036e4144e 100644 --- a/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean +++ b/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean @@ -24,7 +24,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section diff --git a/Complexity/Foundations/Combinatorics/FunctionPlaces.lean b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean index 05960b70c0..e2ac749310 100644 --- a/Complexity/Foundations/Combinatorics/FunctionPlaces.lean +++ b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean @@ -27,7 +27,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -97,8 +96,8 @@ theorem card_powersetCard_superset (T : Finset ι) {k : ℕ} (hk : k ≤ Fintype variable {α : Type*} [Fintype α] -/-- The number of functions `f` with `f i ∈ A i` whose set of places with `p` is exactly `S`: choose -a value with `p` at each place of `S` and one without `p` at every other place. -/ +/-- Count the functions satisfying `f i ∈ A i` and having property `p` exactly on `S` by +multiplying the permitted choices at each position. -/ theorem card_pi_places_eq [DecidableEq α] (A : ι → Finset α) (p : α → Prop) [DecidablePred p] (S : Finset ι) : #{f : ι → α | (∀ i, f i ∈ A i) ∧ ({i | p (f i)} : Finset ι) = S} = @@ -112,8 +111,9 @@ theorem card_pi_places_eq [DecidableEq α] (A : ι → Finset α) (p : α → Pr simp only [mem_filter, mem_univ, true_and, Fintype.mem_piFinset, hmem, forall_and, Finset.ext_iff] -/-- The number of functions `f` with `f i ∈ A i` that have `p` at exactly `k` places, as a sum over -the possible sets of these places. -/ +/-- Partition the functions satisfying `f i ∈ A i` and having property `p` at exactly `k` +positions by their size-`k` set of such positions, and sum the corresponding products of +choice counts. -/ theorem card_pi_places_card [DecidableEq α] (A : ι → Finset α) (p : α → Prop) [DecidablePred p] (k : ℕ) : #{f : ι → α | (∀ i, f i ∈ A i) ∧ #{i | p (f i)} = k} = diff --git a/Complexity/Foundations/Data/Finset/OrderGlue.lean b/Complexity/Foundations/Data/Finset/OrderGlue.lean index 02be86b3f3..9a61f690c7 100644 --- a/Complexity/Foundations/Data/Finset/OrderGlue.lean +++ b/Complexity/Foundations/Data/Finset/OrderGlue.lean @@ -27,7 +27,6 @@ Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical counts and identities; they give no native program or instruction-cost certificate. -Source-derived descriptions below are paraphrases. -/ @[expose] public section @@ -171,9 +170,10 @@ theorem filter_glue (hf : ∀ k, IsInner (f k)) (hg : ∀ k, ¬ IsInner (g k)) : simpa only [filter_eq_iff_forall_level Q hQ, glue_innerLevel, glue_outerLevel] using And.intro hf hg -/-- Let every inner letter `s` be `inner i` for its index `i = innerIndex s`, and every outer letter -be `outer o` for its index `o = outerIndex s`. Then a string whose inner letters are at the levels -of `Q` is rebuilt by gluing the letters `inner i` and `outer o` for the indices of its letters. -/ +/-- Reconstruct `u` by extracting and reinserting its inner and outer indices. The hypotheses +require that both index projections have the stated reconstruction property on their +respective classes of letters, and that the inner letters of `u` occur exactly on the +size-`m` set `Q`. -/ theorem glue_index {I O : Type*} {inner : I → α} {outer : O → α} {innerIndex : α → I} {outerIndex : α → O} (hI : ∀ s, IsInner s → inner (innerIndex s) = s) (hO : ∀ s, ¬ IsInner s → outer (outerIndex s) = s) {u : Fin L → α} From 0505439ad73e746828ba9e5ade7ec8bb4577456e Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 10:47:17 -0400 Subject: [PATCH 17/32] fix(wordram): repair parameter proof hygiene and ceiling-log docs --- Complexity/Algorithms/WordRAM/NatClog.lean | 4 ++-- .../Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/NatClog.lean b/Complexity/Algorithms/WordRAM/NatClog.lean index 6feabd6b67..215117b054 100644 --- a/Complexity/Algorithms/WordRAM/NatClog.lean +++ b/Complexity/Algorithms/WordRAM/NatClog.lean @@ -11,7 +11,7 @@ public import Complexity.Computability.WordRAM.Notation public import Mathlib.Data.Nat.Log /-! -# A charged native binary logarithm +# A charged native ceiling binary logarithm ## Main definitions @@ -30,7 +30,7 @@ open Cslib.WordRAM /-- Quotient, initially the input. -/ abbrev arg : Fin 4 := 0 -/-- Floor logarithm output. -/ +/-- Ceiling logarithm output. -/ abbrev result : Fin 4 := 1 /-- Constant one. -/ abbrev one : Fin 4 := 2 diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean index 3ddc4b7cc4..f26ccbe183 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -841,8 +841,9 @@ theorem size_program (κ : ℕ) : (program κ).size = κ + 71 := by induction κ with | zero => rfl | succ κ ih => simp [power, ih] - simp [program, setup, calculate, hp, NatLog.program, NatLog.body, - NatClog.program, NatClog.body, NatSqrt.program, NatSqrt.body] + simp only [program, setup, calculate, NatLog.program, NatLog.body, NatSqrt.program, + NatSqrt.body, NatClog.program, NatClog.body, Code.size.eq_2, Code.size, Nat.reduceAdd, hp, + Code.size_rename] omega /-- The code is the same at every word width. -/ From ad3e09735a116f9868e87cf7ae6af59e5cb6a285 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 12:45:55 -0400 Subject: [PATCH 18/32] feat(wordram): implement shared tensor encodings Complete the shared left/right encoder milestone #1493 above parent b4936ce28 (#1496). Fixed standard-WordRAM code with 32 registers computes every public encodingL/encodingR leaf from actual initialized flat signed inputs. The native bound is 240*(L+1)*10^L instructions plus halt, with explicit magnitude/address inequalities, two alternating buffers, charged initialization/copying and the complete outside frame. The full ordinary native 3SUM objective #1394 remains open; this component supplies no sparse-query or Exact Triangle solver. Validation of tree b598513f02cc064eaf50d3c680684c19289ffd71 against b4936ce28d8b498b364a7947d34f728c55f8b226: all six gates pass together (build --wfail --iofail, mk_all --check, test --wfail --iofail, lint, lint-style, checkInitImports), full citation-aware hygiene --check-simp, changed-file lints (13 library modules, one new test) and diff checks pass. Tests execute both encoders at depths zero, one and two, compare all 100 depth-two leaves with the unchanged public encodings, preserve input/frame sentinels, and cover adjacent word widths and all three copy block orders. Kernel-checked concrete flat executions discharge every width/magnitude premise. Selected abstracts, catalogue and rendering are unaffected. AI assistance: OpenAI Codex implementation and self-review by delegated worker codex-shared-encoding-t3-03b600bd. Independent correctness/API review is pending; this is not an approval or a claim that the complete 3SUM objective is done. --- Complexity.lean | 12 + .../Schonhage/CoordinateTransform.lean | 191 +++++++++++++ .../MatrixMultiplication/Schonhage/Cell.lean | 129 +++++++++ .../Schonhage/Controller.lean | 250 ++++++++++++++++++ .../MatrixMultiplication/Schonhage/Copy.lean | 48 ++++ .../Schonhage/Encoding.lean | 244 +++++++++++++++++ .../MatrixMultiplication/Schonhage/Exec.lean | 136 ++++++++++ .../MatrixMultiplication/Schonhage/Flat.lean | 88 ++++++ .../MatrixMultiplication/Schonhage/Forms.lean | 235 ++++++++++++++++ .../Schonhage/Header.lean | 174 ++++++++++++ .../MatrixMultiplication/Schonhage/Input.lean | 127 +++++++++ .../Schonhage/Meaning.lean | 96 +++++++ .../MatrixMultiplication/Schonhage/Stage.lean | 203 ++++++++++++++ Complexity/Computability/WordRAM/Copy.lean | 29 +- ComplexityTests/SchonhageEncoding.lean | 139 ++++++++++ NOTICE | 3 +- ROADMAP.md | 10 +- UPSTREAM.md | 18 +- 18 files changed, 2119 insertions(+), 13 deletions(-) create mode 100644 Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean create mode 100644 Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean create mode 100644 ComplexityTests/SchonhageEncoding.lean diff --git a/Complexity.lean b/Complexity.lean index 1cdd3b3c05..04ec040e5d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -31,6 +31,7 @@ public import Complexity.Algorithms.Lean.VanEmdeBoas.Layout public import Complexity.Algorithms.Lean.VanEmdeBoas.Successor public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Batch +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Layout @@ -103,6 +104,17 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Query.Traversal public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Cell +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Controller +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Copy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Exec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Flat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Forms +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Stage public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean new file mode 100644 index 0000000000..eb1392c7db --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean @@ -0,0 +1,191 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +public import Complexity.Foundations.Algebra.FiniteMagnitude +import Mathlib.Tactic.Ring +import Mathlib.Tactic.Linarith + +/-! +# Shared coordinate transforms in flat arrays + +After `j` coordinates have been transformed, the array has `10^j * 7^k` entries, where +`j + k = L`. Its entry at `(p, r)` sums the input entries with suffix `r`, weighting their +prefixes by the product of the selected coefficients. The next coordinate transform computes +each new entry from seven entries of this shared array. It does not independently enumerate +all input entries for every final leaf. + +## Main definitions + +* `CoordinateTransform.weight`: product of coefficients along the transformed prefix. +* `CoordinateTransform.value`: prefix expansion at a mixed-radix array position. + +## Main results + +* `CoordinateTransform.value_step`: the seven-to-ten shared coordinate update. +* `CoordinateTransform.stageSize_le`: every stage has at most `10^L` entries. + +## References + +An iterative implementation invariant for [AlmanWilliams2026, Section 2.4.1]. The coefficients +are those of the existing encoding API, adapted from Anthropic, PBC's formal source at +`16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. This module defines mathematical arrays and sums, +not a separate machine or an instruction-counting semantics. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform + +open Finset + +/-- Product of `j` coefficients, reading prefix digits from least significant to most +significant. Values at noncanonical indices are harmless; clients use indices below the powers. -/ +def weight (c : ℕ → ℕ → ℤ) : ℕ → ℕ → ℕ → ℤ + | 0, _, _ => 1 + | j + 1, p, v => weight c j (p / 10) (v / 7) * c (p % 10) (v % 7) + +/-- Value at transformed prefix `p` and original suffix `r`. The remaining suffix has `k` +base-seven digits; the original input index is `v * 7^k + r`. -/ +def value (c : ℕ → ℕ → ℤ) (j k : ℕ) (a : ℕ → ℤ) (p r : ℕ) : ℤ := + ∑ v ∈ range (7 ^ j), weight c j p v * a (v * 7 ^ k + r) + +private theorem sum_blocks (m n : ℕ) (f : ℕ → ℤ) : + ∑ i ∈ range (m * n), f i = ∑ q ∈ range m, ∑ s ∈ range n, f (q * n + s) := by + induction m with + | zero => simp + | succ m ih => + rw [Nat.succ_mul, sum_range_add, ih, sum_range_succ] + +/-- At the initial stage no coordinate is transformed, so the entry is the input itself. -/ +theorem value_zero (c : ℕ → ℕ → ℤ) (k : ℕ) (a : ℕ → ℤ) (p r : ℕ) : + value c 0 k a p r = a r := by + simp [value, weight] + +/-- The next array entry uses seven already shared values. This is the exact algebraic +invariant of the mixed-radix native loop. -/ +theorem value_step (c : ℕ → ℕ → ℤ) (j k : ℕ) (a : ℕ → ℤ) (p r : ℕ) + {t : ℕ} (ht : t < 10) : + value c (j + 1) k a (p * 10 + t) r = + ∑ s ∈ range 7, c t s * value c j (k + 1) a p (s * 7 ^ k + r) := by + simp only [value, pow_succ, sum_blocks] + rw [sum_comm] + refine sum_congr rfl fun s hs => ?_ + have hs7 := mem_range.mp hs + simp only [mul_sum] + refine sum_congr rfl fun q _ => ?_ + have hp : (p * 10 + t) / 10 = p := by omega + have hq : (q * 7 + s) / 7 = q := by omega + simp only [weight, hp, hq, Nat.add_mod, Nat.mul_mod_left, Nat.mod_eq_of_lt ht, + Nat.mod_eq_of_lt hs7, zero_add] + have hidx : (q * 7 + s) * 7 ^ k + r = q * (7 ^ k * 7) + (s * 7 ^ k + r) := by ring + rw [hidx] + ring + +/-- Each prefix product has absolute value at most one. -/ +theorem abs_weight_le {c : ℕ → ℕ → ℤ} (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) + (j p v : ℕ) : |weight c j p v| ≤ 1 := by + induction j generalizing p v with + | zero => simp [weight] + | succ j ih => + rw [weight, abs_mul] + exact (mul_le_mul (ih _ _) (hc _ (Nat.mod_lt _ (by decide)) _ + (Nat.mod_lt _ (by decide))) (abs_nonneg _) (by decide : (0 : ℤ) ≤ 1)).trans + (by norm_num) + +/-- Intermediate transformed values are bounded from the original input values. -/ +theorem abs_value_le {c : ℕ → ℕ → ℤ} (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) + (j k p r : ℕ) {a : ℕ → ℤ} {A : ℤ} + (ha : ∀ v < 7 ^ j, |a (v * 7 ^ k + r)| ≤ A) : + |value c j k a p r| ≤ (7 ^ j : ℕ) * A := by + exact (abs_sum_mul_le (range (7 ^ j)) (fun v _ => abs_weight_le hc j p v) + (fun v hv => ha v (mem_range.mp hv))).trans_eq (by simp) + +/-- Input bounds suffice for every shared intermediate value, including all depths and all +prefixes. Only the original input segment is used in this estimate. -/ +theorem abs_value_le_of_input {c : ℕ → ℕ → ℤ} + (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) {L j k : ℕ} (hjk : j + k = L) + (p r : ℕ) (hr : r < 7 ^ k) {a : ℕ → ℤ} {A : ℤ} + (ha : ∀ i < 7 ^ L, |a i| ≤ A) : |value c j k a p r| ≤ (7 ^ j : ℕ) * A := by + apply abs_value_le hc + intro v hv + have hprod := Nat.mul_le_mul_right (7 ^ k) (Nat.succ_le_of_lt hv) + have hidx : v * 7 ^ k + r < 7 ^ j * 7 ^ k := by nlinarith only [hprod, hr] + rw [← pow_add, hjk] at hidx + exact ha _ hidx + +private theorem index_cons {b L : ℕ} (s : Fin b) (u : Fin L → Fin b) : + (finFunctionFinEquiv (Fin.cons s u) : ℕ) = s.val + b * (finFunctionFinEquiv u : ℕ) := by + simp only [finFunctionFinEquiv_apply, Fin.sum_univ_succ, Fin.cons_zero, Fin.cons_succ, + Fin.val_zero, pow_zero, mul_one, Fin.val_succ, pow_succ, Finset.mul_sum] + congr 1 + apply sum_congr rfl + intro i _ + ring + +/-- Digitwise coefficients as a total flat table. Only the first ten rows and seven columns +are read by the transform. -/ +def table (c : Fin 10 → Fin 7 → ℤ) (t s : ℕ) : ℤ := + c ⟨t % 10, Nat.mod_lt _ (by decide)⟩ ⟨s % 7, Nat.mod_lt _ (by decide)⟩ + +/-- Flat prefix weights are exactly the product of the original coordinate coefficients. +`finFunctionFinEquiv` supplies the existing Mathlib digit numbering, least significant first. -/ +theorem weight_index (c : Fin 10 → Fin 7 → ℤ) {L : ℕ} + (τ : Fin L → Fin 10) (u : Fin L → Fin 7) : + weight (table c) L (finFunctionFinEquiv τ) (finFunctionFinEquiv u) = + ∏ i, c (τ i) (u i) := by + induction L with + | zero => simp [weight] + | succ L ih => + rw [← Fin.cons_self_tail τ, ← Fin.cons_self_tail u, index_cons, index_cons, weight] + have ht : ((τ 0).val + 10 * (finFunctionFinEquiv (Fin.tail τ) : ℕ)) / 10 = + (finFunctionFinEquiv (Fin.tail τ) : ℕ) := by omega + have hu : ((u 0).val + 7 * (finFunctionFinEquiv (Fin.tail u) : ℕ)) / 7 = + (finFunctionFinEquiv (Fin.tail u) : ℕ) := by omega + have htmod : ((τ 0).val + 10 * (finFunctionFinEquiv (Fin.tail τ) : ℕ)) % 10 = + (τ 0).val := by omega + have humod : ((u 0).val + 7 * (finFunctionFinEquiv (Fin.tail u) : ℕ)) % 7 = + (u 0).val := by omega + rw [ht, hu, htmod, humod, ih, Fin.prod_univ_succ] + simp only [table, Nat.mod_eq_of_lt (τ 0).isLt, Nat.mod_eq_of_lt (u 0).isLt, + Fin.cons_zero, Fin.cons_succ] + exact mul_comm _ _ + +/-- At the final stage the flat transform is the tensor encoding sum on digit strings. +The input array is supplied explicitly by its values at Mathlib's numbered string positions. -/ +theorem value_final (c : Fin 10 → Fin 7 → ℤ) (L : ℕ) + (a : (Fin L → Fin 7) → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u, arr (finFunctionFinEquiv u) = a u) (τ : Fin L → Fin 10) : + value (table c) L 0 arr (finFunctionFinEquiv τ) 0 = + ∑ u, a u * ∏ i, c (τ i) (u i) := by + simp only [value, pow_zero, mul_one, add_zero] + rw [← Fin.sum_univ_eq_sum_range] + rw [← (finFunctionFinEquiv (m := 7) (n := L)).sum_comp] + apply sum_congr rfl + intro u _ + rw [weight_index, harr, mul_comm] + +/-- The mixed-radix stage fits into a buffer of `10^(j+k)` entries. -/ +theorem stageSize_le (j k : ℕ) : 10 ^ j * 7 ^ k ≤ 10 ^ (j + k) := by + rw [pow_add] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by decide : 7 ≤ 10) k) + +/-- Even charging every one of the `L` stages at the maximum array size gives only +`L * 10^L` cells. This is a combinatorial bound; native loop and arithmetic costs are separate. -/ +theorem total_stageSize_le (L : ℕ) : + ∑ j ∈ range L, 10 ^ (j + 1) * 7 ^ (L - (j + 1)) ≤ L * 10 ^ L := by + calc + _ ≤ ∑ _j ∈ range L, 10 ^ L := by + apply sum_le_sum + intro j hj + have hjL : j + 1 ≤ L := by have := mem_range.mp hj; omega + simpa only [Nat.add_sub_of_le hjL] using stageSize_le (j + 1) (L - (j + 1)) + _ = _ := by simp + +end Cslib.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean new file mode 100644 index 0000000000..d743deb96a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean @@ -0,0 +1,129 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Forms +import Mathlib.Tactic.Ring + +/-! +# A native mixed-radix encoding cell + +## Main results + +* `Encoding.exec_cell`: charged index arithmetic, coefficient dispatch, signed linear form, + output store and counter increment for one cell of a shared coordinate transform. + +## References + +The linear forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The registers modified by a cell, apart from the nine signed-arithmetic scratch registers. -/ +def CellLocal (i : Fin 32) : Prop := + i = cell ∨ i = block ∨ i = term ∨ i = suffix ∨ i = address ∨ + i = accumulator ∨ i = group ∨ i = temporary + +instance (i : Fin 32) : Decidable (CellLocal i) := + inferInstanceAs (Decidable (i = cell ∨ i = block ∨ i = term ∨ i = suffix ∨ + i = address ∨ i = accumulator ∨ i = group ∨ i = temporary)) + +private theorem cell_preserves (side : Bool) (i : Fin 32) : + 9 ≤ i.val → ¬CellLocal i → (cellBody side).Preserves i := by + decide +kernel +revert + +set_option maxHeartbeats 2000000 in +-- Symbolic execution retains nine instructions' functional register updates; normalizing them +-- and proving the explicit nonlinear address inequalities exceeds the default heartbeat budget. +/-- One output entry, with all index arithmetic and the counter increment charged. A single +ordinary explicit natural-number bound covers the addresses and index products; no fit +condition is imposed on an unspecified intermediate machine state. -/ +theorem exec_cell {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d i : ℕ) (hP : 0 < P) (hnpos : 0 < n) (hi : i < 10 * P * n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hcell : x.regs cell = i) + (hn : x.regs stride = n) (hseven : x.regs seven = 7) + (hten : x.regs ten = 10) (hone : x.regs one = 1) + (v : Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ a, |v a| ≤ B) + (hmem : ∀ a, x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) : + ∃ y, ExecWithin (w + 2) (cellBody side) x y 205 ∧ + y.mem = Function.update x.mem (d + i) + (encode (∑ a, coefficients side ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ a * v a)) ∧ + y.regs cell = i + 1 ∧ + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) := by + let p := i / (10 * n) + let t : Fin 10 := ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ + let r := i % n + have hp : p < P := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + nlinarith [hi] + have hr : r < n := Nat.mod_lt _ hnpos + have hn1 : 1 ≤ n := hnpos + have hPn : n ≤ P * n := by nlinarith + have hmul : p * n < P * n := Nat.mul_lt_mul_of_pos_right hp hnpos + have h10n : Fits (w + 2) (10 * n) := haddr.of_le (by nlinarith) + have hpfit : Fits (w + 2) p := haddr.of_le (by nlinarith) + have hdiv : i / n ≤ i := Nat.div_le_self _ _ + have hdivfit : Fits (w + 2) (i / n) := haddr.of_le (by nlinarith) + have htfit : Fits (w + 2) t.val := haddr.of_le (by have := t.isLt; omega) + have hrfit : Fits (w + 2) r := haddr.of_le (by nlinarith) + have hp7 : Fits (w + 2) (p * 7) := haddr.of_le (by nlinarith) + have hp7n : Fits (w + 2) (p * 7 * n) := haddr.of_le (by nlinarith) + have hp7nr : Fits (w + 2) (p * 7 * n + r) := haddr.of_le (by nlinarith) + have hgroup : Fits (w + 2) (s + (p * 7 * n + r)) := haddr.of_le (by nlinarith) + let z := ((((((((x.set temporary (10 * n)).set block p).set term (i / n)).set term t.val).set + suffix r).set group (p * 7)).set group (p * 7 * n)).set group (p * 7 * n + r)).set + group (s + (p * 7 * n + r)) + have hz : ExecWithin (w + 2) cellSetup x z 9 := by + ram_eval [cellSetup, z, p, t, r, hsrc, hcell, hn, hseven, hten, + h10n, hpfit, hdivfit, htfit, hrfit, hp7, hp7n, hp7nr, hgroup, + temporary, block, term, suffix, group, src, cell, stride, seven, ten, Fin.mk.injEq] + have hzterm : z.regs term = t.val := by + simp [z, temporary, block, term, suffix, group] + have hzgroup : z.regs group = s + (p * 7 * n + r) := by simp [z] + have hzn : z.regs stride = n := by + simpa [z, temporary, block, term, suffix, group, stride, Fin.mk.injEq] using hn + have hzmem : ∀ a, z.mem (s + (p * 7 * n + r) + a.val * n) = encode (v a) := by + intro a + have hidx : s + (p * 7 * n + r) + a.val * n = s + (p * 7 + a.val) * n + r := by ring + simpa only [z, Data.set_mem, hidx] using hmem a + have hspan : Fits (w + 2) (s + (p * 7 * n + r) + 7 * n) := + haddr.of_le (by nlinarith) + obtain ⟨u, hu, huacc, humem, hureg⟩ := exec_dispatch hw side (List.finRange 10) t + (List.mem_finRange _) z B (s + (p * 7 * n + r)) n v hzterm hzgroup hzn hzmem + hB hv hval hspan (haddr.of_le (by omega)) + have zucell : u.regs cell = i := by + rw [hureg cell (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, cell, Fin.mk.injEq] using hcell + have zudst : u.regs dst = d := by + rw [hureg dst (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, dst, Fin.mk.injEq] using hdst + have zuone : u.regs one = 1 := by + rw [hureg one (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, one, Fin.mk.injEq] using hone + have hdfit : Fits (w + 2) (d + i) := haddr.of_le (by nlinarith) + have hinc : Fits (w + 2) (i + 1) := haddr.of_le (by nlinarith) + let y := ((u.set address (d + i)).write (d + i) (u.regs accumulator)).set cell (i + 1) + have hy : ExecWithin (w + 2) cellFinish u y 3 := by + ram_eval [cellFinish, y, zucell, zudst, zuone, hdfit, hinc, + address, cell, accumulator, dst, one, Fin.mk.injEq] + have he : ExecWithin (w + 2) (cellBody side) x y 205 := by + simpa only [cellBody, List.length_finRange] using (hz.seq (hu.seq hy)).mono + (by decide : 9 + ((3 * 10 + 163) + 3) ≤ 205) + refine ⟨y, he, ?_, ?_, fun a ha hl => he.regs_eq (cell_preserves side a ha hl)⟩ + · simp only [y, Data.set_mem, Data.write_mem, humem, z, huacc] + rfl + · simp [y] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean new file mode 100644 index 0000000000..04eabe5c36 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean @@ -0,0 +1,250 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Copy +import Mathlib.Tactic.Ring + +/-! +# Alternating buffers for the shared native encoder + +## Main definitions + +* `Encoding.destination`, `Encoding.source`: the alternating buffer addresses. +* `Encoding.Progress`: the mixed-radix array and frame invariant after a stage. + +## References + +The shared encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- Destination of the next coordinate transform; always one of the two fixed buffers. -/ +def destination (L : ℕ) : ℕ → ℕ + | 0 => outputBase L + | j + 1 => if destination L j = outputBase L then scratchBase L else outputBase L + +/-- Source of the next coordinate transform; initially the preserved input segment. -/ +def source (L : ℕ) : ℕ → ℕ + | 0 => 2 + | j + 1 => destination L j + +/-- A destination is either the fixed output block or the scratch block. -/ +theorem destination_cases (L j : ℕ) : + destination L j = outputBase L ∨ destination L j = scratchBase L := by + cases j with + | zero => exact Or.inl rfl + | succ j => unfold destination; split <;> simp + +/-- After the initial transform, the source is also one of the two buffers. -/ +theorem source_cases {L j : ℕ} (hj : 0 < j) : + source L j = outputBase L ∨ source L j = scratchBase L := by + cases j with + | zero => omega + | succ j => exact destination_cases L j + +/-- The two alternating buffer addresses differ at every stage. -/ +theorem source_ne_destination (L j : ℕ) : source L j ≠ destination L j := by + have hpos : 0 < 10 ^ L := by positivity + have h7pos : 0 < 7 ^ L := by positivity + cases j with + | zero => simp only [source, destination, outputBase]; omega + | succ j => + rcases destination_cases L j with h | h + · simp only [source, destination, h, ↓reduceIte] + unfold scratchBase + omega + · have hne : scratchBase L ≠ outputBase L := by unfold scratchBase; omega + simp only [source, destination, h, hne, ↓reduceIte] + exact hne + +/-- Both current addresses are bounded by the end of the second buffer. -/ +theorem buffer_address_le (L j : ℕ) : + source L j ≤ 2 + 2 * 10 ^ L ∧ destination L j ≤ 2 + 2 * 10 ^ L := by + have h7 := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + have hd := destination_cases L j + have hs : source L j = 2 ∨ source L j = outputBase L ∨ source L j = scratchBase L := by + cases j with + | zero => exact Or.inl rfl + | succ j => exact Or.inr (destination_cases L j) + simp only [scratchBase, outputBase] at hs hd + constructor <;> omega + +/-- Arrays stored at consecutive buffers are disjoint, in either order. The initial input +ends exactly at the first output address. -/ +theorem buffers_separated {L j k N : ℕ} (hjk : j + k = L) + (hN : N ≤ 10 ^ L) : + source L j + 10 ^ j * 7 ^ k ≤ destination L j ∨ + destination L j + N ≤ source L j := by + have hsize : 10 ^ j * 7 ^ k ≤ 10 ^ L := by simpa only [hjk] using stageSize_le j k + cases j with + | zero => + left + simp only [Nat.zero_add] at hjk + simp [source, destination, outputBase, hjk] + | succ j => + have hs := source_cases (L := L) (j := j + 1) (by omega) + have hd := destination_cases L (j + 1) + have hne := source_ne_destination L (j + 1) + unfold scratchBase at hs hd + omega + +/-- The shared-array invariant at depth `j`. All fields describe ordinary registers or +mathematical memory values. Execution, timing and width are proved separately. -/ +structure Progress (side : Bool) (L j : ℕ) (arr : ℕ → ℤ) (initial : ℕ → ℕ) + (y : Data 32) : Prop where + /-- Fixed parameters retained across coordinate stages. -/ + fixed : y.regs depth = L ∧ y.regs capacity = 10 ^ L ∧ + y.regs output = outputBase L ∧ y.regs scratch = scratchBase L ∧ + y.regs one = 1 ∧ y.regs seven = 7 ∧ y.regs ten = 10 + /-- Number of transformed coordinates. -/ + stage : y.regs Encoding.stage = j + /-- Number of transformed prefixes. -/ + prefixes : y.regs Encoding.prefixes = 10 ^ j + /-- Original suffix length in the current source. -/ + stride : y.regs Encoding.stride = 7 ^ (L - j) + /-- Current source address. -/ + src : y.regs Encoding.src = source L j + /-- Current destination address. -/ + dst : y.regs Encoding.dst = destination L j + /-- The source holds exactly the shared prefix sums. -/ + values : ∀ p < 10 ^ j, ∀ r < 7 ^ (L - j), + y.mem (source L j + p * 7 ^ (L - j) + r) = + encode (value (table (coefficients side)) j (L - j) arr p r) + /-- Input, headers and all cells outside both work buffers are preserved. -/ + frame : ∀ a, ¬(outputBase L ≤ a ∧ a < scratchBase L + 10 ^ L) → y.mem a = initial a + +/-- The actual header initialization establishes the zero-stage invariant from the original +flat input segment. No scratch values or preinitialized coefficient tables are required. -/ +theorem progress_zero (side : Bool) (L : ℕ) (arr : ℕ → ℤ) (x y : Data 32) + (hy : SetupPost L x y) (hinput : ∀ i < 7 ^ L, x.mem (2 + i) = encode (arr i)) : + Progress side L 0 arr x.mem y := by + refine ⟨⟨hy.depth, hy.capacity, hy.output, hy.scratch, hy.one, hy.seven, hy.ten⟩, + hy.stage, hy.prefixes, ?_, hy.src, hy.dst, ?_, ?_⟩ + · simpa only [Nat.sub_zero] using hy.stride + · intro p hp r hr + have hp0 : p = 0 := by simpa only [pow_zero] using Nat.eq_zero_of_le_zero (by omega : p ≤ 0) + subst p + rw [hy.mem, value_zero] + simpa only [source, Nat.sub_zero, Nat.zero_mul, Nat.add_zero] using hinput r hr + · intro a _ + rw [hy.mem] + +/-- One proved native stage advances the mixed-radix invariant. The signed magnitude bound +comes from the original input and the number of coordinates already transformed. -/ +theorem progress_step {w L j : ℕ} (hw : 0 < w) (hj : j < L) (side : Bool) + (arr : ℕ → ℤ) (initial : ℕ → ℕ) (x : Data 32) (hx : Progress side L j arr initial x) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (stageBody side) x y (207 * 10 ^ L + 12) ∧ + Progress side L (j + 1) arr initial y := by + let k := L - (j + 1) + let n := 7 ^ k + let P := 10 ^ j + have hn : 0 < n := by dsimp only [n]; positivity + have hP : 0 < P := by dsimp only [P]; positivity + have hk : L - j = k + 1 := by omega + have hjk : j + (k + 1) = L := by omega + have hjk' : j + 1 + k = L := by omega + have hstride : 7 ^ (L - j) = 7 * n := by rw [hk, pow_succ'] + have hN : 10 * P * n ≤ 10 ^ L := by + have h := stageSize_le (j + 1) k + simpa only [hjk', pow_succ', P, n] using h + have hcoeff : ∀ t < 10, ∀ s < 7, |table (coefficients side) t s| ≤ 1 := by + intro t _ s _ + unfold table coefficients + cases side + · exact abs_phi_le_one _ _ + · exact abs_psi_le_one _ _ + let v (i : ℕ) (a : Fin 7) : ℤ := value (table (coefficients side)) j (k + 1) arr + (i / (10 * n)) (a.val * n + i % n) + let B : ℤ := (7 ^ j : ℕ) * A + have hB : 0 ≤ B := mul_nonneg (by positivity) hA + have hsuffix (i : ℕ) (a : Fin 7) : a.val * n + i % n < 7 ^ (k + 1) := by + have hm := Nat.mul_le_mul_right n (Nat.succ_le_of_lt a.isLt) + have hr := Nat.mod_lt i hn + rw [pow_succ'] + change a.val * n + i % n < 7 * n + nlinarith only [hm, hr] + have hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B := by + intro i _ a + exact abs_value_le_of_input hcoeff hjk _ _ (hsuffix i a) ha + have hload : ∀ i < 10 * P * n, ∀ a, + x.mem (source L j + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a) := by + intro i hi a + have hp : i / (10 * n) < 10 ^ j := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + dsimp only [P] at hi + nlinarith only [hi] + have hidx : source L j + (i / (10 * n) * 7 + a.val) * n + i % n = + source L j + i / (10 * n) * 7 ^ (L - j) + (a.val * n + i % n) := by + rw [hstride] + ring + rw [hidx, hx.values _ hp _ (by rw [hk]; exact hsuffix i a)] + rw [hk] + have hvalue : 7 * B < (2 : ℤ) ^ (w - 1) := by + have hpow : 7 ^ (j + 1) ≤ 7 ^ L := Nat.pow_le_pow_right (by decide) (by omega) + have hpowZ : ((7 ^ (j + 1) : ℕ) : ℤ) ≤ (7 ^ L : ℕ) := by exact_mod_cast hpow + have hprod := mul_le_mul_of_nonneg_right hpowZ hA + simpa only [Nat.pow_succ', Nat.cast_mul, Nat.cast_ofNat, mul_assoc, B] using + hprod.trans_lt hmag + have hbound := buffer_address_le L j + have hcap : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + have hnative : Fits (w + 2) (source L j + destination L j + 64 + 64 * P * n) := + haddr.of_le (by unfold addressBound; nlinarith only [hbound.1, hbound.2, hN, hcap]) + have hseparate : source L j + 7 * P * n ≤ destination L j ∨ + destination L j + 10 * P * n ≤ source L j := by + have h := buffers_separated (k := L - j) (by omega : j + (L - j) = L) hN + simpa only [hstride, P, Nat.mul_assoc, Nat.mul_comm, Nat.mul_left_comm] using h + obtain ⟨y, he, hm, hf, hsrc, hdst, hstr, hp, hstage, hreg⟩ := exec_stageBody hw side x + P n (source L j) (destination L j) j (outputBase L) (scratchBase L) hP hn hx.src hx.dst + hx.stage (by simpa only [hstride] using hx.stride) hx.prefixes hx.fixed.2.2.2.2.2.1 + hx.fixed.2.2.2.2.2.2 hx.fixed.2.2.2.2.1 hx.fixed.2.2.1 hx.fixed.2.2.2.1 v B hB hv hload + hvalue hnative (haddr.of_le (by unfold addressBound; omega)) hseparate + refine ⟨y, he.mono (by omega), ?_⟩ + have hr (a : Fin 32) (ha : 9 ≤ a.val) (hl : ¬StageLocal a) : y.regs a = x.regs a := hreg a ha hl + refine ⟨?_, hstage, ?_, ?_, hsrc, hdst, ?_, ?_⟩ + · rcases hx.fixed with ⟨hd, hc, ho, hs, h1, h7, h10⟩ + exact ⟨(hr depth (by decide) (by decide)).trans hd, + (hr capacity (by decide) (by decide)).trans hc, + (hr output (by decide) (by decide)).trans ho, + (hr scratch (by decide) (by decide)).trans hs, + (hr one (by decide) (by decide)).trans h1, + (hr seven (by decide) (by decide)).trans h7, + (hr ten (by decide) (by decide)).trans h10⟩ + · simpa only [P, pow_succ'] using hp + · exact hstr + · intro p hpp r hrr + have hrr' : r < n := hrr + have hpp' : p < 10 * P := by simpa only [pow_succ', P] using hpp + have hprod := Nat.mul_le_mul_right n (Nat.succ_le_of_lt hpp') + have hi : p * n + r < 10 * P * n := by nlinarith only [hprod, hrr'] + have hdiv : (p * n + r) / n = p := by + rw [Nat.mul_comm p n, Nat.mul_add_div hn, Nat.div_eq_of_lt hrr', Nat.add_zero] + have hmod : (p * n + r) % n = r := Nat.mul_add_mod_of_lt hrr' + have hvout := hm (p * n + r) hi + rw [show stageResult side n v (p * n + r) = value (table (coefficients side)) + (j + 1) k arr ((p * n + r) / n) ((p * n + r) % n) from stageResult_value side j k _ arr, + hdiv, hmod] at hvout + simpa only [source, n, k, Nat.add_assoc] using hvout + · intro a haout + rw [hf a ?_] + · exact hx.frame a haout + · rcases destination_cases L j with hd | hd <;> rw [hd] <;> unfold scratchBase at * <;> omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean new file mode 100644 index 0000000000..8ada703386 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean @@ -0,0 +1,48 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +/-! +# Copying the final encoding to its fixed output block + +## Main results + +* `Encoding.exec_copyOut`: reuse the native array copy, including equal blocks and both + possible disjoint address orders, with its charged `7n+4` bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM + +/-- The final copy uses seven instructions per output word plus four instructions of setup +and loop termination. Equal source/output blocks are permitted, so odd and even depths use +the same fixed program. All other registers retain their values. -/ +theorem exec_copyOut {w : ℕ} (x : Data 32) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs capacity)) + (hd : Fits w (x.regs output + x.regs capacity)) + (hdisj : x.regs src + x.regs capacity ≤ x.regs output ∨ + x.regs output + x.regs capacity ≤ x.regs src ∨ x.regs src = x.regs output) : + ∃ y, ExecWithin w copyOut x y (7 * x.regs capacity + 4) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (x.regs src) (x.regs output) (x.regs capacity) ∧ + (∀ i, i ≠ cell → i ≠ address → i ≠ accumulator → i ≠ one → y.regs i = x.regs i) := by + obtain ⟨z, hz, hm, _, _, _⟩ := ArrayCopy.exec_of_separated_or_eq (x.comap copyRegisters) + h1 hs hd hdisj + have he := hz.rename copyRegisters_injective + refine ⟨z.extend copyRegisters x, he, hm, ?_⟩ + intro i hc ha hv ho + apply he.regs_eq + have hp : ∀ i : Fin 32, i ≠ cell → i ≠ address → i ≠ accumulator → i ≠ one → + copyOut.Preserves i := by decide + exact hp i hc ha hv ho + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean new file mode 100644 index 0000000000..10afd12bbb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean @@ -0,0 +1,244 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full +public import Complexity.Computability.WordRAM.Signed.Arithmetic +public import Complexity.Computability.WordRAM.Copy + +/-! +# Native shared tensor encoding + +The two fixed programs use unsigned addresses and counters, and sign-and-magnitude encoded +integer entries. Address 1 holds the depth; the `7^L` encoded entries begin at address 2. +The ordinary `State.init` length header is at address 0. Output and scratch are consecutive +blocks of `10^L` cells immediately after the input. Each coordinate transform reads the +previous stage and writes the other buffer. No initialization of unread scratch is needed. +A final charged copy puts the result in the first buffer, also at depth zero. + +The coordinate transform is a new iterative implementation of the linear forms in +[AlmanWilliams2026, Section 2.4.1]. The coefficients and mathematical encoding are selectively +adapted from Anthropic's formal-math revision `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +[`Step`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/Step.lean) +and [`Recursion`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/Recursion.lean). The source's +recursive machine and execution semantics are not used. + +## Main definitions + +* `Encoding.program`: fixed native code for either coefficient table. +* `Encoding.input`: concrete input for `State.init`, including the depth header. +* `Encoding.outputBase`, `Encoding.scratchBase`: disjoint output and scratch blocks. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Seven variable digits: the three outer variables, then the four inner variables. -/ +def leftAt (s : Fin 7) : LeftVar := + if h : s.val < 3 then .x ⟨s.val, h⟩ + else .p ⟨(s.val - 3) / 2, by omega⟩ ⟨(s.val - 3) % 2, Nat.mod_lt _ (by decide)⟩ + +/-- Right-variable digits use the same order as the left-variable digits. -/ +def rightAt (s : Fin 7) : RightVar := + if h : s.val < 3 then .y ⟨s.val, h⟩ + else .q ⟨(s.val - 3) / 2, by omega⟩ ⟨(s.val - 3) % 2, Nat.mod_lt _ (by decide)⟩ + +/-- Row-major order on the nine `P` terms, followed by `P0`. -/ +def termAt (t : Fin 10) : Term := + if h : t.val < 9 then .P ⟨t.val / 3, by omega⟩ ⟨t.val % 3, Nat.mod_lt _ (by decide)⟩ + else .P0 + +/-- Explicit numbering of the seven left variables. -/ +def leftDigits : Fin 7 ≃ LeftVar where + toFun := leftAt + invFun + | .x i => ⟨i.val, by omega⟩ + | .p i j => ⟨3 + 2 * i.val + j.val, by omega⟩ + left_inv := by decide + right_inv := by decide + +/-- Explicit numbering of the seven right variables. -/ +def rightDigits : Fin 7 ≃ RightVar where + toFun := rightAt + invFun + | .y j => ⟨j.val, by omega⟩ + | .q i j => ⟨3 + 2 * i.val + j.val, by omega⟩ + left_inv := by decide + right_inv := by decide + +/-- Explicit numbering of all ten terms. -/ +def termDigits : Fin 10 ≃ Term where + toFun := termAt + invFun + | .P i j => ⟨3 * i.val + j.val, by omega⟩ + | .P0 => 9 + left_inv := by decide + right_inv := by decide + +/-- The left and right coefficient tables, with digits made explicit. -/ +def coefficients (rightSide : Bool) (t : Fin 10) (s : Fin 7) : ℤ := + if rightSide then psi (termAt t) (rightAt s) else phi (termAt t) (leftAt s) + +/-- Every actual coefficient is zero, one or minus one. -/ +theorem coefficients_values (side : Bool) (t : Fin 10) (s : Fin 7) : + coefficients side t s = 0 ∨ coefficients side t s = 1 ∨ coefficients side t s = -1 := by + decide +revert + +/-- The input block ends where the output block begins. -/ +def outputBase (L : ℕ) : ℕ := 2 + 7 ^ L + +/-- The scratch block immediately follows the entire output block. -/ +def scratchBase (L : ℕ) : ℕ := outputBase L + 10 ^ L + +/-- Concrete input: depth header followed by the signed input entries in flat order. +The caller encodes each integer with `Signed.encode` before passing the list to `State.init`. +The native unsigned length header is installed by `State.init` itself. -/ +def input (L : ℕ) (a : Fin (7 ^ L) → ℤ) : List ℕ := + L :: List.ofFn (fun i => encode (a i)) + +/-- The concrete left-array input, using Mathlib's base-seven string numbering. -/ +def inputL {L : ℕ} (a : LeftStr L → ℤ) : List ℕ := + input L fun i => a fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ) + +/-- The concrete right-array input, in exactly the same flat memory layout. -/ +def inputR {L : ℕ} (a : RightStr L → ℤ) : List ℕ := + input L fun i => a fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ) + +/-- All thirty-two registers are fixed independently of depth and width. Registers 0–8 +are precisely the existing signed-arithmetic workspace. -/ +abbrev depth : Fin 32 := 9 +@[inherit_doc depth] abbrev inputSize : Fin 32 := 10 +@[inherit_doc depth] abbrev capacity : Fin 32 := 11 +@[inherit_doc depth] abbrev src : Fin 32 := 12 +@[inherit_doc depth] abbrev dst : Fin 32 := 13 +@[inherit_doc depth] abbrev output : Fin 32 := 14 +@[inherit_doc depth] abbrev scratch : Fin 32 := 15 +@[inherit_doc depth] abbrev stride : Fin 32 := 16 +@[inherit_doc depth] abbrev prefixes : Fin 32 := 17 +@[inherit_doc depth] abbrev stage : Fin 32 := 18 +@[inherit_doc depth] abbrev cell : Fin 32 := 19 +@[inherit_doc depth] abbrev block : Fin 32 := 20 +@[inherit_doc depth] abbrev term : Fin 32 := 21 +@[inherit_doc depth] abbrev suffix : Fin 32 := 22 +@[inherit_doc depth] abbrev address : Fin 32 := 23 +@[inherit_doc depth] abbrev accumulator : Fin 32 := 24 +@[inherit_doc depth] abbrev one : Fin 32 := 25 +@[inherit_doc depth] abbrev seven : Fin 32 := 26 +@[inherit_doc depth] abbrev ten : Fin 32 := 27 +@[inherit_doc depth] abbrev group : Fin 32 := 28 +@[inherit_doc depth] abbrev temporary : Fin 32 := 29 +@[inherit_doc depth] abbrev stageSize : Fin 32 := 30 + +/-- One coefficient in `{0, 1, -1}` times one array entry. Zero coefficients cause no load. +Nonzero coefficients use the proved native signed addition or subtraction block. -/ +def summand (s : Fin 7) (c : ℤ) : Code .standard 32 := + if c = 0 then ram[ accumulator ← accumulator ] + else .seq (ram[ + temporary ← #s.val; + address ← temporary * stride; address ← group + address; + right ← *address; left ← accumulator ]) + (.seq (if c = 1 then add else subtract) (ram[ accumulator ← result ])) + +/-- A finite unrolling of the seven coefficients. This recursion constructs code; it does +not recurse on the input depth. -/ +def sumCode : List (Fin 7 × ℤ) → Code .standard 32 + | [] => ram[ accumulator ← accumulator ] + | (s, c) :: rest => .seq (summand s c) (sumCode rest) + +/-- One linear form, including accumulator initialization. -/ +def form (side : Bool) (t : Fin 10) : Code .standard 32 := + .seq (ram[ accumulator ← #0 ]) + (sumCode (List.ofFn fun s => (s, coefficients side t s))) + +/-- A fixed finite dispatch on the ten possible term digits. -/ +def dispatch (side : Bool) : List (Fin 10) → Code .standard 32 + | [] => ram[ accumulator ← #0 ] + | t :: rest => ram[ + temporary ← #t.val; + if term = temporary then [ ⟪form side t⟫ ] else [ ⟪dispatch side rest⟫ ] ] + +/-- Decode one flat output index into its mixed-radix coordinates and source group. -/ +def cellSetup : Code .standard 32 := ram[ + temporary ← ten * stride; + block ← cell / temporary; + term ← cell / stride; term ← term % ten; + suffix ← cell % stride; + group ← block * seven; group ← group * stride; + group ← group + suffix; group ← src + group ] + +/-- Store the computed entry and advance the flat index. -/ +def cellFinish : Code .standard 32 := ram[ + address ← dst + cell; *address ← accumulator; + cell ← cell + one ] + +/-- One output cell. The flat index is `(prefix * 10 + term) * stride + suffix`; +its seven source addresses are `(prefix * 7 + s) * stride + suffix`. -/ +def cellBody (side : Bool) : Code .standard 32 := + .seq cellSetup (.seq (dispatch side (List.finRange 10)) cellFinish) + +/-- Divide the suffix stride and compute the next stage size. -/ +def stageHead : Code .standard 32 := ram[ + stride ← stride / seven; + stageSize ← prefixes * ten; stageSize ← stageSize * stride; + cell ← #0 ] + +/-- Alternate the two buffers and advance the prefix and stage counters. -/ +def stageTail : Code .standard 32 := ram[ + src ← dst; + if dst = output then [ dst ← scratch ] else [ dst ← output ]; + prefixes ← prefixes * ten; stage ← stage + one ] + +/-- Transform the next coordinate, then alternate the output buffers. -/ +def stageBody (side : Bool) : Code .standard 32 := + .seq stageHead (.seq (.loop (.lt cell stageSize) (cellBody side)) stageTail) + +/-- Load the actual depth header and initialize the power counters. -/ +def setupHead : Code .standard 32 := ram[ + one ← #1; seven ← #7; ten ← #10; + address ← #1; depth ← *address; + inputSize ← #1; capacity ← #1; stage ← #0 ] + +/-- One multiplication by each radix and one counter increment. -/ +def powerBody : Code .standard 32 := ram[ + inputSize ← inputSize * seven; capacity ← capacity * ten; stage ← stage + one ] + +/-- Compute the concrete buffer addresses from the powers and initialize the first stage. -/ +def setupTail : Code .standard 32 := ram[ + temporary ← #2; output ← temporary + inputSize; scratch ← output + capacity; + src ← #2; dst ← output; stride ← inputSize; prefixes ← #1; stage ← #0 ] + +/-- Read the depth and compute both powers in a charged native loop. -/ +def setup : Code .standard 32 := + .seq setupHead (.seq (.loop (.lt stage depth) powerBody) setupTail) + +/-- Reuse the existing seven-register array copy inside the encoder's register file. -/ +def copyRegisters : Fin 7 → Fin 32 := ![src, output, capacity, cell, address, accumulator, one] + +/-- The seven copy registers are distinct. -/ +theorem copyRegisters_injective : Function.Injective copyRegisters := by decide + +/-- A charged copy into the fixed output block, permitting disjoint blocks in either order +or identical source and destination. -/ +def copyOut : Code .standard 32 := ArrayCopy.program.rename copyRegisters + +/-- The complete fixed native encoder, excluding its final halt. The returned `output` +register identifies the first output cell; the `capacity` register holds its length. -/ +def program (side : Bool) : Code .standard 32 := ram[ + ⟪setup⟫; + while stage < depth do [ ⟪stageBody side⟫ ]; + ⟪copyOut⟫ ] + +/-- The code performs no word-width queries. -/ +theorem program_readsWidth (side : Bool) : ¬(program side).ReadsWidth := by + cases side <;> decide + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean new file mode 100644 index 0000000000..1d80c56e63 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean @@ -0,0 +1,136 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Controller +import Mathlib.Tactic.Ring + +/-! +# Complete charged execution of the shared encoder + +## Main results + +* `Encoding.exec_coordinates`: all shared coordinate transforms, with charged loop control. +* `Encoding.exec`: complete initialization, transforms and fixed-output copy with an explicit + `240 * (L+1) * 10^L` native instruction bound. + +The code is directly over the standard unsigned WordRAM. Its signed add/subtract blocks use +two extra target bits and respectively at most fifteen/seventeen actual instructions. There +is no whole-program signed compiler in this construction and no uncharged simulation premise. + +## References + +The encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- Explicit total instruction bound, including header preparation, all index arithmetic, +stage and cell loop control, and the final copy. A final halt costs one additional instruction. -/ +def timeBound (L : ℕ) : ℕ := 240 * (L + 1) * 10 ^ L + +/-- Every coordinate stage is executed natively. Charging every stage at the maximum buffer +size gives `L * (207 * 10^L + 14) + 2` instructions, including both outer loop controls. -/ +theorem exec_coordinates {w L : ℕ} (hw : 0 < w) (side : Bool) (arr : ℕ → ℤ) + (initial : ℕ → ℕ) (x : Data 32) (hx : Progress side L 0 arr initial x) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (.loop (.lt stage depth) (stageBody side)) x y + (L * (207 * 10 ^ L + 14) + 2) ∧ Progress side L L arr initial y := by + let I (y : Data 32) : Prop := ∃ j, j ≤ L ∧ Progress side L j arr initial y + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt stage depth).Holds y) : + ∃ z, ExecWithin (w + 2) (stageBody side) y z (207 * 10 ^ L + 12) ∧ I z ∧ + L - z.regs stage < L - y.regs stage := by + obtain ⟨j, hj, hprogress⟩ := hy + have hjL : j < L := by simpa only [Cond.holds_lt, hprogress.stage, hprogress.fixed.1] using hc + obtain ⟨z, he, hz⟩ := progress_step hw hjL side arr initial y hprogress A hA ha hmag haddr + exact ⟨z, he, ⟨j + 1, by omega, hz⟩, by rw [hprogress.stage, hz.stage]; omega⟩ + obtain ⟨y, he, ⟨j, hj, hy⟩, hc⟩ := ExecWithin.loop_invariant I + (fun y => L - y.regs stage) hbody ⟨0, Nat.zero_le _, hx⟩ + have hjL : j = L := by + simp only [Cond.holds_lt, hy.stage, hy.fixed.1] at hc + omega + subst j + refine ⟨y, ?_, hy⟩ + simpa only [hx.stage, Nat.sub_zero, Nat.add_assoc] using he + +/-- The encoder runs from a concrete flat memory layout and preserves every cell outside +its output/scratch region. Only the original input magnitude and explicit address bounds are +assumed; every intermediate signed arithmetic fit is derived in the coordinate-stage proof. -/ +theorem exec {w L : ℕ} (hw : 0 < w) (side : Bool) (arr : ℕ → ℤ) (x : Data 32) + (hheader : x.mem 1 = L) (hinput : ∀ i < 7 ^ L, x.mem (2 + i) = encode (arr i)) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (program side) x y (timeBound L) ∧ + (∀ p < 10 ^ L, y.mem (outputBase L + p) = + encode (value (table (coefficients side)) L 0 arr p 0)) ∧ + (∀ a, ¬(outputBase L ≤ a ∧ a < scratchBase L + 10 ^ L) → y.mem a = x.mem a) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + obtain ⟨z, hz, hsetup⟩ := exec_setup L x hheader haddr + have hzero := progress_zero side L arr x z hsetup hinput + obtain ⟨u, hu, hp⟩ := exec_coordinates hw side arr x.mem z hzero A hA ha hmag haddr + have hsource := hp.src + have hout := hp.fixed.2.2.1 + have hcap := hp.fixed.2.1 + have hcap1 : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + have hsfit : Fits (w + 2) (u.regs src + u.regs capacity) := by + rw [hsource, hcap] + apply haddr.of_le + have hb := (buffer_address_le L L).1 + unfold addressBound + omega + have hdfit : Fits (w + 2) (u.regs output + u.regs capacity) := by + rw [hout, hcap] + apply haddr.of_le + have h7 := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound outputBase + omega + have hdisj : u.regs src + u.regs capacity ≤ u.regs output ∨ + u.regs output + u.regs capacity ≤ u.regs src ∨ u.regs src = u.regs output := by + rw [hsource, hout, hcap] + by_cases hL : L = 0 + · rcases hL with rfl + left + decide + · rcases source_cases (L := L) (j := L) (by omega) with h | h + · exact Or.inr (Or.inr h) + · right + left + rw [h] + rfl + obtain ⟨y, hy, hymem, hyr⟩ := exec_copyOut u (haddr.of_le (by unfold addressBound; omega)) + hsfit hdfit hdisj + have he : ExecWithin (w + 2) (program side) x y (timeBound L) := by + have hwhole := hz.seq (hu.seq hy) + apply hwhole.mono + rw [hcap] + unfold timeBound + have hLcap : L ≤ L * 10 ^ L := by nlinarith only [Nat.mul_le_mul_left L hcap1] + nlinarith only [hcap1, hLcap] + refine ⟨y, he, ?_, ?_, ?_, ?_⟩ + · intro p hpp + rw [hymem, hsource, hout, hcap, ArrayCopy.copyPrefix_read hpp] + have h := hp.values p hpp 0 (by simp) + simpa only [Nat.sub_self, pow_zero, Nat.mul_one, Nat.add_zero] using h + · intro a haout + rw [hymem, hsource, hout, hcap, ArrayCopy.copyPrefix_outside ?_] + · exact hp.frame a haout + · unfold scratchBase at haout + omega + · exact (hyr output (by decide) (by decide) (by decide) (by decide)).trans hout + · exact (hyr capacity (by decide) (by decide) (by decide) (by decide)).trans hcap + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean new file mode 100644 index 0000000000..9ced15aa52 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Input + +/-! +# Flat standard-WordRAM clients for the complete encoders + +## Main results + +* `Encoding.runs_left`, `Encoding.runs_right`: actual flat runs from `State.init`, the complete + output memory block, preserved outside frame, and a charged final halt. +* `Encoding.program_length_le`: one finite code-size bound independent of depth and width. + +The final halt returns the output block's address. Its length is `10^L` and its cells contain +the canonical signed encodings of the public mathematical values. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +set_option maxRecDepth 4096 in +-- Kernel reduction flattens the finite instruction list, including each signed arithmetic block. +/-- A single code-size bound for both fixed encoders, including the final halt. -/ +theorem program_length_le (side : Bool) : ((program side).program output).length ≤ 4096 := by + cases side <;> decide + +/-- Every one of the `10^L` flat output cells corresponds to exactly one original leaf. -/ +theorem leafIndex_bijective (L : ℕ) : Function.Bijective (leafIndex (L := L)) := by + have h : Function.Bijective (fun τ : Leaf L => fun i => termDigits.symm (τ i)) := + (Equiv.piCongrRight fun _ : Fin L => termDigits.symm).bijective + change Function.Bijective (fun τ : Leaf L => + finFunctionFinEquiv (fun i => termDigits.symm (τ i))) + exact finFunctionFinEquiv.bijective.comp h + +/-- Actual flat native left-encoding execution. The prefix exposes every output memory cell; +the same run then halts at its fixed final instruction, charging one more instruction. -/ +theorem runs_left {w L : ℕ} (hw : 0 < w) (a : LeftStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputL a) ∧ + ∃ y n, n ≤ timeBound L ∧ + Runs (w + 2) ((program false).program output) (State.init (inputL a)) + ⟨(program false).size, y⟩ n ∧ + Executes (w + 2) ((program false).program output) (State.init (inputL a)) + (outputBase L) (n + 1) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingL a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputL a) : State 32).data.mem b) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, _, hv, hf, ho, _⟩ := exec_left hw a A hA ha hmag haddr + refine ⟨hi, y, n, hn, ?_, ?_, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := (program false).program output) (Program.Contains.self _).append_left + · simpa only [ho, State.init] using he.executes output + +/-- Actual flat native right-encoding execution, with all output cells and the same charged +standard-WordRAM calling convention and final halt. -/ +theorem runs_right {w L : ℕ} (hw : 0 < w) (a : RightStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputR a) ∧ + ∃ y n, n ≤ timeBound L ∧ + Runs (w + 2) ((program true).program output) (State.init (inputR a)) + ⟨(program true).size, y⟩ n ∧ + Executes (w + 2) ((program true).program output) (State.init (inputR a)) + (outputBase L) (n + 1) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingR a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputR a) : State 32).data.mem b) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, _, hv, hf, ho, _⟩ := exec_right hw a A hA ha hmag haddr + refine ⟨hi, y, n, hn, ?_, ?_, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := (program true).program output) (Program.Contains.self _).append_left + · simpa only [ho, State.init] using he.executes output + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean new file mode 100644 index 0000000000..33ed8e8815 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean @@ -0,0 +1,235 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Foundations.Algebra.FiniteMagnitude +import Mathlib.Tactic.Ring + +/-! +# Native execution of the encoding forms + +The form code reads seven strided entries and uses only the existing native signed arithmetic +blocks. These results account for those blocks' actual instructions and their scratch registers. + +## Main results + +* `Encoding.exec_summand`: a coefficient in `{0, 1, -1}` takes at most 23 instructions. + +## References + +The linear forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Registers outside the signed workspace and the form's three local registers. -/ +def FormFrame (x y : Data 32) : Prop := + y.mem = x.mem ∧ ∀ i, 9 ≤ i.val → i ≠ address → i ≠ accumulator → i ≠ temporary → + y.regs i = x.regs i + +/-- Form frames compose without changing any memory cell. -/ +theorem FormFrame.trans {x y z : Data 32} (hxy : FormFrame x y) (hyz : FormFrame y z) : + FormFrame x z := + ⟨hyz.1.trans hxy.1, fun i hi ha hc ht => + (hyz.2 i hi ha hc ht).trans (hxy.2 i hi ha hc ht)⟩ + +private theorem fits_of_abs_lt {w : ℕ} (hw : 0 < w) {a : ℤ} + (ha : |a| < (2 : ℤ) ^ (w - 1)) : GenericRAM.Signed.Fits w a := by + have h := abs_lt.mp ha + exact ⟨hw, h.1.le, h.2⟩ + +/-- A single coefficient is executed by real loads and signed arithmetic. The seven address +computations fit whenever the one-past-end address fits. The two operands must fit the signed +source range; the sum itself may use the two extra target bits supplied by the arithmetic block. -/ +theorem exec_summand {w : ℕ} (x : Data 32) (s : Fin 7) (c a v : ℤ) (base n : ℕ) + (hc : c = 0 ∨ c = 1 ∨ c = -1) + (hacc : x.regs accumulator = encode a) (hbase : x.regs group = base) + (hn : x.regs stride = n) (hmem : x.mem (base + s.val * n) = encode v) + (ha : GenericRAM.Signed.Fits w a) (hv : GenericRAM.Signed.Fits w v) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (summand s c) x y 23 ∧ + y.regs accumulator = encode (a + c * v) ∧ FormFrame x y := by + rcases hc with rfl | rfl | rfl + · refine ⟨x.set accumulator (x.regs accumulator), (ExecWithin.copy).mono (by decide), ?_, ?_⟩ + · simp [hacc] + · refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [Data.set_regs, Function.update_of_ne hi] + all_goals + have hs : Fits (w + 2) s.val := hseven.of_le (by omega) + have hmul : Fits (w + 2) (s.val * n) := haddr.of_le (by nlinarith [s.isLt]) + have hsum : Fits (w + 2) (base + s.val * n) := haddr.of_le (by nlinarith [s.isLt]) + let z := ((((x.set temporary s.val).set address (s.val * n)).set address + (base + s.val * n)).set right (encode v)).set left (encode a) + have hz : ExecWithin (w + 2) + (ram[ temporary ← #s.val; address ← temporary * stride; + address ← group + address; right ← *address; left ← accumulator ]) x z 5 := by + ram_eval [z, hs, hmul, hsum, hn, hbase, hacc, hmem, + temporary, address, group, stride, right, left, accumulator, Fin.mk.injEq] + have hza : z.regs left = encode a := by simp [z] + have hzv : z.regs right = encode v := by + simp [z, left, right] + have hzframe : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i hi hia hic hit + have hil : i ≠ left := by intro h; subst i; simp at hi + have hir : i ≠ right := by intro h; subst i; simp at hi + simp only [z, Data.set_regs, Function.update_of_ne hil, Function.update_of_ne hir, + Function.update_of_ne hia, Function.update_of_ne hit] + · obtain ⟨y, he, hyr, hym, hyreg⟩ := exec_add ha hv z hza hzv + refine ⟨y.set accumulator (y.regs result), ?_, ?_, ?_⟩ + · simpa only [summand, show ¬(1 : ℤ) = 0 by decide, ↓reduceIte] using + (hz.seq (he.seq (ExecWithin.copy (d := accumulator) (s := result)))).mono + (by decide : 5 + (15 + 1) ≤ 23) + · simp [GenericRAM.Signed.Arithmetic.eval, hyr] + · refine ⟨hym.trans hzframe.1, ?_⟩ + intro i hi hia hic hit + simpa only [Data.set_regs, Function.update_of_ne hic] using + (hyreg i (Or.inr hi)).trans (hzframe.2 i hi hia hic hit) + · obtain ⟨y, he, hyr, hym, hyreg⟩ := exec_subtract ha hv z hza hzv + refine ⟨y.set accumulator (y.regs result), ?_, ?_, ?_⟩ + · simpa only [summand, show ¬(-1 : ℤ) = 0 by decide, + show ¬(-1 : ℤ) = 1 by decide, ↓reduceIte] using + (hz.seq (he.seq (ExecWithin.copy (d := accumulator) (s := result)))).mono + (by decide : 5 + (17 + 1) ≤ 23) + · simp [GenericRAM.Signed.Arithmetic.eval, hyr, sub_eq_add_neg] + · refine ⟨hym.trans hzframe.1, ?_⟩ + intro i hi hia hic hit + simpa only [Data.set_regs, Function.update_of_ne hic] using + (hyreg i (Or.inr hi)).trans (hzframe.2 i hi hia hic hit) + +/-- Execute any finite list of the fixed digit/coefficients. Partial sums are bounded by +`|a| + length * B`; the theorem derives each arithmetic operand's fit from that inequality. -/ +theorem exec_sumCode {w : ℕ} (hw : 0 < w) (pairs : List (Fin 7 × ℤ)) + (x : Data 32) (a B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hc : ∀ sc ∈ pairs, sc.2 = 0 ∨ sc.2 = 1 ∨ sc.2 = -1) + (hacc : x.regs accumulator = encode a) (hbase : x.regs group = base) + (hn : x.regs stride = n) (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfitB : B < (2 : ℤ) ^ (w - 1)) + (hfit : |a| + pairs.length * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (sumCode pairs) x y (23 * pairs.length + 1) ∧ + y.regs accumulator = encode (a + (pairs.map fun sc => sc.2 * v sc.1).sum) ∧ + FormFrame x y := by + induction pairs generalizing x a with + | nil => + refine ⟨x.set accumulator (x.regs accumulator), ExecWithin.copy, ?_, ?_⟩ + · simpa using hacc + · refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [Data.set_regs, Function.update_of_ne hi] + | cons sc rest ih => + obtain ⟨s, c⟩ := sc + have hc0 := hc (s, c) (by simp) + have hcv : |c * v s| ≤ B := by + rcases hc0 with rfl | rfl | rfl + · simpa using hB + · simpa using hv s + · simpa using hv s + have ha : GenericRAM.Signed.Fits w a := + fits_of_abs_lt hw (by have := abs_nonneg a; simp only [List.length_cons, + Nat.cast_add, Nat.cast_one] at hfit; nlinarith [Nat.cast_nonneg (α := ℤ) rest.length]) + have hvs := fits_of_abs_lt hw ((hv s).trans_lt hfitB) + obtain ⟨y, he, hyacc, hyframe⟩ := exec_summand x s c a (v s) base n hc0 hacc + hbase hn (hmem s) ha hvs haddr hseven + have hybase : y.regs group = base := (hyframe.2 group (by decide) (by decide) + (by decide) (by decide)).trans hbase + have hyn : y.regs stride = n := (hyframe.2 stride (by decide) (by decide) + (by decide) (by decide)).trans hn + have hfit' : |a + c * v s| + rest.length * B < (2 : ℤ) ^ (w - 1) := by + have ht := abs_add_le a (c * v s) + simp only [List.length_cons, Nat.cast_add, Nat.cast_one] at hfit + nlinarith + obtain ⟨z, hz, hzacc, hzframe⟩ := ih y (a + c * v s) + (fun sc hsc => hc sc (by simp [hsc])) hyacc hybase hyn + (fun s => by rw [hyframe.1]; exact hmem s) hfit' + refine ⟨z, ?_, ?_, hyframe.trans hzframe⟩ + · simpa only [sumCode, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using he.seq hz + · simpa only [List.map_cons, List.sum_cons, add_assoc] using hzacc + +/-- A complete coefficient row, including initialization, costs at most 163 instructions. +All seven entries have absolute value at most `B`, so every partial sum has magnitude at +most `7B`; this ordinary explicit width condition is sufficient for every signed operation. -/ +theorem exec_form {w : ℕ} (hw : 0 < w) (side : Bool) (t : Fin 10) + (x : Data 32) (B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hbase : x.regs group = base) (hn : x.regs stride = n) + (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfit : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (form side t) x y 163 ∧ + y.regs accumulator = encode (∑ s, coefficients side t s * v s) ∧ FormFrame x y := by + let z := x.set accumulator 0 + have hz : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [z, Data.set_regs, Function.update_of_ne hi] + obtain ⟨y, he, hacc, hf⟩ := exec_sumCode hw + (List.ofFn fun s => (s, coefficients side t s)) z 0 B base n v + (by intro sc hsc; obtain ⟨s, rfl⟩ := List.mem_ofFn.mp hsc; exact coefficients_values side t s) + (by simp [z, encode]) + ((hz.2 group (by decide) (by decide) (by decide) (by decide)).trans hbase) + ((hz.2 stride (by decide) (by decide) (by decide) (by decide)).trans hn) + (by simpa only [z, Data.set_mem] using hmem) hB hv (by linarith) + (by simpa using hfit) haddr hseven + refine ⟨y, ?_, ?_, hz.trans hf⟩ + · simpa only [form, List.length_ofFn] using + (ExecWithin.const (fits_zero (w := w + 2))).seq he + · simpa only [List.map_ofFn, List.sum_ofFn, Function.comp_apply, zero_add] using hacc + +/-- The finite term dispatch costs at most three instructions per inspected row in addition +to the row's 163-instruction bound. All ten forms share the same native register file. -/ +theorem exec_dispatch {w : ℕ} (hw : 0 < w) (side : Bool) (rows : List (Fin 10)) + (t : Fin 10) (ht : t ∈ rows) (x : Data 32) (B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hterm : x.regs term = t.val) (hbase : x.regs group = base) (hn : x.regs stride = n) + (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfit : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hten : Fits (w + 2) 10) : + ∃ y, ExecWithin (w + 2) (dispatch side rows) x y (3 * rows.length + 163) ∧ + y.regs accumulator = encode (∑ s, coefficients side t s * v s) ∧ FormFrame x y := by + induction rows generalizing x with + | nil => simp at ht + | cons q rest ih => + let z := x.set temporary q.val + have hz : ExecWithin (B := .standard) (w + 2) (ram[ temporary ← #q.val ]) x z 1 := + ExecWithin.const (hten.of_le (by omega)) + have hzf : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i _ _ _ hi + simp only [z, Data.set_regs, Function.update_of_ne hi] + have hzt : z.regs term = t.val := by + simpa [z, term, temporary, Fin.mk.injEq] using hterm + have hzb : z.regs group = base := by + simpa [z, group, temporary, Fin.mk.injEq] using hbase + have hzn : z.regs stride = n := by + simpa [z, stride, temporary, Fin.mk.injEq] using hn + have hzm : ∀ s, z.mem (base + s.val * n) = encode (v s) := hmem + by_cases hqt : t = q + · subst t + obtain ⟨y, he, hacc, hf⟩ := exec_form hw side q z B base n v hzb hzn hzm hB hv + hfit haddr (hten.of_le (by decide)) + refine ⟨y, ?_, hacc, hzf.trans hf⟩ + apply (hz.seq (ExecWithin.branch_pos ?_ he)).mono + · simp only [List.length_cons] + omega + · simp only [Cond.holds_eq, hzt, z, Data.set_regs, Function.update_self] + · have htrest : t ∈ rest := (List.mem_cons.mp ht).resolve_left hqt + obtain ⟨y, he, hacc, hf⟩ := ih htrest z hzt hzb hzn hzm + refine ⟨y, ?_, hacc, hzf.trans hf⟩ + apply (hz.seq (ExecWithin.branch_neg ?_ he)).mono + · simp only [List.length_cons] + omega + · simp only [Cond.holds_eq, hzt, z, Data.set_regs, Function.update_self] + exact fun h => hqt (Fin.ext h) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean new file mode 100644 index 0000000000..5beb51aafe --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean @@ -0,0 +1,174 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +/-! +# Charged initialization from the actual depth header + +## Main results + +* `Encoding.exec_setup`: load the header and compute both powers, buffer addresses and + loop parameters within `5L+18` native instructions, preserving every memory cell. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM + +/-- An explicit bound for the encoder's unsigned addresses, index products and counters. -/ +def addressBound (L : ℕ) : ℕ := L + 64 + 64 * 10 ^ L + +/-- Registers and memory after header initialization. This is only a postcondition; it +contains no execution, resource or intermediate-fit assumption. -/ +structure SetupPost (L : ℕ) (x y : Data 32) : Prop where + /-- Memory is unchanged. -/ + mem : y.mem = x.mem + /-- Depth loaded from memory address one. -/ + depth : y.regs Encoding.depth = L + /-- Computed input size. -/ + inputSize : y.regs Encoding.inputSize = 7 ^ L + /-- Computed buffer capacity. -/ + capacity : y.regs Encoding.capacity = 10 ^ L + /-- First output address. -/ + output : y.regs Encoding.output = outputBase L + /-- First scratch address. -/ + scratch : y.regs Encoding.scratch = scratchBase L + /-- Original input base. -/ + src : y.regs Encoding.src = 2 + /-- First destination buffer. -/ + dst : y.regs Encoding.dst = outputBase L + /-- First source suffix length. -/ + stride : y.regs Encoding.stride = 7 ^ L + /-- The empty transformed prefix. -/ + prefixes : y.regs Encoding.prefixes = 1 + /-- No coordinates transformed yet. -/ + stage : y.regs Encoding.stage = 0 + /-- Counter increment. -/ + one : y.regs Encoding.one = 1 + /-- Input radix. -/ + seven : y.regs Encoding.seven = 7 + /-- Output radix. -/ + ten : y.regs Encoding.ten = 10 + +/-- Header loads, both power computations, address arithmetic and counter initialization +are included. The only fit premise is the explicit bound on ordinary natural numbers. -/ +theorem exec_setup {w : ℕ} (L : ℕ) (x : Data 32) (hheader : x.mem 1 = L) + (haddr : Fits w (addressBound L)) : + ∃ y, ExecWithin w setup x y (5 * L + 18) ∧ SetupPost L x y := by + have h1 : Fits w 1 := haddr.of_le (by unfold addressBound; omega) + have h2 : Fits w 2 := haddr.of_le (by unfold addressBound; omega) + have h7 : Fits w 7 := haddr.of_le (by unfold addressBound; omega) + have h10 : Fits w 10 := haddr.of_le (by unfold addressBound; omega) + have hpow (b j : ℕ) (hb : b ≤ 10) (hj : j ≤ L) : Fits w (b ^ j) := by + apply haddr.of_le + have hbj := Nat.pow_le_pow_left hb j + have hjL := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hj + unfold addressBound + omega + let I (y : Data 32) : Prop := + y.mem = x.mem ∧ y.regs depth = L ∧ y.regs inputSize = 7 ^ y.regs stage ∧ + y.regs capacity = 10 ^ y.regs stage ∧ y.regs stage ≤ L ∧ + y.regs one = 1 ∧ y.regs seven = 7 ∧ y.regs ten = 10 + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt stage depth).Holds y) : + ∃ z, ExecWithin w powerBody y z 3 ∧ I z ∧ L - z.regs stage < L - y.regs stage := by + obtain ⟨hm, hd, hi, hcap, hj, ho, hs, ht⟩ := hy + have hjL : y.regs stage < L := by simpa only [Cond.holds_lt, hd] using hc + have hpi : Fits w (y.regs inputSize * y.regs seven) := by + rw [hi, hs, ← pow_succ] + exact hpow 7 _ (by decide) (by omega) + have hpc : Fits w (y.regs capacity * y.regs ten) := by + rw [hcap, ht, ← pow_succ] + exact hpow 10 _ (by decide) (by omega) + have hjfit : Fits w (y.regs stage + y.regs one) := by + rw [ho] + exact haddr.of_le (by unfold addressBound; omega) + have hpi' : Fits w (y.regs inputSize * 7) := by simpa only [hs] using hpi + have hpc' : Fits w (y.regs capacity * 10) := by simpa only [ht] using hpc + have hjfit' : Fits w (y.regs stage + 1) := by simpa only [ho] using hjfit + let z := ((y.set inputSize (y.regs inputSize * 7)).set capacity + (y.regs capacity * 10)).set stage (y.regs stage + 1) + have he : ExecWithin w powerBody y z 3 := by + ram_eval [powerBody, z, ho, hs, ht, hpi', hpc', hjfit', + inputSize, capacity, stage, one, seven, ten, Fin.mk.injEq] + refine ⟨z, he, ?_, ?_⟩ + · refine ⟨hm, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [z, depth, inputSize, capacity, stage, Function.update_apply] using hd + · simp +decide [z, inputSize, capacity, stage, pow_succ, hi] + · simp +decide [z, inputSize, capacity, stage, pow_succ, hcap] + · simpa [z] using Nat.succ_le_of_lt hjL + · simpa +decide [z, one, inputSize, capacity, stage, Function.update_apply] using ho + · simpa +decide [z, seven, inputSize, capacity, stage, Function.update_apply] using hs + · simpa +decide [z, ten, inputSize, capacity, stage, Function.update_apply] using ht + · simp only [z, Data.set_regs, Function.update_self] + omega + let x0 := (((((((x.set one 1).set seven 7).set ten 10).set address 1).set depth L).set + inputSize 1).set capacity 1).set stage 0 + have hx0 : I x0 := by + simp +decide [I, x0, depth, inputSize, capacity, stage, one, seven, ten, address] + have hxrun : ExecWithin w setupHead x x0 8 := by + ram_eval [setupHead, x0, hheader, h1, h7, h10, fits_zero, + one, seven, ten, address, depth, inputSize, capacity, stage, Fin.mk.injEq] + obtain ⟨u, hu, hi, hc⟩ := ExecWithin.loop_invariant I (fun y => L - y.regs stage) hbody hx0 + obtain ⟨hm, hd, hin, hcap, hj, ho, hs, ht⟩ := hi + have hend : u.regs stage = L := by + simp only [Cond.holds_lt, hd] at hc + omega + rw [hend] at hin hcap + have hout : Fits w (2 + 7 ^ L) := haddr.of_le (by + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega) + have hscratch : Fits w (2 + 7 ^ L + 10 ^ L) := haddr.of_le (by + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega) + let y := (((((((u.set temporary 2).set output (outputBase L)).set scratch (scratchBase L)).set + src 2).set dst (outputBase L)).set stride (7 ^ L)).set prefixes 1).set stage 0 + have hyrun : ExecWithin w setupTail u y 8 := by + ram_eval [setupTail, y, outputBase, scratchBase, hin, hcap, h2, h1, hout, hscratch, + temporary, output, scratch, src, dst, stride, prefixes, stage, inputSize, capacity, + Fin.mk.injEq] + refine ⟨y, ?_, ?_⟩ + · have hcost : 8 + ((L - x0.regs stage) * (3 + 2) + 2 + 8) ≤ 5 * L + 18 := by + simp only [x0, Data.set_regs, Function.update_self, Nat.sub_zero] + omega + exact (hxrun.seq (hu.seq hyrun)).mono hcost + · constructor + all_goals simp +decide [y, outputBase, scratchBase, temporary, output, scratch, src, dst, + stride, prefixes, stage, depth, inputSize, capacity, one, seven, ten, + hm, hd, hin, hcap, ho, hs, ht] + +/-- The actual flat input fits the target machine. Each signed input value uses the existing +canonical encoding; the depth and the ordinary unsigned length header are bounded separately. -/ +theorem input_fits {w L : ℕ} (hw : 0 < w) (a : Fin (7 ^ L) → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ i, |a i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : InputFits (w + 2) (input L a) := by + have hpow : (1 : ℤ) ≤ (7 ^ L : ℕ) := by + exact_mod_cast Nat.one_le_pow L 7 (by decide) + have hAfit : A < (2 : ℤ) ^ (w - 1) := by nlinarith + refine ⟨?_, ?_⟩ + · simp only [input, List.length_cons, List.length_ofFn] + apply haddr.of_le + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega + · intro v hv + rcases List.mem_cons.mp hv with rfl | hv + · exact haddr.of_le (by unfold addressBound; omega) + · obtain ⟨i, rfl⟩ := List.mem_ofFn.mp hv + have hi : GenericRAM.Signed.Fits w (a i) := by + have h := abs_lt.mp ((ha i).trans_lt hAfit) + exact ⟨hw, h.1.le, h.2⟩ + exact (Cslib.WordRAM.Signed.encode_fits hi).mono (by omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean new file mode 100644 index 0000000000..727d0f96f8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean @@ -0,0 +1,127 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Exec +import Mathlib.Data.List.GetD + +/-! +# Actual initialized-input clients for the shared encoders + +## Main results + +* `Encoding.exec_input`: flat signed array, concrete depth header and actual `State.init`. +* `Encoding.exec_left`, `Encoding.exec_right`: all public tensor encodings, at every depth. + +The two programs and their thirty-two registers are independent of depth and word width. +Inputs are signed words encoded with `WordRAM.Signed.encode`; the depth and the machine's +length header are ordinary unsigned words. All input and reachable data words fit the target +width. Output and scratch are disjoint concrete blocks immediately after the input. + +## References + +The encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +private theorem getD_ofFn {N : ℕ} (f : Fin N → ℤ) (i : Fin N) : + (List.ofFn f).getD i.val 0 = f i := by + rw [List.getD_eq_getElem _ _ (by simp)] + simp + +/-- The complete native encoder starts from the ordinary `State.init` convention with a +real depth header and encoded input words. It returns all flat shared-transform values, keeps +the input and every outside cell, and leaves only target-sized words in memory and registers. -/ +theorem exec_input {w L : ℕ} (hw : 0 < w) (side : Bool) (a : Fin (7 ^ L) → ℤ) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i, |a i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (input L a) ∧ + ∃ y, ExecWithin (w + 2) (program side) (State.init (input L a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ p < 10 ^ L, y.mem (outputBase L + p) = encode + (value (table (coefficients side)) L 0 (fun i => (List.ofFn a).getD i 0) p 0)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (input L a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + have hi := input_fits hw a A hA ha hmag haddr + let x : Data 32 := (State.init (input L a)).data + have hheader : x.mem 1 = L := by + simp [x, State.init, Data.init, input] + have hinput (i : ℕ) (hi : i < 7 ^ L) : + x.mem (2 + i) = encode ((List.ofFn a).getD i 0) := by + have hidx : 2 + i = (i + 1) + 1 := by omega + change (Data.init (input L a) : Data 32).mem (2 + i) = _ + rw [hidx, Data.init_mem_succ] + simp only [input, List.getD_cons_succ] + rw [List.getD_eq_getElem _ _ (by simpa only [List.length_ofFn] using hi)] + simpa using congrArg encode (getD_ofFn a ⟨i, hi⟩).symm + have ha' (i : ℕ) (hi : i < 7 ^ L) : |(List.ofFn a).getD i 0| ≤ A := by + rw [getD_ofFn a ⟨i, hi⟩] + exact ha _ + obtain ⟨y, he, hv, hf, ho, hc⟩ := exec hw side (fun i => (List.ofFn a).getD i 0) x + hheader hinput A hA ha' hmag haddr + exact ⟨hi, y, he, he.bounded (Data.Bounded.init hi), hv, hf, ho, hc⟩ + +/-- Complete left encoder on arbitrary signed arrays indexed by the original seven-symbol +strings. Every one of the `10^L` leaves receives the unchanged public `encodingL` value. -/ +theorem exec_left {w L : ℕ} (hw : 0 < w) (a : LeftStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputL a) ∧ + ∃ y, ExecWithin (w + 2) (program false) (State.init (inputL a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingL a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputL a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + let f : Fin (7 ^ L) → ℤ := fun i => a (fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ)) + obtain ⟨hi, y, he, hb, hv, hf, ho, hc⟩ := exec_input hw false f A hA (fun _ => ha _) hmag haddr + refine ⟨hi, y, he, hb, ?_, hf, ho, hc⟩ + intro τ + have harr (u : Fin L → Fin 7) : (List.ofFn f).getD (finFunctionFinEquiv u).val 0 = + a (fun i => leftAt (u i)) := by + rw [getD_ofFn] + simp only [f, Equiv.symm_apply_apply] + have h := hv (leafIndex τ) (leafIndex τ).isLt + rw [value_final_left a _ harr τ] at h + exact h + +/-- Complete right encoder on arbitrary signed arrays with the original `encodingR` API. -/ +theorem exec_right {w L : ℕ} (hw : 0 < w) (a : RightStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputR a) ∧ + ∃ y, ExecWithin (w + 2) (program true) (State.init (inputR a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingR a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputR a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + let f : Fin (7 ^ L) → ℤ := fun i => a (fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ)) + obtain ⟨hi, y, he, hb, hv, hf, ho, hc⟩ := exec_input hw true f A hA (fun _ => ha _) hmag haddr + refine ⟨hi, y, he, hb, ?_, hf, ho, hc⟩ + intro τ + have harr (u : Fin L → Fin 7) : (List.ofFn f).getD (finFunctionFinEquiv u).val 0 = + a (fun i => rightAt (u i)) := by + rw [getD_ofFn] + simp only [f, Equiv.symm_apply_apply] + have h := hv (leafIndex τ) (leafIndex τ).isLt + rw [value_final_right a _ harr τ] at h + exact h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean new file mode 100644 index 0000000000..a22fed2332 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Stage +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform + +/-! +# Agreement of the flat tensor transform with the public encodings + +The digit numbering is supplied by Mathlib's `finFunctionFinEquiv`. The explicit alphabet +numberings contain exactly the original seven variables and ten terms. The final mixed-radix +transform is `encodingL` or `encodingR` for every depth, including zero. + +## Main results + +* `Encoding.value_final_left`, `Encoding.value_final_right`: unchanged public encoding semantics. + +## References + +The original encodings and coefficients are from [AlmanWilliams2026, Section 2.4.1], selectively +adapted from Anthropic, PBC's formal source; see `Encoding` for the exact pin and paths. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- The numbered output position of a term string. -/ +def leafIndex {L : ℕ} (τ : Leaf L) : Fin (10 ^ L) := + finFunctionFinEquiv fun i => termDigits.symm (τ i) + +private theorem value_final_alphabet {α : Type} [Fintype α] (e : Fin 7 ≃ α) + (c : Term → α → ℤ) {L : ℕ} (a : (Fin L → α) → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => e (u i))) + (τ : Leaf L) : + value (table (fun t s => c (termAt t) (e s))) L 0 arr (leafIndex τ) 0 = + encodeWith c τ a := by + have h := value_final (fun t s => c (termAt t) (e s)) L + (fun u => a (fun i => e (u i))) arr harr (fun i => termDigits.symm (τ i)) + change value (table (fun t s => c (termAt t) (e s))) L 0 arr + (finFunctionFinEquiv fun i => termDigits.symm (τ i)) 0 = _ + rw [h] + unfold encodeWith + refine Fintype.sum_equiv (Equiv.piCongrRight fun _ : Fin L => e) _ _ ?_ + intro u + congr 1 + apply Finset.prod_congr rfl + intro i _ + change c (termDigits (termDigits.symm (τ i))) (e (u i)) = c (τ i) (e (u i)) + rw [Equiv.apply_symm_apply] + +/-- The flat shared left transform equals the existing `encodingL` on every leaf. -/ +theorem value_final_left {L : ℕ} (a : LeftStr L → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => leftAt (u i))) + (τ : Leaf L) : + value (table (coefficients false)) L 0 arr (leafIndex τ) 0 = encodingL a τ := + value_final_alphabet leftDigits phi a arr harr τ + +/-- The flat shared right transform equals the existing `encodingR` on every leaf. -/ +theorem value_final_right {L : ℕ} (a : RightStr L → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => rightAt (u i))) + (τ : Leaf L) : + value (table (coefficients true)) L 0 arr (leafIndex τ) 0 = encodingR a τ := + value_final_alphabet rightDigits psi a arr harr τ + +/-- The native stage's row-wise specification is the next shared-prefix value. This includes +the quotient/remainder decomposition of its actual flat loop counter. -/ +theorem stageResult_value (side : Bool) (j k i : ℕ) (arr : ℕ → ℤ) : + stageResult side (7 ^ k) + (fun i a => value (table (coefficients side)) j (k + 1) arr + (i / (10 * 7 ^ k)) (a.val * 7 ^ k + i % 7 ^ k)) i = + value (table (coefficients side)) (j + 1) k arr (i / 7 ^ k) (i % 7 ^ k) := by + have hdiv : i / 7 ^ k / 10 = i / (10 * 7 ^ k) := by + rw [Nat.div_div_eq_div_mul, Nat.mul_comm (7 ^ k) 10] + have hprefix : i / (10 * 7 ^ k) * 10 + i / 7 ^ k % 10 = i / 7 ^ k := by + simpa only [hdiv, Nat.mul_comm] using Nat.div_add_mod (i / 7 ^ k) 10 + have he := value_step (table (coefficients side)) j k arr (i / (10 * 7 ^ k)) + (i % 7 ^ k) (Nat.mod_lt (i / 7 ^ k) (by decide : 0 < 10)) + rw [hprefix] at he + unfold stageResult + rw [he, ← Fin.sum_univ_eq_sum_range] + apply Finset.sum_congr rfl + intro a _ + simp only [table, Nat.mod_mod, Nat.mod_eq_of_lt a.isLt] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean new file mode 100644 index 0000000000..85c382fd0b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean @@ -0,0 +1,203 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Cell + +/-! +# Charged shared-coordinate array loop + +## Main results + +* `Encoding.exec_cells`: all cells in one shared coordinate transform, preserving the input + and every memory cell outside the destination block. + +## References + +The forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The linear-form value at flat output position `i` of a stage with suffix stride `n`. -/ +def stageResult (side : Bool) (n : ℕ) (v : ℕ → Fin 7 → ℤ) (i : ℕ) : ℤ := + ∑ a, coefficients side ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ a * v i a + +/-- The complete cell loop has at most `207 * (10 * P * n) + 2` instructions. The seven +source entries for each output are shared values from the previous array, not separately +enumerated original input terms. Disjointness is permitted in either address order. -/ +theorem exec_cells {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d : ℕ) (hP : 0 < P) (hnpos : 0 < n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hcell : x.regs cell = 0) + (hn : x.regs stride = n) (hseven : x.regs seven = 7) + (hten : x.regs ten = 10) (hone : x.regs one = 1) + (hsize : x.regs stageSize = 10 * P * n) + (v : ℕ → Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B) + (hmem : ∀ i < 10 * P * n, ∀ a, + x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) + (hdisj : s + 7 * P * n ≤ d ∨ d + 10 * P * n ≤ s) : + ∃ y, ExecWithin (w + 2) (.loop (.lt cell stageSize) (cellBody side)) x y + (207 * (10 * P * n) + 2) ∧ + (∀ i < 10 * P * n, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 10 * P * n) → y.mem a = x.mem a) ∧ + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) := by + let N := 10 * P * n + let I (y : Data 32) : Prop := + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) ∧ y.regs cell ≤ N ∧ + (∀ i < y.regs cell, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + y.regs cell) → y.mem a = x.mem a) + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt cell stageSize).Holds y) : + ∃ z, ExecWithin (w + 2) (cellBody side) y z 205 ∧ I z ∧ + N - z.regs cell < N - y.regs cell := by + obtain ⟨hr, hiN, hm, hf⟩ := hy + have hys : y.regs src = s := (hr src (by decide) (by decide)).trans hsrc + have hyd : y.regs dst = d := (hr dst (by decide) (by decide)).trans hdst + have hyn : y.regs stride = n := (hr stride (by decide) (by decide)).trans hn + have hy7 : y.regs seven = 7 := (hr seven (by decide) (by decide)).trans hseven + have hy10 : y.regs ten = 10 := (hr ten (by decide) (by decide)).trans hten + have hy1 : y.regs one = 1 := (hr one (by decide) (by decide)).trans hone + have hysz : y.regs stageSize = N := (hr stageSize (by decide) (by decide)).trans hsize + have hi : y.regs cell < N := by simpa only [Cond.holds_lt, hysz] using hc + have hyinput (a : Fin 7) : + y.mem (s + (y.regs cell / (10 * n) * 7 + a.val) * n + y.regs cell % n) = + encode (v (y.regs cell) a) := by + have hp : y.regs cell / (10 * n) < P := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + dsimp only [N] at hi + nlinarith + have hmod := Nat.mod_lt (y.regs cell) hnpos + have hprefix : y.regs cell / (10 * n) * 7 + a.val < P * 7 := by omega + have hproduct := Nat.mul_le_mul_right n (Nat.succ_le_of_lt hprefix) + have hoff : (y.regs cell / (10 * n) * 7 + a.val) * n + y.regs cell % n < + P * 7 * n := by nlinarith only [hproduct, hmod] + rw [Nat.mul_comm P 7] at hoff + rw [hf _ ?_] + · exact hmem _ hi a + · rcases hdisj with h | h <;> dsimp only [N] at hiN <;> omega + obtain ⟨z, he, hzmem, hzcell, hzreg⟩ := exec_cell hw side y P n s d (y.regs cell) + hP hnpos hi hys hyd rfl hyn hy7 hy10 hy1 (v (y.regs cell)) B hB (hv _ hi) + hyinput hval haddr + refine ⟨z, he, ?_, by rw [hzcell]; omega⟩ + refine ⟨fun a ha hl => (hzreg a ha hl).trans (hr a ha hl), by omega, ?_, ?_⟩ + · intro i hzi + rw [hzcell] at hzi + by_cases hic : i = y.regs cell + · subst i + simp only [hzmem, Function.update_self, stageResult] + · have hin : i < y.regs cell := by omega + rw [hzmem, Function.update_of_ne (by omega : d + i ≠ d + y.regs cell)] + exact hm i hin + · intro a ha + rw [hzcell] at ha + have hne : a ≠ d + y.regs cell := by omega + rw [hzmem, Function.update_of_ne hne] + exact hf a (by omega) + have hx : I x := by + refine ⟨fun _ _ _ => rfl, by simp [hcell, N], ?_, ?_⟩ + · simp only [hcell, Nat.not_lt_zero, false_implies, implies_true] + · intro a _ + rfl + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs cell) hbody hx + obtain ⟨hr, hi, hm, hf⟩ := hy + have hysz : y.regs stageSize = N := (hr stageSize (by decide) (by decide)).trans hsize + have hend : y.regs cell = N := by + simp only [Cond.holds_lt, hysz] at hc + omega + refine ⟨y, ?_, ?_, ?_, hr⟩ + · simpa only [hcell, N, Nat.sub_zero, Nat.add_comm, Nat.mul_comm] using he + · simpa only [hend] using hm + · simpa only [hend] using hf + +/-- Registers changed by the stage controller or its cell loop. -/ +def StageLocal (i : Fin 32) : Prop := + CellLocal i ∨ i = stride ∨ i = stageSize ∨ i = src ∨ i = dst ∨ i = prefixes ∨ i = stage + +instance (i : Fin 32) : Decidable (StageLocal i) := + inferInstanceAs (Decidable (CellLocal i ∨ i = stride ∨ i = stageSize ∨ i = src ∨ + i = dst ∨ i = prefixes ∨ i = stage)) + +private theorem stage_preserves (side : Bool) (i : Fin 32) : + 9 ≤ i.val → ¬StageLocal i → (stageBody side).Preserves i := by + decide +kernel +revert + +/-- A complete coordinate stage, including stride division, stage-size arithmetic, both +cell-loop control instructions per entry, buffer alternation and the next-stage counters. -/ +theorem exec_stageBody {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d j o q : ℕ) (hP : 0 < P) (hnpos : 0 < n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hj : x.regs stage = j) + (hn : x.regs stride = 7 * n) (hp : x.regs prefixes = P) + (hseven : x.regs seven = 7) (hten : x.regs ten = 10) (hone : x.regs one = 1) + (hout : x.regs output = o) (hscratch : x.regs scratch = q) + (v : ℕ → Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B) + (hmem : ∀ i < 10 * P * n, ∀ a, + x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) (hjfit : Fits (w + 2) (j + 1)) + (hdisj : s + 7 * P * n ≤ d ∨ d + 10 * P * n ≤ s) : + ∃ y, ExecWithin (w + 2) (stageBody side) x y (207 * (10 * P * n) + 12) ∧ + (∀ i < 10 * P * n, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 10 * P * n) → y.mem a = x.mem a) ∧ + y.regs src = d ∧ y.regs dst = (if d = o then q else o) ∧ + y.regs stride = n ∧ y.regs prefixes = 10 * P ∧ y.regs stage = j + 1 ∧ + (∀ a, 9 ≤ a.val → ¬StageLocal a → y.regs a = x.regs a) := by + have hPn : n ≤ P * n := by nlinarith + have h10P : Fits (w + 2) (P * 10) := haddr.of_le (by nlinarith) + have hN : Fits (w + 2) (P * 10 * n) := haddr.of_le (by nlinarith) + have hnfit : Fits (w + 2) n := haddr.of_le (by nlinarith) + have hdiv : 7 * n / 7 = n := by omega + let z := (((x.set stride n).set stageSize (P * 10)).set stageSize (P * 10 * n)).set cell 0 + have hz : ExecWithin (w + 2) stageHead x z 4 := by + ram_eval [stageHead, z, hn, hseven, hp, hten, hdiv, hnfit, h10P, hN, + stride, prefixes, ten, seven, stageSize, cell, Fin.mk.injEq] + have hzr (a : Fin 32) (ha : a ≠ stride ∧ a ≠ stageSize ∧ a ≠ cell) : + z.regs a = x.regs a := by + simp only [z, Data.set_regs, Function.update_of_ne ha.1, Function.update_of_ne ha.2.1, + Function.update_of_ne ha.2.2] + obtain ⟨u, hu, hum, huf, hur⟩ := exec_cells hw side z P n s d hP hnpos + ((hzr src (by decide)).trans hsrc) ((hzr dst (by decide)).trans hdst) + (by simp [z]) (by simp [z, stride, stageSize, cell]) + ((hzr seven (by decide)).trans hseven) ((hzr ten (by decide)).trans hten) + ((hzr one (by decide)).trans hone) + (by simp [z, stageSize, cell, Nat.mul_comm]) v B hB hv hmem hval haddr hdisj + have ur (a : Fin 32) (ha : 9 ≤ a.val) (hl : ¬CellLocal a) + (hzl : a ≠ stride ∧ a ≠ stageSize ∧ a ≠ cell) : u.regs a = x.regs a := + (hur a ha hl).trans (hzr a hzl) + have us : u.regs dst = d := (ur dst (by decide) (by decide) (by decide)).trans hdst + have uo : u.regs output = o := (ur output (by decide) (by decide) (by decide)).trans hout + have uq : u.regs scratch = q := (ur scratch (by decide) (by decide) (by decide)).trans hscratch + have up : u.regs prefixes = P := (ur prefixes (by decide) (by decide) (by decide)).trans hp + have uj : u.regs stage = j := (ur stage (by decide) (by decide) (by decide)).trans hj + have ut : u.regs ten = 10 := (ur ten (by decide) (by decide) (by decide)).trans hten + have u1 : u.regs one = 1 := (ur one (by decide) (by decide) (by decide)).trans hone + have un : u.regs stride = n := by + rw [hur stride (by decide) (by decide)] + simp [z, stride, stageSize, cell] + let y := (((u.set src d).set dst (if d = o then q else o)).set prefixes (P * 10)).set + stage (j + 1) + have hy : ExecWithin (w + 2) stageTail u y 6 := by + by_cases hdo : d = o + all_goals ram_eval [stageTail, y, us, uo, uq, up, uj, ut, u1, hdo, h10P, hjfit, + src, dst, output, scratch, prefixes, stage, ten, one, Fin.mk.injEq] + have he : ExecWithin (w + 2) (stageBody side) x y (207 * (10 * P * n) + 12) := + (hz.seq (hu.seq hy)).mono (by omega) + refine ⟨y, he, hum, huf, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simp [y, src, dst, prefixes, stage] + · simp [y, dst, prefixes, stage] + · simpa [y, src, dst, prefixes, stage, stride] using un + · simp [y, prefixes, stage, Nat.mul_comm] + · simp [y] + · intro a ha hl + exact he.regs_eq (stage_preserves side a ha hl) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Computability/WordRAM/Copy.lean b/Complexity/Computability/WordRAM/Copy.lean index 62e6600b36..1652067c00 100644 --- a/Complexity/Computability/WordRAM/Copy.lean +++ b/Complexity/Computability/WordRAM/Copy.lean @@ -1,7 +1,7 @@ /- -Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Copyright (c) 2026 Samuel Schlesinger, Erik Demaine. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Samuel Schlesinger +Authors: Samuel Schlesinger, Erik Demaine -/ module @@ -10,10 +10,10 @@ module public import Complexity.Computability.WordRAM.Allocation /-! -# Copying disjoint arrays on the word RAM +# Copying arrays on the word RAM The seven-register copy routine charges every address calculation, load, store, increment, -and loop test. Its source precedes its destination, as in a bump-allocated growing array. +and loop test. Source and destination may be disjoint in either order or equal. It is a fixed program: neither the length nor either address is baked into the code. ## Main definitions @@ -29,6 +29,8 @@ It is a fixed program: neither the length nor either address is baked into the c * `Cslib.WordRAM.ArrayCopy.exec`: copying `n` words within `7 * n + 4` instructions, preserving the source and all cells outside the destination. +* `Cslib.WordRAM.ArrayCopy.exec_of_separated_or_eq`: the same native program also supports + disjoint blocks in the reverse address order or identical source and destination. ## References @@ -39,7 +41,7 @@ It is a fixed program: neither the length nor either address is baked into the c namespace Cslib.WordRAM.ArrayCopy -/-- The memory after copying a prefix into a disjoint destination block. -/ +/-- The memory after copying a prefix into a destination block. -/ def copyPrefix (mem : ℕ → ℕ) (src dst k : ℕ) : ℕ → ℕ := fun a => if dst ≤ a ∧ a < dst + k then mem (src + (a - dst)) else mem a @@ -94,10 +96,11 @@ def program : Code .standard 7 := ram[ idx ← #0; one ← #1; while idx < len d /-- Low-level structured copying charges register setup and all loop instructions, excluding the final halt. The one-past-end addresses must fit a word. Boundedness of the input memory and registers is a separate premise when using `ExecWithin.bounded` to obtain bounded-word execution. -/ -theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) +theorem exec_of_separated_or_eq {w : ℕ} (x : Data 7) (h1 : Fits w 1) (hs : Fits w (x.regs src + x.regs len)) (hd : Fits w (x.regs dst + x.regs len)) - (hdisj : x.regs src + x.regs len ≤ x.regs dst) : + (hdisj : x.regs src + x.regs len ≤ x.regs dst ∨ + x.regs dst + x.regs len ≤ x.regs src ∨ x.regs src = x.regs dst) : ∃ y, ExecWithin w program x y (7 * x.regs len + 4) ∧ y.mem = copyPrefix x.mem (x.regs src) (x.regs dst) (x.regs len) ∧ y.regs src = x.regs src ∧ y.regs dst = x.regs dst ∧ y.regs len = x.regs len := by @@ -118,7 +121,7 @@ theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) have hread : y.mem (y.regs src + y.regs idx) = x.mem (x.regs src + y.regs idx) := by rw [hym, hys] apply copyPrefix_outside - omega + rcases hdisj with h | h | h <;> omega refine ⟨?z, ?run, ?inv, ?decrease⟩ case run => ram_eval [body, hsrc, hdst, hinc, hyo] case inv => @@ -150,6 +153,16 @@ theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) omega · simpa [hend] using hym +/-- Forward copying between disjoint arrays, retaining the original calling convention. -/ +theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs len)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (7 * x.regs len + 4) ∧ + y.mem = copyPrefix x.mem (x.regs src) (x.regs dst) (x.regs len) ∧ + y.regs src = x.regs src ∧ y.regs dst = x.regs dst ∧ y.regs len = x.regs len := + exec_of_separated_or_eq x h1 hs hd (Or.inl hdisj) + end Cslib.WordRAM.ArrayCopy end diff --git a/ComplexityTests/SchonhageEncoding.lean b/ComplexityTests/SchonhageEncoding.lean new file mode 100644 index 0000000000..e03bb9f01a --- /dev/null +++ b/ComplexityTests/SchonhageEncoding.lean @@ -0,0 +1,139 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Flat + +-- Full-program regressions intentionally evaluate the native interpreter at elaboration time. +set_option linter.hashCommand false + +/-! +# Executed native shared-encoding regressions + +These clients start with the actual unsigned input convention. The interpreter charges every +native arithmetic block, branch, loop and store. They compare observable memory after execution. +-/ + +namespace ComplexityTests.SchonhageEncoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +private def observe (side : Bool) (w L fuel : ℕ) (a : Fin (7 ^ L) → ℤ) : + Option ((List ℤ × Bool) × ℕ) := + ((program side).eval w fuel (State.init (input L a)).data).map fun (y, time) => + (((List.range (10 ^ L)).map fun i => decode (y.mem (outputBase L + i)), + decide (y.mem 0 = (input L a).length ∧ y.mem 1 = L ∧ + (∀ i : Fin (7 ^ L), y.mem (2 + i.val) = encode (a i)) ∧ + y.mem (scratchBase L + 10 ^ L) = 0)), time) + +-- Kernel reduction follows the finite form dispatch and all actual signed arithmetic blocks. +-- Its nested code and loop reductions exceed the default elaborator recursion depth. +set_option maxRecDepth 16384 in +example : observe false 8 0 2 (fun _ => -3) = some (([-3], true), 31) := by + decide +kernel + +-- These checks run the actual interpreter as regression tests; the general execution +-- theorems are kernel checked separately. VM evaluation avoids quadratic kernel reduction +-- through thousands of functional register updates in each concrete full-program test. +#guard (observe false 12 1 16 (fun i => (i.val : ℤ) + 1)).map Prod.fst == + some ([5, 6, 1, 8, 9, 2, -7, -9, 3, -6], true) + +#guard (observe true 12 1 16 (fun i => (i.val : ℤ) + 1)).map Prod.fst == + some ([5, 7, -6, 7, 9, -10, 1, 2, 3, 6], true) + +#guard (observe false 12 1 16 (fun i => -((i.val : ℤ) + 1))).map Prod.fst == + some ([-5, -6, -1, -8, -9, -2, 7, 9, -3, 6], true) + +-- At width four, the computed scratch address 19 overflows; width five is the first working +-- width for this zero-valued depth-one client. Every output and the adjacent frame are checked. +#guard observe false 4 1 16 (fun _ => 0) == none +#guard (observe false 5 1 16 (fun _ => 0)).map Prod.fst == + some (List.replicate 10 0, true) + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +private def signedLeft (u : LeftStr 2) : ℤ := + if u 0 = .x 0 then -2 else if u 1 = .p 1 0 then 3 else 1 + +private def signedRight (u : RightStr 2) : ℤ := + if u 0 = .q 0 1 then -3 else if u 1 = .y 2 then 2 else 0 + +private def leafAt (i : Fin (10 ^ 2)) : Leaf 2 := + fun ℓ => termAt (finFunctionFinEquiv.symm i ℓ) + +-- Output addresses can be calculated by ordinary executable finite alphabet numberings. +#guard (leafIndex (fun _ : Fin 2 => Term.P0)).val == 99 +#guard (leafIndex (fun _ : Fin 2 => Term.P 0 0)).val == 0 +#guard (leftDigits.symm (.p 1 1)).val == 6 + +-- Two stages alternate buffers and then copy backward from scratch to output. The expected +-- values use the unchanged public mathematical encodings, including every one of the 100 leaves. +#guard (observe false 16 2 128 (fun i => signedLeft + (fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ)))).map Prod.fst == + some (List.ofFn (fun i => encodingL signedLeft (leafAt i)), true) + +#guard (observe true 16 2 128 (fun i => signedRight + (fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ)))).map Prod.fst == + some (List.ofFn (fun i => encodingR signedRight (leafAt i)), true) + +private def copyObserve (s d : ℕ) : Option (List ℕ × Bool × ℕ) := + let x : Data 7 := ⟨![s, d, 3, 0, 0, 0, 0], + fun a => if s ≤ a ∧ a < s + 3 then a - s + 1 else 99⟩ + (ArrayCopy.program.eval 8 4 x).map fun (y, time) => + (List.ofFn (fun i : Fin 3 => y.mem (d + i.val)), + decide ((∀ i : Fin 3, y.mem (s + i.val) = i.val + 1) ∧ + y.mem 0 = 99 ∧ y.mem 12 = 99), time) + +-- Execute the generalized helper in both disjoint orders and with equal blocks. The old +-- forward-copy theorem remains available; these also check nonzero words outside the frame. +#guard copyObserve 4 8 == some ([1, 2, 3], true, 25) +#guard copyObserve 8 4 == some ([1, 2, 3], true, 25) +#guard copyObserve 4 4 == some ([1, 2, 3], true, 25) + +-- Nonzero garbage immediately beyond the two work buffers survives the complete encoder. +#guard ((program false).eval 16 128 + ((State.init (input 2 (fun _ => -1)) : State 32).data.write + (scratchBase 2 + 10 ^ 2) 91)).map + (fun (y, _) => y.mem (scratchBase 2 + 10 ^ 2)) == some 91 + +#guard observe true 8 0 2 (fun _ => -4) == some (([-4], true), 31) + +/-- The explicit width condition itself has a checked boundary. -/ +example : ¬Fits 9 (addressBound 1) ∧ Fits 10 (addressBound 1) := by decide + +/-- A kernel-checked zero-depth flat execution with a negative value, derived from the +general theorem rather than from VM evaluation. All calling/width premises are discharged. -/ +example : ∃ y : Data 32, ∃ n, n ≤ 241 ∧ + Executes 8 ((program false).program output) + (State.init (inputL (fun _ : LeftStr 0 => -3))) 3 n ∧ y.mem 3 = 7 := by + obtain ⟨_, y, n, hn, _, he, hv, _⟩ := runs_left (w := 6) (by decide) + (fun _ : LeftStr 0 => -3) 3 (by decide) (by intro u; decide) (by decide) (by decide) + refine ⟨y, n + 1, ?_, he, ?_⟩ + · norm_num [timeBound] at hn + omega + · have hidx : (leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by + have := (leafIndex (Fin.elim0 : Leaf 0)).isLt + norm_num at this ⊢ + have h := hv (Fin.elim0 : Leaf 0) + have hval : encodingL (fun _ : LeftStr 0 => -3) Fin.elim0 = -3 := + encodeWith_zero phi _ _ + have henc : encode (-3) = 7 := by decide + simpa only [outputBase, pow_zero, hidx, hval, henc, Nat.reduceAdd] using h + +/-- A nonvacuous two-stage client of the complete flat execution theorem. The checked signed +array has a zero `P0,P0` leaf through cancellation and also many nonzero leaves in the VM test. -/ +example : ∃ y : Data 32, ∃ n, n ≤ 72001 ∧ + Executes 18 ((program false).program output) (State.init (inputL signedLeft)) 51 n ∧ + y.mem (outputBase 2 + (leafIndex (fun _ : Fin 2 => Term.P0)).val) = 0 := by + obtain ⟨_, y, n, hn, _, he, hv, _⟩ := runs_left (w := 16) (by decide) + signedLeft 3 (by decide) (by decide +kernel) (by decide) (by decide) + refine ⟨y, n + 1, ?_, he, ?_⟩ + · norm_num [timeBound] at hn + omega + · have hmath : encodingL signedLeft (fun _ : Fin 2 => Term.P0) = 0 := by decide +kernel + simpa only [hmath, show encode 0 = 0 from rfl] using hv (fun _ : Fin 2 => Term.P0) + +end ComplexityTests.SchonhageEncoding diff --git a/NOTICE b/NOTICE index 65eb16f1c4..a28ee14dd6 100644 --- a/NOTICE +++ b/NOTICE @@ -1,8 +1,9 @@ -The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native matrix builder in +The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native programs in Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, +Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/, Complexity/Foundations/Algebra/{FiniteMagnitude,GeometricTail}.lean, Complexity/Foundations/Combinatorics/{BinomialUpperBound,FunctionPlaces}.lean, Complexity/Foundations/Data/Finset/{Average,OrderGlue,Quotient}.lean, diff --git a/ROADMAP.md b/ROADMAP.md index 5805e12162..7e50f2a319 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -428,8 +428,11 @@ each statement against the numbered result. Organize modules by subject, not cha shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set bounds, with the empty zero-depth root handled explicitly. The finite thin-product component ([#1463]) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and - sparse encoding/leaf/passed-set totals. These counts are not native instruction costs. It still - needs native tensor/sparse-query programs, native Chan–He preprocessing, the reduction + sparse encoding/leaf/passed-set totals. Those counts are not native instruction costs. + The shared native left/right encoders ([#1493]) compute all `10^L` leaf values from `7^L` + signed inputs within `240*(L+1)*10^L` standard WordRAM instructions, plus the final halt, + with fixed code/registers and concrete initialized inputs, frames and explicit width bounds. + Band/tile preparation, native pruned/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width @@ -560,3 +563,6 @@ not planned, not as proved. [#1442]: https://github.com/cslib-community/complexitylib/issues/1442 [#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 +[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 +[#1463]: https://github.com/cslib-community/complexitylib/issues/1463 +[#1493]: https://github.com/cslib-community/complexitylib/issues/1493 diff --git a/UPSTREAM.md b/UPSTREAM.md index dde42fa023..87a5bc5c21 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -206,8 +206,22 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he from exact contributing-leaf counts. Generic function-position counts, ordered gluing, binomial and finite-sum bounds live in small Foundations modules. Mathlib `Finset.orderEmbOfFin` indexes both a level set and its complement, and `Finset.piecewise` restricts answers. No source machine, - problem-statement universe or solver/time hypothesis is imported. Native tensor/sparse-query - programs, native preprocessing/solvers and full input/time/width composition, including the + problem-statement universe or solver/time hypothesis is imported. + `Algorithms/WordRAM/MatrixMultiplication/Schonhage/` implements the complete shared left/right + encoding milestone ([#1493](https://github.com/cslib-community/complexitylib/issues/1493)). + Fixed standard-WordRAM code with 32 registers transforms one coordinate at a time between + two disjoint `10^L` buffers. It starts from `State.init` with a depth header and `7^L` + sign-and-magnitude words, computes all public `encodingL`/`encodingR` values, and preserves + the input and every cell outside the two buffers. Mathlib's `finFunctionFinEquiv` supplies + least-significant-first string numbering. All stages, powers, indices, address arithmetic, + signed add/subtract blocks and the final output copy cost at most `240*(L+1)*10^L` + instructions; halt adds one. Signed operands have magnitude bounded from the original + input by `7^L*A < 2^(w-1)`; the explicit natural bound `L+64+64*10^L` must fit target width + `w+2`. The two extra bits and actual arithmetic-block costs come from the existing native + signed kernels, without a copied source machine or a whole-program simulation hypothesis. + The existing array-copy interface is retained, with a narrow generalization for disjoint + blocks in either address order or identical blocks. Band/tile preparation, native pruned + and sparse-query programs, native preprocessing/solvers and full input/time/width composition, including the improved Section 4 route to exponent 1.9992, remain in #1394. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes From 3b9234da6bc3c0daad2cd167aeb4fd8f9fc592b6 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 13:01:20 -0400 Subject: [PATCH 19/32] test(wordram): connect encoder witnesses to charged runs Connect the zero-depth and two-stage regression memory witnesses to the actual prefix Runs already proved by runs_left, rather than discarding that conjunct. Bound the prefixes by 240 and 72000 instructions and expose the same executions' charged halt at n+1. All library code is unchanged from ad3e09735a116f9868e87cf7ae6af59e5cb6a285 (tree b598513f02cc064eaf50d3c680684c19289ffd71). Validation of tree 8277017f2c63a5851b63a5d4b49775a9fb96d62d against actual parent b4936ce28d8b498b364a7947d34f728c55f8b226: all six gates pass together (build --wfail --iofail, mk_all --check, test --wfail --iofail, lint, lint-style, checkInitImports), full citation-aware hygiene --check-simp, fresh changed-file lints (13 library modules, one test), strict PR-draft declaration-name check and staged/unstaged diff checks pass. Independent nonauthor agent review 8034f3da approved exact ad3e09735/tree b598513f, covering full code/model/cost/width/source and ArrayCopy with no blockers. It is inside-fleet review, not outside approval. This test-only delta addresses its optional witness-connection finding; focused final test/source-mapping review remains pending through the parent coordinator. No selected abstract, catalogue or rendering impact. The full ordinary native 3SUM objective #1394 remains open; this implements encoder issue #1493. AI assistance: OpenAI Codex implementation/self-review by delegated worker codex-shared-encoding-t3-03b600bd. Publication remains on hold for focused review; this commit is not approval or a whole-3SUM completion claim. --- ComplexityTests/SchonhageEncoding.lean | 27 ++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/ComplexityTests/SchonhageEncoding.lean b/ComplexityTests/SchonhageEncoding.lean index e03bb9f01a..6f9d10937a 100644 --- a/ComplexityTests/SchonhageEncoding.lean +++ b/ComplexityTests/SchonhageEncoding.lean @@ -106,14 +106,15 @@ example : ¬Fits 9 (addressBound 1) ∧ Fits 10 (addressBound 1) := by decide /-- A kernel-checked zero-depth flat execution with a negative value, derived from the general theorem rather than from VM evaluation. All calling/width premises are discharged. -/ -example : ∃ y : Data 32, ∃ n, n ≤ 241 ∧ +example : ∃ y : Data 32, ∃ n, n ≤ 240 ∧ + Runs 8 ((program false).program output) + (State.init (inputL (fun _ : LeftStr 0 => -3))) ⟨(program false).size, y⟩ n ∧ Executes 8 ((program false).program output) - (State.init (inputL (fun _ : LeftStr 0 => -3))) 3 n ∧ y.mem 3 = 7 := by - obtain ⟨_, y, n, hn, _, he, hv, _⟩ := runs_left (w := 6) (by decide) + (State.init (inputL (fun _ : LeftStr 0 => -3))) 3 (n + 1) ∧ y.mem 3 = 7 := by + obtain ⟨_, y, n, hn, hr, he, hv, _⟩ := runs_left (w := 6) (by decide) (fun _ : LeftStr 0 => -3) 3 (by decide) (by intro u; decide) (by decide) (by decide) - refine ⟨y, n + 1, ?_, he, ?_⟩ - · norm_num [timeBound] at hn - omega + refine ⟨y, n, ?_, hr, he, ?_⟩ + · simpa only [timeBound, pow_zero, Nat.reduceAdd, Nat.mul_one] using hn · have hidx : (leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by have := (leafIndex (Fin.elim0 : Leaf 0)).isLt norm_num at this ⊢ @@ -125,14 +126,16 @@ example : ∃ y : Data 32, ∃ n, n ≤ 241 ∧ /-- A nonvacuous two-stage client of the complete flat execution theorem. The checked signed array has a zero `P0,P0` leaf through cancellation and also many nonzero leaves in the VM test. -/ -example : ∃ y : Data 32, ∃ n, n ≤ 72001 ∧ - Executes 18 ((program false).program output) (State.init (inputL signedLeft)) 51 n ∧ +example : ∃ y : Data 32, ∃ n, n ≤ 72000 ∧ + Runs 18 ((program false).program output) (State.init (inputL signedLeft)) + ⟨(program false).size, y⟩ n ∧ + Executes 18 ((program false).program output) (State.init (inputL signedLeft)) 51 (n + 1) ∧ y.mem (outputBase 2 + (leafIndex (fun _ : Fin 2 => Term.P0)).val) = 0 := by - obtain ⟨_, y, n, hn, _, he, hv, _⟩ := runs_left (w := 16) (by decide) + obtain ⟨_, y, n, hn, hr, he, hv, _⟩ := runs_left (w := 16) (by decide) signedLeft 3 (by decide) (by decide +kernel) (by decide) (by decide) - refine ⟨y, n + 1, ?_, he, ?_⟩ - · norm_num [timeBound] at hn - omega + refine ⟨y, n, ?_, hr, he, ?_⟩ + · norm_num [timeBound] at hn ⊢ + exact hn · have hmath : encodingL signedLeft (fun _ : Fin 2 => Term.P0) = 0 := by decide +kernel simpa only [hmath, show encode 0 = 0 from rfl] using hv (fun _ : Fin 2 => Term.P0) From 68bbe40e3ae2e1164fb7586b5948fe65a2db9da8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:22:45 -0400 Subject: [PATCH 20/32] refactor(schonhage): reuse binomial bounds and sharpen signed tests Address the bounded nonblocking review findings on #1496. Keep Nat.choose_le_exp_mul_div_pow unchanged while deriving it from Cslib.sum_choose_le_exp_one_mul_div_pow in the existing real binomial module; remove the redundant module and migrate its import. Preserve Anthropic, Samuel Schlesinger, OpenAI and Erik Demaine credit. Kernel-check an actual mixed inner/outer L2,m1 signed pruned result of -8, remove two signature-only tests and retain positive admitted-parameter checks. Paraphrase the identified source-derived sentence, shorten generic source boilerplate and record the generic naming follow-up. No public mathematical statement or definition changes meaning; no selected abstract or endpoint promotion. Final validation/source/review evidence is in PR #1496. AI-assisted implementation by codex-three-sum-continuation-t3-3dda8289. --- Complexity.lean | 1 - .../Schonhage/Parameters.lean | 2 +- .../Schonhage/Sparse.lean | 4 +- .../Foundations/Algebra/FiniteMagnitude.lean | 5 +- .../Foundations/Algebra/GeometricTail.lean | 5 +- .../Combinatorics/BinomialUpperBound.lean | 51 ------------------- .../Combinatorics/FunctionPlaces.lean | 5 +- .../Foundations/Data/Finset/OrderGlue.lean | 5 +- .../Foundations/Data/Nat/Choose/Real.lean | 32 +++++++++++- ComplexityTests/SchonhageThinProduct.lean | 28 ++++------ NOTICE | 6 ++- UPSTREAM.md | 1 + 12 files changed, 58 insertions(+), 87 deletions(-) delete mode 100644 Complexity/Foundations/Combinatorics/BinomialUpperBound.lean diff --git a/Complexity.lean b/Complexity.lean index 1cdd3b3c05..9674671a25 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1937,7 +1937,6 @@ public import Complexity.Foundations.Combinatorics.AffineDisperser.IndependentSo public import Complexity.Foundations.Combinatorics.AffineDisperser.Threshold public import Complexity.Foundations.Combinatorics.BalancedSchedules public import Complexity.Foundations.Combinatorics.BinomialTail -public import Complexity.Foundations.Combinatorics.BinomialUpperBound public import Complexity.Foundations.Combinatorics.CentralBinomial public import Complexity.Foundations.Combinatorics.DepthReduction public import Complexity.Foundations.Combinatorics.Expander.AffineFamily diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean index 10b5d1679e..38a936be5b 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Parameters.lean @@ -9,7 +9,7 @@ module public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Sparse public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling -public import Complexity.Foundations.Combinatorics.BinomialUpperBound +public import Complexity.Foundations.Data.Nat.Choose.Real public import Mathlib.Analysis.Complex.ExponentialBounds /-! diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean index b18c209f3c..1b2cab585a 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Sparse.lean @@ -261,8 +261,8 @@ private theorem sum_alpha_small_orders_le (m : ℕ) : · simp [alpha, saving] · exact (sum_alpha_small_orders_lt m hm).le -/-- In the proof of Lemma 11: We use |U| α_d for the orders d ≤ m/9 and β_d for the orders -d > m/9. -/ +/-- Split the order sum at `m / 9`, applying the query-dependent estimate below the cutoff +and the layout-dependent estimate above it. -/ private theorem card_leaves_le_split {L m : ℕ} (U : Finset (OutStr L)) (hU : ∀ w ∈ U, (innerSetO w).card = m) : (leaves U).card diff --git a/Complexity/Foundations/Algebra/FiniteMagnitude.lean b/Complexity/Foundations/Algebra/FiniteMagnitude.lean index e18125684f..4bf60b86e2 100644 --- a/Complexity/Foundations/Algebra/FiniteMagnitude.lean +++ b/Complexity/Foundations/Algebra/FiniteMagnitude.lean @@ -20,10 +20,9 @@ public import Mathlib.Algebra.Order.BigOperators.Ring.Finset ## References -Adapted selectively from [AlmanWilliams2026, Section 2] and +Adapted selectively from [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical -counts and identities; they give no native program or instruction-cost certificate. +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @[expose] public section diff --git a/Complexity/Foundations/Algebra/GeometricTail.lean b/Complexity/Foundations/Algebra/GeometricTail.lean index c9b5095dc8..ca762bc791 100644 --- a/Complexity/Foundations/Algebra/GeometricTail.lean +++ b/Complexity/Foundations/Algebra/GeometricTail.lean @@ -20,10 +20,9 @@ public import Mathlib.Algebra.Order.BigOperators.Ring.Finset ## References -Adapted selectively from [AlmanWilliams2026, Section 2] and +Adapted selectively from [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical -counts and identities; they give no native program or instruction-cost certificate. +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @[expose] public section diff --git a/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean b/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean deleted file mode 100644 index 6036e4144e..0000000000 --- a/Complexity/Foundations/Combinatorics/BinomialUpperBound.lean +++ /dev/null @@ -1,51 +0,0 @@ -/- -Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Authors: Anthropic, PBC, Erik Demaine --/ - -module --- upstream: candidate - -public import Complexity.Init -public import Mathlib.Analysis.Complex.Exponential -public import Mathlib.Data.Nat.Choose.Bounds - -/-! -# An exponential upper bound on binomial coefficients - -## Main results - -* `Nat.choose_le_exp_mul_div_pow`: the bound `(e n / k)^k`, including `k = 0`. - -## References - -Adapted selectively from [AlmanWilliams2026, Section 2] and -[Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical -counts and identities; they give no native program or instruction-cost certificate. --/ - -@[expose] public section - -namespace Nat - -/-- `binom(n, k) ≤ (e n / k) ^ k`: indeed `binom(n, k) ≤ n ^ k / k!`, and -`k ^ k / k! ≤ e ^ k` is one term of the exponential series. For `k = 0` both sides are `1`. -/ -theorem choose_le_exp_mul_div_pow (n k : ℕ) : - (n.choose k : ℝ) ≤ (Real.exp 1 * (n : ℝ) / (k : ℝ)) ^ k := by - obtain rfl | hk := k.eq_zero_or_pos - · rw [choose_zero_right, pow_zero, cast_one] - have hk' : (0 : ℝ) < k := cast_pos.2 hk - have hfact : (0 : ℝ) < k.factorial := cast_pos.2 k.factorial_pos - have hexp : (k : ℝ) ^ k ≤ Real.exp k * k.factorial := - (div_le_iff₀ hfact).1 (Real.pow_div_factorial_le_exp _ hk'.le k) - rw [div_pow, mul_pow, ← Real.exp_nat_mul, mul_one] - refine (choose_le_pow_div k n).trans ?_ - rw [div_le_div_iff₀ hfact (pow_pos hk' k)] - calc (n : ℝ) ^ k * (k : ℝ) ^ k ≤ (n : ℝ) ^ k * (Real.exp k * k.factorial) := - mul_le_mul_of_nonneg_left hexp (pow_nonneg n.cast_nonneg k) - _ = Real.exp k * (n : ℝ) ^ k * k.factorial := by ring - - -end Nat diff --git a/Complexity/Foundations/Combinatorics/FunctionPlaces.lean b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean index e2ac749310..206c61f3da 100644 --- a/Complexity/Foundations/Combinatorics/FunctionPlaces.lean +++ b/Complexity/Foundations/Combinatorics/FunctionPlaces.lean @@ -23,10 +23,9 @@ public import Mathlib.Data.Fintype.BigOperators ## References -Adapted selectively from [AlmanWilliams2026, Section 2] and +Adapted selectively from [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical -counts and identities; they give no native program or instruction-cost certificate. +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @[expose] public section diff --git a/Complexity/Foundations/Data/Finset/OrderGlue.lean b/Complexity/Foundations/Data/Finset/OrderGlue.lean index 9a61f690c7..3cff17ec35 100644 --- a/Complexity/Foundations/Data/Finset/OrderGlue.lean +++ b/Complexity/Foundations/Data/Finset/OrderGlue.lean @@ -23,10 +23,9 @@ public import Mathlib.Data.Finset.Sort ## References -Adapted selectively from [AlmanWilliams2026, Section 2] and +Adapted selectively from [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical -counts and identities; they give no native program or instruction-cost certificate. +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @[expose] public section diff --git a/Complexity/Foundations/Data/Nat/Choose/Real.lean b/Complexity/Foundations/Data/Nat/Choose/Real.lean index 8265f9eab3..b8e5e8f6aa 100644 --- a/Complexity/Foundations/Data/Nat/Choose/Real.lean +++ b/Complexity/Foundations/Data/Nat/Choose/Real.lean @@ -1,7 +1,7 @@ /- -Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Copyright (c) 2026 Samuel Schlesinger, Anthropic, PBC, Erik Demaine. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Samuel Schlesinger, OpenAI +Authors: Samuel Schlesinger, OpenAI, Anthropic, PBC, Erik Demaine -/ module @@ -34,6 +34,7 @@ generating-function bound, motivated by the geometric thresholds in Appendix A.3 * `Cslib.sum_choose_le_exp_mul_pow`: a real-parameter generating-function bound. * `Cslib.sum_choose_le_exp_one_mul_div_pow`: the bound with parameter `q / e`. +* `Nat.choose_le_exp_mul_div_pow`: its single-binomial-coefficient consequence, for all `n, k`. * `Cslib.sum_choose_le_two_rpow_div`: the geometric tail under an exact integer budget test. * `Cslib.sum_choose_le_two_pow_div_add_one`: its natural-number rounded form. * `Cslib.le_mul_logb_of_two_pow_le_sum_choose_sq`: the logarithmic consequence. @@ -113,6 +114,33 @@ theorem sum_choose_le_exp_one_mul_div_pow {q e : ℕ} (heq : e ≤ q) : rw [mul_div_assoc, mul_pow, ← Real.exp_nat_mul] simp +end Cslib + +namespace Nat + +/-- A single binomial coefficient is at most `(exp 1 * n / k)^k`, including `k = 0`. +This follows from `Cslib.sum_choose_le_exp_one_mul_div_pow` when `k ≤ n`; otherwise the +coefficient vanishes. The statement is also present in +[Anthropic's formal source](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Choose.lean#L52-L53); +this proof reuses the partial-sum bound. -/ +theorem choose_le_exp_mul_div_pow (n k : ℕ) : + (n.choose k : ℝ) ≤ (Real.exp 1 * (n : ℝ) / (k : ℝ)) ^ k := by + by_cases hkn : k ≤ n + · calc + (n.choose k : ℝ) ≤ ∑ j ∈ Finset.range (k + 1), (n.choose j : ℝ) := + Finset.single_le_sum (fun j _ => Nat.cast_nonneg (n.choose j)) + (Finset.mem_range.mpr (by omega)) + _ ≤ (Real.exp 1 * (n : ℝ) / (k : ℝ)) ^ k := + Cslib.sum_choose_le_exp_one_mul_div_pow hkn + · rw [choose_eq_zero_of_lt (by omega), cast_zero] + positivity + +end Nat + +namespace Cslib + +open Finset + /-- For any input size `N`, the tail with dimension `C * N` and radius `Γ * N / M` is at most `2 ^ (N / B)` when the exact integer test `B * Γ * ⌈log₂ (4 * C * M)⌉ ≤ M` holds. This is an original corollary of `sum_choose_le_exp_mul_pow`, motivated by Appendix A.3 of diff --git a/ComplexityTests/SchonhageThinProduct.lean b/ComplexityTests/SchonhageThinProduct.lean index 3eff3864fc..9ab0067ef8 100644 --- a/ComplexityTests/SchonhageThinProduct.lean +++ b/ComplexityTests/SchonhageThinProduct.lean @@ -75,27 +75,29 @@ example : Pruned.result 1 (encodingL (bandArrayL outerLayout X 0)) (encodingR (bandArrayR outerLayout Y 1)) (wantedStrings outerLayout W 0 1) (outStrOfPos outerLayout 2 3) = 0 := by decide +kernel --- Every queried product entry, through the general correctness theorem. -example (I J : Fin 4) (h : (I, J) ∈ W) : - Pruned.result 1 (encodingL (bandArrayL outerLayout X (bandOf 1 0 I))) - (encodingR (bandArrayR outerLayout Y (bandOf 1 0 J))) - (wantedStrings outerLayout W (bandOf 1 0 I) (bandOf 1 0 J)) - (outStrOfPos outerLayout I J) = (X * Y) I J := - thin_product_spec outerLayout X Y W I J h - example : ∑ T ∈ tiles 1 0 4, (wantedStrings outerLayout W T.1 T.2).card = 3 := by decide +kernel example : ∑ T ∈ tiles 1 0 4, (leaves (wantedStrings outerLayout W T.1 T.2)).card = 3 := by decide example : wantedStrings outerLayout W 1 1 = ∅ := by decide example : (leaves (wantedStrings outerLayout W 1 1)).card = 0 := by decide +kernel --- Four signed summands at a genuine inner level; the tensor's error contributes nothing. +-- Four signed summands with a single inner output; there is no outer error coefficient here. private def A : Matrix (Fin 1) (Fin 4) ℤ := !![-3, 2, 0, 5] private def B : Matrix (Fin 4) (Fin 1) ℤ := !![2; -1; 4; 0] example : Full.result 1 (bandArrayL innerLayout A 0) (bandArrayR innerLayout B 0) (outStrOfPos innerLayout 0 0) = -8 := by decide +kernel +-- A mixed inner/outer output checks the exact-inner-count zeroing that eliminates error terms. +private def mixedX (_Q : Finset (Fin 2)) : LeftMat 2 1 := + fun _ π => A 0 (finProdFinEquiv (π 0)) +private def mixedY (_Q : Finset (Fin 2)) : RightMat 2 1 := + fun π _ => B (finProdFinEquiv (π 0)) 0 +private def mixedOutput : OutStr 2 := ![.z0, .z 0 0] + +example : Pruned.result 2 (encodingL (arrayL 1 mixedX)) (encodingR (arrayR 1 mixedY)) + {mixedOutput} mixedOutput = -8 := by decide +kernel + example : Pruned.result 1 (encodingL (bandArrayL innerLayout A 0)) (encodingR (bandArrayR innerLayout B 0)) (wantedStrings innerLayout ({(0, 0)} : Finset (Fin 1 × Fin 1)) 0 0) @@ -127,14 +129,6 @@ example : D 1 ^ 18 ≤ admittedN ∧ singletonQuery.card * 2 ^ 1 ≤ admittedN ^ · rfl · norm_num [singletonQuery, admittedN] -example (X' : Matrix (Fin admittedN) (Fin (D 1)) ℤ) - (Y' : Matrix (Fin (D 1)) (Fin admittedN) ℤ) : - Pruned.result 19 (encodingL (bandArrayL admittedLayout X' (bandOf 19 1 0))) - (encodingR (bandArrayR admittedLayout Y' (bandOf 19 1 0))) - (wantedStrings admittedLayout singletonQuery (bandOf 19 1 0) (bandOf 19 1 0)) - (outStrOfPos admittedLayout 0 0) = (X' * Y') 0 0 := - thin_product_spec admittedLayout X' Y' singletonQuery 0 0 (by simp [singletonQuery]) - example : ((2 * numBands (19 * 1) 1 admittedN * 10 ^ (19 * 1) : ℕ) : ℝ) ≤ 4 * (D 1 : ℝ) ^ (-(1 / 18 : ℝ)) * (admittedN : ℝ) ^ 2 := by diff --git a/NOTICE b/NOTICE index 65eb16f1c4..4f0b9376da 100644 --- a/NOTICE +++ b/NOTICE @@ -4,7 +4,7 @@ Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, Complexity/Foundations/Algebra/{FiniteMagnitude,GeometricTail}.lean, -Complexity/Foundations/Combinatorics/{BinomialUpperBound,FunctionPlaces}.lean, +Complexity/Foundations/Combinatorics/FunctionPlaces.lean, Complexity/Foundations/Data/Finset/{Average,OrderGlue,Quotient}.lean, Complexity/Foundations/Data/Int/Residue.lean, Complexity/Foundations/Data/Nat/CeilDiv.lean, @@ -14,6 +14,10 @@ Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. +The single-coefficient binomial bound in Complexity/Foundations/Data/Nat/Choose/Real.lean +retains its source credit from that adaptation; its proof now derives the statement from the +library's existing partial-binomial-sum bound. + ThreeSumApsp: a Lean 4 formalization of "Truly Subquadratic 3SUM and Truly Subcubic APSP via Triangles in Sparse Lopsided Graphs" Copyright 2026 Anthropic, PBC diff --git a/UPSTREAM.md b/UPSTREAM.md index dde42fa023..3dfd78c523 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -287,6 +287,7 @@ agreement during upstream review. | `Foundations/Combinatorics/{FiniteCover,Hoeffding,Hypergeometric,PowersetTail,Sunflower}` | Consider Mathlib combinatorics; probabilistic-method lemmas belong near `Combinatorics/Probabilistic`. | | `Foundations/Data/Finset/Expect` | Product-type expectation helpers extracted from `Foundations/Combinatorics/Expander/Products` (#317); Mathlib candidates. | | `Foundations/Data/Finset/Markov` | Mathlib candidate: `Algebra/Order/BigOperators/Group/Finset` (`Finset.card_filter_mul_le_sum`, `Finset.mul_card_filter_le_sum`). | +| `Foundations/Data/Finset/OrderGlue` | Follow-up to #1496: rename the generic `forall_level_iff`, `exists_innerLevel`, `outerLevel_notMem` and `filter_glue` using order-embedding/complement terminology (for example `forall_iff_orderEmb_and_compl`), retaining this topic module. Migrate the concrete `Schonhage/Levels` and `Schonhage/Batch` clients together with the active native band-preparation client; defer that API migration until the latter is stable. | | `Foundations/Data/Matrix/Permanent` | Mathlib candidate: Laplace expansion of the permanent along row and column 0, beside `LinearAlgebra/Matrix/Permanent` (mirroring `Matrix.det_succ_row_zero`). | | `Foundations/LinearAlgebra/{Rank,Matrix/CutRank,Matrix/Lindsey}` | Mathlib candidates: `LinearAlgebra/Dimension/LinearMap` and `LinearAlgebra/Matrix/Rank`; placement of Lindsey's lemma needs agreement. | | `Probability/PairwiseIndependentHash/Shift` | Consider integration into `PairwiseIndependentHash`. | From 99b8a1be735b890c0084e258ea3b17e33bb99c71 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 17:32:42 -0400 Subject: [PATCH 21/32] doc(matrix): replace copied source prose with API descriptions Resolve the documentation-only requested changes on #1464: https://github.com/cslib-community/complexitylib/pull/1464#issuecomment-6477111132 Rewrite source-quotation fragments and related explanations in Alphabets, Identity, Full and Pruned for the local Lean declarations. Preserve the mathematical attribution, bibliography keys, source pin and copyright. A comment-aware comparison with 4f3226cd proves every noncomment byte is identical in all four files. No declaration, proof, definition, program, import or test changes. Existing checked source-statement quotations and notation/quantifier/model/resource mapping in the PR remain unchanged. The final source-comment screen compares against quoted fragments in the actual pinned Sec2 and Statements/PaperStatements files, including the known local renames; no substantial quoted fragment remains unmarked. Validation is recorded for this exact tree in PR #1464. Source checkpoint tree 8c3b87bb870d4c4e2202917357d19a7a139296e8. No abstract/catalogue/model/scope or rendering impact; no new headline. The full native 1.9992 endpoint remains open under #1394. Ancestor and descendant remotes are unchanged. AI assistance: documentation repair and validation by Codex continuation worker codex-three-sum-continuation-t3-3dda8289. Existing independent mathematical/API and checked-source review coverage is retained; outside review of the bounded documentation delta is requested, with no new review agent commissioned. --- .../Schonhage/Alphabets.lean | 92 +++++------ .../MatrixMultiplication/Schonhage/Full.lean | 119 +++++--------- .../Schonhage/Identity.lean | 37 ++--- .../Schonhage/Pruned.lean | 152 +++++++----------- 4 files changed, 162 insertions(+), 238 deletions(-) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean index d9d6a76f9a..34bcd43e19 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Alphabets.lean @@ -34,7 +34,8 @@ functions over integers; no arithmetic operation is treated as a native machine Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine -semantics or running-time statement is imported. Source-derived prose is paraphrased below. +semantics or running-time statement is imported. The docstrings below explain the local +declarations in our own words. -/ @[expose] public section @@ -43,29 +44,28 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.2: We call x₁, x₂, x₃, p₁₁, p₁₂, p₂₁, p₂₂ the seven left variables. --/ +/-- Left input coordinates: three `x` coordinates and a two-by-two family of `p` coordinates. -/ inductive LeftVar : Type | x (i : Fin 3) | p (i j : Fin 2) deriving DecidableEq, Fintype -/-- Section 2.2: and y₁, y₂, y₃, q₁₁, q₁₂, q₂₁, q₂₂ the seven right variables. --/ +/-- Right input coordinates: three `y` coordinates and a two-by-two family of `q` coordinates. -/ inductive RightVar : Type | y (j : Fin 3) | q (i j : Fin 2) deriving DecidableEq, Fintype -/-- Section 2.2: We will also use ten output variables z_ij (for 1 ≤ i, j ≤ 3) and z₀. +/-- Output coordinates comprise a three-by-three outer family and one distinguished inner +coordinate. -/ inductive OutVar : Type | z (i j : Fin 3) | z0 deriving DecidableEq, Fintype -/-- Section 2.2: Schönhage's identity has ten terms, which we name P_ij (for 1 ≤ i, j ≤ 3, -corresponding to the entries of p̂ and q̂) and P₀. +/-- The multiplication labels consist of nine indexed labels `P i j` and the additional label +`P0`. -/ inductive Term : Type | P (i j : Fin 3) @@ -91,46 +91,44 @@ def zForm (i j : Fin 3) : OutVar → ℤ := Pi.single (OutVar.z i j) 1 -/ def z0Form : OutVar → ℤ := Pi.single OutVar.z0 1 -/-- Section 2.2: the 3 × 3 matrix `p̂` of linear forms, p̂ = (p₁₁, p₁₂, 0; p₂₁, p₂₂, 0; -p₁₁-p₂₁, --p₁₂-p₂₂, 0). +/-- Extend the two-by-two `p` family to three rows by cancelling each column sum; the third column +is zero. -/ def pHat : Matrix (Fin 3) (Fin 3) (LeftVar → ℤ) := !![pForm 0 0, pForm 0 1, 0; pForm 1 0, pForm 1 1, 0; -pForm 0 0 - pForm 1 0, -pForm 0 1 - pForm 1 1, 0] -/-- Section 2.2: the 3 × 3 matrix `q̂` of linear forms, q̂ = (q₁₁, q₁₂, -q₁₁-q₁₂; q₂₁, q₂₂, --q₂₁-q₂₂; 0, 0, 0). +/-- Extend the two-by-two `q` family to three columns by cancelling each row sum; the third row is +zero. -/ def qHat : Matrix (Fin 3) (Fin 3) (RightVar → ℤ) := !![qForm 0 0, qForm 0 1, -qForm 0 0 - qForm 0 1; qForm 1 0, qForm 1 1, -qForm 1 0 - qForm 1 1; 0, 0, 0] -/-- Section 2.2: the linear form `φ_λ` in the left variables, φ_{P_ij} = x_i + p̂_ij and φ_{P₀} = --(x₁ + x₂ + x₃). Section 2.3.1: let φ_λ(s) ∈ {0, ±1} denote the coefficient of s in φ_λ; this is -`phi lam s`. +/-- Left coefficients for each multiplication label. An indexed label adds its `x` coordinate to +the corresponding `pHat` form; `P0` negates the sum of the three `x` coordinates. -/ def phi : Term → LeftVar → ℤ | .P i j => xForm i + pHat i j | .P0 => -(xForm 0 + xForm 1 + xForm 2) -/-- Section 2.2: the linear form `ψ_λ` in the right variables, ψ_{P_ij} = y_j + q̂_ij and ψ_{P₀} = -y₁ + y₂ + y₃. Section 2.3.1: `ψ_λ(t)`, the coefficient of `t` in `ψ_λ`, is `psi lam t`. +/-- Right coefficients for each multiplication label. Indexed labels use `y` plus `qHat`, whereas +`P0` uses the sum of the three `y` coordinates. -/ def psi : Term → RightVar → ℤ | .P i j => yForm j + qHat i j | .P0 => yForm 0 + yForm 1 + yForm 2 -/-- Section 2.2: the linear form `χ_λ` in the output variables, χ_{P_ij} = z_ij + z₀ and χ_{P₀} = -z₀. +/-- Output coefficients: each indexed label occurs in its own outer coordinate and in `z0`; `P0` +occurs only in `z0`. -/ def chi : Term → OutVar → ℤ | .P i j => zForm i j + z0Form | .P0 => z0Form -/-- Section 2.2: the output variable `z₀` is inner, the `z_ij` are outer. --/ +/-- Select the distinguished output coordinate, excluding the indexed outer coordinates. -/ def OutVar.IsInner : OutVar → Prop | .z _ _ => False | .z0 => True @@ -139,36 +137,28 @@ instance OutVar.instDecidablePredIsInner : DecidablePred OutVar.IsInner | .z _ _ => isFalse fun h => h | .z0 => isTrue trivial -/-- Section 2.2: We say that a term λ contributes to an output variable z if z appears in χ_λ. --/ +/-- A label is relevant to an output coordinate when its output coefficient is nonzero. -/ def Term.Contributes (lam : Term) (z : OutVar) : Prop := chi lam z ≠ 0 instance Term.instDecidableContributes (lam : Term) (z : OutVar) : Decidable (lam.Contributes z) := inferInstanceAs (Decidable (chi lam z ≠ 0)) -/-- The term that the private leaf has at a level where the output string has the variable `z`: -`P₀` for `z₀` and `P_ij` for `z_ij`. +/-- Choose the indexed label for an outer coordinate and `P0` for the inner coordinate. Applying +this choice coordinatewise defines a private leaf. -/ def OutVar.privateTerm : OutVar → Term | .z i j => .P i j | .z0 => .P0 -/-- Section 2.3.1: A left string of length L is a string u = u₁ u₂ ⋯ u_L of L left variables, and -we call u_ℓ its variable at level ℓ. --/ +/-- A length-`L` tuple of left coordinates, indexed from zero. -/ abbrev LeftStr (L : ℕ) : Type := Fin L → LeftVar -/-- Section 2.3.1: right strings of length `L`. --/ +/-- A length-`L` tuple with entries in `RightVar`. -/ abbrev RightStr (L : ℕ) : Type := Fin L → RightVar -/-- Section 2.3.1: output strings of length `L`. --/ +/-- A length-`L` tuple with entries in `OutVar`. -/ abbrev OutStr (L : ℕ) : Type := Fin L → OutVar -/-- Section 2.3.1: We refer to the call reached by choosing the terms τ₁, …, τ_k at levels 1, …, k -as the vertex τ₁ ⋯ τ_k, a string of k terms. It is at depth k. --/ +/-- The sequence of multiplication labels identifying a depth-`k` recursion vertex. -/ abbrev Vertex (k : ℕ) : Type := Fin k → Term -/-- Section 2.3.1: the leaves, the vertices τ = τ₁ ⋯ τ_L at depth L. --/ +/-- A terminal vertex, whose label sequence has the full recursion depth. -/ abbrev Leaf (L : ℕ) : Type := Vertex L /-! ### The alphabets -/ @@ -209,46 +199,40 @@ def Term.equiv : Term ≃ Option (Fin 3 × Fin 3) where left_inv t := by cases t <;> rfl right_inv t := by rcases t with _ | ⟨_, _⟩ <;> rfl -/-- Section 2.2: there are seven left variables. --/ +/-- The left alphabet has cardinality seven. -/ theorem card_leftVar : Fintype.card LeftVar = 7 := by simp [Fintype.card_congr LeftVar.equiv] -/-- Section 2.2: there are seven right variables. --/ +/-- The right alphabet has cardinality seven. -/ theorem card_rightVar : Fintype.card RightVar = 7 := by simp [Fintype.card_congr RightVar.equiv] -/-- Section 2.2: Schönhage's identity has ten terms. --/ +/-- There are ten multiplication labels. -/ theorem card_term : Fintype.card Term = 10 := by simp [Fintype.card_congr Term.equiv] /-! ### Which terms contribute to which output variables -/ -/-- Section 2.2, second observation: every term contributes to z₀. --/ +/-- The inner coordinate receives a contribution from every multiplication label. -/ @[simp] theorem Term.contributes_z0 (lam : Term) : lam.Contributes .z0 := by cases lam <;> simp [Term.Contributes, chi, zForm, z0Form] -/-- Section 2.2, second observation: only P_ij contributes to z_ij. --/ +/-- For an outer coordinate, relevance determines both indices of the multiplication label. -/ @[simp] theorem Term.contributes_z_iff (lam : Term) (i j : Fin 3) : lam.Contributes (.z i j) ↔ lam = .P i j := by cases lam <;> simp [Term.Contributes, chi, zForm, z0Form, Pi.single_apply, eq_comm] -/-- The second observation in one statement: a term contributes to `z` if and only if `z` is -inner, that is `z₀`, or the term is `z.privateTerm`, which is `P_ij` for `z = z_ij`. (This is the -term that the private leaf of Section 2.4.3 has at a level with the variable `z`.) +/-- Relevance has two possibilities: the output is inner, or the label is the private label +selected by that output. -/ theorem Term.contributes_iff (lam : Term) (z : OutVar) : lam.Contributes z ↔ z.IsInner ∨ lam = z.privateTerm := by decide +revert -/-- A sum over the terms contributing to `z` has the one summand `P_ij` for `z = z_ij`, and runs -over all terms for `z = z₀`. This is how step (4) of the recursions assembles the slice at `z`. +/-- Separate decoding into its two cases: an outer output selects one summand; the inner output +sums the whole label family. -/ theorem Term.sum_contributes {A : Type*} [AddCommMonoid A] (c : Term → A) (z : OutVar) : ∑ lam : Term with lam.Contributes z, c lam @@ -259,9 +243,7 @@ theorem Term.sum_contributes {A : Type*} [AddCommMonoid A] (c : Term → A) (z : | z i j => simp [filter_eq'] | z0 => simp -/-- Section 2.2: the χ_λ forms are all sums of output variables (without any minus signs or other -coefficients). --/ +/-- Each output coefficient is the zero-or-one indicator of `Term.Contributes`. -/ theorem chi_eq_ite (lam : Term) (z : OutVar) : chi lam z = if lam.Contributes z then 1 else 0 := by decide +revert diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean index ed13169001..801a2b80b3 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Full.lean @@ -38,7 +38,8 @@ instruction-cost interpretation. Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine -semantics or running-time statement is imported. Source-derived prose is paraphrased below. +semantics or running-time statement is imported. The docstrings below explain the local +declarations in our own words. -/ @[expose] public section @@ -47,23 +48,17 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.3.1: An array a on the left strings of length L assigns an integer a[u] to each u -that is a left string of length L. So such an array is a function `LeftStr L → ℤ`, and likewise -for right strings, output strings and leaves. - -the slice of a at a left variable s is the array a_s on the strings of length L - 1 given by -a_s[u'] := a[s u']; the same definition is used for every alphabet. +/-- Fix the first coordinate of a tuple-indexed array and retain the shorter tuple as its +argument. The construction works for any coordinate alphabet and value type. -/ def sliceAt {α β : Type} {L : ℕ} (a : (Fin (L + 1) → α) → β) (s : α) : (Fin L → α) → β := fun u' => a (Fin.cons s u') -/-- Section 2.3.1, step (2) of `Full.result`: A_λ := ∑_s φ_λ(s) a_s. --/ +/-- Apply the left coefficient form for `lam` to all first-coordinate slices of the input array. -/ def encodeStepL {L : ℕ} (lam : Term) (a : LeftStr (L + 1) → ℤ) : LeftStr L → ℤ := fun u' => ∑ s, phi lam s * sliceAt a s u' -/-- Section 2.3.1, step (2) of `Full.result`: B_λ := ∑_t ψ_λ(t) b_t. --/ +/-- Apply the right coefficient form for `lam` to all first-coordinate slices of the input array. -/ def encodeStepR {L : ℕ} (lam : Term) (b : RightStr (L + 1) → ℤ) : RightStr L → ℤ := fun v' => ∑ t, psi lam t * sliceAt b t v' @@ -84,31 +79,27 @@ def Full.run : (L : ℕ) → (LeftStr L → ℤ) → (RightStr L → ℤ) → (O | .z0 => ∑ lam, (R lam).1 (Fin.tail w), fun τ => (R (τ 0)).2 (Fin.tail τ)) -/-- The array that `Full.result(a, b)` returns. --/ +/-- Project the output array from full evaluation, discarding its record of leaf operands. -/ def Full.result (L : ℕ) (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : OutStr L → ℤ := (Full.run L a b).1 -/-- The two numbers that `Full.result(a, b)` multiplies at the leaf `τ`, the left one first. --/ +/-- Read the ordered pair of leaf operands recorded by `Full.run`. -/ def Full.multipliedAt (L : ℕ) (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (τ : Leaf L) : ℤ × ℤ := (Full.run L a b).2 τ /-! ### 2.3.2 Unraveling the recursion computation -/ -/-- Section 2.3.2: Φ_τ(a) := ∑_u a[u] ∏_{ℓ=1}^{L} φ_{τ_ℓ}(u_ℓ), the sum over all left strings `u`. +/-- Evaluate the left encoding at one leaf by weighting each input entry with the product of its +coordinate coefficients. -/ def encodeLeft {L : ℕ} (τ : Leaf L) (a : LeftStr L → ℤ) : ℤ := ∑ u, a u * ∏ ℓ, phi (τ ℓ) (u ℓ) -/-- Section 2.3.2: Ψ_τ(b) := ∑_v b[v] ∏_{ℓ=1}^{L} ψ_{τ_ℓ}(v_ℓ), the sum over all right strings -`v`. +/-- Evaluate the right encoding at one leaf using the corresponding products of right +coefficients. -/ def encodeRight {L : ℕ} (τ : Leaf L) (b : RightStr L → ℤ) : ℤ := ∑ v, b v * ∏ ℓ, psi (τ ℓ) (v ℓ) -/-- Section 2.3.2: a vertex τ₁ ⋯ τ_k contributes to an output string w if τ_ℓ contributes to w_ℓ -at every level ℓ ≤ k, for a leaf, that is, for `k = L`: the leaf `τ` contributes to `w` if `τ_ℓ` -contributes to `w_ℓ` at every level `ℓ`. --/ +/-- A leaf is relevant to an output tuple when its labels are relevant at every coordinate. -/ def Leaf.Contributes {L : ℕ} (τ : Leaf L) (w : OutStr L) : Prop := ∀ ℓ, (τ ℓ).Contributes (w ℓ) @@ -116,71 +107,57 @@ instance Leaf.instDecidableContributes {L : ℕ} (τ : Leaf L) (w : OutStr L) : Decidable (Leaf.Contributes τ w) := inferInstanceAs (Decidable (∀ ℓ, (τ ℓ).Contributes (w ℓ))) -/-- Equation (2): bilinearProduct(a, b)[w] := ∑_{τ contributing to w} Φ_τ(a) Ψ_τ(b). --/ +/-- For each output tuple, sum the products of left and right encodings over its relevant leaves. -/ def bilinearProduct {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : OutStr L → ℤ := fun w => ∑ τ : Leaf L with Leaf.Contributes τ w, encodeLeft τ a * encodeRight τ b -/-- Equation (3): γ(s, t, z) := ∑_{λ contributing to z} φ_λ(s) ψ_λ(t). +/-- The coefficient linking one left, right and output coordinate, obtained by summing products of +the relevant label coefficients. -/ def coefficient (s : LeftVar) (t : RightVar) (z : OutVar) : ℤ := ∑ lam : Term with lam.Contributes z, phi lam s * psi lam t -/-- Section 2.4.1: The encoding of a is the array of the 10^L numbers Φ_τ(a), indexed by the -leaves τ. --/ +/-- Package all left-encoding values into an array indexed by leaves. -/ def encodingL {L : ℕ} (a : LeftStr L → ℤ) : Leaf L → ℤ := fun τ => encodeLeft τ a -/-- Section 2.4.1: The encoding of b, the array of the numbers Ψ_τ(b). --/ +/-- Package all right-encoding values into an array indexed by leaves. -/ def encodingR {L : ℕ} (b : RightStr L → ℤ) : Leaf L → ℤ := fun τ => encodeRight τ b -/-- Section 2.4.2: For a set U of output strings of length L, we write leaves(U) for the set of -leaves that contribute to some output string of U. --/ +/-- Take the union of the relevant-leaf sets of all requested output tuples. -/ def leaves {L : ℕ} (U : Finset (OutStr L)) : Finset (Leaf L) := univ.filter fun τ => ∃ w ∈ U, Leaf.Contributes τ w -/-- Section 2.3.1: an array on the left strings of length `L` has 7^L entries in total. --/ +/-- Counting length-`L` left tuples gives `7^L`. -/ theorem card_leftStr (L : ℕ) : Fintype.card (LeftStr L) = 7 ^ L := by rw [Fintype.card_fun, card_leftVar, Fintype.card_fin] -/-- Section 2.3.1: an array on the right strings of length `L` has `7^L` entries. --/ +/-- Counting length-`L` right tuples gives `7^L`. -/ theorem card_rightStr (L : ℕ) : Fintype.card (RightStr L) = 7 ^ L := by rw [Fintype.card_fun, card_rightVar, Fintype.card_fin] -/-- Section 2.3.1: At depth k there are 10^k vertices; in particular there are `10^L` leaves. --/ +/-- A depth-`k` label sequence has ten independent choices at each position. -/ theorem card_vertex (k : ℕ) : Fintype.card (Vertex k) = 10 ^ k := by rw [Fintype.card_fun, card_term, Fintype.card_fin] -/-- Section 2.3.1: φ_λ(s) ∈ {0, ±1}. --/ +/-- Every left coefficient has absolute value at most one. -/ theorem abs_phi_le_one (lam : Term) (s : LeftVar) : |phi lam s| ≤ 1 := by decide +revert -/-- Section 2.3.1: `ψ_λ(t) ∈ {0, ±1}`. --/ +/-- Every right coefficient has absolute value at most one. -/ theorem abs_psi_le_one (lam : Term) (t : RightVar) : |psi lam t| ≤ 1 := by decide +revert -/-- Section 2.3.1: for each term λ, at most three of the seven values φ_λ(s) are nonzero. --/ +/-- The support of any left coefficient form has cardinality at most three. -/ theorem card_support_phi_le (lam : Term) : (univ.filter fun s => phi lam s ≠ 0).card ≤ 3 := by decide +revert -/-- Section 2.3.1: and similarly for the seven values ψ_λ(t). --/ +/-- The support of any right coefficient form has cardinality at most three. -/ theorem card_support_psi_le (lam : Term) : (univ.filter fun t => psi lam t ≠ 0).card ≤ 3 := by decide +revert /-! ### Which leaves contribute to an output string -/ -/-- In the proof of Lemma 7: the leaves contributing to zw' are the λτ' with λ contributing to z -and τ' contributing to w'. Here `λ = τ 0`, `τ' = Fin.tail τ`, `z = w 0` and `w' = Fin.tail w`. --/ +/-- Split coordinatewise leaf relevance into the first label and the remaining label tuple. -/ theorem Leaf.contributes_succ {L : ℕ} {τ : Leaf (L + 1)} {w : OutStr (L + 1)} : Leaf.Contributes τ w ↔ (τ 0).Contributes (w 0) ∧ Leaf.Contributes (Fin.tail τ) (Fin.tail w) := by @@ -205,9 +182,7 @@ theorem Leaf.sum_contributes_succ {L : ℕ} (w : OutStr (L + 1)) (f : Leaf (L + refine sum_congr rfl fun lam _ => ?_ by_cases h : lam.Contributes (w 0) <;> simp [h] -/-- In the proof of Lemma 8: the leaves contributing to `w` are the leaves whose term at level ℓ -contributes to w_ℓ, independently for each ℓ. --/ +/-- The relevant-leaf filter is a product of the allowed label sets at individual coordinates. -/ theorem Leaf.filter_contributes_eq_piFinset {L : ℕ} (w : OutStr L) : (univ.filter fun τ : Leaf L => Leaf.Contributes τ w) = Fintype.piFinset fun ℓ => univ.filter fun lam : Term => lam.Contributes (w ℓ) := by @@ -228,18 +203,17 @@ section encodeWith variable {α : Type} [Fintype α] (c : Term → α → ℤ) {L : ℕ} -/-- The number `∑_u a[u] ∏_ℓ c_{τ_ℓ}(u_ℓ)`, for an array `a` on the strings over a finite alphabet -and the coefficients `c` of a family of linear forms, one for each term. By definition it is -`Φ_τ(a)` for `c = φ` and `Ψ_τ(b)` for `c = ψ`. +/-- Encoding for an arbitrary coefficient family: weight each array entry by the product of +coefficients selected by the leaf. This specializes to both input encodings. -/ def encodeWith (τ : Leaf L) (a : (Fin L → α) → ℤ) : ℤ := ∑ u, a u * ∏ ℓ, c (τ ℓ) (u ℓ) -/-- For `L = 0` the only string is empty, and so are the products over the levels. --/ +/-- With no coordinates, the encoding evaluates the array at its unique empty argument. -/ theorem encodeWith_zero (τ : Leaf 0) (a : (Fin 0 → α) → ℤ) : encodeWith c τ a = a Fin.elim0 := by simp [encodeWith, Subsingleton.elim (default : Fin 0 → α) Fin.elim0] -/-- The number at the leaf `λτ'` for `a` is the number at the leaf `τ'` for `∑_s c_λ(s) a_s`. +/-- Process the first label of an encoding by combining the first-coordinate slices, then encode +the resulting shorter array. -/ theorem encodeWith_cons (lam : Term) (τ' : Leaf L) (a : (Fin (L + 1) → α) → ℤ) : encodeWith c (Fin.cons lam τ' : Leaf (L + 1)) a @@ -253,22 +227,19 @@ end encodeWith /-! ### Lemma 7 -/ -/-- In the proof of Lemma 7: for every leaf λτ' we have Φ_{λτ'}(a) = ∑_s φ_λ(s) Φ_{τ'}(a_s) = -Φ_{τ'}(A_λ). --/ +/-- The left encoding at a cons leaf is the tail encoding of the array produced by `encodeStepL`. -/ theorem encodeLeft_cons {L : ℕ} (lam : Term) (τ' : Leaf L) (a : LeftStr (L + 1) → ℤ) : encodeLeft (Fin.cons lam τ' : Leaf (L + 1)) a = encodeLeft τ' (encodeStepL lam a) := encodeWith_cons phi lam τ' a -/-- In the proof of Lemma 7: and similarly Ψ_{λτ'}(b) = Ψ_{τ'}(B_λ). +/-- The right encoding at a cons leaf is the tail encoding of the array produced by +`encodeStepR`. -/ theorem encodeRight_cons {L : ℕ} (lam : Term) (τ' : Leaf L) (b : RightStr (L + 1) → ℤ) : encodeRight (Fin.cons lam τ' : Leaf (L + 1)) b = encodeRight τ' (encodeStepR lam b) := encodeWith_cons psi lam τ' b -/-- For L = 0, the only leaf is the empty string τ, and Φ_τ(a) = a […], as the products over the -levels are empty (proof of Lemma 7). --/ +/-- The zero-depth left encoding reads the array at the empty tuple. -/ private theorem encodeLeft_zero (τ : Leaf 0) (a : LeftStr 0 → ℤ) : encodeLeft τ a = a Fin.elim0 := encodeWith_zero phi τ a @@ -278,7 +249,8 @@ private theorem encodeRight_zero (τ : Leaf 0) (b : RightStr 0 → ℤ) : encodeRight τ b = b Fin.elim0 := encodeWith_zero psi τ b -/-- **Lemma 7**, first half. At the leaf τ, Full.result(a, b) multiplies Φ_τ(a) by Ψ_τ(b). +/-- The operand record of full evaluation agrees with `encodeLeft` and `encodeRight` at every +leaf. -/ theorem Full.multipliedAt_eq_encoding {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) (τ : Leaf L) : @@ -294,8 +266,8 @@ theorem Full.multipliedAt_eq_encoding {L : ℕ} (a : LeftStr L → ℤ) (b : Rig = Full.multipliedAt L (encodeStepL (τ 0) a) (encodeStepR (τ 0) b) (Fin.tail τ) := rfl rw [hstep, ih, ← encodeLeft_cons, ← encodeRight_cons, Fin.cons_self_tail] -/-- Step (4) of `Full.result`, as it is read in the proof of Lemma 7: `c[z w'] = ∑_{λ contributing -to z} C_λ[w']`. +/-- Unfold full evaluation once and express decoding as a sum over labels relevant to the leading +output coordinate. -/ theorem Full.result_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightStr (L + 1) → ℤ) (w : OutStr (L + 1)) : @@ -304,9 +276,7 @@ theorem Full.result_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightStr ( rw [Term.sum_contributes] rfl -/-- In the proof of Lemma 7: `bilinearProduct(a, b)[z w'] = ∑_{λ contributing to z} -bilinearProduct(A_λ, B_λ)[w']`. --/ +/-- The bilinear product obeys the same first-coordinate decoding recurrence as full evaluation. -/ theorem bilinearProduct_succ {L : ℕ} (a : LeftStr (L + 1) → ℤ) (b : RightStr (L + 1) → ℤ) (w : OutStr (L + 1)) : bilinearProduct a b w = ∑ lam : Term with lam.Contributes (w 0), @@ -336,9 +306,8 @@ theorem full_spec {L : ℕ} (a : LeftStr L → ℤ) (b : RightStr L → ℤ) : /-! ### Lemma 8 -/ -/-- The display in the proof of Lemma 8: the sum, over the leaves `τ` contributing to `w`, of `∏_ℓ -φ_{τ_ℓ}(u_ℓ) ψ_{τ_ℓ}(v_ℓ)` is the product of sums ∏_ℓ ∑_{τ_ℓ contributing to w_ℓ} φ_{τ_ℓ}(u_ℓ) -ψ_{τ_ℓ}(v_ℓ) = ∏_ℓ γ(u_ℓ, v_ℓ, w_ℓ) by (3). +/-- Expand the product of one-coordinate coefficients as a sum of coefficient products indexed by +relevant leaves. -/ theorem prod_gamma_eq_sum_contributes {L : ℕ} (u : LeftStr L) (v : RightStr L) (w : OutStr L) : ∏ ℓ, coefficient (u ℓ) (v ℓ) (w ℓ) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean index dea69212a0..27995291ad 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Identity.lean @@ -33,7 +33,8 @@ The error term is retained; it cannot be discarded for unrestricted inputs. Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine -semantics or running-time statement is imported. Source-derived prose is paraphrased below. +semantics or running-time statement is imported. The docstrings below explain the local +declarations in our own words. -/ @[expose] public section @@ -70,13 +71,15 @@ noncomputable def formR (f : RightVar → ℤ) : TriPoly := ∑ t, MvPolynomial. -/ noncomputable def formO (f : OutVar → ℤ) : TriPoly := ∑ z, MvPolynomial.C (f z) * polyO z -/-- Section 2.2: G := ∑_{i,j=1}^{3} x_i y_j z_ij + (∑_{i,j=1}^{2} p_ij q_ij) z₀. +/-- The target polynomial combines the nine outer `x`-`y` products with the four `p`-`q` products +attached to the inner coordinate. -/ noncomputable def G : TriPoly := (∑ i : Fin 3, ∑ j : Fin 3, polyL (.x i) * polyR (.y j) * polyO (.z i j)) + (∑ i : Fin 2, ∑ j : Fin 2, polyL (.p i j) * polyR (.q i j)) * polyO .z0 -/-- Lemma 6: E := ∑_{i,j=1}^{3} (x_i q̂_ij + p̂_ij y_j + p̂_ij q̂_ij) z_ij. +/-- The error polynomial collects the additional outer-coordinate terms involving `pHat` or +`qHat`. It has no inner-coordinate summand. -/ noncomputable def E : TriPoly := ∑ i : Fin 3, ∑ j : Fin 3, @@ -128,50 +131,48 @@ theorem formR_single (t : RightVar) : formR (Pi.single t 1) = polyR t := by theorem formO_single (z : OutVar) : formO (Pi.single z 1) = polyO z := by simp [formO_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `φ_{P_ij} = x_i + p̂_ij`. +/-- Interpret an indexed left coefficient form as the sum of its `x` variable and its extended `p` +form. -/ theorem formL_phi_P (i j : Fin 3) : formL (phi (.P i j)) = polyL (.x i) + formL (pHat i j) := by simp [phi, xForm, formL_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `ψ_{P_ij} = y_j + q̂_ij`. +/-- Interpret an indexed right coefficient form as the sum of its `y` variable and its extended +`q` form. -/ theorem formR_psi_P (i j : Fin 3) : formR (psi (.P i j)) = polyR (.y j) + formR (qHat i j) := by simp [psi, yForm, formR_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `χ_{P_ij} = z_ij + z₀`. +/-- Interpret an indexed output form as the sum of the corresponding outer variable and the inner +variable. -/ theorem formO_chi_P (i j : Fin 3) : formO (chi (.P i j)) = polyO (.z i j) + polyO .z0 := by simp [chi, zForm, z0Form, formO_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `φ_{P₀} = -(x₁ + x₂ + x₃)`. --/ +/-- The distinguished left form is the negative sum of the three `x` polynomials. -/ theorem formL_phi_P0 : formL (phi .P0) = -(polyL (.x 0) + polyL (.x 1) + polyL (.x 2)) := by simp [phi, xForm, formL_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `ψ_{P₀} = y₁ + y₂ + y₃`. --/ +/-- The distinguished right form is the sum of the three `y` polynomials. -/ theorem formR_psi_P0 : formR (psi .P0) = polyR (.y 0) + polyR (.y 1) + polyR (.y 2) := by simp [psi, yForm, formR_eq_linearCombination] -/-- Section 2.2, the display of the linear forms: `χ_{P₀} = z₀`. --/ +/-- The distinguished output form denotes the inner polynomial variable alone. -/ theorem formO_chi_P0 : formO (chi .P0) = polyO .z0 := formO_single .z0 /-! ### Lemma 6 -/ -/-- Section 2.2: every column of p̂ […] sums to zero. --/ +/-- Summing `pHat` over the row index gives the zero coefficient function. -/ theorem pHat_column_sum (j : Fin 3) : ∑ i, pHat i j = 0 := by fin_cases j <;> simp [Fin.sum_univ_three, pHat] -/-- Section 2.2: every row of q̂ sums to zero. --/ +/-- Summing `qHat` over the column index gives the zero coefficient function. -/ theorem qHat_row_sum (i : Fin 3) : ∑ j, qHat i j = 0 := by fin_cases i <;> simp [Fin.sum_univ_three, qHat] -/-- Section 2.2: Notice that ∑_{i,j=1}^{3} p̂_ij q̂_ij = ∑_{i,j=1}^{2} p_ij q_ij is the desired -inner product. +/-- The product sum for the extended arrays reduces to the four products from their original +two-by-two arrays. -/ theorem sum_pHat_mul_qHat : ∑ i : Fin 3, ∑ j : Fin 3, formL (pHat i j) * formR (qHat i j) diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean index e581c0e200..ac6930015f 100644 --- a/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/Pruned.lean @@ -42,7 +42,8 @@ hold without that qualification. Part of [AlmanWilliams2026, Section 2]. Direct mathematical adaptation of [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). Definitions are selectively extracted from that pin's `PaperStatements.lean`; no machine -semantics or running-time statement is imported. Source-derived prose is paraphrased below. +semantics or running-time statement is imported. The docstrings below explain the local +declarations in our own words. -/ @[expose] public section @@ -51,21 +52,18 @@ open Finset namespace Cslib.Algorithms.MatrixMultiplication.Schonhage -/-- Section 2.4.2: for a set S of output strings and an output variable z, the slice S_z is the -set of output strings w' with z w' ∈ S. --/ +/-- Retain the tails of requested tuples whose first coordinate equals `z`. -/ def sliceSet {n : ℕ} (S : Finset (OutStr (n + 1))) (z : OutVar) : Finset (OutStr n) := univ.filter fun w' => (Fin.cons z w' : OutStr (n + 1)) ∈ S -/-- Section 2.4.2, step (2) of `Pruned.result`: let S_λ be the union of the slices S_z over the z -to which λ contributes. +/-- The requests for a child label are the union of slices for output coordinates to which that +label contributes. -/ def childSet {n : ℕ} (S : Finset (OutStr (n + 1))) (lam : Term) : Finset (OutStr n) := (univ.filter fun z => lam.Contributes z).biUnion fun z => sliceSet S z -/-- A call of `Pruned.result`. `vertex` is the vertex `τ₁⋯τ_k` at which it is made, as the pair -of its depth `k` and the string of terms. `passed` is the set that is passed to it, as the pair -of the length of its strings and the set. +/-- One mathematical call record, pairing its label-sequence position with the request set +supplied there. The dependent pairs retain the two tuple lengths. -/ structure PrunedCall where /-- The call depth and the terms identifying its vertex. @@ -75,9 +73,7 @@ structure PrunedCall where -/ passed : Σ n, Finset (OutStr n) -/-- The same call, seen from one level higher: the vertex `τ₁⋯τ_k` below the child `λ` is the -vertex `λ τ₁⋯τ_k`. --/ +/-- Embed a child call into its parent by prepending the child label to its recorded vertex. -/ def PrunedCall.under (lam : Term) (c : PrunedCall) : PrunedCall where vertex := ⟨c.vertex.1 + 1, (Fin.cons lam c.vertex.2 : Vertex (c.vertex.1 + 1))⟩ passed := c.passed @@ -105,31 +101,24 @@ def Pruned.run : (n : ℕ) → (Leaf n → ℤ) → (Leaf n → ℤ) → Finset ⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ ::ₘ univ.val.bind fun lam => ((R lam).2).map (PrunedCall.under lam)) -/-- The array that `Pruned.result(S)` returns. --/ +/-- Project the output values from the pruned recursion and discard the call multiset. -/ def Pruned.result (n : ℕ) (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : OutStr n → ℤ := (Pruned.run n encA encB S).1 -/-- All the calls that `Pruned.result(S)` makes, itself included, each with the set passed to it. --/ +/-- Project the multiset of call records, retaining repetitions and the root record. -/ def Pruned.calls (n : ℕ) (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : Multiset PrunedCall := (Pruned.run n encA encB S).2 -/-- The leaves that `Pruned.result(U)`, called at the root, visits: the vertices at depth `L` -among its calls. --/ +/-- A leaf is visited when some recorded call has that full-depth vertex. -/ def Pruned.Visits {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (τ : Leaf L) : Prop := ∃ c ∈ Pruned.calls L encA encB U, c.vertex = ⟨L, τ⟩ -/-- The total size of the sets passed to the calls that `Pruned.result(U)` makes. --/ +/-- Add the cardinalities of the request sets carried by all call records, with multiplicity. -/ def Pruned.totalSize {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) : ℕ := ((Pruned.calls L encA encB U).map fun c => c.passed.2.card).sum -/-- Section 2.4.3: the leaves contributing to an output string with inner set Q are the 10^m -leaves that choose any term at each of the m levels of Q, but P_ij at every level outside Q where -the output string has z_ij. We call the one that chooses P₀ at every level of Q the private leaf -of the output string. +/-- Choose the private label at every output coordinate. The resulting leaf is relevant to that +output tuple and distinguishes it from every other output tuple. -/ def privateLeaf {L : ℕ} (w : OutStr L) : Leaf L := fun ℓ => (w ℓ).privateTerm @@ -144,21 +133,18 @@ def computeEncoding {α : Type} [Fintype α] (c : Term → α → ℤ) : | 0, a, _ => a Fin.elim0 | L + 1, a, τ => computeEncoding c L (fun u' => ∑ s, c (τ 0) s * sliceAt a s u') (Fin.tail τ) -/-- At the leaf `τ` the procedure of Section 2.4.1 stores `∑_u a[u] ∏_ℓ c_{τ_ℓ}(u_ℓ)`. --/ +/-- Recursive encoding agrees with the closed coefficient-product formula `encodeWith`. -/ theorem computeEncoding_eq_encodeWith {α : Type} [Fintype α] (c : Term → α → ℤ) {L : ℕ} (a : (Fin L → α) → ℤ) (τ : Leaf L) : computeEncoding c L a τ = encodeWith c τ a := by induction L with | zero => rw [computeEncoding, encodeWith_zero] | succ n ih => rw [computeEncoding, ih, ← encodeWith_cons, Fin.cons_self_tail] -/-- The procedure of Section 2.4.1 computes the encoding of `a`. --/ +/-- Specializing recursive encoding to `phi` gives the existing left-encoding array. -/ theorem computeEncoding_phi {L : ℕ} (a : LeftStr L → ℤ) : computeEncoding phi L a = encodingL a := funext (computeEncoding_eq_encodeWith phi a) -/-- The procedure of Section 2.4.1 computes the encoding of `b`. --/ +/-- Specializing recursive encoding to `psi` gives the existing right-encoding array. -/ theorem computeEncoding_psi {L : ℕ} (b : RightStr L → ℤ) : computeEncoding psi L b = encodingR b := funext (computeEncoding_eq_encodeWith psi b) @@ -171,30 +157,28 @@ theorem mem_sliceSet {n : ℕ} (S : Finset (OutStr (n + 1))) (z : OutVar) (w' : w' ∈ sliceSet S z ↔ (Fin.cons z w' : OutStr (n + 1)) ∈ S := by simp [sliceSet] -/-- Section 2.4.2, step (2): i.e., S_{P_ij} := S_{z_ij} ∪ S_{z₀}. --/ +/-- An indexed child combines the matching outer slice with the inner slice of the request set. -/ theorem childSet_P {n : ℕ} (S : Finset (OutStr (n + 1))) (i j : Fin 3) : childSet S (.P i j) = sliceSet S (.z i j) ∪ sliceSet S .z0 := by have hz : (univ.filter fun z => (Term.P i j).Contributes z) = {.z i j, .z0} := by decide +revert rw [childSet, hz, biUnion_insert, singleton_biUnion] -/-- Section 2.4.2, step (2): and S_{P₀} := S_{z₀}. --/ +/-- The distinguished child needs only the inner slice of the request set. -/ theorem childSet_P0 {n : ℕ} (S : Finset (OutStr (n + 1))) : childSet S .P0 = sliceSet S .z0 := by have hz : (univ.filter fun z => Term.P0.Contributes z) = {.z0} := by decide rw [childSet, hz, singleton_biUnion] -/-- Section 2.4.2, step (4): These restrictions make sense, since S_{z_ij} ⊆ S_{P_ij} and S_{z₀} ⊆ -S_λ for every λ, by step (2): `S_z ⊆ S_λ` whenever `λ` contributes to `z`. +/-- Every requested suffix in a relevant output slice belongs to the corresponding child request +set. -/ theorem sliceSet_subset_childSet {n : ℕ} (S : Finset (OutStr (n + 1))) {lam : Term} {z : OutVar} (h : lam.Contributes z) : sliceSet S z ⊆ childSet S lam := subset_biUnion_of_mem (fun z => sliceSet S z) (mem_filter.mpr ⟨mem_univ z, h⟩) -/-- In the proof of Lemma 10: S_λ consists of the last L-k-1 variables of each string in S such -that λ contributes to the first variable of that string. +/-- A suffix belongs to a child request set precisely when it comes from a requested parent tuple +whose leading coordinate is relevant to that child label. -/ theorem mem_childSet {n : ℕ} {S : Finset (OutStr (n + 1))} {lam : Term} {w' : OutStr n} : w' ∈ childSet S lam ↔ ∃ w ∈ S, lam.Contributes (w 0) ∧ Fin.tail w = w' := by @@ -207,7 +191,8 @@ theorem mem_childSet {n : ℕ} {S : Finset (OutStr (n + 1))} {lam : Term} {w' : /-! ### Lemma 10, first claim: the values -/ -/-- The array `C_λ` of step (3) of `Pruned.result`; it is the zero array if `S_λ = ∅`. +/-- Evaluate a child on its requested suffixes, using the zero array when that child has no +requests. -/ def Pruned.childResult {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) (lam : Term) : OutStr n → ℤ := @@ -215,8 +200,7 @@ def Pruned.childResult {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset Pruned.result n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam) else 0 -/-- Steps (2) to (4) of `Pruned.result`, written out. --/ +/-- Unfold the pruned values once, exposing the child arrays used for decoding. -/ theorem Pruned.result_succ {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) : Pruned.result (n + 1) encA encB S = S.piecewise (fun w => match w 0 with @@ -224,8 +208,8 @@ theorem Pruned.result_succ {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Fin | .z0 => ∑ lam, Pruned.childResult encA encB S lam (Fin.tail w)) 0 := by simp only [Pruned.result, Pruned.run, Pruned.childResult, apply_ite Prod.fst] -/-- Step (4) of `Pruned.result` at one string: the entry at `w ∈ S` is the sum of the `C_λ`, at -the rest of `w`, over the terms `λ` that contribute to the first variable of `w`. +/-- At a requested tuple, decoding sums child values at its tail over labels relevant to its +leading coordinate; every unrequested tuple has value zero. -/ theorem Pruned.result_succ_apply {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) : @@ -235,9 +219,8 @@ theorem Pruned.result_succ_apply {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) rw [Pruned.result_succ, Finset.piecewise, Term.sum_contributes] rfl -/-- What `Pruned.result` returns, for arbitrary arrays in place of the two encodings: at each -string of `S`, the sum, over the leaves contributing to it, of the product of the two numbers -looked up at the leaf in step (1). +/-- For arbitrary supplied leaf arrays, the pruned result is zero off the request set and is a sum +of relevant leaf products on that set. -/ theorem Pruned.result_eq_piecewise_sum {n : ℕ} (encA encB : Leaf n → ℤ) (S : Finset (OutStr n)) : Pruned.result n encA encB S @@ -289,21 +272,19 @@ theorem Pruned.result_eq_piecewise_full {n : ℕ} (a : LeftStr n → ℤ) (b : R /-! ### Lemma 10, second claim: the calls that are made and the sets passed to them -/ -/-- Section 2.3.2: a vertex τ₁ ⋯ τ_k contributes to an output string w if τ_ℓ contributes to w_ℓ -at every level ℓ ≤ k. Only `k ≤ L` occurs; the hypothesis `h` is there so that `w_ℓ` makes sense. +/-- Test relevance for an initial label sequence against the matching prefix of an output tuple. +Longer label sequences fail the depth condition. -/ def Vertex.Contributes {k L : ℕ} (τ : Vertex k) (w : OutStr L) : Prop := ∀ (ℓ : Fin k) (h : (ℓ : ℕ) < L), (τ ℓ).Contributes (w ⟨ℓ, h⟩) -/-- Section 2.3.2: for a vertex at depth `L`, that is, a leaf, contributes to means that `τ_ℓ` -contributes to `w_ℓ` at every level. --/ +/-- At full depth, vertex relevance coincides with the existing leaf-relevance predicate. -/ theorem Vertex.contributes_iff_leaf {L : ℕ} (τ : Leaf L) (w : OutStr L) : Vertex.Contributes τ w ↔ Leaf.Contributes τ w := ⟨fun h ℓ => h ℓ ℓ.2, fun h ℓ _ => h ℓ⟩ -/-- The vertex `λ τ₁⋯τ_k` contributes to `w` if and only if `λ` contributes to the first variable -of `w` and `τ₁⋯τ_k` contributes to the rest of `w`. +/-- Decompose relevance of a nonempty vertex into relevance of its head and relevance of its +tail. -/ theorem Vertex.contributes_cons {k L : ℕ} {lam : Term} {τ : Vertex k} {w : OutStr (L + 1)} : Vertex.Contributes (Fin.cons lam τ : Vertex (k + 1)) w @@ -316,7 +297,8 @@ theorem Vertex.contributes_cons {k L : ℕ} {lam : Term} {τ : Vertex k} {w : Ou | zero => exact h0 | succ i => exact h1 i (Nat.lt_of_succ_lt_succ h) -/-- The calls made by the child `λ` of `Pruned.result(S)`: none if `S_λ = ∅`. +/-- Obtain a child call multiset from its recursion, or the empty multiset when its request set is +empty. -/ def Pruned.childCalls {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) (lam : Term) : Multiset PrunedCall := @@ -324,16 +306,14 @@ def Pruned.childCalls {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset ( Pruned.calls n (sliceAt encA lam) (sliceAt encB lam) (childSet S lam) else 0 -/-- The calls made by `Pruned.result(S)`: the call itself, and the calls made by the children `λ` -with `S_λ ≠ ∅`. --/ +/-- Unfold call recording into the parent record and the embedded records from nonempty children. -/ theorem Pruned.calls_succ {n : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) : Pruned.calls (n + 1) encA encB S = ⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ ::ₘ univ.val.bind fun lam => (Pruned.childCalls encA encB S lam).map (.under lam) := by simp only [Pruned.calls, Pruned.run, Pruned.childCalls, apply_ite Prod.snd] -/-- The calls of `Pruned.result(S)`, one level unfolded (steps (2) and (3)): the call itself, and -for each term `λ` with `S_λ ≠ ∅` the calls of `Pruned_λ(S_λ)`. +/-- Membership in the positive-depth call multiset means being the root record or an embedded call +of some nonempty child. -/ theorem Pruned.mem_calls_succ {n : ℕ} {encA encB : Leaf (n + 1) → ℤ} {S : Finset (OutStr (n + 1))} {c : PrunedCall} : @@ -373,9 +353,7 @@ private theorem PrunedCall.vertex_under_eq_iff {lam : Term} {c : PrunedCall} {k obtain rfl := eq_of_heq h simp [PrunedCall.under] -/-- `Pruned.result(U)`, called at the root, calls the vertex `τ`. For a leaf this is -`Pruned.Visits`. --/ +/-- A vertex is called when its depth and label tuple occur in the recorded call multiset. -/ def Pruned.Called {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (τ : Vertex k) : Prop := ∃ c ∈ Pruned.calls L encA encB U, c.vertex = ⟨k, τ⟩ @@ -389,8 +367,8 @@ theorem Pruned.called_root {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (O | zero => exact ⟨_, Multiset.mem_singleton_self _, rfl⟩ | succ n => exact ⟨_, Pruned.mem_calls_succ.mpr (.inl rfl), rfl⟩ -/-- Step (3): a vertex `λ τ'` below the root is called exactly if `S_λ ≠ ∅` and the child `λ` -calls `τ'`. +/-- A non-root vertex is called exactly when its leading child has requests and the tail vertex is +called within that child. -/ theorem Pruned.called_succ {n k : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) (τ : Vertex (k + 1)) : @@ -406,8 +384,8 @@ theorem Pruned.called_succ {n k : ℕ} (encA encB : Leaf (n + 1) → ℤ) (S : F exact ⟨c'.under (τ 0), Pruned.mem_calls_succ.mpr (.inr ⟨τ 0, hne, c', hc', rfl⟩), PrunedCall.vertex_under_eq_iff.mpr ⟨rfl, hc'τ⟩⟩ -/-- The vertex `λ τ'` contributes to a string of `S` exactly if `τ'` contributes to a string of -`S_λ`. +/-- Existence of a requested tuple relevant to a cons vertex reduces to the same question for the +tail vertex and the child request set. -/ private theorem exists_contributes_childSet {n k : ℕ} (S : Finset (OutStr (n + 1))) (τ : Vertex (k + 1)) : @@ -423,9 +401,8 @@ private theorem exists_contributes_childSet {n k : ℕ} (S : Finset (OutStr (n + · rintro ⟨w, hw, hτ⟩ exact ⟨_, mem_childSet.mpr ⟨w, hw, ((hcons w).mp hτ).1, rfl⟩, ((hcons w).mp hτ).2⟩ -/-- A vertex at depth `k ≤ L` is called by `Pruned.result(U)` if and only if it is the root or it -contributes to some string of `U` (proof of Lemma 10). The induction follows the recursion: the -vertex `λ τ'` is called by `Pruned.result(U)` exactly if `τ'` is called by `Pruned.result(U_λ)`. +/-- For vertices within the recursion depth, being called is equivalent to being the root or being +relevant to at least one requested tuple. -/ theorem Pruned.called_iff_root_or {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (hkL : k ≤ L) (τ : Vertex k) : @@ -451,12 +428,9 @@ theorem Pruned.called_iff_root_or {L k : ℕ} (encA encB : Leaf L → ℤ) (U : · exact absurd hk (Nat.succ_ne_zero k) · exact ⟨⟨w', hw'⟩, ihτ.mpr (.inr ⟨w', hw', hτ'⟩)⟩ -/-- In the proof of Lemma 10: the vertex is called if and only if this set is nonempty, that is, -if and only if the vertex contributes to some string of `U`. - -NOTE. As printed this fails for the root when `U = ∅` (the root is always called). The -hypothesis `hk` excludes only that case. `hkL` says that `τ` is a vertex of the recursion tree, -whose depth is at most `L` (Section 2.3.1); the type `Vertex k` exists for every `k`. +/-- Away from the empty-request root exception, a vertex within the recursion depth is called +exactly when some requested tuple is relevant to it. The hypothesis `hk` admits every +positive-depth vertex and also admits the root when requests are nonempty. -/ theorem Pruned.called_iff {L k : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (hk : 1 ≤ k ∨ U.Nonempty) (hkL : k ≤ L) (τ : Vertex k) : @@ -518,10 +492,8 @@ private theorem mem_iff_root {L : ℕ} (U : Finset (OutStr L)) (w' : OutStr L) : simp only [isSuffixFrom_zero] exact ⟨fun h => ⟨w', h, fun ℓ => ℓ.elim0, rfl⟩, fun ⟨_, hw, _, h⟩ => h ▸ hw⟩ -/-- Proof of Lemma 10 (announced in Section 2.4.2): the set passed to the vertex τ₁⋯τ_k consists -of the suffixes w_{k+1}⋯w_L of the strings w of U to which the vertex contributes. For a call -`c`, `c.vertex.1` is the depth `k`, `c.vertex.2` the vertex, `c.passed.1` the length `L - k` of -the strings passed, and `c.passed.2` the set passed. +/-- A recorded call receives exactly the suffixes of original requests relevant to its vertex. Its +recorded remaining length and depth identify the suffix position. -/ theorem Pruned.mem_passed_iff {L : ℕ} {encA encB : Leaf L → ℤ} {U : Finset (OutStr L)} {c : PrunedCall} (hc : c ∈ Pruned.calls L encA encB U) (w' : OutStr c.passed.1) : @@ -554,8 +526,8 @@ theorem Pruned.visits_iff_root_or_mem_leaves {L : ℕ} (encA encB : Leaf L → simpa only [mem_leaves, Vertex.contributes_iff_leaf] using Pruned.called_iff_root_or encA encB U (le_refl L) τ -/-- **Lemma 10**, second claim. The leaves it visits are exactly those of leaves(U). The -hypothesis `0 < L ∨ U.Nonempty` is explained at `pruned_spec`. +/-- With positive depth or nonempty requests, the visited-leaf set is exactly `leaves U`. The +unconditional root case is given separately by `Pruned.visits_iff_root_or_mem_leaves`. -/ theorem Pruned.visits_iff_mem_leaves {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (h : 0 < L ∨ U.Nonempty) (τ : Leaf L) : Pruned.Visits encA encB U τ ↔ τ ∈ leaves U := @@ -565,7 +537,8 @@ theorem Pruned.visits_iff_mem_leaves {L : ℕ} (encA encB : Leaf L → ℤ) (U : /-! ### Lemma 10, third claim: the total size of the sets passed -/ -/-- The leaf `λ τ'` is in `leaves(S)` if and only if `τ'` is in `leaves(S_λ)`. +/-- Membership in a positive-depth relevant-leaf set reduces to membership of the tail in the +child relevant-leaf set. -/ theorem mem_leaves_succ {n : ℕ} {S : Finset (OutStr (n + 1))} {τ : Leaf (n + 1)} : τ ∈ leaves S ↔ Fin.tail τ ∈ leaves (childSet S (τ 0)) := by @@ -578,8 +551,8 @@ theorem mem_leaves_succ {n : ℕ} {S : Finset (OutStr (n + 1))} {τ : Leaf (n + obtain ⟨w, hw, h0, rfl⟩ := mem_childSet.mp hw' exact ⟨w, hw, Leaf.contributes_succ.mpr ⟨h0, h1⟩⟩ -/-- `leaves(S)` is the disjoint union, over the terms `λ`, of the leaves `λ τ'` with `τ'` in -`leaves(S_λ)`. +/-- Count relevant leaves by summing the relevant-leaf counts of all children; distinct leading +labels keep these families disjoint. -/ theorem card_leaves_succ {n : ℕ} (S : Finset (OutStr (n + 1))) : (leaves S).card = ∑ lam, (leaves (childSet S lam)).card := by @@ -604,9 +577,8 @@ theorem privateLeaf_injective {L : ℕ} : Function.Injective (privateLeaf (L := have hterm : Function.Injective OutVar.privateTerm := by decide fun _ _ h => funext fun ℓ => hterm (congrFun h ℓ) -/-- The map of the proof of Lemma 10, at the root: an output string goes to the leaf that picks -`P_ij` where the string has `z_ij` and `P₀` where it has `z₀`. The leaf contributes to the string -and determines it, so a set of output strings is no larger than its set of leaves. +/-- The private-leaf injection bounds the number of requested tuples by the number of their +relevant leaves. -/ theorem card_le_card_leaves {n : ℕ} (S : Finset (OutStr n)) : S.card ≤ (leaves S).card := by refine card_le_card_of_injOn privateLeaf (fun w hw => ?_) privateLeaf_injective.injOn @@ -663,8 +635,8 @@ private theorem sum_sizeAt (s : Multiset PrunedCall) (N : ℕ) (h : ∀ c ∈ s, rw [ih fun c' hc' => h c' (Multiset.mem_cons_of_mem hc'), sum_ite_eq, ite_eq_left (mem_range.mpr (h c (Multiset.mem_cons_self c s)))] -/-- End of the proof of Lemma 10: the sets passed to the vertices at each of the L+1 depths have -total size at most |leaves(U)|. +/-- At any fixed depth, the sum of all passed request-set cardinalities is bounded by the root +request set's relevant-leaf count. -/ theorem Pruned.sum_card_passed_le {L : ℕ} (encA encB : Leaf L → ℤ) (U : Finset (OutStr L)) (k : ℕ) : (((Pruned.calls L encA encB U).filter fun c => c.vertex.1 = k).map fun c => c.passed.2.card).sum From 4f6de797203eb5dac02b2cadd9e8fa33e7427d57 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:06 -0400 Subject: [PATCH 22/32] fix(imports): deduplicate aggregate after counting integration --- Complexity.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 432ee4c7f5..5c2c4eead3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -6,7 +6,6 @@ public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.ExactTriangle.Witnesses -public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction From 02f1b495322d8cfee81969186795670e3574defa Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:08 -0400 Subject: [PATCH 23/32] fix(imports): deduplicate aggregate after query-splitting integration --- Complexity.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 69abff1939..f12edfac12 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -6,7 +6,6 @@ public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.ExactTriangle.Witnesses -public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction From b79ae8e407f9f0349d2c14beb0d3e1aac8187e04 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:53:41 -0400 Subject: [PATCH 24/32] fix(imports): regenerate aggregate after Chan-He parameter integration --- Complexity.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index c31f3baadd..cd891a6997 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -90,14 +90,14 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static public import Complexity.Algorithms.WordRAM.Histogram -public import Complexity.Algorithms.WordRAM.NatClog -public import Complexity.Algorithms.WordRAM.NatLog -public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.Karger.Contract public import Complexity.Algorithms.WordRAM.Karger.FastCut public import Complexity.Algorithms.WordRAM.Karger.Graph public import Complexity.Algorithms.WordRAM.KargerMinCut public import Complexity.Algorithms.WordRAM.KargerStein +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree From bd9bd2b09eb1d325f5b3b541ec52f71c7001a804 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:24:27 -0400 Subject: [PATCH 25/32] fix(imports): regenerate aggregate after shared-encoder integration --- Complexity.lean | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 9f2c0f857a..a97bde25be 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -103,6 +103,11 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Query.Traversal public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static +public import Complexity.Algorithms.WordRAM.Karger.Contract +public import Complexity.Algorithms.WordRAM.Karger.FastCut +public import Complexity.Algorithms.WordRAM.Karger.Graph +public import Complexity.Algorithms.WordRAM.KargerMinCut +public import Complexity.Algorithms.WordRAM.KargerStein public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Cell public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Controller public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Copy @@ -114,11 +119,6 @@ public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Heade public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Input public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Stage -public import Complexity.Algorithms.WordRAM.Karger.Contract -public import Complexity.Algorithms.WordRAM.Karger.FastCut -public import Complexity.Algorithms.WordRAM.Karger.Graph -public import Complexity.Algorithms.WordRAM.KargerMinCut -public import Complexity.Algorithms.WordRAM.KargerStein public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree From 9c8b9fcc3ccc5a6282bbd3b3e3cbcfc4c0070c62 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:31:21 -0400 Subject: [PATCH 26/32] perf(wordram): reduce native parameter proof heartbeat budget --- Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean index f26ccbe183..26812d6412 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -193,8 +193,8 @@ private theorem time_zero (n U : ℕ) : time 0 n U - 20 = 44 + 5 * Nat.sqrt n + dsimp [time] omega -set_option maxHeartbeats 800000 in --- Elaborating twelve native segments and their explicit register frames exceeds the default budget. +set_option maxHeartbeats 300000 in +-- Measured 256376 heartbeats for the twelve native segments and explicit register frames. private theorem exec_calculate {w n U : ℕ} (x : Data 24) (hlen : x.regs len = n) (hU : x.regs magnitude = U) (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h3 : x.regs three = 3) From 452924eae8db34ac5c3ae00dcad77e8b5e40dea7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:33:03 -0400 Subject: [PATCH 27/32] chore(wordram): scope measured parameter proof budget to reduction task --- scripts/heartbeat_allowlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/heartbeat_allowlist.txt b/scripts/heartbeat_allowlist.txt index fc443bbffa..02ca8b8a21 100644 --- a/scripts/heartbeat_allowlist.txt +++ b/scripts/heartbeat_allowlist.txt @@ -6,3 +6,6 @@ # #1528: closed incidence-table certificates from #1281. Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean 8000000 + +# #1583: split native parameter segment/frame proofs to reach the default budget. +Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean 300000 From d91296fb62bec52aefb720992de592f0739c1d78 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:35:06 -0400 Subject: [PATCH 28/32] perf(schonhage): focus cell arithmetic and use the default heartbeat budget --- .../MatrixMultiplication/Schonhage/Cell.lean | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean index d743deb96a..160e70cd78 100644 --- a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean @@ -42,9 +42,6 @@ private theorem cell_preserves (side : Bool) (i : Fin 32) : 9 ≤ i.val → ¬CellLocal i → (cellBody side).Preserves i := by decide +kernel +revert -set_option maxHeartbeats 2000000 in --- Symbolic execution retains nine instructions' functional register updates; normalizing them --- and proving the explicit nonlinear address inequalities exceeds the default heartbeat budget. /-- One output entry, with all index arithmetic and the counter increment charged. A single ordinary explicit natural-number bound covers the addresses and index products; no fit condition is imposed on an unspecified intermediate machine state. -/ @@ -65,23 +62,26 @@ theorem exec_cell {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) let p := i / (10 * n) let t : Fin 10 := ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ let r := i % n + -- Keep nonlinear arithmetic focused on index bounds, rather than the execution context. have hp : p < P := by apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr - nlinarith [hi] + nlinarith only [hi] have hr : r < n := Nat.mod_lt _ hnpos have hn1 : 1 ≤ n := hnpos - have hPn : n ≤ P * n := by nlinarith + have hPn : n ≤ P * n := Nat.le_mul_of_pos_left n hP have hmul : p * n < P * n := Nat.mul_lt_mul_of_pos_right hp hnpos - have h10n : Fits (w + 2) (10 * n) := haddr.of_le (by nlinarith) - have hpfit : Fits (w + 2) p := haddr.of_le (by nlinarith) + have h10n : Fits (w + 2) (10 * n) := haddr.of_le (by nlinarith only [hPn]) + have hpfit : Fits (w + 2) p := haddr.of_le (by nlinarith only [hp, hn1]) have hdiv : i / n ≤ i := Nat.div_le_self _ _ - have hdivfit : Fits (w + 2) (i / n) := haddr.of_le (by nlinarith) + have hdivfit : Fits (w + 2) (i / n) := haddr.of_le (by nlinarith only [hdiv, hi]) have htfit : Fits (w + 2) t.val := haddr.of_le (by have := t.isLt; omega) - have hrfit : Fits (w + 2) r := haddr.of_le (by nlinarith) - have hp7 : Fits (w + 2) (p * 7) := haddr.of_le (by nlinarith) - have hp7n : Fits (w + 2) (p * 7 * n) := haddr.of_le (by nlinarith) - have hp7nr : Fits (w + 2) (p * 7 * n + r) := haddr.of_le (by nlinarith) - have hgroup : Fits (w + 2) (s + (p * 7 * n + r)) := haddr.of_le (by nlinarith) + have hrfit : Fits (w + 2) r := haddr.of_le (by nlinarith only [hr, hPn]) + have hp7 : Fits (w + 2) (p * 7) := haddr.of_le (by nlinarith only [hp, hn1]) + have hp7n : Fits (w + 2) (p * 7 * n) := haddr.of_le (by nlinarith only [hmul]) + have hp7nr : Fits (w + 2) (p * 7 * n + r) := + haddr.of_le (by nlinarith only [hmul, hr, hPn]) + have hgroup : Fits (w + 2) (s + (p * 7 * n + r)) := + haddr.of_le (by nlinarith only [hmul, hr, hPn]) let z := ((((((((x.set temporary (10 * n)).set block p).set term (i / n)).set term t.val).set suffix r).set group (p * 7)).set group (p * 7 * n)).set group (p * 7 * n + r)).set group (s + (p * 7 * n + r)) @@ -99,7 +99,7 @@ theorem exec_cell {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) have hidx : s + (p * 7 * n + r) + a.val * n = s + (p * 7 + a.val) * n + r := by ring simpa only [z, Data.set_mem, hidx] using hmem a have hspan : Fits (w + 2) (s + (p * 7 * n + r) + 7 * n) := - haddr.of_le (by nlinarith) + haddr.of_le (by nlinarith only [hmul, hr, hPn]) obtain ⟨u, hu, huacc, humem, hureg⟩ := exec_dispatch hw side (List.finRange 10) t (List.mem_finRange _) z B (s + (p * 7 * n + r)) n v hzterm hzgroup hzn hzmem hB hv hval hspan (haddr.of_le (by omega)) @@ -112,8 +112,8 @@ theorem exec_cell {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) have zuone : u.regs one = 1 := by rw [hureg one (by decide) (by decide) (by decide) (by decide)] simpa [z, temporary, block, term, suffix, group, one, Fin.mk.injEq] using hone - have hdfit : Fits (w + 2) (d + i) := haddr.of_le (by nlinarith) - have hinc : Fits (w + 2) (i + 1) := haddr.of_le (by nlinarith) + have hdfit : Fits (w + 2) (d + i) := haddr.of_le (by nlinarith only [hi]) + have hinc : Fits (w + 2) (i + 1) := haddr.of_le (by nlinarith only [hi]) let y := ((u.set address (d + i)).write (d + i) (u.regs accumulator)).set cell (i + 1) have hy : ExecWithin (w + 2) cellFinish u y 3 := by ram_eval [cellFinish, y, zucell, zudst, zuone, hdfit, hinc, From d8f41623613bcf6207688216e23ee62f905ff2e3 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:37:55 -0400 Subject: [PATCH 29/32] fix(doc): restore unrelated upstream coordination records Restore the exact 231-line tail common to both integration parents after review caught an overbroad conflict-marker match. The intended 3SUM integration prose is retained; no Lean change. AI-assisted correction by OpenAI Codex. --- UPSTREAM.md | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/UPSTREAM.md b/UPSTREAM.md index 900334b68b..153abe289b 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -208,3 +208,234 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution are retained; the native proofs and representation choices are local. +* **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at + `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes + `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. + This redesigns the local WordRAM/GenericRAM core with a register and word-addressed memory bank + per sort, `Fin (2 ^ w)` words, and absorbing halt/fault states. A halt retains the data without + choosing an output register; `RelSeries` run paths permit terminal padding. Before migrating + local clients, bridge their operation bases, input/output conventions and exact halting costs. + Algorithms, interpretation transfer, native simulations and time-class APIs remain separate + follow-ups. The existing local modules remain candidates, not copies of this submission. +* **Encoded sparse memory.** `Computability/GenericRAM/SparseMemory/*` reuses Mathlib's `AList` + with constant value type and the existing native `ComputableTidilyInO` equality, find and filter + routines. Its certificates expose actual native TM witnesses, parameterized time/space bounds, + and polynomial-time corollaries, and feed the fixed-program step and whole-run simulators in + `GenericRAM/Simulation/{Turing,Run}` (#298, #292). +* **Stateful algorithms prototype.** `Foundations/Semantics/Stateful/*` reuses Quang Dao's merged + `PFunctor.FreeM` for adaptive clients and substitution, adding persistent representation + simulation, interface composition, semantic Hoare rules and separate native-resource + certificates. Its queue and reset-and-replay constructions deliberately count backend calls; + arbitrary pure continuations do not have machine-time certificates. `CostHoare` adds client + operation-cost contracts and separate method-budget composition. The `stateful` Aesop rules + support these contracts. `WordRAM/Controller` adds fixed-code client refinements with charged + preparation, control flow and final halt; the adaptive stack example constructs its empty + representation from the standard input. `WordRAM/Function` gives separate nonuniform and + uniform RAM function-time predicates, with named-certificate reuse by `polytime`. Arbitrary continuation + synthesis, iterative clients and a Turing-machine simulation remain open. + `Foundations/Data/OrderedSet` and `Foundations/Semantics/Stateful/OrderedSet` provide neutral + optional-minimum contracts, a typed finite-set interface and adaptive clients, with no tree or + cost dependency. Bounded-key implementations may specialize insertion admissibility. + `Algorithms/Lean/SearchTree/*` reuses Mathlib's `BinaryTree`, height and node count for an + unbalanced ordered-set realization. Finite-set behavior, strict invariants and height-bounded + node visits are proved, including for the pure deletion operation `erase`; allocation, + comparison implementation and client computation are excluded from that cost model. It is not + a balanced tree or machine-time certificate. + `WordRAM/Dynamic` separately connects + fixed bounded-word operation programs and faithful memory transcripts to those contracts. + The direct shared-heap queue uses the existing FIFO operation signature and native operation + traces, with a hidden two-stack representation, a concrete-register potential, framed storage, + and separately charged initialization. Its `2 * capacity` reservation is explicit; external + client computation and argument delivery are not charged by the operation-history bound. + Coordinate placement and overlap with Srinivas's #685 and the merged #401 (`Algorithms/Lean/Query`) + before upstream submission; #685 is not copied here. This is a prototype of composition and + refinement, not a replacement for their query models or a general cell-probe lower-bound semantics. +* **Earlier review requests.** The withdrawn #1099–#1101 requested abstract word encodings and + textbook sources for FP/poly, P/poly and their machine simulation. The withdrawn #869 and + #871–#875 requested composition above configuration level; use `TransformsTapes`-style specs. + +These are coordination references, not a second record of live PR status. Check the relevant +upstream discussion before starting or submitting overlapping work. + +## Placement decisions not already recorded in source + +Paths below omit `Complexity/` and `.lean`. The default CSLib destination mirrors the source +path. A destination recorded in a source marker takes precedence; the alternatives below need +agreement during upstream review. + +| Local module or declarations | Integration note | +| --- | --- | +| `Computability/Machines/Turing/MultiTape/Encodings/StreamFold` | The candidate `streamFold_sum` generalizes the vendored proof; `streamFold` is its compatibility corollary. When resynchronizing or removing the copy, preserve the summed contract and shared proof until upstream includes them. | +| `Computability/Machines/Turing/MultiTape/Encodings/Length` | Add to the tidy branch's `Encodings/List`. | +| `Computability/Machines/Turing/MultiTape/TapeLemmas` | `exists_visitedByTapeHead_eq_Icc` and `workTapes_runFrom_eq_of_notMem_visitedByTapeHead` were relocated from `Plumbing/Shadow` (#316); upstream with `TapeLemmas`. | +| `Computability/Machines/Turing/MultiTape/Configuration` | `Turing.Action.instFinite` was relocated from `Machine/ChoiceTable` (#354); place beside `Action` in upstream `Configuration`. | +| `Computability/Machines/Turing/MultiTape/Plumbing/ExtendTapesView` | Derive the old `tapeEmb`/`noTapes` lemmas from `viewCfg`; `oneTapeCfg i = viewCfg (tapeEmb i)` by `rfl`. | +| `Computability/Machines/Turing/MultiTape/Probabilistic/IterateWhile` | Its private tape helpers parallel the vendored deterministic `Combinators/IterateWhile`; share them once the tidy branch exposes them. | +| `Computability/Machines/Turing/MultiTape/Plumbing/TransformsTapes` | `runFrom_nop_withState` was relocated from `Combinators/TidyNormalForm` (#354); place beside `runFrom_nop_one` in upstream `Plumbing/TransformsTapes`. | +| `Computability/Machines/Turing/MultiTape/Encodings/Decoder` | Total decoders with tidy certificates; add to the tidy branch's `Encodings` beside `Option`/`Pair`/`List`. | +| `Computability/Machines/Turing/MultiTape/Encodings/NatPower` | Addition to the tidy branch's `Encodings/Nat`: `encNat (2 ^ n)` and its linear clock certificate. | +| `Computability/Circuit/{Unfolding,MapOp}`, `Computability/Circuit/Encoding/MapOp` | Consider integrating into `Circuit/Formula`, `Circuit/Program`, and `Circuit/Encoding`, respectively. | +| `Computability/Circuit/Boolean/FirstOrder` | Consider a descriptive-complexity directory. | +| `Foundations/Data/BitString/{Agree,Block}` | Add to CSLib's `Foundations/Data/BitString`. | +| `Foundations/Data/List/{Fold,Lookup,Counting,Append}` | Fold invariants, lookup and register-file bounds, predicate counts, and append identities including unary headers; `Basic` reexports these topics. Consider Mathlib's `Data/List/GetD` and `Data/List/Basic`. | +| `Foundations/Relation/RelatesWithinSteps` | Bounded reachability helpers; consider Mathlib's `Logic/Relation`. | +| `Foundations/BooleanAnalysis/Fourier` | Consider Mathlib beside `Analysis/Fourier/FiniteAbelian`. | +| `Foundations/BooleanAnalysis/{Bernoulli,CoordinateSampling,Fibers,HarmonicTransform,LightPatterns,MirrorSets}`, `Foundations/Analysis/HarmonicMean` | Consider Mathlib probability APIs; generic finite-type `harmonicMean` is split into `Foundations/Analysis/HarmonicMean` (#317). | +| `Foundations/BooleanAnalysis/HadamardTester` | Consider the PCP hierarchy. | +| `Foundations/Combinatorics/CentralBinomial` | Consider Mathlib's `Data/Nat/Choose/Central` (`Nat.centralBinom_sq_mul_le`) and `Combinatorics/Choose`. | +| `Foundations/Combinatorics/{FiniteCover,Hoeffding,Hypergeometric,PowersetTail,Sunflower}` | Consider Mathlib combinatorics; probabilistic-method lemmas belong near `Combinatorics/Probabilistic`. | +| `Foundations/Data/Finset/Expect` | Product-type expectation helpers extracted from `Foundations/Combinatorics/Expander/Products` (#317); Mathlib candidates. | +| `Foundations/Data/Finset/Markov` | Mathlib candidate: `Algebra/Order/BigOperators/Group/Finset` (`Finset.card_filter_mul_le_sum`, `Finset.mul_card_filter_le_sum`). | +| `Foundations/Data/Matrix/Permanent` | Mathlib candidate: Laplace expansion of the permanent along row and column 0, beside `LinearAlgebra/Matrix/Permanent` (mirroring `Matrix.det_succ_row_zero`). | +| `Foundations/LinearAlgebra/{Rank,Matrix/CutRank,Matrix/Lindsey}` | Mathlib candidates: `LinearAlgebra/Dimension/LinearMap` and `LinearAlgebra/Matrix/Rank`; placement of Lindsey's lemma needs agreement. | +| `Probability/PairwiseIndependentHash/Shift` | Consider integration into `PairwiseIndependentHash`. | +| `Probability/PairwiseIndependentHash/Toeplitz` | Move `Matrix.toeplitz` to Mathlib beside `LinearAlgebra/Matrix/Circulant`. | +| `Probability/ApproximateCounting/Relative` | Extract the natural-number root lemmas to Mathlib. | +| `Foundations/Data/Nat/Bits` | `Nat.foldr_bit_bits` is a candidate for Mathlib's `Data/Nat/Bits`; its local relocation is complete, but upstream integration is still pending. | + +## Deprecation policy and breaking changes + +From `2110aa5a` (2026-10-19), renaming, moving or removing a public declaration already on `main` +left a deprecated alias or theorem at the old full name; the sweep integrated at `7738dcea` added +aliases for the names that changed before the rule existed and restored old binder orders where +they had changed. The library supports no downstream users yet, so #587 deleted every deprecated +declaration, and such changes now update all clients in this repository instead (`AGENTS.md`, +Proof hygiene). This list records earlier changes that had no mechanical successor. + +* **Signature changes under the old name.** `GeneralizedGeography.decide_mem_eq_any` became the + more general `List.decide_mem_eq_any`; `RotationMap.le_edgeExpansion_of_spectralExpansion_le` + takes `V` implicitly; `PolyTimeComplete` takes `{β}` before `C`; `PMF.iid_map` has the opposite + orientation; `eval_toUnbounded_atomsOf` changed its signature. +* **Removed or made private, without a successor of the same shape.** + - Raz–Wigderson helpers: `bobAnswer_aliceMatching`, `aliceGraph_swap_swap`, `bobGraph_swap_swap`, + `exists_run_oneRun`, `run_oneRun_of_disjointness`, `run_oneRun_of_mem`. + - Graph non-isomorphism: `PublicCoin.entry`, `entry_fin`, `value`, `value_fin` (use `matrixBit`) + and `mem_orbit_self`. + - Communication: `PublicCoin.majority` and `le_card_filter_ne_of_majority_ne` (use + `BitString.strictMajorityOn` and `card_filter_eq_le_half_of_strictMajorityOn_ne`); + `ecomplexity_le_succ`, `IsFoolingSet.le_ecomplexity`, + `PublicCoin.Solves.sq_mul_two_pow_le_four_pow`. + - Hamiltonian cycle: `SAT.rowLength`, `rowLength_sub_one`, `diamondArcs`, `literalArcs`, + `diamondGraphArcs`, `diamondGraphVertices`, `mem_diamondGraphArcs`, `mem_diamondGraphVertices` + (use `Diamond.graph`); `Diamond.Follows` and its lemmas are private. + - `SAT.SparseIndependentSet.mem_clauseEdges` and `mem_variableEdges`; the eight + `CookLevin.determined_of_mem_*` lemmas (private); `SharpSAT.getD_idxOf`, + `getD_mem_of_isFirst`, `isFirst_idxOf`; `Crypto.le_length_encPreimagePrefix` and + `UniformLearning.toOuterMeasure_uniformOfFintype_ne` (private); + `UniformLearning.signVal_mul_chi_mem_Icc`. + - Landings of 2026-10-19 before the rule: about 25 `AffineDisperser` names; the natural-proofs + predicates `IsNatural`, `IsLarge`, `IsUseful`, `IsCircuitTest`, `truthTableGenerator` and their + lemmas, moved into the `NaturalProof` namespace; 12 Ladner helpers (among them `boolDecoder`, + `natDecoder`, `ofBits`, `dropFalsesT`); `BakerGillSolovay.unaryLanguage` and three lemmas. +* **Meaning strengthened under the old name.** On 2026-10-20 `Turing.MultiTapeNTM.DecidesInSpace` + started to require that every branch halt (Sipser, Definition 8.1), so `NL` and `NPSPACE` + follow the textbook convention. The former meaning is `Turing.MultiTapeNTM.RecognizesInSpace` + (`DecidesInSpace.recognizesInSpace`); membership proofs now also need a per-instance halting + bound, the second component of `DecidesInSpace` (read back with + `DecidesInSpace.exists_runsInTime`). +* **Word-RAM operation bases.** On 2026-10-21 the word RAM became parametric in a basis of + operations. `Cslib.WordRAM.Op`, `Instr`, `Program` and `Code` take a basis `B` before the register + count, and `DecidableInTime`, `DecidableInTimeAt`, `LinearTime` and `PolynomialTime` take it as + their first explicit argument; the previous instruction set is `Cslib.WordRAM.Basis.standard`. + The constructors `Op.bin` and `Op.width` became definitions of the same names over the standard + basis, so construction is unchanged but pattern matching, `Op.bin.injEq` and `Op.width.injEq` + are gone (match on `Op.alu`). `Op`, `Instr` and `Code` no longer derive `Repr`. The binder `B` of + `Exec.loop`, `ExecWithin.loop` and `ExecWithin.loop_invariant` is now `T`, and the `B : Set β` + binder of the `recode` lemmas in `WordRAM/Time` is now `A'`; named-argument callers must follow. + On the same date `Cslib.Circuits.Signature.Hom` and `Signature.Hom.Preserves` moved, with their + names unchanged, from `Circuit/MapOp` to `Circuit/SignatureHom`, which `Circuit/MapOp` imports. +* **Word-RAM bases with undefined values.** On 2026-10-23 `Cslib.WordRAM.Basis.interp` became + `Option`-valued, and an operation whose value is `none` faults. The field + `Basis.interp_mono_of_not_readsWidth` replaces `Basis.interp_eq_of_not_readsWidth`; it only keeps + values that fit a word. + `Basis.Hom.preserves` is now a field of its own, an equation per word size, operation and + arguments, rather than `Signature.Hom.Preserves`. `StandardOp.eval`, `Basis.standard_interp_bin` + and `Basis.standard_interp_width` wrap their old values in `some`. `Op.eval_alu` now reads + `(B.interp w f (x.regs ∘ args)).bind (x.put w d)`: an undefined value propagates, and the word + check follows for a defined one. `Exec.alu` and `ExecWithin.alu` take the value `v` and the + hypothesis `B.interp w f (x.regs ∘ args) = some v`. `Basis.standard` keeps its semantics; the new + `Basis.exact` faults on `x - y` for `y > x` and on `x / 0` and `x % 0`. +* **Renamed modules.** On 2026-10-20 `Circuit/Boolean/MassProduction/{Sharp,SharpParameters, + SharpStatement}` became `…/{Fine,FineParameters,FineStatement}`, with their `Sharp` declarations + renamed to `Fine`. Imports of the old module paths must be updated: a module stub would have no + upstream counterpart, so none is kept. +* **Withdrawn results of 2026-10-22 (#372).** On 2026-10-22 the maintainer withdrew results merged + that morning without adequate correctness review, deleting them without aliases. An alias would + keep exactly the names being withdrawn, and none of them had been submitted to cslib or used + downstream. + - Regev's reduction (`Crypto/Lattice/LWE.lean`, whole module) and OT completeness + (`Crypto/Multiparty/ObliviousTransfer.lean`, whole module). + - `ACC0.lean`: `SymPlusCompilation`, `symPlusCompilation_zero`, + `exists_fastSymPlusEval_of_symPlusCompilation`, `exists_mem_NEXP_notMem_ACC0`, + `not_NEXP_subset_ACC0`. The duplicate `Cslib.Circuits.Boolean.SigmaP_subset_EXP` and + `SigmaP_subset_NEXP` were also deleted; use `Cslib.Complexity.SigmaP_subset_EXP` and + `SigmaP_subset_NEXP`, now in `Classes/Inclusions`. + - `IdentityTesting.lean`: `kabanets_impagliazzo`, `kabanets_impagliazzo_of_EXP_subset_P`, + `PermPITCertified`, `exists_certified_perm_family_of_PITInt_mem_P_of_mem_VP`, + `NEXP_eq_EXP_of_NEXP_subset_SigmaP`. + - `Hastad.lean`: `hastadSATProb` and its three lemmas, `hastadGraphSATProb`, and + `hastadGraphSATProb_eq_add_hastadGraphLinearCorr`. The SAT conjuncts were dropped from + `hastadGraphProb_longCode_of_satisfies` (now `graphLinearProb_longCode_of_satisfies`) and + `hastad_gapLabelCover_dichotomy` (now `gapLabelCover_dichotomy`). + - The conditional sumset-extractor frontier layer (#391), whose extractor-family hypothesis was + never discharged: `Circuit/Frontier/Extractor.lean` and `Complexity/FrontierExtractor.lean` + (whole modules), and the decimal restatements + `eventually_456249_div_100000_sub_mul_lt_innerSize`, + `circuitLowerBound_456249_div_100000`, `averageCaseBound_456249_div_100000`, + `eventually_456249_div_100000_sub_mul_lt_innerSize_of_nondeterministicDecides` and + `eventually_456249_div_100000_sub_mul_lt_card_of_aggregates`. Combine the exact-`L` theorems + with `Gaussian.circuitCoefficient_ge_456249` instead. + - #392 withdrew Gentry's bootstrapping (`Crypto/Computational/Bootstrapping.lean` and + `Bootstrapping/{HomomorphicEval,KeyChain,KeyEncryption}.lean`, whole modules), whose + `Bootstrappable` interface assumed the content of the theorem. From the Håstad modules it + deleted `GapExactThreeDistinctSAT`, `no_GapExactThreeDistinctSAT_eq_empty`, `xorThreeCNF` and + its five lemmas, `hastadLinearProb_le_of_hastadLinearCorr_le` and the same-day alias + `Cslib.PCP.Hastad.fourierCoeff_sq_signVal_le_one`. The kept long-code, folding and projection + lemmas moved to `Cslib.BitString` in `Foundations/BooleanAnalysis/LongCode` (`IsFolded` + became `IsOdd`), and `Hastad/ExactThreeDistinct` merged into + `PCP/Approximation/MaxExactThreeSAT`, keeping its declaration names; imports of the deleted + module path must be updated. `hastad_gapLabelCover_dichotomy` (now `gapLabelCover_dichotomy`) + now chooses `η` before the label sets. + - Follow-up #390 (same date and policy): the rest of the `ACC0.lean` Williams scaffolding, + `SymPlusCircuit` and its API, `evalMask` and its lemmas, `zetaTransform`, `fastZeta`, + `fastZeta_eq_zetaTransform`, `parity_mem_EXP`, `parity_mem_NEXP`, + `exists_mem_NEXP_notMem_{AC0,AC0Mod,SIZEMod}`, `exists_mem_SIZEMod_of_mem_AC0Mod`, + `NEXP_subset_PPoly_of_NEXP_subset_ACC0`, `PH_eq_SigmaP_two_of_NEXP_subset_ACC0` and + `Kannan.language_mem_SigmaP_two_of_NEXP_subset_ACC0`; the remaining `ACC0` development is + split into `ACC0/Basic` and `ACC0/Simulation`, and `NC_subset_PPoly`, `AC_subset_PPoly` and + `size_majorityDeMorganCircuit_le_sq` moved to `DepthClasses`, `FanInTwo` and `ThresholdNC1`, + all with names unchanged. The module + `Circuit/Arithmetic/IdentityTesting` is deleted. Its Schwartz–Zippel restatements + (`MvPolynomial.card_mul_card_roots_le`, `card_roots_le`, + `exists_eval_ne_zero_of_totalDegree_lt_card`; use Mathlib's + `MvPolynomial.schwartz_zippel_totalDegree`), `encArith*`, `PITInt` and its lemmas, the + arithmetic circuit combinators, the permanent chain (`minorVar` through + `isLaplacePermChain_laplacePermCircuit`), `intPermFamily` and `exists_mem_NEXP_notMem_SIZE` + are deleted. `Matrix.permanent_succ_{column,row}_zero` moved to + `Foundations/Data/Matrix/Permanent`, `Cslib.Circuits.Circuit.eval_polynomial` to + `Arithmetic/VP`, and `permFamily`, `isPFamily_permFamily` and + `totalDegree_permanent_genericMatrix_le` to `Arithmetic/Determinant`, with names unchanged. +* **Two multigraph namespaces.** Since 2026-10-20 the frontier multigraph + (`Cslib.Circuits.Frontier.Multigraph`) defines its simple graph as that of the multigraph given by + its ends (`Cslib.Multigraph`), and its module publicly imports the latter. Their shared base names + `toSimpleGraph`, `toSimpleGraph_adj`, `edgeCut` and `mem_edgeCut` are ambiguous when both + namespaces are visible, for example inside `namespace Cslib.Circuits.Frontier` or with both + opened; use dot notation (`G.toSimpleGraph_adj`) or full names. Upstreaming the frontier + multigraph therefore follows `Foundations/Combinatorics/Multigraph.lean`. + +The [historical ledger](https://github.com/cslib-community/complexitylib/blob/d0e6373970029a91018528463ca7a5ccd0fc9fd1/UPSTREAM.md) +preserves earlier integration notes and per-module follow-up ideas. + +* **Word-RAM word-size contracts (2026-10-22).** Time and encoding arguments now depend on + word size, and an ordinary predicate `allowed : α → ℕ → Prop` admits input/width pairs. + `WordSizeDependentProgram` selects code only by width; the primary decision and function classes + require a fixed register count and uniformly bounded instruction-list length. + `Uniform` variants retain one fixed program, + which may read the width. Existing fixed-code clients prove the uniform variants and can + forget uniformity. The `LogWord` wrappers express the transdichotomous assumption, require a + positive constant, and remain conditional on input fitting. Asymptotic time keeps correctness + at every admitted fitting input + and makes only the runtime estimate eventual, with constants shared by all widths. Width + schedules connect to Mathlib's real-valued big-O after casting. No effective selection of code, + width or encoding, or Turing-machine complexity identification, follows. These breaking local + API changes intentionally retain no deprecated aliases, as authorized by Erik for this redesign. From c3e1bcd3c8d9bb1dc4819d1b0a994bf52119731b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 00:25:16 -0400 Subject: [PATCH 30/32] doc(order-glue): restore paper citation and finite scope --- Complexity/Foundations/Data/Finset/OrderGlue.lean | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Complexity/Foundations/Data/Finset/OrderGlue.lean b/Complexity/Foundations/Data/Finset/OrderGlue.lean index 3cff17ec35..9a61f690c7 100644 --- a/Complexity/Foundations/Data/Finset/OrderGlue.lean +++ b/Complexity/Foundations/Data/Finset/OrderGlue.lean @@ -23,9 +23,10 @@ public import Mathlib.Data.Finset.Sort ## References -Adapted selectively from +Adapted selectively from [AlmanWilliams2026, Section 2] and [Anthropic's pinned formal source](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2). -Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +Source pin: `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. These are finite mathematical +counts and identities; they give no native program or instruction-cost certificate. -/ @[expose] public section From 6a86e258db83b7724f5bd93c43c0cbc4221a992f Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 01:34:50 -0400 Subject: [PATCH 31/32] fix(imports): regenerate thin-product aggregate after main integration --- Complexity.lean | 3 --- 1 file changed, 3 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 26b3e885de..4cf21c14b4 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -43,9 +43,6 @@ public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Sparse public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling -public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full -public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity -public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned public import Complexity.Algorithms.Reachability.BreadthFirst public import Complexity.Algorithms.Reachability.InductiveCounting public import Complexity.Algorithms.Reachability.InductiveCounting.Bounds From 61e7fe769e7f9de5b977185885850d08c93193ab Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 02:05:56 -0400 Subject: [PATCH 32/32] doc(wordram): separate shared-encoder integration records Keep the reviewed finite-matrix supplier records intact and record native-encoder scope and source credit separately, so squash-queue integration does not overlap the ancestor documentation hunks. Preserve code, tests, concrete bounds and the complete unrelated upstream-record suffix. Queue revisions are used only for read-only projection, never as parents. --- NOTICE | 7 +++++-- ROADMAP.md | 10 +++++----- UPSTREAM.md | 42 ++++++++++++++++++++++-------------------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/NOTICE b/NOTICE index 3314cc0f08..f62270f42b 100644 --- a/NOTICE +++ b/NOTICE @@ -1,9 +1,8 @@ -The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native programs in +The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native matrix builder in Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/, Complexity/Foundations/Algebra/{FiniteMagnitude,GeometricTail}.lean, Complexity/Foundations/Combinatorics/FunctionPlaces.lean, Complexity/Foundations/Data/Finset/{Average,OrderGlue,Quotient}.lean, @@ -28,3 +27,7 @@ This product is licensed under the Apache License, Version 2.0 (see LICENSE). The prime-window proof also adapts Mathlib/NumberTheory/Bertrand.lean, Copyright (c) 2020 Patrick Stevens; authors Patrick Stevens, Bolton Bailey, under the Apache License, Version 2.0. + +The shared tensor encoder in +Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/ +is also part of the Anthropic adaptation described above. diff --git a/ROADMAP.md b/ROADMAP.md index db0d5f2ed0..64f55a89bc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -425,6 +425,9 @@ each statement against the numbered result. Organize modules by subject, not cha theorems; simulating operations by programs over smaller bases; structured programs and time classes over arbitrary value domains with weighted or bit costs; simulations between instances; the typed core proposed in [CSLib #1158](https://github.com/leanprover/cslib/pull/1158). +- **Shared native matrix encoders** ([#1493]): compute all `10^L` leaf values from `7^L` + signed inputs within `240*(L+1)*10^L` standard WordRAM instructions, plus the final halt, + with fixed code/registers and concrete initialized inputs, frames and explicit width bounds. - **Word RAM:** an AC⁰ basis; wider-fragment and Turing-machine simulations; transfer of Anthropic's modular machine and 3SUM/APSP algorithms; direct 3SUM adaptation ([#1394]) has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance @@ -436,11 +439,8 @@ each statement against the numbered result. Organize modules by subject, not cha shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set bounds, with the empty zero-depth root handled explicitly. The finite thin-product component ([#1463]) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and - sparse encoding/leaf/passed-set totals. Those counts are not native instruction costs. - The shared native left/right encoders ([#1493]) compute all `10^L` leaf values from `7^L` - signed inputs within `240*(L+1)*10^L` standard WordRAM instructions, plus the final halt, - with fixed code/registers and concrete initialized inputs, frames and explicit width bounds. - Band/tile preparation, native pruned/sparse-query programs, native Chan–He preprocessing, the reduction + sparse encoding/leaf/passed-set totals. These counts are not native instruction costs. It still + needs native tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width diff --git a/UPSTREAM.md b/UPSTREAM.md index 09304c8ec7..59c18cc943 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -147,6 +147,22 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he theorems preserve the complete source memory and register bank. This is forward correctness, not reflection of source faults; transfer from Anthropic's modular signed machine and its 3SUM/APSP algorithms remains separate. +* **Native shared tensor encoder.** `Algorithms/WordRAM/MatrixMultiplication/Schonhage/` implements the complete shared left/right + encoding milestone ([#1493](https://github.com/cslib-community/complexitylib/issues/1493)). + Fixed standard-WordRAM code with 32 registers transforms one coordinate at a time between + two disjoint `10^L` buffers. It starts from `State.init` with a depth header and `7^L` + sign-and-magnitude words, computes all public `encodingL`/`encodingR` values, and preserves + the input and every cell outside the two buffers. Mathlib's `finFunctionFinEquiv` supplies + least-significant-first string numbering. All stages, powers, indices, address arithmetic, + signed add/subtract blocks and the final output copy cost at most `240*(L+1)*10^L` + instructions; halt adds one. Signed operands have magnitude bounded from the original + input by `7^L*A < 2^(w-1)`; the explicit natural bound `L+64+64*10^L` must fit target width + `w+2`. The two extra bits and actual arithmetic-block costs come from the existing native + signed kernels, without a copied source machine or a whole-program simulation hypothesis. + The existing array-copy interface is retained, with a narrow generalization for disjoint + blocks in either address order or identical blocks. + This is the encoder component only; band/tile preparation and full native composition remain + in [#1394](https://github.com/cslib-community/complexitylib/issues/1394). * **Direct 3SUM adaptation.** `Algorithms/ThreeSum/Convolution.lean` adapts the explicit Convolution-3SUM to Exact Triangle correctness reduction from Anthropic's [`formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp), @@ -207,26 +223,12 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he and finite-sum bounds live in small Foundations modules. Mathlib `Finset.orderEmbOfFin` indexes both a level set and its complement, and `Finset.piecewise` restricts answers. No source machine, problem-statement universe or solver/time hypothesis is imported. - `Algorithms/WordRAM/MatrixMultiplication/Schonhage/` implements the complete shared left/right - encoding milestone ([#1493](https://github.com/cslib-community/complexitylib/issues/1493)). - Fixed standard-WordRAM code with 32 registers transforms one coordinate at a time between - two disjoint `10^L` buffers. It starts from `State.init` with a depth header and `7^L` - sign-and-magnitude words, computes all public `encodingL`/`encodingR` values, and preserves - the input and every cell outside the two buffers. Mathlib's `finFunctionFinEquiv` supplies - least-significant-first string numbering. All stages, powers, indices, address arithmetic, - signed add/subtract blocks and the final output copy cost at most `240*(L+1)*10^L` - instructions; halt adds one. Signed operands have magnitude bounded from the original - input by `7^L*A < 2^(w-1)`; the explicit natural bound `L+64+64*10^L` must fit target width - `w+2`. The two extra bits and actual arithmetic-block costs come from the existing native - signed kernels, without a copied source machine or a whole-program simulation hypothesis. - The existing array-copy interface is retained, with a narrow generalization for disjoint - blocks in either address order or identical blocks. - The existing signed and unsigned APIs were reused; no source machine or separate run semantics - were copied. Band/tile preparation, native pruned and sparse-query programs, the full reduction - host, native Chan–He preprocessing, fast Exact Triangle and full input/time/width/asymptotic - composition remain in [#1394](https://github.com/cslib-community/complexitylib/issues/1394), - including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's - copyright and attribution are retained; the native proofs and representation choices are local. + The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. + Tensor/sparse query programs, the full reduction host, native Chan–He preprocessing, fast Exact + Triangle and full input/time/width/asymptotic composition remain in + [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved + Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution + are retained; the native proofs and representation choices are local. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`.