From 6094ec9cc975f8e361f8ea49146ac4bea36aa115 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 05:13:43 -0400 Subject: [PATCH 01/22] 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 bcc3d2303..d661ead1c 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 000000000..d52e77943 --- /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 000000000..5083b77db --- /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 000000000..737cbf386 --- /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 81584c241..3ab01a2df 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 8d53f5250..5b3b23001 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 f465fed04..87d9cb528 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/22] 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 d661ead1c..e6ac94554 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 000000000..6799e79fc --- /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 000000000..d358d0cd8 --- /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 000000000..dbeb6390e --- /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 000000000..94ae6d738 --- /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 000000000..41df09a87 --- /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 3ab01a2df..95b9c8d57 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 5b3b23001..d31b9ede1 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 87d9cb528..2bc29e783 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/22] 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 6799e79fc..affaadd2e 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 d52e77943..84694440f 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 2bc29e783..8b49547da 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 8cf6daceb..f42e7901a 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/22] 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 f42e7901a..af7ca6f2c 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/22] 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 affaadd2e..fbb2834b6 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 dbeb6390e..9693a5302 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 94ae6d738..d15f95b19 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/22] 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 e6ac94554..0f88b5283 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 fbb2834b6..86aba7824 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 000000000..5015daf5e --- /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 000000000..0b5e39cb9 --- /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 000000000..875f7e52d --- /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 000000000..f6622c6a7 --- /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 000000000..3f3c74fee --- /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 000000000..c88317bc1 --- /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 000000000..602c73737 --- /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 000000000..1768d67f6 --- /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 95b9c8d57..e48c2ecb6 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/22] 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 d31b9ede1..044ec8d47 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 8b49547da..abb96bb7a 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/22] 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 86aba7824..17ee53686 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 5015daf5e..4f6f435c3 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 0b5e39cb9..dfaeebb6a 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 f6622c6a7..eee6215f9 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/22] 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 0f88b5283..58c9b1d11 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 000000000..d9d6a76f9 --- /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 000000000..a1c8d82b6 --- /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 000000000..15b7942f4 --- /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 000000000..30043faf4 --- /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 000000000..62f3fb1d8 --- /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 e48c2ecb6..5fb9b6c14 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 044ec8d47..999c5e63c 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 abb96bb7a..bfa84a869 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/22] 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 62f3fb1d8..a70da4e16 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/22] 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 a1c8d82b6..ed1316900 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 15b7942f4..dea69212a 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 30043faf4..e581c0e20 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 844522a3dbb30980410ff9e07272ed0df0b9402c Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:44:21 -0400 Subject: [PATCH 12/22] 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 58c9b1d11..1cdd3b3c0 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 000000000..9421e25bb --- /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 000000000..354678f42 --- /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 000000000..38d7001a9 --- /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 000000000..71e660aa6 --- /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 000000000..a11ba4e55 --- /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 000000000..464b2a19c --- /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 000000000..9cd46e8f7 --- /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 000000000..49c113b4a --- /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 000000000..dd34af063 --- /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 000000000..99c8cad79 --- /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 000000000..fa60e6dd2 --- /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 000000000..f47c81e9d --- /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 000000000..05960b70c --- /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 000000000..02be86b3f --- /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 000000000..3eff3864f --- /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 5fb9b6c14..65eb16f1c 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 999c5e63c..5805e1216 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 bfa84a869..dde42fa02 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 b4936ce28d8b498b364a7947d34f728c55f8b226 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 10:47:14 -0400 Subject: [PATCH 13/22] 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 9421e25bb..262f3f864 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 354678f42..684266bc9 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 38d7001a9..da18d521e 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 71e660aa6..7a932b680 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 a11ba4e55..facc0f50c 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 464b2a19c..10b5d1679 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 9cd46e8f7..b18c209f3 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 49c113b4a..cfe5b34c1 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 dd34af063..980e8f750 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 99c8cad79..e18125684 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 fa60e6dd2..c9b5095dc 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 f47c81e9d..6036e4144 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 05960b70c..e2ac74931 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 02be86b3f..9a61f690c 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 68bbe40e3ae2e1164fb7586b5948fe65a2db9da8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:22:45 -0400 Subject: [PATCH 14/22] 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 1cdd3b3c0..9674671a2 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 10b5d1679..38a936be5 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 b18c209f3..1b2cab585 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 e18125684..4bf60b86e 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 c9b5095dc..ca762bc79 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 6036e4144..000000000 --- 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 e2ac74931..206c61f3d 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 9a61f690c..3cff17ec3 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 8265f9eab..b8e5e8f6a 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 3eff3864f..9ab0067ef 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 65eb16f1c..4f0b9376d 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 dde42fa02..3dfd78c52 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 15/22] 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 d9d6a76f9..34bcd43e1 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 ed1316900..801a2b80b 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 dea69212a..27995291a 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 e581c0e20..ac6930015 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 fc7b892df14feb91dedca3ca02c340c0ba7b63c9 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 18:48:57 -0400 Subject: [PATCH 16/22] refactor(exact-triangle): move reusable bounds and clarify finite APIs Address #1546 and the placement/naming findings from #1441, #1445 and #1455. Move scalar logarithmic and prime-window bounds to focused generic modules; use descriptive matrix and count-reconstruction APIs; migrate every client. Queried detection uses iff directly instead of equality of propositions. Remove local scalar wrappers in favor of Mathlib facts. Surviving statement and definition meanings are unchanged. Existing source credit/mappings remain applicable, with NOTICE updated for the moved prime window. No native program, quantitative bound or 3SUM endpoint changes. Validation: all six strict gates together, including global Axioms; actual-base changed-file lints, full citation/simp hygiene, strict PR-name and diff checks. Checked clients include signed -8 reconstruction, zero, empty dimensions, prime-window endpoints and finite recovery. Nonauthoring coordinator review /root/finite_api_review approves exact tree 4c4ee7c098be0781af340e4ccbde927a10b0f3e0 against base 68bbe40e3ae2e1164fb7586b5948fe65a2db9da8 under the delegated-review exception for mechanical API/proof refactors. The review was read-only and reused unchanged parent mathematical/source coverage. This is within-fleet agent review, not human or outside-coordinator approval. Curated abstracts: no impact on statements, scope, models or rendering. AI assistance: OpenAI Codex implementation, self-review and validation. --- Complexity.lean | 2 + .../Algorithms/ExactTriangle/Hashing.lean | 162 +++++------------- .../Algorithms/ExactTriangle/Lopsided.lean | 36 ++-- .../Algorithms/ExactTriangle/Splitting.lean | 36 ++-- .../Algorithms/ExactTriangle/Witnesses.lean | 26 +-- .../Foundations/Data/Int/LogBounds.lean | 34 ++++ .../Foundations/Data/Matrix/IntegerBits.lean | 37 ++-- .../NumberTheory/SqrtPrimeWindow.lean | 115 +++++++++++++ ComplexityTests/ExactTriangleHashing.lean | 14 +- ComplexityTests/ExactTriangleSplitting.lean | 6 +- ComplexityTests/Lopsided.lean | 22 +-- NOTICE | 2 +- UPSTREAM.md | 10 +- 13 files changed, 289 insertions(+), 213 deletions(-) create mode 100644 Complexity/Foundations/Data/Int/LogBounds.lean create mode 100644 Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean diff --git a/Complexity.lean b/Complexity.lean index 9674671a2..814920cc5 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -2092,6 +2092,7 @@ 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.LogBounds public import Complexity.Foundations.Data.Int.Order public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.Data.List.Append @@ -2217,6 +2218,7 @@ 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.NumberTheory.SqrtPrimeWindow public import Complexity.Foundations.Order.BooleanSubalgebra public import Complexity.Foundations.Order.Changes public import Complexity.Foundations.Order.UpperLower diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 17ee53686..417712b37 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -9,15 +9,14 @@ module public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Foundations.Data.Int.Residue -public import Complexity.Foundations.NumberTheory.PrimeWindow +public import Complexity.Foundations.NumberTheory.SqrtPrimeWindow 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 +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`. @@ -33,7 +32,7 @@ by `C * κ * n^3 * log n / sqrt D`. The prime count used here is proved for ever * `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. +* `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. @@ -53,71 +52,19 @@ The source pin is namespace Cslib.Algorithms.ExactTriangle -open Classical in -/-- 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 - /-- 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 prime in `primesInRange D` whose modular-zero count is minimal among all primes in that +/-- A prime in `Nat.sqrtPrimeWindow 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, + p ∈ Nat.sqrtPrimeWindow D ∧ ∀ q ∈ Nat.sqrtPrimeWindow 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) - -/-! -### The primes in the range -/ - -/-- 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 - 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 - -/-- 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] - 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 - -/-- 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 - 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`. -/ @@ -246,24 +193,6 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : end Counting -/-- 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] - -- 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 -/ @@ -275,18 +204,21 @@ variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) minimum of the modular-zero count. -/ theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by + have hsqrt : (4 : ℝ) ≤ Real.sqrt D := Real.le_sqrt_of_sq_le (by + norm_num + exact_mod_cast hD) 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⟩ + Nat.exists_prime_half_le_and_lt (Real.sqrt D) hsqrt + exact Finset.exists_min_image (Nat.sqrtPrimeWindow D) (countZeroMod wAB wBC wAC) ⟨q, + Nat.mem_sqrtPrimeWindow.2 hq⟩ /-- 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) : - ∀ q ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p ≤ (falsePositiveCount wAB wBC wAC) - q := by + ∀ q ∈ Nat.sqrtPrimeWindow 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 @@ -301,15 +233,16 @@ 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) : - (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + (((Nat.sqrtPrimeWindow 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 hsqrt := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + set divisors := (Nat.sqrtPrimeWindow 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 + obtain ⟨hprime, hge, -⟩ := Nat.mem_sqrtPrimeWindow.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 := @@ -335,18 +268,19 @@ edge weights bounded by `n^κ`, the sum of false-positive counts is at most `n^3 -/ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) : - ((∑ p ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p : ℕ) : ℝ) + ((∑ p ∈ Nat.sqrtPrimeWindow D, (falsePositiveCount wAB wBC wAC) p : ℕ) : ℝ) ≤ (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by classical - have hsqrt := four_le_sqrt hD + have hsqrt := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)) -- 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 + have hswap : ∑ p ∈ Nat.sqrtPrimeWindow D, (falsePositiveCount wAB wBC wAC) p = + ∑ t : Fin n × Fin n × Fin n, + ((Nat.sqrtPrimeWindow 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 : ℝ) + (((Nat.sqrtPrimeWindow 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 @@ -359,7 +293,7 @@ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : Weights 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 : ℝ) + (((Nat.sqrtPrimeWindow 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 @@ -370,34 +304,24 @@ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : Weights end Selection -/-- 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 → - 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⟩⟩ - /-- 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) : - (falsePositiveCount wAB wBC wAC) p * (primesInRange D).card ≤ ∑ q ∈ primesInRange D, + (falsePositiveCount wAB wBC wAC) p * (Nat.sqrtPrimeWindow D).card ≤ ∑ q ∈ Nat.sqrtPrimeWindow D, (falsePositiveCount wAB wBC wAC) q := by rw [mul_comm, ← smul_eq_mul] exact Finset.card_nsmul_le_sum _ _ _ hp.falsePositiveCount_le /-- 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) : +private 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] + have hsqrt : 0 < Real.sqrt D := Real.sqrt_pos.mpr + (by exact_mod_cast (show 0 < D by omega)) -- `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) @@ -410,9 +334,9 @@ theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : 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 : ℝ} +theorem falsePositiveCount_le_of_card_sqrtPrimeWindow_lower_bound {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) + (hcard : c * (Real.sqrt D / Real.log D) ≤ ((Nat.sqrtPrimeWindow 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 : ℝ) ^ κ) @@ -421,8 +345,9 @@ theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ -- 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 + set numPrimes := (Nat.sqrtPrimeWindow D).card + have hr : 4 ≤ r := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)) 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) @@ -459,7 +384,7 @@ theorem exists_falsePositiveCount_le : 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 + obtain ⟨c, hc, hcard⟩ := Nat.exists_pos_mul_sqrt_div_log_le_card_sqrtPrimeWindow 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) @@ -471,7 +396,8 @@ theorem exists_falsePositiveCount_le : 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 + (falsePositiveCount_le_of_card_sqrtPrimeWindow_lower_bound 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 @@ -481,20 +407,20 @@ theorem exists_falsePositiveCount_le : /-- 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 +noncomputable def falsePositiveConst : ℝ := Classical.choose exists_falsePositiveCount_le -/-- The universal false-positive constant `Hashing.falsePositiveConst` is at least one. +/-- The universal false-positive constant `falsePositiveConst` is at least one. -/ -theorem Hashing.one_le_falsePositiveConst : 1 ≤ Hashing.falsePositiveConst := +theorem one_le_falsePositiveConst : 1 ≤ falsePositiveConst := (Classical.choose_spec exists_falsePositiveCount_le).1 /-- 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`. +false-positive count by `falsePositiveConst * κ * n^3 * log n / sqrt D`. -/ -theorem Hashing.falsePositiveCount_le_const_mul {n D p : ℕ} {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) +theorem 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 * + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ falsePositiveConst * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt D) := (Classical.choose_spec exists_falsePositiveCount_le).2 hD hDn hκ wAB wBC wAC hT hp diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean index 84694440f..3b2dbc0b2 100644 --- a/Complexity/Algorithms/ExactTriangle/Lopsided.lean +++ b/Complexity/Algorithms/ExactTriangle/Lopsided.lean @@ -31,8 +31,8 @@ fast solver or subquadratic running time. * `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. +* `LopsidedGraph.mul_apply_eq_commonNeighborCount_ofMatrices`: the converse for zero/one matrices. +* `LopsidedGraph.mul_apply_eq_sum_signedBitInstance_counts`: signed products from `4 * β²` counts. ## References @@ -74,7 +74,7 @@ noncomputable def commonNeighborCount {n : ℕ} (I : LopsidedGraph n) (a b : Fin (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. -/ +The theorem `mul_apply_eq_commonNeighborCount_ofMatrices` requires zero/one entries. -/ 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 @@ -181,16 +181,15 @@ theorem counting_eqOn_iff (ans : Fin n × Fin n → ℕ) : 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 → ℕ) +theorem detection_iff_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 + ∀ q ∈ I.W, ans q ≠ 0 ↔ I.HasCommonNeighbor q.1 q.2 := 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) ℤ) +theorem mul_apply_eq_commonNeighborCount_ofMatrices {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 @@ -204,8 +203,8 @@ theorem zero_one {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D 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 + ofMatrices (Matrix.bit (if negativeX then (X.map negPart) else (X.map posPart)) i) + (Matrix.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. -/ @@ -224,18 +223,19 @@ 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] + ((ofMatrices (Matrix.bit X i) (Matrix.bit Y j) W).commonNeighborCount a b : ℤ) := by + rw [Matrix.mul_eq_sum_two_pow_smul_bit_mul_bit 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)] + rw [Matrix.smul_apply, smul_eq_mul, mul_apply_eq_commonNeighborCount_ofMatrices _ _ W + (Matrix.bit_eq_zero_or_one X i) + (Matrix.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) ℤ) +theorem mul_apply_eq_sum_signedBitInstance_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) * @@ -248,10 +248,10 @@ theorem entries_from_counts {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + ((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 + obtain ⟨hXpos, hXneg⟩ := Matrix.map_posPart_lt_and_map_negPart_lt X hX + obtain ⟨hYpos, hYneg⟩ := Matrix.map_posPart_lt_and_map_negPart_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] + conv_lhs => rw [Matrix.eq_map_posPart_sub_map_negPart X, Matrix.eq_map_posPart_sub_map_negPart 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, diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean index 4f6f435c3..2d1773d1e 100644 --- a/Complexity/Algorithms/ExactTriangle/Splitting.lean +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -131,23 +131,15 @@ def totalChunks {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D p : ℕ) : ℕ := 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) + have hsqrt : Real.sqrt (D : ℝ) ≤ D := + Real.sqrt_le_self_iff.mpr (Or.inr (by exact_mod_cast hD)) + exact (hsqrt.trans (by exact_mod_cast hDn)).trans (le_self_pow₀ hn two_ne_zero) variable {n D g p : ℕ} @@ -163,7 +155,8 @@ theorem sqrt_lt_sOf_add_one (D : ℕ) : Real.sqrt D < (sOf D : ℝ) + 1 := /-- `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] + linarith [(Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)), 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 @@ -270,7 +263,8 @@ private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) 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 hr4 : 4 ≤ r := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)) have hxr : x * r = (n : ℝ) ^ 2 := div_mul_cancel₀ _ (by linarith) have hx3 : r ^ 3 ≤ x := by rw [hx, le_div_iff₀ (by linarith)] @@ -304,8 +298,10 @@ private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) 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) + have hsqrtD := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : + (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + have hsqrtn := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD.trans hDn) : + (4 : ℝ) ≤ Real.sqrt (n : ℝ)) constructor · calc 3 * Real.sqrt D / 4 ≤ Real.sqrt D - 1 := by linarith _ ≤ (sOf D : ℝ) := by linarith [sqrt_lt_sOf_add_one D] @@ -385,10 +381,10 @@ private theorem totalChunks_le_add_sOf (hD : 16 ≤ D) (hDn : D ≤ n) (hp0 : p 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) : +theorem totalChunks_le (hD : 16 ≤ D) (hDn : D ≤ n) (hp : p ∈ Nat.sqrtPrimeWindow 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 + obtain ⟨hprime, hge, hlt⟩ := Nat.mem_sqrtPrimeWindow.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⟩ @@ -397,9 +393,9 @@ theorem totalChunks_le (hD : 16 ≤ D) (hDn : D ≤ n) (hp : p ∈ primesInRange /-- 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) +theorem card_middle_lopInstance (hD : 16 ≤ D) (hg1 : 1 ≤ g) (hp : p ∈ Nat.sqrtPrimeWindow D) (ι : InstanceIndex p) : Fintype.card (lopInstance wAB wBC wAC D g p ι).M ≤ D := by - obtain ⟨-, -, hlt⟩ := mem_primesInRange.mp hp + obtain ⟨-, -, hlt⟩ := Nat.mem_sqrtPrimeWindow.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 _) @@ -475,7 +471,7 @@ theorem card_instanceIndices (D g p : ℕ) : /-- 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) : + (hp : p ∈ Nat.sqrtPrimeWindow 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 diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean index dfaeebb6a..815a0b5ad 100644 --- a/Complexity/Algorithms/ExactTriangle/Witnesses.lean +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -275,13 +275,13 @@ theorem pieceSize_le_two_mul_sqrt_div (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.s /-- 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`. +`(2 * 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) : ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) - ≤ (2 * Hashing.falsePositiveConst + 2) * (κ * (n : ℝ) ^ 3 * Real.log n / (g : ℝ)) := by + ≤ (2 * 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 @@ -290,11 +290,15 @@ theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fi 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 + falsePositiveConst * B := by + have hF := 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 + calc + Real.sqrt D ≤ D := by + apply Real.sqrt_le_self_iff.mpr + exact Or.inr (by exact_mod_cast (show 1 ≤ D by omega)) + _ ≤ n := by exact_mod_cast 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 @@ -304,8 +308,8 @@ theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fi 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 + _ ≤ (2 * (falsePositiveConst * B) + 2 * B) / (g : ℝ) := by gcongr + _ = (2 * falsePositiveConst + 2) * (B / (g : ℝ)) := by ring /-! ### The output of the reduction -/ @@ -319,7 +323,7 @@ theorem recovery_correct (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) {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 + have hp0 : p ≠ 0 := (Nat.mem_sqrtPrimeWindow.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. @@ -363,8 +367,8 @@ theorem answersCorrect_of_counting (wAB wBC wAC : Fin n → Fin n → ℤ) (D g 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) + exact (lopInstance wAB wBC wAC D g p ι).detection_iff_of_counting + (counts ι) (hcounts ι hι) q 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. -/ @@ -377,7 +381,7 @@ theorem recovery_vertexBound {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 {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 + (2 * 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)⟩ diff --git a/Complexity/Foundations/Data/Int/LogBounds.lean b/Complexity/Foundations/Data/Int/LogBounds.lean new file mode 100644 index 000000000..57fd66785 --- /dev/null +++ b/Complexity/Foundations/Data/Int/LogBounds.lean @@ -0,0 +1,34 @@ +/- +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.Algebra.Order.Ring.Int +public import Mathlib.Data.Nat.Log + +/-! +# Integer magnitude and binary logarithms + +## Main results + +* `Int.abs_lt_two_pow_log_succ`: a natural magnitude bound supplies enough binary digits. +-/ + +@[expose] public section + +namespace Int + +/-- An integer bounded in magnitude by `U` has magnitude below `2^(log₂ U+1)`, including +`U = 0`. This is the scalar consequence of `Nat.lt_pow_succ_log_self`. -/ +theorem abs_lt_two_pow_log_succ {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 Int diff --git a/Complexity/Foundations/Data/Matrix/IntegerBits.lean b/Complexity/Foundations/Data/Matrix/IntegerBits.lean index 5083b77db..dd5f54d29 100644 --- a/Complexity/Foundations/Data/Matrix/IntegerBits.lean +++ b/Complexity/Foundations/Data/Matrix/IntegerBits.lean @@ -10,7 +10,6 @@ module 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 @@ -22,24 +21,25 @@ parts. These are algebraic identities, with no algorithm or running-time asserti ## Main definitions -* `IntMatrix.bit`: the binary digit matrix of a given weight. +* `Matrix.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. +* `Matrix.eq_sum_two_pow_smul_bit`: expansion of a nonnegative bounded matrix. +* `Matrix.mul_eq_sum_two_pow_smul_bit_mul_bit`: expansion of a product into digit products. +* `Matrix.eq_map_posPart_sub_map_negPart`: reconstruction from pointwise signed parts. +* `Matrix.map_posPart_lt_and_map_negPart_lt`: bounds for both pointwise signed parts. ## 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. +`matBit` and `Footnote8.binary/product`. The adaptation permits general index types and uses +Mathlib's existing pointwise signed parts. -/ @[expose] public section -namespace Cslib.IntMatrix +namespace Cslib.Matrix /-- 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. -/ @@ -58,7 +58,7 @@ private theorem emod_two_pow_eq_sum_digits (x : ℤ) (β : ℕ) : 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 ℤ) (β : ℕ) +theorem eq_sum_two_pow_smul_bit {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 @@ -73,11 +73,12 @@ theorem bit_eq_zero_or_one {m n : Type*} (X : Matrix m n ℤ) (i : ℕ) (a : m) 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 ℤ) +theorem mul_eq_sum_two_pow_smul_bit_mul_bit {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] + conv_lhs => rw [eq_sum_two_pow_smul_bit X β hX, eq_sum_two_pow_smul_bit Y β hY] rw [Matrix.sum_mul] refine Finset.sum_congr rfl fun i _ => ?_ rw [Matrix.mul_sum] @@ -86,14 +87,14 @@ theorem product {m d n : Type*} [Fintype d] (X : Matrix m d ℤ) (Y : Matrix d n /-- 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 ℤ) : +theorem eq_map_posPart_sub_map_negPart {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 ℤ) {β : ℕ} +theorem map_posPart_lt_and_map_negPart_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 @@ -101,12 +102,4 @@ theorem parts_lt {m n : Type*} (X : Matrix m n ℤ) {β : ℕ} 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 +end Cslib.Matrix diff --git a/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean b/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean new file mode 100644 index 000000000..bedbc303b --- /dev/null +++ b/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean @@ -0,0 +1,115 @@ +/- +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.NumberTheory.PrimeWindow + +/-! +# Prime windows below a square root + +This finite window is independent of the application that chooses a prime from it. Membership +has both real-endpoint and integer-square characterizations, including the closed lower and +open upper endpoints. Counting reuses the general prime-window estimates. + +## Main definitions + +* `Nat.sqrtPrimeWindow`: the primes in `[sqrt D / 2, sqrt D)`. + +## Main results + +* `Nat.mem_sqrtPrimeWindow`, `Nat.mem_sqrtPrimeWindow_iff_sq`: membership tests. +* `Nat.sqrtPrimeWindow_eq_filter`: an integer-only enumeration. +* `Nat.card_sqrtPrimeWindow_lt_sqrt`: the upper cardinality bound. +* `Nat.exists_pos_mul_sqrt_div_log_le_card_sqrtPrimeWindow`: a uniform lower bound. + +## References + +Adapted from the prime-window definitions and estimates in +[Anthropic's pinned hashing source](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean). +-/ + +@[expose] public section + +namespace Nat + +open Classical in +/-- Prime numbers in the half-open real interval `[sqrt D / 2, sqrt D)`. +-/ +noncomputable def sqrtPrimeWindow (D : ℕ) : Finset ℕ := + (Finset.range D).filter fun p => p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D + +/-- Window membership is equivalent to primality and the two real endpoint inequalities. The +finite enumeration bound in `sqrtPrimeWindow` excludes no member of the interval. +-/ +theorem mem_sqrtPrimeWindow {D p : ℕ} : + p ∈ sqrtPrimeWindow D ↔ p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D := by + unfold sqrtPrimeWindow + 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 + +/-- Characterize window membership using primality and integer-square inequalities, including the +closed lower and open upper endpoints. +-/ +theorem mem_sqrtPrimeWindow_iff_sq {D p : ℕ} : + p ∈ sqrtPrimeWindow D ↔ p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + rw [mem_sqrtPrimeWindow] + 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 + +/-- Enumerate the real prime window using a decidable filter of the integers below `D`. +-/ +theorem sqrtPrimeWindow_eq_filter (D : ℕ) : + sqrtPrimeWindow D = (Finset.range D).filter fun p => p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + ext p + rw [mem_sqrtPrimeWindow_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⟩ + +/-- For `D ≥ 16`, the number of primes in `sqrtPrimeWindow D` is strictly less than `sqrt D`. +-/ +theorem card_sqrtPrimeWindow_lt_sqrt (D : ℕ) (hD : 16 ≤ D) : + ((sqrtPrimeWindow D).card : ℝ) < Real.sqrt D := by + have hpos : 0 < Real.sqrt D := Real.sqrt_pos.mpr (by exact_mod_cast (by omega : 0 < D)) + -- The primes in the range are among the numbers `1, …, ⌈√D⌉ - 1`. + have hsub : sqrtPrimeWindow D ⊆ Finset.Ico 1 ⌈Real.sqrt D⌉₊ := by + intro p hp + obtain ⟨hprime, -, hlt⟩ := mem_sqrtPrimeWindow.1 hp + exact Finset.mem_Ico.2 ⟨hprime.one_lt.le, Nat.lt_ceil.2 hlt⟩ + have hcard : (sqrtPrimeWindow 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 : ((sqrtPrimeWindow D).card : ℝ) + 1 ≤ (⌈Real.sqrt D⌉₊ : ℝ) := by exact_mod_cast hcard + linarith [Nat.ceil_lt_add_one hpos.le] + +/-- 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_pos_mul_sqrt_div_log_le_card_sqrtPrimeWindow : + ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → + c * (Real.sqrt D / Real.log D) ≤ ((sqrtPrimeWindow 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_sqrtPrimeWindow.2 ⟨hp, hge, hlt⟩⟩ + +end Nat diff --git a/ComplexityTests/ExactTriangleHashing.lean b/ComplexityTests/ExactTriangleHashing.lean index 41df09a87..16acad7bb 100644 --- a/ComplexityTests/ExactTriangleHashing.lean +++ b/ComplexityTests/ExactTriangleHashing.lean @@ -20,17 +20,17 @@ 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] +private theorem window16 : Nat.sqrtPrimeWindow 16 = {2, 3} := by + rw [Nat.sqrtPrimeWindow_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] +example : 2 ∈ Nat.sqrtPrimeWindow 16 ∧ 5 ∉ Nat.sqrtPrimeWindow 25 ∧ 3 ∈ Nat.sqrtPrimeWindow 25 := by + simp only [Nat.mem_sqrtPrimeWindow_iff_sq] decide -example : 2 ∉ primesInRange 17 := by rw [mem_primesInRange_iff_sq]; decide +example : 2 ∉ Nat.sqrtPrimeWindow 17 := by rw [Nat.mem_sqrtPrimeWindow_iff_sq]; decide example : countZeroMod ab z z 2 = 8 ∧ countZeroMod ab z z 3 = 4 := by unfold countZeroMod weightSum ab z @@ -104,8 +104,8 @@ example : ¬WeightsBoundedBy (fun _ _ : Fin 1 => (0 : ℤ)) (fun _ _ => 0) (fun -- 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) + falsePositiveConst * (1 * (16 : ℝ) ^ 3 * Real.log 16 / Real.sqrt 16) := by + apply 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, ?_⟩ diff --git a/ComplexityTests/ExactTriangleSplitting.lean b/ComplexityTests/ExactTriangleSplitting.lean index 1768d67f6..3125f4812 100644 --- a/ComplexityTests/ExactTriangleSplitting.lean +++ b/ComplexityTests/ExactTriangleSplitting.lean @@ -43,8 +43,8 @@ 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] +private theorem prime3 : 3 ∈ Nat.sqrtPrimeWindow 16 := by + rw [Nat.mem_sqrtPrimeWindow_iff_sq] decide -- All-zero matrices supply an admitted, nonempty splitting with the promised bounds. @@ -139,7 +139,7 @@ example : ∃ L, IsScanOrder zeroWeight zeroWeight zeroWeight 16 2 3 private theorem falseSelected : IsSelectedPrime falseWeight zeroWeight zeroWeight 16 3 := by refine ⟨prime3, fun q hq => ?_⟩ - obtain ⟨hprime, -, hsq⟩ := mem_primesInRange_iff_sq.mp hq + obtain ⟨hprime, -, hsq⟩ := Nat.mem_sqrtPrimeWindow_iff_sq.mp hq have hq : q = 2 ∨ q = 3 := by have := hprime.two_le have : q < 4 := by nlinarith diff --git a/ComplexityTests/Lopsided.lean b/ComplexityTests/Lopsided.lean index 737cbf386..ecae7913f 100644 --- a/ComplexityTests/Lopsided.lean +++ b/ComplexityTests/Lopsided.lean @@ -5,6 +5,7 @@ Authors: Erik Demaine -/ import Complexity.Algorithms.ExactTriangle.Lopsided +import Complexity.Foundations.Data.Int.LogBounds import Mathlib.LinearAlgebra.Matrix.Notation /-! Concrete tests for padding, counts, queried answers and signed reconstruction. -/ @@ -80,7 +81,7 @@ example : (signedBitInstance signedX signedY {(0, 0)} true false 0 1).commonNeig classical simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] norm_num [signedX, signedY, - Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Cslib.Matrix.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 @@ -88,7 +89,7 @@ example : (signedBitInstance signedX signedY {(0, 0)} false true 1 0).commonNeig classical simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] norm_num [signedX, signedY, - Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Cslib.Matrix.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. @@ -98,26 +99,27 @@ example : (∑ i ∈ Finset.range 2, ∑ j ∈ Finset.range 2, (2 : ℤ) ^ (i + - ((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 + rw [← mul_apply_eq_sum_signedBitInstance_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 : Cslib.Matrix.bit (!![-1, 0, 3] : Matrix (Fin 1) (Fin 3) ℤ) 1 0 0 = 1 := by + norm_num [Cslib.Matrix.bit] example : |(-7 : ℤ)| < 2 ^ (Nat.log 2 7 + 1) := by - exact Cslib.IntMatrix.abs_lt_two_pow_log (-7) (by norm_num) + exact Int.abs_lt_two_pow_log_succ (-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) + exact Int.abs_lt_two_pow_log_succ 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)} + have h := mul_apply_eq_commonNeighborCount_ofMatrices + (!![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 @@ -128,7 +130,7 @@ example : (signedBitInstance signedX (-signedY) {(0, 0)} true true 0 1).commonNe classical simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] norm_num [signedX, signedY, - Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Cslib.Matrix.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 = @@ -149,7 +151,7 @@ example : Set.EqOn (fun _ : Fin 0 × Fin 0 => (99 : ℕ)) -- 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 + have h := mul_apply_eq_sum_signedBitInstance_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 diff --git a/NOTICE b/NOTICE index 4f0b9376d..dcdc12ee9 100644 --- a/NOTICE +++ b/NOTICE @@ -9,7 +9,7 @@ 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 -Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow}.lean and +Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow,SqrtPrimeWindow}.lean and Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. diff --git a/UPSTREAM.md b/UPSTREAM.md index 3dfd78c52..070a0711a 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -177,14 +177,18 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he 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. + with descriptive lemmas in `Cslib.Matrix`. The scalar magnitude-bit bound, including zero, + lives in `Foundations/Data/Int/LogBounds.lean`. Count answers use `Set.EqOn`; detection + consumes a direct iff on queried edges. 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. `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. + predicate. The application-independent `Nat.sqrtPrimeWindow` API lives in + `Foundations/NumberTheory/SqrtPrimeWindow.lean`; its integer-square characterization supports + native enumeration, and splitting and witness recovery reuse it. 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 From 418983924a4b15ce5746af8b4f37994700832726 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 19:15:52 -0400 Subject: [PATCH 17/22] refactor(exact-triangle): share square-root bounds and refine names Address all three optional findings from the outside FINAL review of PR #1556 at fc7b892df14feb91dedca3ca02c340c0ba7b63c9. A shared Real.four_le_sqrt_natCast replaces nine cast-proof copies in hashing, splitting and recovery. Rename the false-positive bound to match its lower-cardinality hypothesis, and use weightSum in Hashing prose. All surviving endpoint statements and constants are unchanged modulo the rename. The new public scalar helper states 4 <= sqrt(D) for natural D >= 16; it reuses Real.le_sqrt_of_sq_le and cast normalization. Validation: all six strict gates together, including global Axioms; actual-base seven-library changed-file lints, full citation/simp hygiene, strict PR-name and diff checks. All four edited modules have clean Lean MCP diagnostics. Exact validated tree: 4028ff397a1a5ff89a104bf538f82a482960bb3e. The original outside coverage remains https://github.com/cslib-community/complexitylib/pull/1556#issuecomment-6482807935 Focused outside review is requested for this delta and the shared public helper; prior approval is not claimed to cover the new API addition. No native program or quantitative endpoint changes. Curated abstracts: no impact on statements, scope or rendering. AI assistance: OpenAI Codex. --- .../Algorithms/ExactTriangle/Hashing.lean | 28 ++++++++----------- .../Algorithms/ExactTriangle/Splitting.lean | 12 +++----- .../Algorithms/ExactTriangle/Witnesses.lean | 3 +- .../NumberTheory/SqrtPrimeWindow.lean | 5 ++++ 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 417712b37..e8d4c7064 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -78,7 +78,7 @@ private theorem eq_add_toNat_emod_iff {p r : ℕ} (hp : p ≠ 0) (hr : r < p) (u ← Int.add_emod] /-! -### Counting the triples with `S(a,b,c) ≡ 0 (mod p)` -/ +### Counting triples with a vanishing modular weight sum -/ section Counting @@ -121,7 +121,8 @@ theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiv 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. + -- A triple whose `weightSum` is divisible by `p` 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] @@ -178,8 +179,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : 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. + -- A zero `weightSum` modulo `p` means `wAC a c + wBC b c ≡ -wAB a b`, and the right side + -- 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 @@ -204,9 +205,7 @@ variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) minimum of the modular-zero count. -/ theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by - have hsqrt : (4 : ℝ) ≤ Real.sqrt D := Real.le_sqrt_of_sq_le (by - norm_num - exact_mod_cast hD) + have hsqrt : (4 : ℝ) ≤ Real.sqrt D := Real.four_le_sqrt_natCast hD obtain ⟨q, hq⟩ := Nat.exists_prime_half_le_and_lt (Real.sqrt D) hsqrt exact Finset.exists_min_image (Nat.sqrtPrimeWindow D) (countZeroMod wAB wBC wAC) ⟨q, @@ -235,8 +234,7 @@ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB (t : Fin n × Fin n × Fin n) (ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0) : (((Nat.sqrtPrimeWindow D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) ≤ Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by - have hsqrt := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + have hsqrt := Real.four_le_sqrt_natCast hD set divisors := (Nat.sqrtPrimeWindow 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 @@ -244,7 +242,7 @@ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB obtain ⟨hrange, -, hdvd⟩ := Finset.mem_filter.1 hp obtain ⟨hprime, hge, -⟩ := Nat.mem_sqrtPrimeWindow.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^κ`. + -- Their product divides the nonzero `weightSum`, whose absolute value is at most `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 @@ -271,8 +269,7 @@ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : Weights ((∑ p ∈ Nat.sqrtPrimeWindow D, (falsePositiveCount wAB wBC wAC) p : ℕ) : ℝ) ≤ (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by classical - have hsqrt := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + have hsqrt := Real.four_le_sqrt_natCast hD -- Count the pairs (prime, false positive of that prime) triple by triple. have hswap : ∑ p ∈ Nat.sqrtPrimeWindow D, (falsePositiveCount wAB wBC wAC) p = ∑ t : Fin n × Fin n × Fin n, @@ -334,7 +331,7 @@ private theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : 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_card_sqrtPrimeWindow_lower_bound {n D p : ℕ} {κ c : ℝ} +theorem falsePositiveCount_le_of_le_card_sqrtPrimeWindow {n D p : ℕ} {κ c : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) (hc : 0 < c) (hcard : c * (Real.sqrt D / Real.log D) ≤ ((Nat.sqrtPrimeWindow D).card : ℝ)) (hD : 16 ≤ D) (hDn : D ≤ n) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : @@ -346,8 +343,7 @@ theorem falsePositiveCount_le_of_card_sqrtPrimeWindow_lower_bound {n D p : ℕ} set r := Real.sqrt D set Y := 3 * (n : ℝ) ^ κ with hY set numPrimes := (Nat.sqrtPrimeWindow D).card - have hr : 4 ≤ r := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + have hr : 4 ≤ r := Real.four_le_sqrt_natCast 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) @@ -396,7 +392,7 @@ theorem exists_falsePositiveCount_le : linarith [hlog3] calc ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ 4 / c * ((n : ℝ) ^ 3 * Real.log (3 * (n : ℝ) ^ κ) / Real.sqrt D) := - (falsePositiveCount_le_of_card_sqrtPrimeWindow_lower_bound wAB wBC wAC) + (falsePositiveCount_le_of_le_card_sqrtPrimeWindow 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 diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean index 2d1773d1e..1433c4182 100644 --- a/Complexity/Algorithms/ExactTriangle/Splitting.lean +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -155,8 +155,7 @@ theorem sqrt_lt_sOf_add_one (D : ℕ) : Real.sqrt D < (sOf D : ℝ) + 1 := /-- `s > 0` because `D ≥ 16`. -/ private theorem sOf_pos (hD : 16 ≤ D) : (0 : ℝ) < sOf D := by - linarith [(Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)), sqrt_lt_sOf_add_one D] + linarith [Real.four_le_sqrt_natCast 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 @@ -263,8 +262,7 @@ private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) 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 := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)) + have hr4 : 4 ≤ r := Real.four_le_sqrt_natCast 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)] @@ -298,10 +296,8 @@ private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) 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 := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) : - (4 : ℝ) ≤ Real.sqrt (D : ℝ)) - have hsqrtn := (Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD.trans hDn) : - (4 : ℝ) ≤ Real.sqrt (n : ℝ)) + have hsqrtD := Real.four_le_sqrt_natCast hD + have hsqrtn := Real.four_le_sqrt_natCast (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] diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean index 815a0b5ad..02c8cba7a 100644 --- a/Complexity/Algorithms/ExactTriangle/Witnesses.lean +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -282,8 +282,7 @@ theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fi (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : IsSelectedPrime wAB wBC wAC D p) : ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) ≤ (2 * 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 hsqrt : 4 ≤ Real.sqrt (D : ℝ) := Real.four_le_sqrt_natCast 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)) diff --git a/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean b/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean index bedbc303b..7c00a95c8 100644 --- a/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean +++ b/Complexity/Foundations/NumberTheory/SqrtPrimeWindow.lean @@ -22,6 +22,7 @@ open upper endpoints. Counting reuses the general prime-window estimates. ## Main results +* `Real.four_le_sqrt_natCast`: the shared endpoint bound for natural parameters at least 16. * `Nat.mem_sqrtPrimeWindow`, `Nat.mem_sqrtPrimeWindow_iff_sq`: membership tests. * `Nat.sqrtPrimeWindow_eq_filter`: an integer-only enumeration. * `Nat.card_sqrtPrimeWindow_lt_sqrt`: the upper cardinality bound. @@ -35,6 +36,10 @@ Adapted from the prime-window definitions and estimates in @[expose] public section +/-- The real square root of a natural window parameter at least 16 is at least four. -/ +theorem Real.four_le_sqrt_natCast {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + namespace Nat open Classical in From 02f1b495322d8cfee81969186795670e3574defa Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:08 -0400 Subject: [PATCH 18/22] 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 69abff193..f12edfac1 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 d8f41623613bcf6207688216e23ee62f905ff2e3 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:37:55 -0400 Subject: [PATCH 19/22] 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 900334b68..153abe289 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 cde3015cfbccd2c7495d6fd66bf976949f9ea067 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:50:49 -0400 Subject: [PATCH 20/22] doc(exact-triangle): retain reviewed Lopsided source citation --- Complexity/Algorithms/ExactTriangle/Lopsided.lean | 1 + 1 file changed, 1 insertion(+) diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean index 598f9dd6d..3b2dbc0b2 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. From c3e1bcd3c8d9bb1dc4819d1b0a994bf52119731b Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 00:25:16 -0400 Subject: [PATCH 21/22] 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 3cff17ec3..9a61f690c 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 22/22] 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 26b3e885d..4cf21c14b 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