From 6094ec9cc975f8e361f8ea49146ac4bea36aa115 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 05:13:43 -0400 Subject: [PATCH 01/20] feat(exactTriangle): relate lopsided queries and matrix products Adapt the finite counting interface and signed digit expansion from anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5 for milestone #1438. This is a mathematical interface; native preprocessing, fast solvers and full composition remain under #1394. --- Complexity.lean | 2 + .../Algorithms/ExactTriangle/Lopsided.lean | 265 ++++++++++++++++++ .../Foundations/Data/Matrix/IntegerBits.lean | 112 ++++++++ ComplexityTests/Lopsided.lean | 163 +++++++++++ NOTICE | 5 +- ROADMAP.md | 8 +- UPSTREAM.md | 11 + 7 files changed, 561 insertions(+), 5 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Lopsided.lean create mode 100644 Complexity/Foundations/Data/Matrix/IntegerBits.lean create mode 100644 ComplexityTests/Lopsided.lean diff --git a/Complexity.lean b/Complexity.lean index bcc3d2303d..d661ead1c1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1,6 +1,7 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs +public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2084,6 +2085,7 @@ public import Complexity.Foundations.Data.List.OfFn public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary +public import Complexity.Foundations.Data.Matrix.IntegerBits public import Complexity.Foundations.Data.Matrix.Permanent public import Complexity.Foundations.Data.Matrix.Reindex public import Complexity.Foundations.Data.Matrix.TotallyRegular diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean new file mode 100644 index 0000000000..d52e779437 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Lopsided.lean @@ -0,0 +1,265 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Matrix.IntegerBits +public import Mathlib.Data.Fintype.EquivFin +import Mathlib.Tactic.Ring + +/-! +# Finite lopsided triangle queries as matrix products + +Two outer parts have `n` vertices each; the finite middle part embeds into `Fin D`. +The matrices count common neighbors at every outer pair. Only pairs in `W` are graph edges, +so only those counts are triangle counts. Answers outside `W` are unconstrained. + +This mathematical interface gives no native program, preprocessing cost, sparsity bound, +fast solver or subquadratic running time. + +## Main definitions + +* `LopsidedGraph`: two adjacency relations, a finite middle part and queried outer edges. +* `LopsidedGraph.matX`, `LopsidedGraph.matY`: padded biadjacency matrices. +* `LopsidedGraph.signedBitInstance`: four sign choices for each pair of binary digits. + +## Main results + +* `LopsidedGraph.matX_mul_matY_apply`: entries count common middle neighbors. +* `LopsidedGraph.matX_mul_matY_apply_ne_zero_iff`: nonzero entries detect common neighbors. +* `LopsidedGraph.zero_one`: the converse for zero/one matrices. +* `LopsidedGraph.entries_from_counts`: bounded signed products from `4 * β²` counts. + +## References + +Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean) +and the lopsided definitions in [PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L798-L852). +The adaptation uses `Set.EqOn`, pointwise signed parts, and direct finite cardinalities. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +/-- An unweighted tripartite graph whose outer parts each have `n` vertices. -/ +structure LopsidedGraph (n : ℕ) where + /-- The middle vertices. -/ + M : Type + /-- The middle part is finite. -/ + [fintypeM : Fintype M] + /-- Edges from the first outer part to the middle. -/ + adjA : Fin n → M → Prop + /-- Edges from the middle to the second outer part. -/ + adjB : M → Fin n → Prop + /-- Queried outer edges. -/ + W : Finset (Fin n × Fin n) + +attribute [instance] LopsidedGraph.fintypeM + +namespace LopsidedGraph + +/-- On a pair in `W`, a common middle neighbor closes a triangle. +For a pair outside `W`, this predicate does not assert an outer edge. -/ +def HasCommonNeighbor {n : ℕ} (I : LopsidedGraph n) (a b : Fin n) : Prop := + ∃ v : I.M, I.adjA a v ∧ I.adjB v b + +open Classical in +/-- Number of common middle neighbors. At pairs in `W` this is the triangle count. -/ +noncomputable def commonNeighborCount {n : ℕ} (I : LopsidedGraph n) (a b : Fin n) : ℕ := + (Finset.univ.filter fun v : I.M => I.adjA a v ∧ I.adjB v b).card + +/-- Two thin matrices describe adjacency where their entries equal one. +The counting theorem `zero_one` requires all entries to be zero or one. -/ +def ofMatrices {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) : LopsidedGraph n where + M := Fin D + adjA a v := X a v = 1 + adjB v b := Y v b = 1 + W := W + +open Classical in +/-- The count for a matrix-defined graph uses the ordinary finite middle indices. -/ +theorem commonNeighborCount_ofMatrices {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (a b : Fin n) : + (ofMatrices X Y W).commonNeighborCount a b = + (Finset.univ.filter fun v : Fin D => X a v = 1 ∧ Y v b = 1).card := by + unfold ofMatrices commonNeighborCount + congr 1 + ext v + simp + +open Classical in +/-- The first biadjacency matrix, with zero columns outside the injection range. -/ +noncomputable def matX {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) : + Matrix (Fin n) (Fin D) ℤ := + Matrix.of fun a j => if ∃ v : I.M, e v = j ∧ I.adjA a v then 1 else 0 + +open Classical in +/-- The second biadjacency matrix, with zero rows outside the injection range. -/ +noncomputable def matY {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) : + Matrix (Fin D) (Fin n) ℤ := + Matrix.of fun j b => if ∃ v : I.M, e v = j ∧ I.adjB v b then 1 else 0 + +variable {n D : ℕ} (I : LopsidedGraph n) (e : I.M ↪ Fin D) + +/-- Every first biadjacency entry is zero or one. -/ +theorem matX_eq_zero_or_one (a : Fin n) (j : Fin D) : + I.matX e a j = 0 ∨ I.matX e a j = 1 := by + classical + simp only [matX, Matrix.of_apply] + split_ifs <;> simp + +/-- Every second biadjacency entry is zero or one. -/ +theorem matY_eq_zero_or_one (j : Fin D) (b : Fin n) : + I.matY e j b = 0 ∨ I.matY e j b = 1 := by + classical + simp only [matY, Matrix.of_apply] + split_ifs <;> simp + +/-- A middle part of cardinality at most `D` admits the required numbering. -/ +theorem nonempty_embedding (h : Fintype.card I.M ≤ D) : Nonempty (I.M ↪ Fin D) := + Function.Embedding.nonempty_of_card_le (by simpa using h) + +open Classical in +/-- At a middle vertex, the first matrix records its adjacency. -/ +theorem matX_apply_emb (a : Fin n) (v : I.M) : + I.matX e a (e v) = if I.adjA a v then 1 else 0 := by + simp [matX] + +open Classical in +/-- At a middle vertex, the second matrix records its adjacency. -/ +theorem matY_apply_emb (v : I.M) (b : Fin n) : + I.matY e (e v) b = if I.adjB v b then 1 else 0 := by + simp [matY] + +/-- An unused column is zero. -/ +theorem matX_apply_of_notMem_range (a : Fin n) {j : Fin D} (hj : j ∉ Set.range e) : + I.matX e a j = 0 := + ite_eq_right fun ⟨v, hv, _⟩ => hj ⟨v, hv⟩ + +/-- An unused row is zero. -/ +theorem matY_apply_of_notMem_range {j : Fin D} (hj : j ∉ Set.range e) (b : Fin n) : + I.matY e j b = 0 := + ite_eq_right fun ⟨v, hv, _⟩ => hj ⟨v, hv⟩ + +/-- A pair has a common neighbor if and only if its count is nonzero. -/ +theorem commonNeighborCount_ne_zero_iff (a b : Fin n) : + I.commonNeighborCount a b ≠ 0 ↔ I.HasCommonNeighbor a b := by + classical + rw [commonNeighborCount, Finset.card_ne_zero, Finset.filter_nonempty_iff] + simp [HasCommonNeighbor] + +/-- Every product entry counts common middle neighbors, independently of the numbering. -/ +theorem matX_mul_matY_apply (a b : Fin n) : + (I.matX e * I.matY e) a b = (I.commonNeighborCount a b : ℤ) := by + classical + rw [Matrix.mul_apply, commonNeighborCount, Finset.card_filter, Nat.cast_sum] + -- Only the columns of the middle vertices matter. + rw [← Finset.sum_subset (Finset.subset_univ (Finset.univ.map e)) fun j _ hj => by + rw [I.matX_apply_of_notMem_range e a (by simpa using hj), zero_mul], Finset.sum_map] + refine Finset.sum_congr rfl fun v _ => ?_ + rw [I.matX_apply_emb, I.matY_apply_emb] + by_cases hA : I.adjA a v <;> by_cases hB : I.adjB v b <;> simp [hA, hB] + +/-- A nonzero product entry detects a common middle neighbor. -/ +theorem matX_mul_matY_apply_ne_zero_iff (a b : Fin n) : + (I.matX e * I.matY e) a b ≠ 0 ↔ I.HasCommonNeighbor a b := by + rw [matX_mul_matY_apply, Ne, Nat.cast_eq_zero] + exact I.commonNeighborCount_ne_zero_iff a b + +/-- On queried outer edges, correct natural counts are exactly the integer product entries. +Ordinary `Set.EqOn` leaves all unqueried entries unconstrained. -/ +theorem counting_eqOn_iff (ans : Fin n × Fin n → ℕ) : + Set.EqOn ans (fun q => I.commonNeighborCount q.1 q.2) (I.W : Set _) ↔ + Set.EqOn (fun q => (ans q : ℤ)) (fun q => (I.matX e * I.matY e) q.1 q.2) + (I.W : Set _) := by + simp only [Set.EqOn, matX_mul_matY_apply, Nat.cast_inj] + +/-- Counts on queried edges provide their detection answers. -/ +theorem detection_eqOn_of_counting (ans : Fin n × Fin n → ℕ) + (h : Set.EqOn ans (fun q => I.commonNeighborCount q.1 q.2) (I.W : Set _)) : + Set.EqOn (fun q => ans q ≠ 0) (fun q => I.HasCommonNeighbor q.1 q.2) (I.W : Set _) := by + intro q hq + change (ans q ≠ 0) = I.HasCommonNeighbor q.1 q.2 + apply propext + rw [h hq, commonNeighborCount_ne_zero_iff] + +/-- For zero/one matrices, each product entry is the count in the graph they describe. -/ +theorem zero_one {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (hX : ∀ a j, X a j = 0 ∨ X a j = 1) + (hY : ∀ j b, Y j b = 0 ∨ Y j b = 1) (a b : Fin n) : + (X * Y) a b = ((ofMatrices X Y W).commonNeighborCount a b : ℤ) := by + classical + rw [Matrix.mul_apply, commonNeighborCount, Finset.card_filter, Nat.cast_sum] + refine Finset.sum_congr rfl fun j _ => ?_ + rcases hX a j with hx | hx <;> rcases hY j b with hy | hy <;> + simp [ofMatrices, hx, hy] + +/-- One of four sign choices for a pair of binary digits; `true` selects the negative part. +Every instance has exactly `D` middle vertices and the original queried edges `W`. -/ +def signedBitInstance (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (negativeX negativeY : Bool) (i j : ℕ) : LopsidedGraph n := + ofMatrices (IntMatrix.bit (if negativeX then (X.map negPart) else (X.map posPart)) i) + (IntMatrix.bit (if negativeY then (Y.map negPart) else (Y.map posPart)) j) W + +/-- The finite family of sign/digit instances for a magnitude width `β`. +Different slots may describe the same graph. -/ +def signedBitInstances (X : Matrix (Fin n) (Fin D) ℤ) (Y : Matrix (Fin D) (Fin n) ℤ) + (W : Finset (Fin n × Fin n)) (β : ℕ) : + (Bool × Bool × Fin β × Fin β) → LopsidedGraph n := + fun q => signedBitInstance X Y W q.1 q.2.1 q.2.2.1.val q.2.2.2.val + +/-- The indexed family has `4 * β²` slots, allowing repeated or zero instances. -/ +theorem card_signedBit_indices (β : ℕ) : + Fintype.card (Bool × Bool × Fin β × Fin β) = 4 * β ^ 2 := by + simp [pow_two, Nat.mul_assoc, Nat.mul_left_comm, Nat.mul_comm] + +/-- Each nonnegative product entry is a weighted sum of digit-graph counts. -/ +private theorem mul_apply_eq_sum_counts {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (β : ℕ) + (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) (hY : ∀ j b, 0 ≤ Y j b ∧ Y j b < 2 ^ β) (a b : Fin n) : + (X * Y) a b = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, (2 : ℤ) ^ (i + j) * + ((ofMatrices (IntMatrix.bit X i) (IntMatrix.bit Y j) W).commonNeighborCount a b : ℤ) := by + rw [IntMatrix.product X Y β hX hY, Matrix.sum_apply] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Matrix.sum_apply] + refine Finset.sum_congr rfl fun j _ => ?_ + rw [Matrix.smul_apply, smul_eq_mul, zero_one _ _ W (IntMatrix.bit_eq_zero_or_one X i) + (IntMatrix.bit_eq_zero_or_one Y j)] + +/-- Signed bounded product entries are weighted sums of four count instances per digit +pair, with signs `+, −, −, +`. Only counts at the same queried edge are needed. The absolute +bounds are strict; `β = 0` therefore permits only zero entries. -/ +theorem entries_from_counts {n D : ℕ} (X : Matrix (Fin n) (Fin D) ℤ) + (Y : Matrix (Fin D) (Fin n) ℤ) (W : Finset (Fin n × Fin n)) (β : ℕ) + (hX : ∀ a j, |X a j| < 2 ^ β) (hY : ∀ j b, |Y j b| < 2 ^ β) (a b : Fin n) : + (X * Y) a b = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, (2 : ℤ) ^ (i + j) * + (((signedBitInstance X Y W false false i j).commonNeighborCount + a b : ℤ) + - ((signedBitInstance X Y W false true i j).commonNeighborCount + a b : ℤ) + - ((signedBitInstance X Y W true false i j).commonNeighborCount + a b : ℤ) + + ((signedBitInstance X Y W true true i j).commonNeighborCount + a b : ℤ)) := by + simp only [signedBitInstance, Bool.false_eq_true, ↓reduceIte] + obtain ⟨hXpos, hXneg⟩ := IntMatrix.parts_lt X hX + obtain ⟨hYpos, hYneg⟩ := IntMatrix.parts_lt Y hY + -- `XY` is a signed sum of the four products of a part of `X` and a part of `Y`. + conv_lhs => rw [IntMatrix.parts X, IntMatrix.parts Y] + rw [Matrix.sub_mul, Matrix.mul_sub, Matrix.mul_sub] + simp only [Matrix.sub_apply] + rw [mul_apply_eq_sum_counts _ _ W β hXpos hYpos, + mul_apply_eq_sum_counts _ _ W β hXpos hYneg, + mul_apply_eq_sum_counts _ _ W β hXneg hYpos, + mul_apply_eq_sum_counts _ _ W β hXneg hYneg] + simp only [mul_sub, mul_add, Finset.sum_sub_distrib, Finset.sum_add_distrib] + ring + +end LopsidedGraph + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/Data/Matrix/IntegerBits.lean b/Complexity/Foundations/Data/Matrix/IntegerBits.lean new file mode 100644 index 0000000000..5083b77db3 --- /dev/null +++ b/Complexity/Foundations/Data/Matrix/IntegerBits.lean @@ -0,0 +1,112 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Group.PosPart +public import Mathlib.Data.Matrix.Basic +public import Mathlib.Data.Nat.Log +import Mathlib.Tactic.Positivity +import Mathlib.Tactic.Ring + +/-! +# Binary digits of integer matrices + +Finite binary expansions and their products, using Mathlib's pointwise positive and negative +parts. These are algebraic identities, with no algorithm or running-time assertion. + +## Main definitions + +* `IntMatrix.bit`: the binary digit matrix of a given weight. + +## Main results + +* `IntMatrix.binary`: expansion of a nonnegative bounded matrix. +* `IntMatrix.product`: expansion of a product into products of digit matrices. +* `IntMatrix.abs_lt_two_pow_log`: an explicit integer bit bound, including zero. + +## References + +Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean), +`matBit` and `Footnote8.binary/product`. General index types and the integer bit bound are original +adaptation code. +-/ + +@[expose] public section + +namespace Cslib.IntMatrix + +/-- The binary digit of weight `2^i`, entry by entry. For negative entries this is the +Euclidean remainder of the quotient; reconstruction below assumes nonnegative entries. -/ +def bit {m n : Type*} (X : Matrix m n ℤ) (i : ℕ) : Matrix m n ℤ := + Matrix.of fun a j => X a j / 2 ^ i % 2 + +/-- `x mod 2^β` is the sum of the last `β` binary digits of `x`, with their weights. -/ +private theorem emod_two_pow_eq_sum_digits (x : ℤ) (β : ℕ) : + x % 2 ^ β = ∑ i ∈ Finset.range β, 2 ^ i * (x / 2 ^ i % 2) := by + induction β with + | zero => simp + | succ β ih => + have h : x / (2 ^ β * 2) = x / 2 ^ β / 2 := (Int.ediv_ediv_of_nonneg (by positivity)).symm + rw [Finset.sum_range_succ, ← ih, pow_succ, Int.emod_def, Int.emod_def x (2 ^ β), + Int.emod_def (x / 2 ^ β), h] + ring + +/-- A matrix with entries in `[0, 2^β)` is the weighted sum of its first `β` digit matrices. -/ +theorem binary {m n : Type*} (X : Matrix m n ℤ) (β : ℕ) + (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) : + X = ∑ i ∈ Finset.range β, (2 : ℤ) ^ i • bit X i := by + ext a j + rw [Matrix.sum_apply] + simp only [Matrix.smul_apply, bit, Matrix.of_apply, smul_eq_mul] + rw [← emod_two_pow_eq_sum_digits, Int.emod_eq_of_lt (hX a j).1 (hX a j).2] + +/-- Every digit matrix has entries zero or one, even for signed input. -/ +theorem bit_eq_zero_or_one {m n : Type*} (X : Matrix m n ℤ) (i : ℕ) (a : m) + (j : n) : bit X i a j = 0 ∨ bit X i a j = 1 := by + simp only [bit, Matrix.of_apply] + omega + +/-- Expand a product of nonnegative bounded matrices into products of binary digits. -/ +theorem product {m d n : Type*} [Fintype d] (X : Matrix m d ℤ) (Y : Matrix d n ℤ) + (β : ℕ) (hX : ∀ a j, 0 ≤ X a j ∧ X a j < 2 ^ β) (hY : ∀ j b, 0 ≤ Y j b ∧ Y j b < 2 ^ β) : + X * Y = ∑ i ∈ Finset.range β, ∑ j ∈ Finset.range β, + (2 : ℤ) ^ (i + j) • (bit X i * bit Y j) := by + conv_lhs => rw [binary X β hX, binary Y β hY] + rw [Matrix.sum_mul] + refine Finset.sum_congr rfl fun i _ => ?_ + rw [Matrix.mul_sum] + refine Finset.sum_congr rfl fun j _ => ?_ + rw [Matrix.smul_mul, Matrix.mul_smul, smul_smul, pow_add] + +/-- Entrywise positive and negative parts reconstruct an integer matrix. +`Matrix.map` reuses scalar signed parts without adding an ordering instance to matrices. -/ +theorem parts {m n : Type*} (X : Matrix m n ℤ) : + X = X.map posPart - X.map negPart := by + ext a j + exact (posPart_sub_negPart (X a j)).symm + +/-- Both pointwise signed parts of an absolutely bounded matrix satisfy the same nonnegative +bound. This uses the existing ordered-group operations, without introducing matrix variants. -/ +theorem parts_lt {m n : Type*} (X : Matrix m n ℤ) {β : ℕ} + (hX : ∀ a j, |X a j| < 2 ^ β) : + (∀ a j, 0 ≤ (X.map posPart) a j ∧ (X.map posPart) a j < 2 ^ β) ∧ + ∀ a j, 0 ≤ (X.map negPart) a j ∧ (X.map negPart) a j < 2 ^ β := by + have hpow : (0 : ℤ) < 2 ^ β := by positivity + exact ⟨fun a j => ⟨le_max_right _ _, max_lt ((le_abs_self _).trans_lt (hX a j)) hpow⟩, + fun a j => ⟨le_max_right _ _, max_lt ((neg_le_abs _).trans_lt (hX a j)) hpow⟩⟩ + +/-- An integer bounded in magnitude by `U` needs at most `log₂ U + 1` magnitude bits. +This also holds for `U = 0`; no real logarithms or asymptotic claim are involved. -/ +theorem abs_lt_two_pow_log {U : ℕ} (x : ℤ) (hx : |x| ≤ U) : + |x| < 2 ^ (Nat.log 2 U + 1) := by + have h := Nat.lt_pow_succ_log_self (by decide : 1 < 2) U + have hcast : (U : ℤ) < 2 ^ (Nat.log 2 U + 1) := by exact_mod_cast h + exact hx.trans_lt hcast + +end Cslib.IntMatrix diff --git a/ComplexityTests/Lopsided.lean b/ComplexityTests/Lopsided.lean new file mode 100644 index 0000000000..737cbf386a --- /dev/null +++ b/ComplexityTests/Lopsided.lean @@ -0,0 +1,163 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Lopsided +import Mathlib.LinearAlgebra.Matrix.Notation + +/-! Concrete tests for padding, counts, queried answers and signed reconstruction. -/ + +open Cslib.Algorithms.ExactTriangle +open LopsidedGraph + +private abbrev twoNeighbors : LopsidedGraph 2 where + M := Fin 2 + adjA a _ := a = 0 + adjB _ b := b = 1 + W := {(0, 1)} + +private def padded : Fin 2 ↪ Fin 3 := ⟨fun v => ⟨v.val, by omega⟩, + fun _ _ h => Fin.ext (show _ from congrArg (fun v : Fin 3 => v.val) h)⟩ + +example : twoNeighbors.commonNeighborCount 0 1 = 2 := by + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +example : twoNeighbors.HasCommonNeighbor 0 1 := ⟨0, rfl, rfl⟩ + +example : ¬twoNeighbors.HasCommonNeighbor 1 1 := by + simp [HasCommonNeighbor, twoNeighbors] + +example : twoNeighbors.matX padded 0 2 = 0 ∧ twoNeighbors.matY padded 2 1 = 0 := by + have h : (2 : Fin 3) ∉ Set.range padded := by + rintro ⟨v, hv⟩ + have hv' : v.val = 2 := congrArg (fun w : Fin 3 => w.val) hv + have := v.isLt + omega + exact ⟨matX_apply_of_notMem_range _ _ _ h, matY_apply_of_notMem_range _ _ h _⟩ + +example : (twoNeighbors.matX padded * twoNeighbors.matY padded) 0 1 = 2 := by + rw [matX_mul_matY_apply] + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +-- Values away from the sole query are deliberately incorrect and still allowed. +example : Set.EqOn (fun q : Fin 2 × Fin 2 => if q = (0, 1) then 2 else 99) + (fun q => twoNeighbors.commonNeighborCount q.1 q.2) (twoNeighbors.W : Set _) := by + intro q hq + have h : q = (0, 1) := by simpa [twoNeighbors] using hq + subst q + norm_num [commonNeighborCount, twoNeighbors, Finset.univ_fin2] + rfl + +private abbrev emptyMiddle : LopsidedGraph 1 where + M := Fin 0 + adjA _ _ := True + adjB _ _ := True + W := {(0, 0)} + +example : emptyMiddle.commonNeighborCount 0 0 = 0 := by + simp [commonNeighborCount, emptyMiddle] + +example : ¬emptyMiddle.HasCommonNeighbor 0 0 := by + simp [HasCommonNeighbor, emptyMiddle] + +example : (emptyMiddle.matX (Function.Embedding.refl _) * + emptyMiddle.matY (Function.Embedding.refl _)) 0 0 = 0 := by + rw [matX_mul_matY_apply] + simp [commonNeighborCount, emptyMiddle] + +private def signedX : Matrix (Fin 1) (Fin 2) ℤ := !![-3, 2] +private def signedY : Matrix (Fin 2) (Fin 1) ℤ := !![2; -1] + +example : (signedX * signedY) 0 0 = -8 := by + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : (signedBitInstance signedX signedY {(0, 0)} true false 0 1).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +example : (signedBitInstance signedX signedY {(0, 0)} false true 1 0).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +-- Apply the signed reconstruction theorem to an actual product, including all four signs. +example : (∑ i ∈ Finset.range 2, ∑ j ∈ Finset.range 2, (2 : ℤ) ^ (i + j) * + (((signedBitInstance signedX signedY {(0, 0)} false false i j).commonNeighborCount 0 0 : ℤ) + - ((signedBitInstance signedX signedY {(0, 0)} false true i j).commonNeighborCount 0 0 : ℤ) + - ((signedBitInstance signedX signedY {(0, 0)} true false i j).commonNeighborCount 0 0 : ℤ) + + ((signedBitInstance signedX signedY {(0, 0)} true true i j).commonNeighborCount 0 0 : ℤ))) + = -8 := by + rw [← entries_from_counts signedX signedY {(0, 0)} 2 + (by intro a j; fin_cases a; fin_cases j <;> norm_num [signedX]) + (by intro j b; fin_cases j <;> fin_cases b <;> norm_num [signedY])] + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : Cslib.IntMatrix.bit (!![-1, 0, 3] : Matrix (Fin 1) (Fin 3) ℤ) 1 0 0 = 1 := by + norm_num [Cslib.IntMatrix.bit] + +example : |(-7 : ℤ)| < 2 ^ (Nat.log 2 7 + 1) := by + exact Cslib.IntMatrix.abs_lt_two_pow_log (-7) (by norm_num) + +example : |(0 : ℤ)| < 2 ^ (Nat.log 2 0 + 1) := by + exact Cslib.IntMatrix.abs_lt_two_pow_log 0 (by norm_num) + +example : (signedX * (-signedY)) 0 0 = 8 := by + norm_num [signedX, signedY, Matrix.mul_apply, Fin.sum_univ_two] + +example : (ofMatrices (!![1, 1] : Matrix (Fin 1) (Fin 2) ℤ) !![1; 1] + {(0, 0)}).commonNeighborCount 0 0 = 2 := by + have h := zero_one (!![1, 1] : Matrix (Fin 1) (Fin 2) ℤ) !![1; 1] {(0, 0)} + (by intro a j; fin_cases a; fin_cases j <;> simp) + (by intro j b; fin_cases j <;> fin_cases b <;> simp) 0 0 + norm_num [Matrix.mul_apply, Fin.sum_univ_two] at h + exact_mod_cast h.symm + +example : (signedBitInstance signedX (-signedY) {(0, 0)} true true 0 1).commonNeighborCount + 0 0 = 1 := by + classical + simp only [signedBitInstance, commonNeighborCount_ofMatrices, ↓reduceIte] + norm_num [signedX, signedY, + Cslib.IntMatrix.bit, posPart_def, negPart_def, Finset.univ_fin2, + Finset.filter_insert, Finset.filter_singleton] + +example : (signedBitInstances signedX signedY {(0, 0)} 2 (true, false, 0, 1)).W = + {(0, 0)} := rfl + +example : Fintype.card (Bool × Bool × Fin 2 × Fin 2) = 16 := by decide + +-- Empty outer parts with a genuinely nonempty middle part still give an empty product. +example : (0 : Matrix (Fin 0) (Fin 3) ℤ) * (0 : Matrix (Fin 3) (Fin 0) ℤ) = 0 := by + ext a b + exact Fin.elim0 a + +example : Set.EqOn (fun _ : Fin 0 × Fin 0 => (99 : ℕ)) + (fun q => + (ofMatrices (0 : Matrix (Fin 0) (Fin 3) ℤ) 0 ∅).commonNeighborCount q.1 q.2) + (∅ : Set (Fin 0 × Fin 0)) := by + simp + +-- With beta zero, strict magnitude bounds are satisfied by zero matrices of nonempty shape. +example : ((0 : Matrix (Fin 1) (Fin 2) ℤ) * (0 : Matrix (Fin 2) (Fin 1) ℤ)) 0 0 = 0 := by + have h := entries_from_counts (0 : Matrix (Fin 1) (Fin 2) ℤ) 0 {(0, 0)} 0 + (by simp) (by simp) 0 0 + simpa only [Finset.range_zero, Finset.sum_empty] using h + +-- A queried edge with disjoint middle neighborhoods is a negative triangle query. +example : let I := ofMatrices (!![1, 0] : Matrix (Fin 1) (Fin 2) ℤ) !![0; 1] {(0, 0)} + (0, 0) ∈ I.W ∧ I.commonNeighborCount 0 0 = 0 := by + dsimp only + constructor + · simp [ofMatrices] + · rw [commonNeighborCount_ofMatrices] + norm_num [Finset.univ_fin2, Finset.filter_insert, Finset.filter_singleton] diff --git a/NOTICE b/NOTICE index 81584c2411..3ab01a2df3 100644 --- a/NOTICE +++ b/NOTICE @@ -1,8 +1,9 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/Defs.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Lopsided}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Foundations/Data/Finset/Average.lean and +Complexity/Foundations/Data/Finset/Average.lean, +Complexity/Foundations/Data/Matrix/IntegerBits.lean and Complexity/Foundations/NumberTheory/PrimeCounting.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. diff --git a/ROADMAP.md b/ROADMAP.md index 8d53f5250f..5b3b230013 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -421,8 +421,10 @@ each statement against the numbered result. Organize modules by subject, not cha transfer of Anthropic's modular machine and 3SUM/APSP algorithms; direct 3SUM adaptation ([#1394]) has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including - ordinary distinct-position inputs and explicit size bounds. It still needs native Chan–He - preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ + ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting + interface ([#1438]) adds padding, detection and signed digit reconstruction; it still needs + modular Exact Triangle hashing/false-positive splitting, tensor/sparse-query programs, native + Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded almost-surely terminating structured loops; random-tape equivalence and BPP/RP/ZPP simulation @@ -546,6 +548,6 @@ not planned, not as proved. [#1155]: https://github.com/cslib-community/complexitylib/issues/1155 [#1351]: https://github.com/cslib-community/complexitylib/issues/1351 [#1394]: https://github.com/cslib-community/complexitylib/issues/1394 - [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 +[#1438]: https://github.com/cslib-community/complexitylib/issues/1438 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index f465fed043..87d9cb5288 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -165,6 +165,17 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he length, count and magnitude bounds; `scanPairs` counts candidate-prime/element pairs and is not a charged running time. Generic finite-average and prime-counting lemmas live in Foundations. See milestone [#1435](https://github.com/cslib-community/complexitylib/issues/1435). + `Algorithms/ExactTriangle/Lopsided.lean` adapts the finite unweighted lopsided query/counting + interface from the same pin's `Sec3/MatrixLanguage.lean`: arbitrary-injection zero padding, + common-neighbor counts as product entries, detection, the zero/one converse, and signed products + from `4 * β²` count instances with the original queried edges. Its digit identities live in + `Foundations/Data/Matrix/IntegerBits.lean`, reuse Mathlib's pointwise positive/negative parts, + and give an explicit `Nat.log 2 U + 1` magnitude-bit bound including zero. Query answers use + `Set.EqOn`; no additional weighted-triangle predicate or source machine is introduced. + This is the mathematical interface milestone + [#1438](https://github.com/cslib-community/complexitylib/issues/1438), with no sparsity assumption, + preprocessing/time claim or solver. Modular hashing/false-positive splitting and tensor/sparse + query programs remain in #1394, alongside the native and asymptotic composition below. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full input/time/width/asymptotic composition remain in From dec963a5dd581ae8acecb846e7b87b1bf7e5c78e Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:02:25 -0400 Subject: [PATCH 02/20] feat(exactTriangle): prove modular counts and the selected-prime bound --- Complexity.lean | 4 + .../Algorithms/ExactTriangle/Hashing.lean | 482 ++++++++++++++++++ .../Algorithms/ExactTriangle/Weights.lean | 61 +++ .../Foundations/NumberTheory/PrimeWindow.lean | 346 +++++++++++++ .../Foundations/RingTheory/CyclicIntRing.lean | 84 +++ ComplexityTests/ExactTriangleHashing.lean | 115 +++++ NOTICE | 9 +- ROADMAP.md | 6 +- UPSTREAM.md | 12 +- 9 files changed, 1113 insertions(+), 6 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Hashing.lean create mode 100644 Complexity/Algorithms/ExactTriangle/Weights.lean create mode 100644 Complexity/Foundations/NumberTheory/PrimeWindow.lean create mode 100644 Complexity/Foundations/RingTheory/CyclicIntRing.lean create mode 100644 ComplexityTests/ExactTriangleHashing.lean diff --git a/Complexity.lean b/Complexity.lean index d661ead1c1..e6ac945545 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -1,7 +1,9 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs +public import Complexity.Algorithms.ExactTriangle.Hashing public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2190,6 +2192,7 @@ public import Complexity.Foundations.NumberTheory.AKS.Introspective public import Complexity.Foundations.NumberTheory.AKS.SmallR public import Complexity.Foundations.NumberTheory.Harmonic public import Complexity.Foundations.NumberTheory.PrimeCounting +public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.Order.BooleanSubalgebra public import Complexity.Foundations.Order.Changes public import Complexity.Foundations.Order.UpperLower @@ -2200,6 +2203,7 @@ public import Complexity.Foundations.Probability.SecondMoment public import Complexity.Foundations.Quantum.Measurement public import Complexity.Foundations.Relation.InductiveCounting public import Complexity.Foundations.Relation.RelatesWithinSteps +public import Complexity.Foundations.RingTheory.CyclicIntRing public import Complexity.Foundations.Semantics.Stateful.Amortized public import Complexity.Foundations.Semantics.Stateful.Basic public import Complexity.Foundations.Semantics.Stateful.Composition diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean new file mode 100644 index 0000000000..6799e79fc7 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -0,0 +1,482 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Foundations.NumberTheory.PrimeWindow +public import Complexity.Foundations.RingTheory.CyclicIntRing +public import Mathlib.Data.Matrix.Mul + +/-! +# Modular counts and prime selection for integer Exact Triangle + +Let `S(a,b,c)` denote `weightSum`, `F(p)` the false-positive count and `Z₀` the zero count. A +minimum-count prime in `[sqrt D / 2, sqrt D)` also minimises false positives. For weights +bounded by `n^κ`, with `16 ≤ D ≤ n` and `1 ≤ κ`, a universal constant bounds its false positives +by `C * κ * n^3 * log n / sqrt D`. The prime count used here is proved for every admitted `D`. + +## Main definitions + +* `countZeroMod`, `falsePositiveCount`, `zeroCount`: counts of finite triples. +* `IsSelectedPrime`: a minimum-count prime in the specified window. +* `matP`, `matQ`: cyclic polynomial matrices encoding edge residues. + +## Main results + +* `countZeroMod_eq`: modular count is the sum of false positives and true zeros. +* `coeff_matP_mul_matQ`, `falsePositiveCount_add_zeroCount_eq_sum_coeff`: coefficient + identities. +* `exists_isSelectedPrime`: the minimum exists. +* `Hashing.falsePositiveCount_le_const_mul`: the uniform false-positive bound. + +This is a mathematical reduction step. No native program or charged running-time bound is +asserted. The supplied three matrices retain the existing `HasZeroTriangle` semantics. + +## References + +Direct adaptation of [Anthropic's +Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), +with the minimal count/selection definitions extracted from its `PaperStatements.lean`. +Source-derived prose below is paraphrased. The source pin is +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Classical in +/-- Primes in the half-open window `[sqrt D / 2, sqrt D)`. -/ +noncomputable def primesInRange (D : ℕ) : Finset ℕ := + (Finset.range D).filter fun p => p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D + +/-- Number of triangles whose sum vanishes modulo `p`. -/ +def countZeroMod {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (p : ℕ) : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => + weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≡ 0 [ZMOD (p : ℤ)]).card + +/-- A window prime minimises the modular count over all window primes. -/ +def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ) : Prop := + p ∈ primesInRange D ∧ ∀ q ∈ primesInRange D, + countZeroMod wAB wBC wAC p ≤ countZeroMod wAB wBC wAC q + +private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + +private theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : + ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by + have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) + have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) + omega + +/-! +### The primes in the range -/ + +/-- Proof of Theorem 17, a prime p ∈ [√D/2, √D): the primes in the range are the primes `p` with +`√D/2 ≤ p < √D`. (The bound `p < D` in the definition excludes none of them.) +-/ +theorem mem_primesInRange {D p : ℕ} : + p ∈ primesInRange D ↔ p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D := by + unfold primesInRange + rw [Finset.mem_filter, Finset.mem_range] + refine ⟨fun h => h.2, fun h => ⟨?_, h⟩⟩ + -- From `p < √D` we get `p ≤ p² < D`. + have hsq : p ^ 2 < D := by exact_mod_cast (Real.lt_sqrt (Nat.cast_nonneg p)).1 h.2.2 + exact (Nat.le_self_pow two_ne_zero p).trans_lt hsq + +/-- Window membership is an exact integer-square test, suitable for native enumeration. -/ +theorem mem_primesInRange_iff_sq {D p : ℕ} : + p ∈ primesInRange D ↔ p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + rw [mem_primesInRange] + have hp0 : (0 : ℝ) ≤ p := Nat.cast_nonneg p + have hsqrt0 : 0 ≤ Real.sqrt (D : ℝ) := Real.sqrt_nonneg _ + have hsqrt : (Real.sqrt (D : ℝ)) ^ 2 = D := Real.sq_sqrt (Nat.cast_nonneg D) + constructor + · rintro ⟨hp, hlo, hhi⟩ + refine ⟨hp, ?_, ?_⟩ + · have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by nlinarith + exact_mod_cast h + · exact_mod_cast (Real.lt_sqrt hp0).1 hhi + · rintro ⟨hp, hlo, hhi⟩ + refine ⟨hp, ?_, (Real.lt_sqrt hp0).2 (by exact_mod_cast hhi)⟩ + have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by exact_mod_cast hlo + nlinarith + +/-- A computable finite enumeration of exactly the same half-open prime window. -/ +theorem primesInRange_eq_filter (D : ℕ) : + primesInRange D = (Finset.range D).filter fun p => p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by + ext p + rw [mem_primesInRange_iff_sq, Finset.mem_filter, Finset.mem_range] + exact ⟨fun h => ⟨(Nat.le_self_pow two_ne_zero p).trans_lt h.2.2, h⟩, fun h => h.2⟩ + +/-- A number `r < p` is the sum of the residues of `u` and `v` in `{0, …, p − 1}`, reduced modulo +`p`, exactly if `u + v ≡ r`. +-/ +private theorem eq_add_toNat_emod_iff {p r : ℕ} (hp : p ≠ 0) (hr : r < p) (u v : ℤ) : + r = ((u % (p : ℤ)).toNat + (v % (p : ℤ)).toNat) % p ↔ u + v ≡ (r : ℤ) [ZMOD (p : ℤ)] := by + rw [← Int.natCast_inj, Int.ModEq, Int.emod_eq_of_lt (Int.natCast_nonneg r) (by exact_mod_cast hr), + eq_comm] + push_cast + rw [natCast_toNat_emod (Nat.pos_of_ne_zero hp), + natCast_toNat_emod (Nat.pos_of_ne_zero hp), + ← Int.add_emod] + +/-! +### Counting the triples with `S(a,b,c) ≡ 0 (mod p)` -/ + +section Counting + +variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- A false positive of p is a triple (a,b,c) with S(a,b,c) ≠ 0 and p ∣ S(a,b,c). -/ +abbrev IsFalsePositive (p : ℕ) (t : Fin n × Fin n × Fin n) : Prop := + weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0 ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 + +/-- let falsePositiveCount(p) denote the number of false positives of p. -/ +def falsePositiveCount (p : ℕ) : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => IsFalsePositive wAB wBC wAC p t).card + +/-- zeroCount, the number of zero triangles. -/ +def zeroCount : ℕ := + (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = + 0).card + +/-- A positive zero count is exactly the existing Exact Triangle decision predicate. -/ +theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wBC wAC := by + classical + simp only [zeroCount, Finset.card_pos, HasZeroTriangle] + constructor + · rintro ⟨⟨a, b, c⟩, h⟩ + exact ⟨a, b, c, (Finset.mem_filter.mp h).2⟩ + · rintro ⟨a, b, c, h⟩ + exact ⟨(a, b, c), Finset.mem_filter.mpr ⟨Finset.mem_univ _, h⟩⟩ + +/-- This number is the number of false positives falsePositiveCount(p) plus zeroCount, the number of +zero triangles, which does not depend on p. +-/ +theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiveCount wAB wBC + wAC) p + (zeroCount wAB wBC wAC) := by + classical + simp only [countZeroMod, falsePositiveCount, zeroCount, Finset.card_filter, ← + Finset.sum_add_distrib] + -- A triple with `p ∣ S(a,b,c)` is a zero triangle or a false positive, and not both. + refine Finset.sum_congr rfl fun t _ => ?_ + by_cases h0 : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 <;> + simp [IsFalsePositive, Int.modEq_zero_iff_dvd, h0] + +/-- let P[a,c] := x^{w(a,c) mod p}, a matrix over `ℤ[x]/(x^p − 1)`. `w mod p` is the residue in +`{0, …, p − 1}`. +-/ +noncomputable def matP (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := + Matrix.of fun a c => CyclicIntRing.x p ^ (wAC a c % (p : ℤ)).toNat + +/-- and Q[c,b] := x^{w(b,c) mod p}. -/ +noncomputable def matQ (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := + Matrix.of fun c b => CyclicIntRing.x p ^ (wBC b c % (p : ℤ)).toNat + +/-- the coefficient of x^r in (PQ)[a,b] is the number of c ∈ C with w(a,c) + w(b,c) ≡ r (mod p). -/ +theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr : r < p) : + CyclicIntRing.coeff hp r ((matP wAC p * matQ wBC p) a b) = + ((Finset.univ.filter fun c : Fin n => + wAC a c + wBC b c ≡ (r : ℤ) [ZMOD (p : ℤ)]).card : ℤ) := by + classical + -- `(PQ)[a,b]` is the sum over `c` of `x^(w(a,c) mod p + w(b,c) mod p)`, and the coefficient of + -- `x^r` in each term is 1 or 0. + rw [Matrix.mul_apply] + simp only [matP, matQ, Matrix.of_apply, ← pow_add] + rw [map_sum] + simp only [CyclicIntRing.coeff_x_pow] + rw [Finset.card_filter] + push_cast + refine Finset.sum_congr rfl fun c _ => ?_ + simp only [eq_add_toNat_emod_iff hp hr] + +/-- then falsePositiveCount(p) + zeroCount is the sum over the pairs (a,b) ∈ A × B of the +coefficient of x^{−w(a,b) mod p} in (PQ)[a,b]. +-/ +theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : + (((falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) : ℕ) : ℤ) = + ∑ a : Fin n, ∑ b : Fin n, + CyclicIntRing.coeff hp ((-wAB a b) % (p : ℤ)).toNat ((matP wAC p * matQ wBC p) a b) := by + classical + rw [← countZeroMod_eq] + unfold countZeroMod + rw [Finset.card_filter, Fintype.sum_prod_type] + push_cast + refine Finset.sum_congr rfl fun a _ => ?_ + rw [Fintype.sum_prod_type] + refine Finset.sum_congr rfl fun b _ => ?_ + have hcast := natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + Finset.card_filter] + push_cast + refine Finset.sum_congr rfl fun c _ => ?_ + -- `S(a,b,c) ≡ 0` says the same as `w(a,c) + w(b,c) ≡ -w(a,b)`, and `-w(a,b)` is congruent to its + -- residue. + have hres : (-wAB a b) % (p : ℤ) ≡ -wAB a b [ZMOD (p : ℤ)] := Int.mod_modEq _ _ + have hiff : weightSum wAB wBC wAC a b c ≡ 0 [ZMOD (p : ℤ)] ↔ + wAC a c + wBC b c ≡ ((((-wAB a b) % (p : ℤ)).toNat : ℕ) : ℤ) [ZMOD (p : ℤ)] := by + rw [hcast, show wAC a c + wBC b c ≡ (-wAB a b) % (p : ℤ) [ZMOD (p : ℤ)] ↔ + wAC a c + wBC b c ≡ -wAB a b [ZMOD (p : ℤ)] from + ⟨fun h => h.trans hres, fun h => h.trans hres.symm⟩, + Int.modEq_iff_dvd, Int.modEq_iff_dvd, + show 0 - weightSum wAB wBC wAC a b c = -wAB a b - (wAC a c + wBC b c) by + simp only [weightSum]; ring] + simp only [hiff] + +end Counting + +/-- the fewer than √D primes in the range. -/ +theorem card_primesInRange_lt (D : ℕ) (hD : 16 ≤ D) : + ((primesInRange D).card : ℝ) < Real.sqrt D := by + have hpos : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] + -- The primes in the range are among the numbers `1, …, ⌈√D⌉ - 1`. + have hsub : primesInRange D ⊆ Finset.Ico 1 ⌈Real.sqrt D⌉₊ := by + intro p hp + obtain ⟨hprime, -, hlt⟩ := mem_primesInRange.1 hp + exact Finset.mem_Ico.2 ⟨hprime.one_lt.le, Nat.lt_ceil.2 hlt⟩ + have hcard : (primesInRange D).card + 1 ≤ ⌈Real.sqrt D⌉₊ := by + have hle := Finset.card_le_card hsub + have hone : 1 ≤ ⌈Real.sqrt D⌉₊ := Nat.one_le_ceil_iff.2 hpos + rw [Nat.card_Ico] at hle + omega + have hcast : ((primesInRange D).card : ℝ) + 1 ≤ (⌈Real.sqrt D⌉₊ : ℝ) := by exact_mod_cast hcard + linarith [Nat.ceil_lt_add_one hpos.le] + +/-! +### Selecting the prime -/ + +section Selection + +variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- There is a prime to select. -/ +theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by + obtain ⟨q, hq⟩ := + Nat.exists_prime_half_le_and_lt (Real.sqrt D) (four_le_sqrt hD) + exact Finset.exists_min_image (primesInRange D) (countZeroMod wAB wBC wAC) ⟨q, + mem_primesInRange.2 hq⟩ + +/-- We select the prime with the smallest count, which is also the prime with the fewest false +positives. +-/ +theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : + IsSelectedPrime wAB wBC wAC D p) : + ∀ q ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p ≤ (falsePositiveCount wAB wBC wAC) + q := by + intro q hq + have h := hp.2 q hq + rw [countZeroMod_eq, countZeroMod_eq] at h + omega + +/-! +### The bound on the number of false positives of the selected prime -/ + +open Classical in +/-- a triple with S(a,b,c) ≠ 0 is a false positive exactly of the primes in the range that divide +S(a,b,c). Since 0 < |S(a,b,c)| ≤ 3n^ν and these primes are at least √D/2, there are at most +log_{√D/2}(3n^ν) of them. +-/ +theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (t : Fin n × Fin n × Fin n) (ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0) : + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + have hsqrt := four_le_sqrt hD + set divisors := (primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t + have hmem : ∀ p ∈ divisors, + p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 := by + intro p hp + obtain ⟨hrange, -, hdvd⟩ := Finset.mem_filter.1 hp + obtain ⟨hprime, hge, -⟩ := mem_primesInRange.1 hrange + exact ⟨hprime, hge, hdvd⟩ + -- The product of these primes divides `S(a,b,c)`, so it is at most `|S(a,b,c)| ≤ 3n^κ`. + have hdvd : ∏ p ∈ divisors, p ∣ (weightSum wAB wBC wAC t.1 t.2.1 t.2.2).natAbs := + Finset.prod_primes_dvd _ (fun p hp => (hmem p hp).1.prime) + fun p hp => Int.natCast_dvd.1 (hmem p hp).2.2 + have hprod : ∏ p ∈ divisors, (p : ℝ) ≤ 3 * (n : ℝ) ^ κ := + calc ∏ p ∈ divisors, (p : ℝ) = ((∏ p ∈ divisors, p : ℕ) : ℝ) := (Nat.cast_prod _ _).symm + _ ≤ ((weightSum wAB wBC wAC t.1 t.2.1 t.2.2).natAbs : ℝ) := by + exact_mod_cast Nat.le_of_dvd (Int.natAbs_pos.2 ht) hdvd + _ = ((|weightSum wAB wBC wAC t.1 t.2.1 t.2.2| : ℤ) : ℝ) := Nat.cast_natAbs _ + _ ≤ 3 * (n : ℝ) ^ κ := (abs_weightSum_le wAB wBC wAC) hT _ _ _ + -- Each of these primes is at least `√D/2`. + have hpow : (Real.sqrt D / 2) ^ divisors.card ≤ ∏ p ∈ divisors, (p : ℝ) := by + rw [← Finset.prod_const] + exact Finset.prod_le_prod₀ (fun _ _ => by linarith) fun p hp => (hmem p hp).2.1 + have hY : 0 < 3 * (n : ℝ) ^ κ := lt_of_lt_of_le (by positivity) (hpow.trans hprod) + rw [Real.le_logb_iff_rpow_le (by linarith) hY, Real.rpow_natCast] + exact hpow.trans hprod + +/-- Hence the numbers of false positives of all the primes in the range add up to at most n³ +log_{√D/2}(3n^ν). +-/ +theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC + ((n : ℝ) ^ κ)) : + ((∑ p ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p : ℕ) : ℝ) + ≤ (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + classical + have hsqrt := four_le_sqrt hD + -- Count the pairs (prime, false positive of that prime) triple by triple. + have hswap : ∑ p ∈ primesInRange D, (falsePositiveCount wAB wBC wAC) p = ∑ t : Fin n × Fin n × + Fin n, + ((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card := by + simp only [falsePositiveCount, Finset.card_filter] + exact Finset.sum_comm + have hterm : ∀ t : Fin n × Fin n × Fin n, + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + intro t + by_cases ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 + · -- A zero triangle is a false positive of no prime. + rw [Finset.filter_eq_empty_iff.2 fun p _ h => h.1 ht, Finset.card_empty, Nat.cast_zero] + have hn : (1 : ℝ) ≤ n := by exact_mod_cast Fin.pos t.1 + have hpow : (1 : ℝ) ≤ (n : ℝ) ^ κ := Real.one_le_rpow hn (by linarith) + exact Real.logb_nonneg (by linarith) (by linarith) + · -- Otherwise this is the bound of `card_falsePositive_primes_le` for this triple. + convert (card_falsePositive_primes_le wAB wBC wAC) hD hT t ht + rw [hswap, Nat.cast_sum] + calc ∑ t : Fin n × Fin n × Fin n, + (((primesInRange D).filter fun p => IsFalsePositive wAB wBC wAC p t).card : ℝ) + ≤ ∑ _t : Fin n × Fin n × Fin n, Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := + Finset.sum_le_sum fun t _ => hterm t + _ = (n : ℝ) ^ 3 * Real.logb (Real.sqrt D / 2) (3 * (n : ℝ) ^ κ) := by + rw [Finset.sum_const, Finset.card_univ, Fintype.card_prod, Fintype.card_prod, + Fintype.card_fin, nsmul_eq_mul] + push_cast + ring + +end Selection + +/-- By the prime number theorem there are Ω(√D/log D) primes in the range. + +NOTE. The modular reduction needs this for every `D ≥ 16`, not only for large `D`, and so it is +stated. It follows from a counting form of Bertrand's postulate; the prime number theorem is +not needed. +-/ +theorem exists_le_card_primesInRange : + ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → + c * (Real.sqrt D / Real.log D) ≤ ((primesInRange D).card : ℝ) := by + obtain ⟨c, hc, hcount⟩ := Nat.exists_forall_mul_sqrt_div_log_le_card_primes + exact ⟨c, hc, fun D hD => hcount D hD _ + fun _ hp hge hlt => mem_primesInRange.2 ⟨hp, hge, hlt⟩⟩ + +/-- falsePositiveCount(p) is at most the average over them, multiplied out. -/ +theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wBC wAC : Fin n → + Fin n → ℤ} + (hp : IsSelectedPrime wAB wBC wAC D p) : + (falsePositiveCount wAB wBC wAC) p * (primesInRange D).card ≤ ∑ q ∈ primesInRange D, + (falsePositiveCount wAB wBC wAC) q := by + rw [mul_comm, ← smul_eq_mul] + exact Finset.card_nsmul_le_sum _ _ _ hp.falsePositiveCount_le + +/-- log D = O(log(√D/2)) for D ≥ 16. -/ +theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : + Real.log D ≤ 4 * Real.log (Real.sqrt D / 2) := by + have hD16 : (16 : ℝ) ≤ D := by exact_mod_cast hD + have hsqrt : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] + -- `log(√D/2) = (log D)/2 - log 2`, and `4 log 2 = log 16 ≤ log D`. + have hlog16 : 4 * Real.log 2 ≤ Real.log D := by + have h := Real.log_le_log (by norm_num) (show (2 : ℝ) ^ 4 ≤ D by linarith) + rw [Real.log_pow] at h + exact_mod_cast h + rw [Real.log_div hsqrt.ne' (by norm_num), Real.log_sqrt (by linarith)] + linarith [hlog16] + +/-- falsePositiveCount(p) is at most the average over them, so [...] falsePositiveCount(p) = O(n³ +log(3n^ν)/√D): if there are at least `c √D/log D` primes in the range, then +`falsePositiveCount(p) ≤ (4/c) n³ log(3n^κ)/√D`. +-/ +theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ} + (wAB wBC wAC : Fin n → Fin n → ℤ) (hc : 0 < c) + (hcard : c * (Real.sqrt D / Real.log D) ≤ ((primesInRange D).card : ℝ)) (hD : 16 ≤ D) + (hDn : D ≤ n) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : + IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ 4 / c * ((n : ℝ) ^ 3 * Real.log (3 * (n : ℝ) ^ κ) + / Real.sqrt D) := by + -- The quantities of the paper's sentence: `r = √D`, `Y = 3n^κ`, and the number of primes in the + -- range. + set r := Real.sqrt D + set Y := 3 * (n : ℝ) ^ κ with hY + set numPrimes := (primesInRange D).card + have hr : 4 ≤ r := four_le_sqrt hD + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlogD_pos : 0 < Real.log D := Real.log_pos (by exact_mod_cast (by omega : 1 < D)) + have hlogr_pos : 0 < Real.log (r / 2) := Real.log_pos (by linarith) + have hlogY_nonneg : 0 ≤ Real.log Y := by + have : 1 ≤ (n : ℝ) ^ κ := Real.one_le_rpow (by linarith) (by linarith) + exact Real.log_nonneg (by rw [hY]; linarith) + -- The selected prime is at most average, after multiplying by the number of primes. + -- False positives of all primes in the + -- range add up to at most `n³ log_{r/2} Y`. + have average : ((falsePositiveCount wAB wBC wAC) p : ℝ) * numPrimes ≤ (n : ℝ) ^ 3 * Real.logb + (r / 2) Y := + le_trans (by exact_mod_cast hp.falsePositiveCount_mul_card_le_sum) + ((sum_falsePositiveCount_le wAB wBC wAC) hD hκ hT) + -- The base of the logarithm changes: `log_{r/2} Y ≤ 4 log Y / log D`. + have hlogD_le : Real.log D ≤ 4 * Real.log (r / 2) := log_le_four_mul_log_sqrt_div_two D hD + have changeBase : Real.logb (r / 2) Y ≤ 4 * Real.log Y / Real.log D := by + rw [Real.logb, div_le_div_iff₀ hlogr_pos hlogD_pos] + linarith [mul_le_mul_of_nonneg_left hlogD_le hlogY_nonneg] + rw [show 4 / c * ((n : ℝ) ^ 3 * Real.log Y / r) = 4 * (n : ℝ) ^ 3 * Real.log Y / (c * r) by + field_simp, le_div_iff₀ (by positivity)] + calc ((falsePositiveCount wAB wBC wAC) p : ℝ) * (c * r) = (falsePositiveCount wAB wBC wAC) p * + (c * (r / Real.log D)) * Real.log D := by field_simp + _ ≤ (falsePositiveCount wAB wBC wAC) p * numPrimes * Real.log D := by gcongr + _ ≤ (n : ℝ) ^ 3 * Real.logb (r / 2) Y * Real.log D := by gcongr + _ ≤ (n : ℝ) ^ 3 * (4 * Real.log Y / Real.log D) * Real.log D := by gcongr + _ = 4 * (n : ℝ) ^ 3 * Real.log Y := by field_simp + +/-- falsePositiveCount(p) = O(n³ log(3n^ν)/√D) = O(ν n³ log n/√D). -/ +theorem exists_falsePositiveCount_le : + ∃ C : ℝ, 1 ≤ C ∧ ∀ {n D p : ℕ} {κ : ℝ}, 16 ≤ D → D ≤ n → 1 ≤ κ → ∀ wAB wBC wAC : Fin n → Fin + n → ℤ, + WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ) → IsSelectedPrime wAB wBC wAC D p → + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ C * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt + D) := by + obtain ⟨c, hc, hcard⟩ := exists_le_card_primesInRange + refine ⟨max 1 (8 / c), le_max_left _ _, fun {n D p κ} hD hDn hκ wAB wBC wAC hT hp => ?_⟩ + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlogn : 0 ≤ Real.log n := Real.log_nonneg (by linarith) + -- `log(3n^κ) = log 3 + κ log n ≤ 2κ log n`, because `3 ≤ n` and `κ ≥ 1`. + have hlog : Real.log (3 * (n : ℝ) ^ κ) ≤ 2 * κ * Real.log n := by + have hlog3 : Real.log 3 ≤ κ * Real.log n := + (Real.log_le_log (by norm_num) (by linarith)).trans (le_mul_of_one_le_left hlogn hκ) + rw [Real.log_mul (by norm_num) (by positivity), Real.log_rpow (by linarith)] + linarith [hlog3] + calc ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ 4 / c * ((n : ℝ) ^ 3 * Real.log (3 * (n : ℝ) ^ + κ) / Real.sqrt D) := + (falsePositiveCount_le_of_le_card_primesInRange wAB wBC wAC) hc (hcard D hD) hD hDn hκ hT hp + _ ≤ 4 / c * ((n : ℝ) ^ 3 * (2 * κ * Real.log n) / Real.sqrt D) := by gcongr + _ = 8 / c * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt D) := by ring + _ ≤ max 1 (8 / c) * (κ * (n : ℝ) ^ 3 * Real.log n / Real.sqrt D) := by + gcongr + exact le_max_right _ _ + +/-- A name for the constant in falsePositiveCount(p) = [...] = O(ν n³ log n/√D), for the bounds on +running times that are built on this one. +-/ +noncomputable def Hashing.falsePositiveConst : ℝ := Classical.choose exists_falsePositiveCount_le + +/-- The constant is at least 1. -/ +theorem Hashing.one_le_falsePositiveConst : 1 ≤ Hashing.falsePositiveConst := + (Classical.choose_spec exists_falsePositiveCount_le).1 + +/-- falsePositiveCount(p) = [...] = O(ν n³ log n/√D), with `Hashing.falsePositiveConst` as the +constant. +-/ +theorem Hashing.falsePositiveCount_le_const_mul {n D p : ℕ} {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) + (hκ : 1 ≤ κ) (wAB wBC wAC : Fin n → Fin n → ℤ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (hp : IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC) p : ℝ) ≤ Hashing.falsePositiveConst * (κ * (n : ℝ) ^ 3 * + Real.log n / Real.sqrt D) := + (Classical.choose_spec exists_falsePositiveCount_le).2 hD hDn hκ wAB wBC wAC hT hp + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Algorithms/ExactTriangle/Weights.lean b/Complexity/Algorithms/ExactTriangle/Weights.lean new file mode 100644 index 0000000000..d358d0cd80 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Weights.lean @@ -0,0 +1,61 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Defs +public import Mathlib.Algebra.Order.Group.Abs +public import Mathlib.Basic.Real.Basic +import Mathlib.Tactic.Linarith + +/-! +# Integer triangle sums and magnitude bounds + +## Main definitions + +* `weightSum`: the sum of the three supplied integer edge weights. +* `WeightsBoundedBy`: a shared real bound on their absolute values. + +## Main results + +* `abs_weightSum_le`: a triangle's magnitude is at most three times the edge bound. + +## References + +Adapted from the weight sum and bound definitions in [Anthropic's +PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L893-L914) +and the triangle inequality in its +[Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean#L246-L255). +The existing `HasZeroTriangle` remains the decision predicate. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-- Sum of the A–B, B–C and A–C weights at a triangle. -/ +def weightSum (a b c : Fin n) : ℤ := wAB a b + wBC b c + wAC a c + +/-- Every supplied edge weight has absolute value at most `U`. -/ +def WeightsBoundedBy (U : ℝ) : Prop := + (∀ a b, ((|wAB a b| : ℤ) : ℝ) ≤ U) ∧ + (∀ b c, ((|wBC b c| : ℤ) : ℝ) ≤ U) ∧ ∀ a c, ((|wAC a c| : ℤ) : ℝ) ≤ U + +/-- The three edge bounds imply a threefold bound on every triangle sum. -/ +theorem abs_weightSum_le {U : ℝ} (h : WeightsBoundedBy wAB wBC wAC U) (a b c : Fin n) : + ((|weightSum wAB wBC wAC a b c| : ℤ) : ℝ) ≤ 3 * U := by + obtain ⟨hAB, hBC, hAC⟩ := h + have habs : |weightSum wAB wBC wAC a b c| ≤ |wAB a b| + |wBC b c| + |wAC a c| := + abs_add_three _ _ _ + have hcast : ((|weightSum wAB wBC wAC a b c| : ℤ) : ℝ) ≤ + ((|wAB a b| : ℤ) : ℝ) + ((|wBC b c| : ℤ) : ℝ) + ((|wAC a c| : ℤ) : ℝ) := by + exact_mod_cast habs + linarith [hAB a b, hBC b c, hAC a c] + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/NumberTheory/PrimeWindow.lean b/Complexity/Foundations/NumberTheory/PrimeWindow.lean new file mode 100644 index 0000000000..dbeb6390e2 --- /dev/null +++ b/Complexity/Foundations/NumberTheory/PrimeWindow.lean @@ -0,0 +1,346 @@ +/- +Copyright (c) 2020 Patrick Stevens. All rights reserved. +Copyright (c) 2026 Anthropic, PBC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Patrick Stevens, Bolton Bailey, Anthropic, PBC +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Analysis.SpecialFunctions.Log.Base +public import Mathlib.Analysis.SpecialFunctions.Pow.Real +public import Mathlib.NumberTheory.PrimeCounting +import Mathlib.Analysis.Complex.ExponentialBounds +import Mathlib.Data.Nat.Prime.Factorial +import Mathlib.NumberTheory.Bertrand +import Mathlib.NumberTheory.Primorial +import Mathlib.Analysis.Convex.SpecificFunctions.Deriv + +/-! +# Explicit prime counts in a window of ratio two + +For every real `x ≥ 4`, the half-open window `[x/2, x)` contains at least `c * x / log x` primes +for one universal positive constant `c`. The proof counts prime factors of a central binomial +coefficient, so no prime-number-theorem hypothesis is assumed. + +## Main results + +* `Nat.exists_prime_half_le_and_lt`: a prime exists in every such window. +* `Nat.exists_forall_mul_div_log_le_card_primes`: the uniform counting bound. +* `Nat.exists_forall_mul_sqrt_div_log_le_card_primes`: the bound at `x = sqrt D`, for all `D ≥ + 16`. + +## References + +Adapted from [Anthropic's +PrimesInWindow.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/PrimesInWindow.lean). +Its central-binomial and concavity estimates adapt `Mathlib/NumberTheory/Bertrand.lean`, +originally by Patrick Stevens and Bolton Bailey; that source credit is retained. +-/ + +@[expose] public section + +open Finset + +namespace Nat + +private theorem one_le_log_of_three_le {x : ℝ} (hx : 3 ≤ x) : 1 ≤ Real.log x := + (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans hx) + + +/-- The primes `p` with `n < p ≤ 2n`: the primes that Bertrand's postulate is about. -/ +private def bertrandPrimes (n : ℕ) : Finset ℕ := (Ioc n (2 * n)).filter Nat.Prime + +/-- Membership in `bertrandPrimes`, spelled out. -/ +private theorem mem_bertrandPrimes {n p : ℕ} : + p ∈ bertrandPrimes n ↔ (n < p ∧ p ≤ 2 * n) ∧ p.Prime := by + simp only [bertrandPrimes, mem_filter, mem_Ioc] + +/-! +## 1. Erdős's upper bound for the central binomial coefficient -/ + +/-- Every prime power in `C(2n, n)` is at most `2n`, so `|s|` of them multiply to at most +`(2n)^|s|`. +-/ +private theorem prod_pow_factorization_le {n : ℕ} (hn : 0 < n) (s : Finset ℕ) : + ∏ p ∈ s, p ^ n.centralBinom.factorization p ≤ (2 * n) ^ s.card := + prod_le_pow_card _ _ _ fun _ _ => Nat.pow_factorization_choose_le (by omega) + +/-- The primes in `(√(2n), n]` contribute at most `4^(2n/3)` to `C(2n, n)`: their exponent is at +most 1, it is 0 above `2n/3`, and the product of the primes up to `2n/3` is at most `4^(2n/3)`. +Adapted from Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0. +-/ +private theorem prod_middle_le {n : ℕ} (hn : 2 < n) (s : Finset ℕ) + (hs : ∀ p ∈ s, p.Prime ∧ Nat.sqrt (2 * n) < p ∧ p ≤ n) : + ∏ p ∈ s, p ^ n.centralBinom.factorization p ≤ 4 ^ (2 * n / 3) := + calc ∏ p ∈ s, p ^ n.centralBinom.factorization p + ≤ ∏ p ∈ s, (if p ≤ 2 * n / 3 then p else 1) := by + refine prod_le_prod fun p hp => ?_ + obtain ⟨hprime, hsqrt, hpn⟩ := hs p hp + split_ifs with h3 + · exact (pow_right_mono₀ hprime.one_lt.le + (Nat.factorization_choose_le_one (Nat.sqrt_lt'.mp hsqrt))).trans (pow_one p).le + · rw [Nat.factorization_centralBinom_of_two_mul_self_lt_three_mul hn hpn (by omega), + pow_zero] + _ = ∏ p ∈ s.filter (· ≤ 2 * n / 3), p := (prod_filter _ _).symm + _ ≤ primorial (2 * n / 3) := by + refine prod_le_prod_of_subset_of_one_le (fun p hp => ?_) + fun p hp _ => (mem_filter.1 hp).2.one_lt.le + obtain ⟨hps, hp3⟩ := mem_filter.1 hp + exact mem_filter.2 ⟨mem_range.2 (by omega), (hs p hps).1⟩ + _ ≤ 4 ^ (2 * n / 3) := primorial_le_four_pow _ + +/-- Erdős's bound, keeping the primes of `(n, 2n]`: `C(2n, n) ≤ (2n)^√(2n) · 4^(2n/3) · (2n)^k`, +where `k` is the number of primes in `(n, 2n]`. Mathlib proves the case `k = 0` on the way to +Bertrand's postulate (`centralBinom_le_of_no_bertrand_prime`). Adapted from Mathlib, +Mathlib/NumberTheory/Bertrand.lean, Apache 2.0. +-/ +private theorem centralBinom_le (n : ℕ) (hn : 2 < n) : + n.centralBinom ≤ + (2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) * (2 * n) ^ (bertrandPrimes n).card := by + -- `C(2n, n)` is the product of its prime powers over the set `T` of the primes up to `2n`. + set T := (range (2 * n + 1)).filter Nat.Prime with hT + have hprod : ∏ p ∈ T, p ^ n.centralBinom.factorization p = n.centralBinom := + (prod_filter_of_ne fun p _ h => by + contrapose h + rw [Nat.factorization_eq_zero_of_not_prime _ h, pow_zero]).trans + n.prod_pow_factorization_centralBinom + -- Split the primes at `n`, and those up to `n` at `√(2n)`. + rw [← hprod, ← prod_filter_mul_prod_filter_not T (· ≤ n), + ← prod_filter_mul_prod_filter_not (T.filter (· ≤ n)) (· ≤ Nat.sqrt (2 * n))] + refine mul_le_mul' (mul_le_mul' ?_ ?_) ?_ + · -- There are at most `√(2n)` primes up to `√(2n)`. + have hsub : (T.filter (· ≤ n)).filter (· ≤ Nat.sqrt (2 * n)) ⊆ Icc 1 (Nat.sqrt (2 * n)) := by + intro p hp + simp only [hT, mem_filter, mem_range] at hp + obtain ⟨⟨⟨-, hprime⟩, -⟩, hsqrt⟩ := hp + exact mem_Icc.mpr ⟨hprime.one_lt.le, hsqrt⟩ + exact (prod_pow_factorization_le (by omega) _).trans (pow_right_mono₀ (by omega) + ((card_le_card hsub).trans_eq (by rw [Nat.card_Icc, Nat.add_sub_cancel]))) + · -- The primes in `(√(2n), n]`. + refine prod_middle_le hn _ fun p hp => ?_ + simp only [hT, mem_filter, mem_range, not_le] at hp + obtain ⟨⟨⟨-, hprime⟩, hpn⟩, hsqrt⟩ := hp + exact ⟨hprime, hsqrt, hpn⟩ + · -- The primes above `n` are those of `(n, 2n]`. + refine (prod_pow_factorization_le (by omega) _).trans + (pow_right_mono₀ (by omega) (card_le_card fun p hp => ?_)) + simp only [hT, mem_filter, mem_range] at hp + obtain ⟨⟨hp2n, hprime⟩, hnp⟩ := hp + exact mem_bertrandPrimes.2 ⟨⟨by omega, by omega⟩, hprime⟩ + +/-! +## 2. The elementary estimate `n^6 · (2n)^(6√(2n)) ≤ 2^n` for `n ≥ 16562` -/ + +/-- For `a ≥ 0` the function `log x + √(2x) · log (2x) - a x` is concave for `x > 1/2`. Adapted from +Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0 (there inside +`Bertrand.real_main_inequality`, for `a = log 4 / 3`). +-/ +private theorem concaveOn_log_add_sqrt_mul_log_sub (a : ℝ) (ha : 0 ≤ a) : + ConcaveOn ℝ (Set.Ioi (1 / 2 : ℝ)) + (fun x : ℝ => Real.log x + √(2 * x) * Real.log (2 * x) - a * x) := by + apply ConcaveOn.sub + · apply ConcaveOn.add + · exact strictConcaveOn_log_Ioi.concaveOn.subset + (Set.Ioi_subset_Ioi (by norm_num)) (convex_Ioi _) + · have h := strictConcaveOn_sqrt_mul_log_Ioi.concaveOn.comp_linearMap + ((2 : ℝ) • (LinearMap.id : ℝ →ₗ[ℝ] ℝ)) + have hset : (⇑((2 : ℝ) • (LinearMap.id : ℝ →ₗ[ℝ] ℝ))) ⁻¹' Set.Ioi 1 = Set.Ioi (1 / 2) := by + ext x + simp only [Set.mem_Ioi, Set.mem_preimage, LinearMap.smul_apply, LinearMap.id_coe, id_eq, + smul_eq_mul] + constructor <;> intro h <;> linarith + rw [hset] at h + exact h + · exact ConvexOn.smul ha (convexOn_id (convex_Ioi _)) + +/-- `log x + √(2x) · log (2x) ≤ (log 2 / 6) · x` for `x ≥ 16562`. The difference of the two sides +is concave, it is nonnegative at `x = 2` and nonpositive at `x = 16562 = 2 · 91²` (where `√(2x) += 182`; the only numerical input is `91^13 ≤ 2^85`), so it is nonpositive from there on. Adapted +from Mathlib, Mathlib/NumberTheory/Bertrand.lean, Apache 2.0 (the same argument proves +`Bertrand.real_main_inequality` there, with other numbers). +-/ +private theorem log_add_sqrt_mul_log_le {x : ℝ} (hx : 16562 ≤ x) : + Real.log x + √(2 * x) * Real.log (2 * x) ≤ Real.log 2 / 6 * x := by + have hlog2 : 0 < Real.log 2 := Real.log_pos (by norm_num) + have hat2 : (0 : ℝ) ≤ + Real.log 2 + √(2 * 2) * Real.log (2 * 2) - Real.log 2 / 6 * 2 := by + rw [Real.sqrt_mul_self (by norm_num), Real.log_mul (by norm_num) (by norm_num)] + linarith + have hat16562 : Real.log 16562 + √(2 * 16562) * Real.log (2 * 16562) + - Real.log 2 / 6 * 16562 ≤ 0 := by + have hlog91 : 13 * Real.log 91 ≤ 85 * Real.log 2 := by + have h := Real.log_le_log (by positivity) (show (91 : ℝ) ^ 13 ≤ 2 ^ 85 by norm_num) + rw [Real.log_pow, Real.log_pow] at h + exact_mod_cast h + have hlogx : Real.log 16562 = Real.log 2 + 2 * Real.log 91 := by + rw [show (16562 : ℝ) = 2 * 91 ^ 2 by norm_num, Real.log_mul (by norm_num) (by norm_num), + Real.log_pow] + norm_num + have hlog2x : Real.log (182 * 182) = 2 * Real.log 2 + 2 * Real.log 91 := by + rw [show (182 * 182 : ℝ) = 2 ^ 2 * 91 ^ 2 by norm_num, + Real.log_mul (by norm_num) (by norm_num), Real.log_pow, Real.log_pow] + norm_num + rw [show (2 * 16562 : ℝ) = 182 * 182 by norm_num, Real.sqrt_mul_self (by norm_num), hlogx, + hlog2x] + -- `366 log 91 ≤ 366 · (85/13) log 2 < (16562/6 - 365) log 2` + linarith only [hlog91, hlog2] + have hconcave := concaveOn_log_add_sqrt_mul_log_sub (Real.log 2 / 6) (by positivity) + have hatx := hconcave.right_le_of_le_left'' + (x := 2) (y := 16562) (z := x) (by norm_num [Set.mem_Ioi]) (by rw [Set.mem_Ioi]; linarith) + (by norm_num) hx (hat16562.trans hat2) + linarith only [hatx, hat16562] + +/-- The same estimate for natural numbers, without logarithms. -/ +private theorem pow_mul_pow_sqrt_le_two_pow {n : ℕ} (hn : 16562 ≤ n) : + n ^ 6 * (2 * n) ^ (6 * Nat.sqrt (2 * n)) ≤ 2 ^ n := by + have hx : (16562 : ℝ) ≤ n := by exact_mod_cast hn + have hreal := log_add_sqrt_mul_log_le hx + have hsqrt : (Nat.sqrt (2 * n) : ℝ) ≤ √(2 * n) := by + exact_mod_cast Real.nat_sqrt_le_real_sqrt (a := 2 * n) + have hlog : 0 ≤ Real.log (2 * n) := Real.log_nonneg (by linarith) + have hpos : (0 : ℝ) < n := by linarith + rw [← Nat.cast_le (α := ℝ)] + push_cast + rw [← Real.log_le_log_iff (by positivity) (by positivity), + Real.log_mul (by positivity) (by positivity), Real.log_pow, Real.log_pow, Real.log_pow] + push_cast + -- `6 log n + 6 ⌊√(2n)⌋ log(2n) ≤ 6 (log n + √(2n) log(2n)) ≤ n log 2` + linarith [hreal, mul_le_mul_of_nonneg_right hsqrt hlog] + +/-! +## 3. The count for `n ≥ 16562` -/ + +/-- `2^n ≤ (2n)^(2k)` for `n ≥ 16562`. If it failed, the sixth power of `4^n < n · (2n)^√(2n) · +4^(2n/3) · (2n)^k` would give `2^(12n) < (n^6 (2n)^(6√(2n))) · 2^(8n) · 2^(3n) ≤ 2^(12n)`. +-/ +private theorem two_pow_le_of_large {n : ℕ} (hn : 16562 ≤ n) : + 2 ^ n ≤ (2 * n) ^ (2 * (bertrandPrimes n).card) := by + by_contra hcon + have hfew : (2 * n) ^ (2 * (bertrandPrimes n).card) ≤ 2 ^ n := (not_le.mp hcon).le + have herdos : 4 ^ n < n * ((2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) + * (2 * n) ^ (bertrandPrimes n).card) := + (Nat.four_pow_lt_mul_centralBinom n (by omega)).trans_le + (Nat.mul_le_mul_left _ (centralBinom_le n (by omega))) + have hsmall := pow_mul_pow_sqrt_le_two_pow hn + have hmiddle : 4 ^ (6 * (2 * n / 3)) ≤ 4 ^ (4 * n) := + Nat.pow_le_pow_right (by norm_num) (by omega) + refine lt_irrefl ((4 ^ n) ^ 6) ?_ + calc (4 ^ n) ^ 6 + < (n * ((2 * n) ^ Nat.sqrt (2 * n) * 4 ^ (2 * n / 3) + * (2 * n) ^ (bertrandPrimes n).card)) ^ 6 := Nat.pow_lt_pow_left herdos (by norm_num) + _ = (n ^ 6 * (2 * n) ^ (6 * Nat.sqrt (2 * n))) * 4 ^ (6 * (2 * n / 3)) + * ((2 * n) ^ (2 * (bertrandPrimes n).card)) ^ 3 := by ring + _ ≤ 2 ^ n * 4 ^ (4 * n) * (2 ^ n) ^ 3 := by gcongr + _ = (4 ^ n) ^ 6 := by + rw [mul_comm 4 n, pow_mul 4 n 4, show (4 : ℕ) ^ n = 2 ^ n * 2 ^ n by rw [← mul_pow]; rfl] + ring + +/-- The count of step 3 with natural logarithms: `n log 2 ≤ 2 k log (2n)`. -/ +private theorem mul_log_two_le_card_bertrandPrimes_mul_log {n : ℕ} (hn : 16562 ≤ n) : + (n : ℝ) * Real.log 2 ≤ 2 * (bertrandPrimes n).card * Real.log (2 * n) := by + have hpow : (2 : ℝ) ^ n ≤ (2 * (n : ℝ)) ^ (2 * (bertrandPrimes n).card) := by + exact_mod_cast two_pow_le_of_large hn + have hlog := Real.log_le_log (by positivity) hpow + rw [Real.log_pow, Real.log_pow] at hlog + exact_mod_cast hlog + +/-! +## 4. Half-open windows `[x/2, x)` with real endpoints -/ + +/-- For a real `x ≥ 4` the natural number `n = ⌈x/2⌉ - 1`, written below as `(⌈x⌉ - 1) / 2`, +satisfies `n ≥ 1`, `n ≥ x/4` and `2n < x`, and every natural number in `(n, 2n]` lies in `[x/2, +x)`. +-/ +private theorem exists_nat_window {x : ℝ} (hx : 4 ≤ x) : + ∃ n : ℕ, 1 ≤ n ∧ x / 4 ≤ (n : ℝ) ∧ 2 * (n : ℝ) < x ∧ + ∀ p : ℕ, n < p → p ≤ 2 * n → x / 2 ≤ (p : ℝ) ∧ (p : ℝ) < x := by + have hle : x ≤ (⌈x⌉₊ : ℝ) := Nat.le_ceil x + have hlt : (⌈x⌉₊ : ℝ) < x + 1 := Nat.ceil_lt_add_one (by linarith) + have hceil : 4 ≤ ⌈x⌉₊ := by exact_mod_cast hx.trans hle + obtain ⟨n, hn⟩ : ∃ n, n = (⌈x⌉₊ - 1) / 2 := ⟨_, rfl⟩ + have hlow : (⌈x⌉₊ : ℝ) ≤ 2 * n + 2 := by exact_mod_cast (by omega : ⌈x⌉₊ ≤ 2 * n + 2) + have hhigh : (2 * n + 1 : ℝ) ≤ (⌈x⌉₊ : ℝ) := by exact_mod_cast (by omega : 2 * n + 1 ≤ ⌈x⌉₊) + refine ⟨n, by omega, by linarith, by linarith, fun p hnp hp2n => ?_⟩ + have hnp' : (n : ℝ) + 1 ≤ p := by exact_mod_cast hnp + have hp2n' : (p : ℝ) ≤ 2 * n := by exact_mod_cast hp2n + exact ⟨by linarith, by linarith⟩ + +/-- For every real `x ≥ 4` there is a prime `p` with `x/2 ≤ p < x`, by Bertrand's postulate. -/ +theorem exists_prime_half_le_and_lt (x : ℝ) (hx : 4 ≤ x) : + ∃ p : ℕ, p.Prime ∧ x / 2 ≤ (p : ℝ) ∧ (p : ℝ) < x := by + obtain ⟨n, hn1, -, -, hwindow⟩ := exists_nat_window hx + obtain ⟨p, hprime, hnp, hp2n⟩ := Nat.exists_prime_lt_and_le_two_mul n (by omega) + exact ⟨p, hprime, hwindow p hnp hp2n⟩ + +/-- If `(n, 2n]` lies in the window and `n ≥ x/4` is large, the window has at least `x / (12 log x)` +primes: `(x/4) log 2 ≤ n log 2 ≤ 2 k log (2n) ≤ 2 k log x`, and `log 2 ≥ 2/3`. +-/ +private theorem div_log_le_card_of_large {x : ℝ} {n : ℕ} {S : Finset ℕ} (hx : 4 ≤ x) + (hn : 16562 ≤ n) (hn4 : x / 4 ≤ (n : ℝ)) (h2n : 2 * (n : ℝ) < x) (hsub : bertrandPrimes n ⊆ S) : + x / (12 * Real.log x) ≤ (S.card : ℝ) := by + have hcard : ((bertrandPrimes n).card : ℝ) ≤ S.card := by exact_mod_cast card_le_card hsub + have hn1 : (1 : ℝ) ≤ n := by exact_mod_cast (by omega : 1 ≤ n) + have hlog : Real.log (2 * n) ≤ Real.log x := Real.log_le_log (by linarith) h2n.le + have hlog0 : 0 ≤ Real.log (2 * n) := Real.log_nonneg (by linarith) + have hlog2 : 2 / 3 ≤ Real.log 2 := by linarith [Real.log_two_gt_d9] + rw [div_le_iff₀ (mul_pos (by norm_num) (Real.log_pos (by linarith)))] + calc x = 6 * (x / 4 * (2 / 3)) := by ring + _ ≤ 6 * (n * Real.log 2) := by gcongr + _ ≤ 6 * (2 * (bertrandPrimes n).card * Real.log (2 * n)) := + mul_le_mul_of_nonneg_left (mul_log_two_le_card_bertrandPrimes_mul_log hn) (by norm_num) + _ ≤ 6 * (2 * S.card * Real.log x) := by gcongr + _ = S.card * (12 * Real.log x) := by ring + +/-- There is a `c > 0` such that, for every real `x ≥ 4`, every finite set of natural numbers that +contains all primes `p` with `x/2 ≤ p < x` has at least `c · x / log x` elements. (This form +applies to the set of those primes however it is written down.) +-/ +theorem exists_forall_mul_div_log_le_card_primes : + ∃ c : ℝ, 0 < c ∧ ∀ x : ℝ, 4 ≤ x → ∀ S : Finset ℕ, + (∀ p : ℕ, p.Prime → x / 2 ≤ (p : ℝ) → (p : ℝ) < x → p ∈ S) → + c * (x / Real.log x) ≤ (S.card : ℝ) := by + -- `66248 = 4 · 16562`: for `x` below it one prime is enough + refine ⟨1 / 66248, by norm_num, fun x hx S hS => ?_⟩ + obtain ⟨n, hn1, hn4, h2n, hwindow⟩ := exists_nat_window hx + have hlogx : 1 ≤ Real.log x := one_le_log_of_three_le (by linarith) + rcases le_or_gt 16562 n with hlarge | hsmall + · -- The primes of `(n, 2n]` lie in the window. + have hsub : bertrandPrimes n ⊆ S := fun p hp => by + obtain ⟨⟨hnp, hp2n⟩, hprime⟩ := mem_bertrandPrimes.1 hp + exact hS p hprime (hwindow p hnp hp2n).1 (hwindow p hnp hp2n).2 + calc 1 / 66248 * (x / Real.log x) ≤ 1 / 12 * (x / Real.log x) := by gcongr; norm_num + _ = x / (12 * Real.log x) := by field_simp + _ ≤ (S.card : ℝ) := div_log_le_card_of_large hx hlarge hn4 h2n hsub + · -- Here `x ≤ 4n < 66248`, and one prime is enough. + obtain ⟨p, hprime, hlow, hhigh⟩ := exists_prime_half_le_and_lt x hx + have hone : (1 : ℝ) ≤ S.card := by + exact_mod_cast Finset.card_pos.mpr ⟨p, hS p hprime hlow hhigh⟩ + have hn : (n : ℝ) ≤ 16562 := by exact_mod_cast hsmall.le + calc 1 / 66248 * (x / Real.log x) ≤ 1 / 66248 * (x / 1) := by gcongr + _ ≤ 1 := by linarith + _ ≤ (S.card : ℝ) := hone + +/-! +## 5. The window `[√D/2, √D)` used in the modular reduction -/ + +/-- There is a `c > 0` such that, for every natural number `D ≥ 16`, every finite set of natural +numbers that contains all primes `p` with `√D/2 ≤ p < √D` has at least `c · √D / log D` +elements. +-/ +theorem exists_forall_mul_sqrt_div_log_le_card_primes : + ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → ∀ S : Finset ℕ, + (∀ p : ℕ, p.Prime → √D / 2 ≤ (p : ℝ) → (p : ℝ) < √D → p ∈ S) → + c * (√D / Real.log D) ≤ (S.card : ℝ) := by + obtain ⟨c, hc, hcount⟩ := exists_forall_mul_div_log_le_card_primes + refine ⟨c, hc, fun D hD S hS => ?_⟩ + have hlog : 0 < Real.log D := Real.log_pos (by exact_mod_cast (by omega : 1 < D)) + calc c * (√D / Real.log D) ≤ c * (√D / (Real.log D / 2)) := by + gcongr + linarith + _ = c * (√D / Real.log (√D)) := by rw [Real.log_sqrt (Nat.cast_nonneg D)] + _ ≤ (S.card : ℝ) := hcount _ ((Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD))) S hS + +end Nat diff --git a/Complexity/Foundations/RingTheory/CyclicIntRing.lean b/Complexity/Foundations/RingTheory/CyclicIntRing.lean new file mode 100644 index 0000000000..94ae6d738f --- /dev/null +++ b/Complexity/Foundations/RingTheory/CyclicIntRing.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.RingTheory.AdjoinRoot + +/-! +# Integer polynomials modulo `X^p - 1` + +## Main definitions + +* `CyclicIntRing`: the integer polynomial quotient. +* `CyclicIntRing.coeff`: coefficients in the unique representative of degree less than `p`. + +## Main results + +* `CyclicIntRing.coeff_x_pow`: powers have a single coefficient at the residue of their + exponent. + +## References + +Adapted from [Anthropic's +Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean#L57-L101). +-/ + +@[expose] public section + +namespace Cslib + +open Polynomial in +/-- The quotient ring `ℤ[X]/(X^p - 1)`. -/ +abbrev CyclicIntRing (p : ℕ) : Type := AdjoinRoot ((X : ℤ[X]) ^ p - C 1) + +namespace CyclicIntRing + +open Polynomial + +variable {p : ℕ} + +/-- The element `x` of `ℤ[x]/(x^p − 1)`. -/ +noncomputable def x (p : ℕ) : CyclicIntRing p := AdjoinRoot.root _ + +/-- The coefficient of degree `r` in an element `z` of the quotient, as a linear map. It is the +coefficient of `x^r` in the unique polynomial of degree less than `p` that represents `z` +(Mathlib's `AdjoinRoot.modByMonicHom` gives that polynomial; it asks for the fact that `x^p − 1` +is monic, which holds as `p ≠ 0`). +-/ +noncomputable def coeff (hp : p ≠ 0) (r : ℕ) : CyclicIntRing p →ₗ[ℤ] ℤ := + lcoeff ℤ r ∘ₗ AdjoinRoot.modByMonicHom (monic_X_pow_sub_C (1 : ℤ) hp) + +/-- The coefficient, in terms of the representing polynomial. -/ +theorem coeff_apply (hp : p ≠ 0) (r : ℕ) (z : CyclicIntRing p) : + coeff hp r z = (AdjoinRoot.modByMonicHom (monic_X_pow_sub_C (1 : ℤ) hp) z).coeff r := + rfl + +/-- In `ℤ[x]/(x^p − 1)` we have `x^p = 1`. -/ +private theorem x_pow_self (p : ℕ) : x p ^ p = 1 := by + have h : AdjoinRoot.mk ((X : ℤ[X]) ^ p - C 1) ((X : ℤ[X]) ^ p - C 1) = 0 := AdjoinRoot.mk_self + rw [map_sub, map_pow, AdjoinRoot.mk_X, C_1, map_one] at h + exact sub_eq_zero.1 h + +/-- The coefficient of `x^r` in the power `x^k` of `ℤ[x]/(x^p − 1)` is 1 if `r = k mod p` and 0 +otherwise. +-/ +theorem coeff_x_pow (hp : p ≠ 0) (k r : ℕ) : + coeff hp r (x p ^ k) = if r = k % p then 1 else 0 := by + have hred : x p ^ k = x p ^ (k % p) := by + conv_lhs => rw [← Nat.div_add_mod k p, pow_add, pow_mul, x_pow_self, one_pow, one_mul] + have hdeg : ((X : ℤ[X]) ^ (k % p)).degree < ((X : ℤ[X]) ^ p - C 1).degree := by + rw [degree_X_pow, degree_X_pow_sub_C (Nat.pos_of_ne_zero hp)] + exact_mod_cast Nat.mod_lt k (Nat.pos_of_ne_zero hp) + rw [coeff_apply, hred, x, ← AdjoinRoot.mk_X, ← map_pow, AdjoinRoot.modByMonicHom_mk, + (modByMonic_eq_self_iff (monic_X_pow_sub_C (1 : ℤ) hp)).2 hdeg, coeff_X_pow] + +end CyclicIntRing + + +end Cslib diff --git a/ComplexityTests/ExactTriangleHashing.lean b/ComplexityTests/ExactTriangleHashing.lean new file mode 100644 index 0000000000..41df09a874 --- /dev/null +++ b/ComplexityTests/ExactTriangleHashing.lean @@ -0,0 +1,115 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Hashing + +/-! +# Modular Exact Triangle counting and prime-selection regressions + +Concrete zeros, false positives, coefficient wraparound, minimum selection, the integer window +boundary and an admitted instance of the universal bound. These assert no native running time. +-/ + +namespace ComplexityTests.ExactTriangleHashing + +open Cslib Cslib.Algorithms.ExactTriangle + +private def ab (a _ : Fin 2) : ℤ := if a = 0 then 0 else 2 +private def z (_ _ : Fin 2) : ℤ := 0 + +private theorem window16 : primesInRange 16 = {2, 3} := by + rw [primesInRange_eq_filter] + decide + +-- Both endpoints matter: prime 2 is admitted at the lower endpoint for D=16; +-- prime 5 is excluded at the upper endpoint for D=25, and prime 3 is admitted there. +example : 2 ∈ primesInRange 16 ∧ 5 ∉ primesInRange 25 ∧ 3 ∈ primesInRange 25 := by + simp only [mem_primesInRange_iff_sq] + decide + +example : 2 ∉ primesInRange 17 := by rw [mem_primesInRange_iff_sq]; decide + +example : countZeroMod ab z z 2 = 8 ∧ countZeroMod ab z z 3 = 4 := by + unfold countZeroMod weightSum ab z + decide + +example : zeroCount ab z z = 4 ∧ falsePositiveCount ab z z 2 = 4 ∧ + falsePositiveCount ab z z 3 = 0 := by + unfold zeroCount falsePositiveCount IsFalsePositive weightSum ab z + decide + +example : countZeroMod ab z z 0 = 4 ∧ falsePositiveCount ab z z 0 = 0 := by + unfold countZeroMod falsePositiveCount IsFalsePositive weightSum ab z + decide + +-- Counting distinguishes the two candidate primes; the minimum is 3. +example : IsSelectedPrime ab z z 16 3 ∧ ¬IsSelectedPrime ab z z 16 2 := by + simp only [IsSelectedPrime, window16] + unfold countZeroMod weightSum ab z + decide + +-- A tie permits either minimum prime; the predicate does not impose a tie-break. +example : IsSelectedPrime (fun _ _ : Fin 1 => 0) (fun _ _ => 0) (fun _ _ => 0) 16 2 ∧ + IsSelectedPrime (fun _ _ : Fin 1 => 0) (fun _ _ => 0) (fun _ _ => 0) 16 3 := by + constructor <;> refine ⟨by rw [window16]; decide, ?_⟩ <;> intro q _ <;> + simp [countZeroMod, weightSum] + +example : HasZeroTriangle ab z z := ⟨0, 0, 0, rfl⟩ +example : 0 < zeroCount ab z z := (zeroCount_pos_iff ab z z).mpr ⟨0, 0, 0, rfl⟩ + +private def one (_ _ : Fin 2) : ℤ := 1 + +example : ¬HasZeroTriangle one one one := by + rintro ⟨a, b, c, h⟩ + norm_num [one] at h + +example : zeroCount one one one = 0 ∧ countZeroMod one one one 3 = 8 ∧ + falsePositiveCount one one one 3 = 8 := by + unfold zeroCount countZeroMod falsePositiveCount IsFalsePositive weightSum one + decide + +-- A negative residue and a positive residue wrap to exponent zero modulo 3. +example : CyclicIntRing.coeff (by decide : 3 ≠ 0) 0 + ((matP (fun _ _ : Fin 2 => (-1 : ℤ)) 3 * + matQ (fun _ _ : Fin 2 => (1 : ℤ)) 3) 0 0) = 2 := by + rw [coeff_matP_mul_matQ (fun _ _ : Fin 2 => (1 : ℤ)) + (fun _ _ : Fin 2 => (-1 : ℤ)) (by decide) 0 0 0 (by decide)] + decide + +-- These matrices are asymmetric: transposing either input changes the selected count. +private def ac (a c : Fin 2) : ℤ := if a = 0 then if c = 0 then -1 else 2 else 0 +private def bc (b c : Fin 2) : ℤ := if b = 0 ∧ c = 1 then 1 else 0 + +example : CyclicIntRing.coeff (by decide : 3 ≠ 0) 0 ((matP ac 3 * matQ bc 3) 0 0) = 1 := by + rw [coeff_matP_mul_matQ bc ac (by decide) 0 0 0 (by decide)] + unfold ac bc + decide + +example : CyclicIntRing.coeff (by decide : 2 ≠ 0) 1 (CyclicIntRing.x 2 ^ 5) = 1 := by + rw [CyclicIntRing.coeff_x_pow] + norm_num + +example : CyclicIntRing.coeff (by decide : 2 ≠ 0) 3 (CyclicIntRing.x 2 ^ 5) = 0 := by + rw [CyclicIntRing.coeff_x_pow] + norm_num + +example : zeroCount (fun _ _ : Fin 0 => 0) (fun _ _ => 0) (fun _ _ => 0) = 0 := by + simp [zeroCount] + +example : ¬WeightsBoundedBy (fun _ _ : Fin 1 => (0 : ℤ)) (fun _ _ => 0) (fun _ _ => 0) (-1) := by + norm_num [WeightsBoundedBy] + +-- The full bound's n,D,kappa hypotheses and input bound have a concrete witness. +example : (falsePositiveCount (fun _ _ : Fin 16 => 0) (fun _ _ => 0) (fun _ _ => 0) 2 : ℝ) ≤ + Hashing.falsePositiveConst * (1 * (16 : ℝ) ^ 3 * Real.log 16 / Real.sqrt 16) := by + apply Hashing.falsePositiveCount_le_const_mul (by decide : 16 ≤ 16) (by decide : 16 ≤ 16) + (by norm_num : (1 : ℝ) ≤ 1) + · norm_num [WeightsBoundedBy] + · refine ⟨by rw [window16]; decide, ?_⟩ + intro q _ + simp [countZeroMod, weightSum] + +end ComplexityTests.ExactTriangleHashing diff --git a/NOTICE b/NOTICE index 3ab01a2df3..95b9c8d57a 100644 --- a/NOTICE +++ b/NOTICE @@ -1,10 +1,11 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/{Defs,Lopsided}.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, Complexity/Foundations/Data/Finset/Average.lean, Complexity/Foundations/Data/Matrix/IntegerBits.lean and -Complexity/Foundations/NumberTheory/PrimeCounting.lean are adapted from +Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow}.lean and +Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision 16a5ce41304fcdaea4e0be7c132e1c361dc26da5. @@ -13,3 +14,7 @@ Sparse Lopsided Graphs" Copyright 2026 Anthropic, PBC This product is licensed under the Apache License, Version 2.0 (see LICENSE). + +The prime-window proof also adapts Mathlib/NumberTheory/Bertrand.lean, +Copyright (c) 2020 Patrick Stevens; authors Patrick Stevens, Bolton Bailey, +under the Apache License, Version 2.0. diff --git a/ROADMAP.md b/ROADMAP.md index 5b3b230013..d31b9ede12 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -422,8 +422,9 @@ each statement against the numbered result. Organize modules by subject, not cha has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting - interface ([#1438]) adds padding, detection and signed digit reconstruction; it still needs - modular Exact Triangle hashing/false-positive splitting, tensor/sparse-query programs, native + interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting + and the selected-prime false-positive bound ([#1442]) are proved mathematically; it still needs + residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded @@ -550,4 +551,5 @@ not planned, not as proved. [#1394]: https://github.com/cslib-community/complexitylib/issues/1394 [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 [#1438]: https://github.com/cslib-community/complexitylib/issues/1438 +[#1442]: https://github.com/cslib-community/complexitylib/issues/1442 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index 87d9cb5288..2bc29e783b 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -174,8 +174,16 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he `Set.EqOn`; no additional weighted-triangle predicate or source machine is introduced. This is the mathematical interface milestone [#1438](https://github.com/cslib-community/complexitylib/issues/1438), with no sparsity assumption, - preprocessing/time claim or solver. Modular hashing/false-positive splitting and tensor/sparse - query programs remain in #1394, alongside the native and asymptotic composition below. + preprocessing/time claim or solver. `Algorithms/ExactTriangle/{Weights,Hashing}.lean` now adapts + the modular counting/coefficient identities, minimum-count prime and uniform false-positive + bound from `Sec3/Theorem17/Hashing.lean`, using supplied matrices and the existing decision + predicate. Its integer-square window characterisation supports later native enumeration. + The quotient/coefficient API lives in `Foundations/RingTheory/CyclicIntRing.lean`; the uniform + all-window prime-count bound lives in `Foundations/NumberTheory/PrimeWindow.lean`, retaining + the source credit to Mathlib, Patrick Stevens and Bolton Bailey. These are mathematical + statements, not native instructions or an assumed solver. See + [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, + witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full input/time/width/asymptotic composition remain in From 0d8da3e78edceaa2e11fa3c31a1b44fce10dbdbd Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:09:24 -0400 Subject: [PATCH 03/20] doc(threeSum): cite the lopsided triangle paper and clarify adaptation scope --- Complexity/Algorithms/ExactTriangle/Hashing.lean | 1 + Complexity/Algorithms/ExactTriangle/Lopsided.lean | 1 + UPSTREAM.md | 13 +++++++------ references.bib | 12 ++++++++++++ 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 6799e79fc7..affaadd2e2 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -39,6 +39,7 @@ asserted. The supplied three matrices retain the existing `HasZeroTriangle` sema ## References +The modular reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Anthropic's Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), with the minimal count/selection definitions extracted from its `PaperStatements.lean`. diff --git a/Complexity/Algorithms/ExactTriangle/Lopsided.lean b/Complexity/Algorithms/ExactTriangle/Lopsided.lean index d52e779437..84694440fe 100644 --- a/Complexity/Algorithms/ExactTriangle/Lopsided.lean +++ b/Complexity/Algorithms/ExactTriangle/Lopsided.lean @@ -36,6 +36,7 @@ fast solver or subquadratic running time. ## References +The lopsided matrix interface is used in [AlmanWilliams2026, Section 3]. Adapted from [Anthropic's MatrixLanguage.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/MatrixLanguage.lean) and the lopsided definitions in [PaperStatements.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/PaperStatements.lean#L798-L852). The adaptation uses `Set.EqOn`, pointwise signed parts, and direct finite cardinalities. diff --git a/UPSTREAM.md b/UPSTREAM.md index 2bc29e783b..8b49547dae 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -158,6 +158,13 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he Given its arguments in registers, it writes one instance in at most `131*t² + 5` instructions at width `w + 2`, excluding the halt; its theorem identifies the three encoded matrices and preserves the input. Layout, signed index/filler bounds and target address bounds are explicit. + The existing signed and unsigned APIs were reused; no source machine or separate run semantics + were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full + input/time/width/asymptotic composition remain in + [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved + Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution + are retained; the native proofs and representation choices are local. + `Algorithms/ThreeSum/ThreeSets.lean` and `ArrayPacking.lean` provide the generic distinct-position frontend and three-array packing. `Algorithms/ThreeSum/ChanHe/` now adapts the core mathematical reduction: minimum-prime searches, collision/heavy-element bounds, light-element arrays, bounded @@ -184,12 +191,6 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he statements, not native instructions or an assumed solver. See [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. - The existing signed and unsigned APIs were reused; no source machine or separate run semantics - were copied. The full reduction host, native Chan–He preprocessing, fast Exact Triangle and full - input/time/width/asymptotic composition remain in - [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved - Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution - are retained; the native proofs and representation choices are local. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. diff --git a/references.bib b/references.bib index 8cf6daceb4..f42e7901a7 100644 --- a/references.bib +++ b/references.bib @@ -103,6 +103,18 @@ @misc{Algolean2026 url = {https://github.com/Shreyas4991/Algolean} } +@misc{AlmanWilliams2026, + author = {Alman, Josh and Vassilevska Williams, Virginia}, + title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in + Sparse Lopsided Graphs}, + year = {2026}, + eprint = {2610.06783}, + archivePrefix = {arXiv}, + primaryClass = {cs.DS}, + doi = {10.48550/arXiv.2610.06783}, + url = {https://arxiv.org/abs/2610.06783v1} +} + @article{AllenderBuhrmanKouckyVanMelkebeekRonneburger2006, author = {Allender, Eric and Buhrman, Harry and Kouck{\'y}, Michal and van Melkebeek, Dieter and Ronneburger, Detlef}, title = {Power from Random Strings}, From 8d80ecce623c8ab4a08742ed6944892ebe9f9b21 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 06:09:49 -0400 Subject: [PATCH 04/20] doc(bibliography): sort the new paper entry --- references.bib | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/references.bib b/references.bib index f42e7901a7..af7ca6f2c2 100644 --- a/references.bib +++ b/references.bib @@ -103,18 +103,6 @@ @misc{Algolean2026 url = {https://github.com/Shreyas4991/Algolean} } -@misc{AlmanWilliams2026, - author = {Alman, Josh and Vassilevska Williams, Virginia}, - title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in - Sparse Lopsided Graphs}, - year = {2026}, - eprint = {2610.06783}, - archivePrefix = {arXiv}, - primaryClass = {cs.DS}, - doi = {10.48550/arXiv.2610.06783}, - url = {https://arxiv.org/abs/2610.06783v1} -} - @article{AllenderBuhrmanKouckyVanMelkebeekRonneburger2006, author = {Allender, Eric and Buhrman, Harry and Kouck{\'y}, Michal and van Melkebeek, Dieter and Ronneburger, Detlef}, title = {Power from Random Strings}, @@ -137,6 +125,18 @@ @article{AllenderRubinstein1988 doi = {10.1137/0217075} } +@misc{AlmanWilliams2026, + author = {Alman, Josh and Vassilevska Williams, Virginia}, + title = {Truly Subquadratic {3SUM} and Truly Subcubic {APSP} via Triangles in + Sparse Lopsided Graphs}, + year = {2026}, + eprint = {2610.06783}, + archivePrefix = {arXiv}, + primaryClass = {cs.DS}, + doi = {10.48550/arXiv.2610.06783}, + url = {https://arxiv.org/abs/2610.06783v1} +} + @article{AlonBoppana1987, author = {Alon, Noga and Boppana, Ravi B.}, title = {The Monotone Circuit Complexity of {B}oolean Functions}, From fd69be7bc459d83cab3863f6f6e2f59239b21595 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:02:59 -0400 Subject: [PATCH 05/20] fix(exactTriangle): repair the prime-window header and hashing documentation --- .../Algorithms/ExactTriangle/Hashing.lean | 115 +++++++++++------- .../Foundations/NumberTheory/PrimeWindow.lean | 4 +- .../Foundations/RingTheory/CyclicIntRing.lean | 1 - 3 files changed, 71 insertions(+), 49 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index affaadd2e2..fbb2834b6e 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -43,7 +43,8 @@ The modular reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Anthropic's Hashing.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Hashing.lean), with the minimal count/selection definitions extracted from its `PaperStatements.lean`. -Source-derived prose below is paraphrased. The source pin is +The declaration docstrings describe the Lean definitions and statements in local notation. +The source pin is `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -/ @@ -52,16 +53,20 @@ Source-derived prose below is paraphrased. The source pin is namespace Cslib.Algorithms.ExactTriangle open Classical in -/-- Primes in the half-open window `[sqrt D / 2, sqrt D)`. -/ +/-- Prime numbers in the half-open real interval `[sqrt D / 2, sqrt D)`. +-/ noncomputable def primesInRange (D : ℕ) : Finset ℕ := (Finset.range D).filter fun p => p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D -/-- Number of triangles whose sum vanishes modulo `p`. -/ +/-- Count vertex triples whose three edge weights have sum congruent to zero modulo `p`. +-/ def countZeroMod {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (p : ℕ) : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≡ 0 [ZMOD (p : ℤ)]).card -/-- A window prime minimises the modular count over all window primes. -/ +/-- A prime in `primesInRange D` whose modular-zero count is minimal among all primes in that +window. Tied minima all satisfy this predicate. +-/ def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ) : Prop := p ∈ primesInRange D ∧ ∀ q ∈ primesInRange D, countZeroMod wAB wBC wAC p ≤ countZeroMod wAB wBC wAC q @@ -81,8 +86,8 @@ private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)) /-! ### The primes in the range -/ -/-- Proof of Theorem 17, a prime p ∈ [√D/2, √D): the primes in the range are the primes `p` with -`√D/2 ≤ p < √D`. (The bound `p < D` in the definition excludes none of them.) +/-- Window membership is equivalent to primality and the two real endpoint inequalities. The +finite enumeration bound in `primesInRange` excludes no member of the interval. -/ theorem mem_primesInRange {D p : ℕ} : p ∈ primesInRange D ↔ p.Prime ∧ Real.sqrt D / 2 ≤ (p : ℝ) ∧ (p : ℝ) < Real.sqrt D := by @@ -93,7 +98,9 @@ theorem mem_primesInRange {D p : ℕ} : have hsq : p ^ 2 < D := by exact_mod_cast (Real.lt_sqrt (Nat.cast_nonneg p)).1 h.2.2 exact (Nat.le_self_pow two_ne_zero p).trans_lt hsq -/-- Window membership is an exact integer-square test, suitable for native enumeration. -/ +/-- Characterize window membership using primality and integer-square inequalities, including the +closed lower and open upper endpoints. +-/ theorem mem_primesInRange_iff_sq {D p : ℕ} : p ∈ primesInRange D ↔ p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by rw [mem_primesInRange] @@ -111,7 +118,8 @@ theorem mem_primesInRange_iff_sq {D p : ℕ} : have h : (D : ℝ) ≤ 4 * (p : ℝ) ^ 2 := by exact_mod_cast hlo nlinarith -/-- A computable finite enumeration of exactly the same half-open prime window. -/ +/-- Enumerate the real prime window using a decidable filter of the integers below `D`. +-/ theorem primesInRange_eq_filter (D : ℕ) : primesInRange D = (Finset.range D).filter fun p => p.Prime ∧ D ≤ 4 * p ^ 2 ∧ p ^ 2 < D := by ext p @@ -137,20 +145,26 @@ section Counting variable {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) -/-- A false positive of p is a triple (a,b,c) with S(a,b,c) ≠ 0 and p ∣ S(a,b,c). -/ +/-- A vertex triple has a nonzero integer weight sum that is congruent to zero modulo `p`. +-/ abbrev IsFalsePositive (p : ℕ) (t : Fin n × Fin n × Fin n) : Prop := weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0 ∧ (p : ℤ) ∣ weightSum wAB wBC wAC t.1 t.2.1 t.2.2 -/-- let falsePositiveCount(p) denote the number of false positives of p. -/ +/-- Count vertex triples satisfying `IsFalsePositive` for the supplied three weight matrices and +modulus. +-/ def falsePositiveCount (p : ℕ) : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => IsFalsePositive wAB wBC wAC p t).card -/-- zeroCount, the number of zero triangles. -/ +/-- Count vertex triples whose three supplied edge weights sum to zero as an integer. +-/ def zeroCount : ℕ := (Finset.univ.filter fun t : Fin n × Fin n × Fin n => weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0).card -/-- A positive zero count is exactly the existing Exact Triangle decision predicate. -/ +/-- The zero-triple count is positive exactly when the supplied matrices satisfy +`HasZeroTriangle`. +-/ theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wBC wAC := by classical simp only [zeroCount, Finset.card_pos, HasZeroTriangle] @@ -160,8 +174,8 @@ theorem zeroCount_pos_iff : 0 < zeroCount wAB wBC wAC ↔ HasZeroTriangle wAB wB · rintro ⟨a, b, c, h⟩ exact ⟨(a, b, c), Finset.mem_filter.mpr ⟨Finset.mem_univ _, h⟩⟩ -/-- This number is the number of false positives falsePositiveCount(p) plus zeroCount, the number of -zero triangles, which does not depend on p. +/-- Partition the modular-zero triples into false positives and exact zero-sum triples. The +exact-zero count is independent of the modulus. -/ theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) := by @@ -173,17 +187,21 @@ theorem countZeroMod_eq (p : ℕ) : (countZeroMod wAB wBC wAC) p = (falsePositiv by_cases h0 : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 = 0 <;> simp [IsFalsePositive, Int.modEq_zero_iff_dvd, h0] -/-- let P[a,c] := x^{w(a,c) mod p}, a matrix over `ℤ[x]/(x^p − 1)`. `w mod p` is the residue in -`{0, …, p − 1}`. +/-- Encode the A–C weight at `(a,c)` as a power of the cyclic polynomial generator, with exponent +equal to its nonnegative residue modulo `p`. -/ noncomputable def matP (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := Matrix.of fun a c => CyclicIntRing.x p ^ (wAC a c % (p : ℤ)).toNat -/-- and Q[c,b] := x^{w(b,c) mod p}. -/ +/-- Encode the B–C weight at `(b,c)` in matrix position `(c,b)`. This orientation makes the +product sum over the middle index `c`. +-/ noncomputable def matQ (p : ℕ) : Matrix (Fin n) (Fin n) (CyclicIntRing p) := Matrix.of fun c b => CyclicIntRing.x p ^ (wBC b c % (p : ℤ)).toNat -/-- the coefficient of x^r in (PQ)[a,b] is the number of c ∈ C with w(a,c) + w(b,c) ≡ r (mod p). -/ +/-- For a nonzero modulus and a residue `r < p`, the coefficient of degree `r` in the cyclic +matrix product counts middle vertices whose two incident weights sum to `r` modulo `p`. +-/ theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr : r < p) : CyclicIntRing.coeff hp r ((matP wAC p * matQ wBC p) a b) = ((Finset.univ.filter fun c : Fin n => @@ -200,8 +218,8 @@ theorem coeff_matP_mul_matQ {p : ℕ} (hp : p ≠ 0) (a b : Fin n) (r : ℕ) (hr refine Finset.sum_congr rfl fun c _ => ?_ simp only [eq_add_toNat_emod_iff hp hr] -/-- then falsePositiveCount(p) + zeroCount is the sum over the pairs (a,b) ∈ A × B of the -coefficient of x^{−w(a,b) mod p} in (PQ)[a,b]. +/-- Recover the total modular-zero count from the cyclic matrix product by summing, over `(a,b)`, +the coefficient at the residue of `-wAB a b`. -/ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : (((falsePositiveCount wAB wBC wAC) p + (zeroCount wAB wBC wAC) : ℕ) : ℤ) = @@ -235,7 +253,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : end Counting -/-- the fewer than √D primes in the range. -/ +/-- For `D ≥ 16`, the number of primes in `primesInRange D` is strictly less than `sqrt D`. +-/ theorem card_primesInRange_lt (D : ℕ) (hD : 16 ≤ D) : ((primesInRange D).card : ℝ) < Real.sqrt D := by have hpos : 0 < Real.sqrt D := by linarith [four_le_sqrt hD] @@ -259,15 +278,17 @@ section Selection variable {n D p : ℕ} {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) -/-- There is a prime to select. -/ +/-- For every `D ≥ 16` and every three supplied weight matrices, the prime window contains a +minimum of the modular-zero count. +-/ theorem exists_isSelectedPrime (D : ℕ) (hD : 16 ≤ D) : ∃ p, IsSelectedPrime wAB wBC wAC D p := by obtain ⟨q, hq⟩ := Nat.exists_prime_half_le_and_lt (Real.sqrt D) (four_le_sqrt hD) exact Finset.exists_min_image (primesInRange D) (countZeroMod wAB wBC wAC) ⟨q, mem_primesInRange.2 hq⟩ -/-- We select the prime with the smallest count, which is also the prime with the fewest false -positives. +/-- A selected minimum of the modular-zero count also minimizes the false-positive count over the +same prime window. -/ theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : IsSelectedPrime wAB wBC wAC D p) : @@ -282,9 +303,8 @@ theorem IsSelectedPrime.falsePositiveCount_le {wAB wBC wAC : Fin n → Fin n → ### The bound on the number of false positives of the selected prime -/ open Classical in -/-- a triple with S(a,b,c) ≠ 0 is a false positive exactly of the primes in the range that divide -S(a,b,c). Since 0 < |S(a,b,c)| ≤ 3n^ν and these primes are at least √D/2, there are at most -log_{√D/2}(3n^ν) of them. +/-- For a fixed nonzero-sum triple and edge weights bounded by `n^κ`, bound the number of window +primes accepting that triple by the base-`sqrt D / 2` logarithm of `3*n^κ`. -/ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (t : Fin n × Fin n × Fin n) (ht : weightSum wAB wBC wAC t.1 t.2.1 t.2.2 ≠ 0) : @@ -316,8 +336,9 @@ theorem card_falsePositive_primes_le (hD : 16 ≤ D) (hT : WeightsBoundedBy wAB rw [Real.le_logb_iff_rpow_le (by linarith) hY, Real.rpow_natCast] exact hpow.trans hprod -/-- Hence the numbers of false positives of all the primes in the range add up to at most n³ -log_{√D/2}(3n^ν). +/-- Sum the per-triple prime-divisibility bound over all `n^3` triples. For `D ≥ 16`, `κ ≥ 1` and +edge weights bounded by `n^κ`, the sum of false-positive counts is at most `n^3 * logb (sqrt D / +2) (3*n^κ)`. -/ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) : @@ -356,11 +377,9 @@ theorem sum_falsePositiveCount_le (hD : 16 ≤ D) (hκ : 1 ≤ κ) (hT : Weights end Selection -/-- By the prime number theorem there are Ω(√D/log D) primes in the range. - -NOTE. The modular reduction needs this for every `D ≥ 16`, not only for large `D`, and so it is -stated. It follows from a counting form of Bertrand's postulate; the prime number theorem is -not needed. +/-- There is a universal positive `c` such that every window with `D ≥ 16` contains at least `c * +sqrt D / log D` primes. The proof uses the all-window counting form of Bertrand’s postulate from +`PrimeWindow`, with no prime-number-theorem assumption. -/ theorem exists_le_card_primesInRange : ∃ c : ℝ, 0 < c ∧ ∀ D : ℕ, 16 ≤ D → @@ -369,7 +388,9 @@ theorem exists_le_card_primesInRange : exact ⟨c, hc, fun D hD => hcount D hD _ fun _ hp hge hlt => mem_primesInRange.2 ⟨hp, hge, hlt⟩⟩ -/-- falsePositiveCount(p) is at most the average over them, multiplied out. -/ +/-- Multiply the selected prime’s minimum false-positive count by the number of window primes to +obtain a bound by the sum of their false-positive counts. +-/ theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wBC wAC : Fin n → Fin n → ℤ} (hp : IsSelectedPrime wAB wBC wAC D p) : @@ -378,7 +399,8 @@ theorem IsSelectedPrime.falsePositiveCount_mul_card_le_sum {n D p : ℕ} {wAB wB rw [mul_comm, ← smul_eq_mul] exact Finset.card_nsmul_le_sum _ _ _ hp.falsePositiveCount_le -/-- log D = O(log(√D/2)) for D ≥ 16. -/ +/-- For `D ≥ 16`, compare `log D` with four times `log (sqrt D / 2)`. +-/ theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : Real.log D ≤ 4 * Real.log (Real.sqrt D / 2) := by have hD16 : (16 : ℝ) ≤ D := by exact_mod_cast hD @@ -391,9 +413,9 @@ theorem log_le_four_mul_log_sqrt_div_two (D : ℕ) (hD : 16 ≤ D) : rw [Real.log_div hsqrt.ne' (by norm_num), Real.log_sqrt (by linarith)] linarith [hlog16] -/-- falsePositiveCount(p) is at most the average over them, so [...] falsePositiveCount(p) = O(n³ -log(3n^ν)/√D): if there are at least `c √D/log D` primes in the range, then -`falsePositiveCount(p) ≤ (4/c) n³ log(3n^κ)/√D`. +/-- For `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by `n^κ`, a positive pointwise prime-count +lower bound `c * sqrt D / log D` gives every selected prime at most `(4/c) * n^3 * log (3*n^κ) / +sqrt D` false positives. -/ theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) (hc : 0 < c) @@ -435,7 +457,9 @@ theorem falsePositiveCount_le_of_le_card_primesInRange {n D p : ℕ} {κ c : ℝ _ ≤ (n : ℝ) ^ 3 * (4 * Real.log Y / Real.log D) * Real.log D := by gcongr _ = 4 * (n : ℝ) ^ 3 * Real.log Y := by field_simp -/-- falsePositiveCount(p) = O(n³ log(3n^ν)/√D) = O(ν n³ log n/√D). -/ +/-- There is a universal `C ≥ 1` such that, for `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by +`n^κ`, every selected prime has at most `C * κ * n^3 * log n / sqrt D` false positives. +-/ theorem exists_falsePositiveCount_le : ∃ C : ℝ, 1 ≤ C ∧ ∀ {n D p : ℕ} {κ : ℝ}, 16 ≤ D → D ≤ n → 1 ≤ κ → ∀ wAB wBC wAC : Fin n → Fin n → ℤ, @@ -461,17 +485,18 @@ theorem exists_falsePositiveCount_le : gcongr exact le_max_right _ _ -/-- A name for the constant in falsePositiveCount(p) = [...] = O(ν n³ log n/√D), for the bounds on -running times that are built on this one. +/-- A universal real constant witnessing `exists_falsePositiveCount_le`. It depends on none of the +input dimensions, weight matrices, exponent or selected prime, and is at least one. -/ noncomputable def Hashing.falsePositiveConst : ℝ := Classical.choose exists_falsePositiveCount_le -/-- The constant is at least 1. -/ +/-- The universal false-positive constant `Hashing.falsePositiveConst` is at least one. +-/ theorem Hashing.one_le_falsePositiveConst : 1 ≤ Hashing.falsePositiveConst := (Classical.choose_spec exists_falsePositiveCount_le).1 -/-- falsePositiveCount(p) = [...] = O(ν n³ log n/√D), with `Hashing.falsePositiveConst` as the -constant. +/-- For `16 ≤ D ≤ n`, `κ ≥ 1` and edge weights bounded by `n^κ`, bound every selected prime’s +false-positive count by `Hashing.falsePositiveConst * κ * n^3 * log n / sqrt D`. -/ theorem Hashing.falsePositiveCount_le_const_mul {n D p : ℕ} {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) (hκ : 1 ≤ κ) (wAB wBC wAC : Fin n → Fin n → ℤ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) diff --git a/Complexity/Foundations/NumberTheory/PrimeWindow.lean b/Complexity/Foundations/NumberTheory/PrimeWindow.lean index dbeb6390e2..9693a53028 100644 --- a/Complexity/Foundations/NumberTheory/PrimeWindow.lean +++ b/Complexity/Foundations/NumberTheory/PrimeWindow.lean @@ -1,6 +1,5 @@ /- -Copyright (c) 2020 Patrick Stevens. All rights reserved. -Copyright (c) 2026 Anthropic, PBC. All rights reserved. +Copyright (c) 2020 Patrick Stevens, 2026 Anthropic, PBC. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Patrick Stevens, Bolton Bailey, Anthropic, PBC -/ @@ -49,7 +48,6 @@ namespace Nat private theorem one_le_log_of_three_le {x : ℝ} (hx : 3 ≤ x) : 1 ≤ Real.log x := (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans hx) - /-- The primes `p` with `n < p ≤ 2n`: the primes that Bertrand's postulate is about. -/ private def bertrandPrimes (n : ℕ) : Finset ℕ := (Ioc n (2 * n)).filter Nat.Prime diff --git a/Complexity/Foundations/RingTheory/CyclicIntRing.lean b/Complexity/Foundations/RingTheory/CyclicIntRing.lean index 94ae6d738f..d15f95b193 100644 --- a/Complexity/Foundations/RingTheory/CyclicIntRing.lean +++ b/Complexity/Foundations/RingTheory/CyclicIntRing.lean @@ -80,5 +80,4 @@ theorem coeff_x_pow (hp : p ≠ 0) (k r : ℕ) : end CyclicIntRing - end Cslib From ce646c75bd6af9c6ace7fadc00bd4fde0c673f72 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:13:16 -0400 Subject: [PATCH 06/20] feat(exactTriangle): split modular queries and recover witnesses Adapt the complete finite Theorem 17 splitting and recovery component, using the existing three weight matrices, lopsided graphs, modular counts and EqOn answer interface. Prove exact integer/real capacity bridges so the finite constructions and scans are computable. Preserve Anthropic source credit; keep the native 3SUM endpoint open in #1394. Validation: all six gates in scripts/check.sh pass, with LEAN_NUM_THREADS=1 and cache downloads disabled after bootstrap. Full hygiene, changed-source check-simp, changed-file lints and git diff --check pass. Curated abstracts and rendering are unaffected. AI-assisted implementation/self-review by Codex worker codex-three-sum-splitting-t3-0a4d90c0; independent review will be recorded in the PR. --- Complexity.lean | 7 + .../Algorithms/ExactTriangle/Hashing.lean | 18 +- .../Algorithms/ExactTriangle/Splitting.lean | 534 ++++++++++++++++++ .../Algorithms/ExactTriangle/Witnesses.lean | 398 +++++++++++++ .../Foundations/Data/Finset/Quotient.lean | 44 ++ Complexity/Foundations/Data/Int/Residue.lean | 41 ++ .../Foundations/Data/Matrix/PairChunks.lean | 114 ++++ Complexity/Foundations/Data/Nat/CeilDiv.lean | 55 ++ .../Foundations/Data/Nat/SqrtRatio.lean | 47 ++ ComplexityTests/ExactTriangleSplitting.lean | 158 ++++++ NOTICE | 8 +- 11 files changed, 1408 insertions(+), 16 deletions(-) create mode 100644 Complexity/Algorithms/ExactTriangle/Splitting.lean create mode 100644 Complexity/Algorithms/ExactTriangle/Witnesses.lean create mode 100644 Complexity/Foundations/Data/Finset/Quotient.lean create mode 100644 Complexity/Foundations/Data/Int/Residue.lean create mode 100644 Complexity/Foundations/Data/Matrix/PairChunks.lean create mode 100644 Complexity/Foundations/Data/Nat/CeilDiv.lean create mode 100644 Complexity/Foundations/Data/Nat/SqrtRatio.lean create mode 100644 ComplexityTests/ExactTriangleSplitting.lean diff --git a/Complexity.lean b/Complexity.lean index e6ac945545..0f88b5283d 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -3,7 +3,9 @@ module -- shake: keep-all --deprecated_module: ignore public import Complexity.Algorithms.ExactTriangle.Defs public import Complexity.Algorithms.ExactTriangle.Hashing public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Algorithms.ExactTriangle.Witnesses public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction @@ -2067,12 +2069,14 @@ public import Complexity.Foundations.Data.Finset.Average public import Complexity.Foundations.Data.Finset.Expect public import Complexity.Foundations.Data.Finset.Markov public import Complexity.Foundations.Data.Finset.Quantile +public import Complexity.Foundations.Data.Finset.Quotient public import Complexity.Foundations.Data.Finset.Sampling public import Complexity.Foundations.Data.Finset.SubsetSum public import Complexity.Foundations.Data.Finset.SymmDiff public import Complexity.Foundations.Data.Hamming public import Complexity.Foundations.Data.Hamming.Expansion public import Complexity.Foundations.Data.Int.Order +public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.Data.List.Append public import Complexity.Foundations.Data.List.BalancedFold public import Complexity.Foundations.Data.List.Basic @@ -2088,6 +2092,7 @@ public import Complexity.Foundations.Data.List.Range public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary public import Complexity.Foundations.Data.Matrix.IntegerBits +public import Complexity.Foundations.Data.Matrix.PairChunks public import Complexity.Foundations.Data.Matrix.Permanent public import Complexity.Foundations.Data.Matrix.Reindex public import Complexity.Foundations.Data.Matrix.TotallyRegular @@ -2109,6 +2114,7 @@ public import Complexity.Foundations.Data.Nat.Bitwise.Projection public import Complexity.Foundations.Data.Nat.Bitwise.Sparse public import Complexity.Foundations.Data.Nat.Bitwise.SumTree public import Complexity.Foundations.Data.Nat.Card +public import Complexity.Foundations.Data.Nat.CeilDiv public import Complexity.Foundations.Data.Nat.Choose public import Complexity.Foundations.Data.Nat.Choose.Real public import Complexity.Foundations.Data.Nat.Div @@ -2123,6 +2129,7 @@ public import Complexity.Foundations.Data.Nat.Prime public import Complexity.Foundations.Data.Nat.Primrec public import Complexity.Foundations.Data.Nat.Radix public import Complexity.Foundations.Data.Nat.Radix.Measurement +public import Complexity.Foundations.Data.Nat.SqrtRatio public import Complexity.Foundations.Data.Option public import Complexity.Foundations.Data.OrderedSet public import Complexity.Foundations.Data.OrderedSet.Spec diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index fbb2834b6e..86aba7824b 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Algorithms.ExactTriangle.Weights +public import Complexity.Foundations.Data.Int.Residue public import Complexity.Foundations.NumberTheory.PrimeWindow public import Complexity.Foundations.RingTheory.CyclicIntRing public import Mathlib.Data.Matrix.Mul @@ -74,15 +75,6 @@ def IsSelectedPrime {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D p : ℕ private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) -private theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : - ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := - Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) - -private theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by - have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) - have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) - omega - /-! ### The primes in the range -/ @@ -134,8 +126,8 @@ private theorem eq_add_toNat_emod_iff {p r : ℕ} (hp : p ≠ 0) (hr : r < p) (u rw [← Int.natCast_inj, Int.ModEq, Int.emod_eq_of_lt (Int.natCast_nonneg r) (by exact_mod_cast hr), eq_comm] push_cast - rw [natCast_toNat_emod (Nat.pos_of_ne_zero hp), - natCast_toNat_emod (Nat.pos_of_ne_zero hp), + rw [Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp), + Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp), ← Int.add_emod] /-! @@ -233,8 +225,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : refine Finset.sum_congr rfl fun a _ => ?_ rw [Fintype.sum_prod_type] refine Finset.sum_congr rfl fun b _ => ?_ - have hcast := natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) - rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + have hcast := Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) _), Finset.card_filter] push_cast refine Finset.sum_congr rfl fun c _ => ?_ diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean new file mode 100644 index 0000000000..5015daf5e1 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -0,0 +1,534 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Hashing +public import Complexity.Algorithms.ExactTriangle.Lopsided +public import Complexity.Foundations.Data.Matrix.PairChunks +public import Complexity.Foundations.Data.Nat.SqrtRatio +import Mathlib.Tactic.GCongr +import Mathlib.Tactic.FieldSimp + +/-! +# Modular splitting of Exact Triangle into finite lopsided queries + +Pieces of the third part and chunks of A–B residue classes define lopsided graphs with a +middle vertex `(c, label)`. The three input matrices retain their original orientations. +All bounds below use `16 ≤ D ≤ n` and `1 ≤ g`; the sharper degree bound also uses `g ≤ sqrt D`. +No native program, fast solver or charged instruction bound is asserted. + +## Main definitions + +* `piece`, `residueClass`, `chunkOf`: vertex blocks and residue query chunks. +* `lopInstance`, `instanceIndices`, `totalChunks`: lopsided graphs and their finite indices. + +## Main results + +* `existsUnique_mem_piece`, `card_piece_le`: unique piece coverage and size. +* `totalChunks_le`, `card_instanceIndices_le`: chunk and instance bounds. +* `card_middle_lopInstance`, `hasCommonNeighbor_lopInstance_iff`: size and modular correctness. +* `ncard_nbr_lopInstance_le`: degree bound. + +## References + +The reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of +[Instances.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Instances.lean), with only required definitions from +`PaperStatements.lean`, [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and [Parameters.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Parameters.lean), at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +Source-derived prose below is paraphrased; quotations used for review are in the PR record. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Cslib.Matrix + +/-- The largest number of query pairs that is "at most n²/√D" (Corollary 15 and Theorem 17; +Theorem 5 has "at most N²/√D"): `⌊n²/√D⌋`. -/ +def queryCap (n D : ℕ) : ℕ := Nat.sqrt ((n ^ 2) ^ 2 / D) + +/-- The integer capacity is exactly the real-valued floor used in the reduction. -/ +theorem queryCap_eq_floor (n D : ℕ) : queryCap n D = ⌊(n : ℝ) ^ 2 / Real.sqrt D⌋₊ := by + rw [queryCap, Nat.sqrt_div_eq_floor_div_sqrt] + norm_cast + +/-- Proof of Theorem 17: "let s := ⌊√D⌋". -/ +def sOf (D : ℕ) : ℕ := Nat.sqrt D + +/-- Proof of Theorem 17: the bound "⌈s/g⌉" on the number of vertices of a piece. -/ +def pieceSize (D g : ℕ) : ℕ := sOf D ⌈/⌉ g + +/-- The integer block size equals the real ceiling, also at the zero-divisor convention. -/ +theorem pieceSize_eq_ceil (D g : ℕ) : pieceSize D g = ⌈(sOf D : ℝ) / (g : ℝ)⌉₊ := by + by_cases hg : g = 0 + · simp [pieceSize, hg] + exact (Nat.ceil_div_eq_ceilDiv _ (Nat.pos_of_ne_zero hg)).symm + +/-- Proof of Theorem 17: "split C into pieces C₁, …, C_h of at most ⌈s/g⌉ vertices each". The paper +does not say how; we take blocks of `⌈s/g⌉` consecutive vertices. `piece n D g k` is the paper's +`C_{k+1}`. -/ +def piece (n D g k : ℕ) : Finset (Fin n) := + Finset.univ.filter fun c : Fin n => c.val / pieceSize D g = k + +/-- Proof of Theorem 17: the number `h` of pieces, `⌈n / ⌈s/g⌉⌉` for the blocks of `piece` +(meaningful when `⌈s/g⌉ ≥ 1`). -/ +def numPieces (n D g : ℕ) : ℕ := n ⌈/⌉ pieceSize D g + +/-- The number of blocks is the ceiling of the real quotient by the block size. -/ +theorem numPieces_eq_ceil (n D g : ℕ) : + numPieces n D g = ⌈(n : ℝ) / (pieceSize D g : ℝ)⌉₊ := by + by_cases h : pieceSize D g = 0 + · simp [numPieces, h] + exact (Nat.ceil_div_eq_ceilDiv _ (Nat.pos_of_ne_zero h)).symm + +/-- The index of an instance built by the reduction of Theorem 17: a residue `ϱ ∈ ℤ_p`, the number +`j` of a chunk of `W_ϱ`, and the number `k` of a piece of `C`. -/ +abbrev InstanceIndex (p : ℕ) : Type := Fin p × ℕ × ℕ + + +/-- Proof of Theorem 17: "For ϱ ∈ ℤ_p let W_ϱ be the set of edges (a,b) ∈ A × B with w(a,b) ≡ ϱ (mod +p)". -/ +def residueClass {n : ℕ} (wAB : Fin n → Fin n → ℤ) (p : ℕ) (ϱ : Fin p) : Finset (Fin n × Fin n) := + Finset.univ.filter fun q : Fin n × Fin n => wAB q.1 q.2 ≡ ((ϱ : ℕ) : ℤ) [ZMOD (p : ℤ)] + +/-- Proof of Theorem 17: "and cut it into chunks of at most n²/√D query pairs". The `j`-th chunk of +`W_ϱ`. -/ +def chunkOf {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D p : ℕ) (ϱ : Fin p) (j : ℕ) : + Finset (Fin n × Fin n) := + chunk (residueClass wAB p ϱ) (queryCap n D) j + +/-- Proof of Theorem 17: "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k form the instance of +Lop-AE-SparseTri(n, D) with query pairs W := 𝒬 and middle part C_k × ℤ_p [...], whose middle +vertices are pairs (vertex, label) [...], with a ∼ (c, σ) ⟺ σ ≡ w(a,c) + ϱ, (c, σ) ∼ b ⟺ σ ≡ +−w(b,c) (mod p)." -/ +def lopInstance {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) : + InstanceIndex p → LopsidedGraph n + | (ϱ, j, k) => + { M := {c : Fin n // c ∈ piece n D g k} × Fin p + adjA := fun a (c, σ) => ((σ : ℕ) : ℤ) ≡ wAC a c + ((ϱ : ℕ) : ℤ) [ZMOD (p : ℤ)] + adjB := fun (c, σ) b => ((σ : ℕ) : ℤ) ≡ -wBC b c [ZMOD (p : ℤ)] + W := chunkOf wAB D p ϱ j } + +/-- Proof of Theorem 17: "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k" there is an instance; these +are the indices of all the instances. This set depends only on the Exact Triangle instance and on +`D`, `g`, `p`, not on any answer of the oracle. -/ +def instanceIndices {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D g p : ℕ) : + Finset (InstanceIndex p) := + Finset.univ.biUnion fun ϱ : Fin p => + ((Finset.range (numChunks (residueClass wAB p ϱ) (queryCap n D))) ×ˢ + (Finset.range (numPieces n D g))).image fun jk => (ϱ, jk) + +/-- Total number of query chunks, summed over residues. -/ +def totalChunks {n : ℕ} (wAB : Fin n → Fin n → ℤ) (D p : ℕ) : ℕ := + ∑ ϱ : Fin p, numChunks (residueClass wAB p ϱ) (queryCap n D) + +variable {n : ℕ} + +/-- `√D ≤ D ≤ n`. -/ +theorem sqrt_le_natCast {D : ℕ} (hD : 1 ≤ D) (hDn : D ≤ n) : Real.sqrt D ≤ n := + calc Real.sqrt D ≤ Real.sqrt D * Real.sqrt D := + le_mul_of_one_le_left (Real.sqrt_nonneg _) (Real.one_le_sqrt.mpr (by exact_mod_cast hD)) + _ = D := Real.mul_self_sqrt (Nat.cast_nonneg D) + _ ≤ n := by exact_mod_cast hDn + +/-- The bound `⌊n²/√D⌋` on the size of a chunk is at least 1, because `√D ≤ n ≤ n²`. -/ +theorem one_le_queryCap {D : ℕ} (hD : 1 ≤ D) (hDn : D ≤ n) : 1 ≤ queryCap n D := by + rw [queryCap_eq_floor] + have hn : (1 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + refine Nat.le_floor ?_ + rw [Nat.cast_one, le_div_iff₀ (Real.sqrt_pos.mpr (by exact_mod_cast hD)), one_mul] + exact (sqrt_le_natCast hD hDn).trans (le_self_pow₀ hn two_ne_zero) + +private theorem four_le_sqrt {D : ℕ} (hD : 16 ≤ D) : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + +variable {n D g p : ℕ} + +/-! ### The pieces -/ + +/-- `s = ⌊√D⌋ ≤ √D`. -/ +theorem sOf_le_sqrt (D : ℕ) : (sOf D : ℝ) ≤ Real.sqrt D := + Real.nat_sqrt_le_real_sqrt + +/-- `√D < s + 1`. -/ +theorem sqrt_lt_sOf_add_one (D : ℕ) : Real.sqrt D < (sOf D : ℝ) + 1 := + Real.real_sqrt_lt_nat_sqrt_succ + +/-- `s > 0` because `D ≥ 16`. -/ +private theorem sOf_pos (hD : 16 ≤ D) : (0 : ℝ) < sOf D := by + linarith [four_le_sqrt hD, sqrt_lt_sOf_add_one D] + +/-- The bound `⌈s/g⌉` on the size of a piece is at least 1. -/ +theorem pieceSize_pos (hD : 16 ≤ D) (hg1 : 1 ≤ g) : 0 < pieceSize D g := by + rw [pieceSize_eq_ceil] + exact Nat.ceil_pos.mpr (div_pos (sOf_pos hD) (by exact_mod_cast hg1)) + +/-- `⌈s/g⌉ ≤ s` because `g ≥ 1`. -/ +theorem pieceSize_le_sOf (hg1 : 1 ≤ g) : pieceSize D g ≤ sOf D := by + rw [pieceSize_eq_ceil] + exact Nat.ceil_le.mpr (div_le_self (Nat.cast_nonneg _) (by exact_mod_cast hg1)) + +/-- A vertex lies in the piece number `k` exactly if its number divided by `⌈s/g⌉` is `k`. -/ +theorem mem_piece {k : ℕ} (c : Fin n) : c ∈ piece n D g k ↔ c.val / pieceSize D g = k := by + simp [piece] + +/-- Proof of Theorem 17: "split C into pieces C₁, …, C_h of at most ⌈s/g⌉ vertices each": every +vertex of `C` lies in exactly one of the `h` pieces. -/ +theorem existsUnique_mem_piece (hD : 16 ≤ D) (hg1 : 1 ≤ g) (c : Fin n) : + ∃! k, k < numPieces n D g ∧ c ∈ piece n D g k := by + refine ⟨c.val / pieceSize D g, ⟨?_, (mem_piece c).mpr rfl⟩, + fun k hk => ((mem_piece c).mp hk.2).symm⟩ + rw [numPieces_eq_ceil] + refine Nat.lt_ceil.mpr (lt_of_le_of_lt Nat.cast_div_le ?_) + exact div_lt_div_of_pos_right (by exact_mod_cast c.isLt) + (by exact_mod_cast pieceSize_pos hD hg1) + +/-- Proof of Theorem 17, "pieces [...] of at most ⌈s/g⌉ vertices each": `|C_k| ≤ ⌈s/g⌉`, because the +numbers of the vertices of the piece number `k` lie in `[k ⌈s/g⌉, (k + 1) ⌈s/g⌉)`. -/ +theorem card_piece_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) (n k : ℕ) : + (piece n D g k).card ≤ pieceSize D g := + Finset.univ.card_filter_div_eq_le (fun _ _ _ _ h => Fin.ext h) (pieceSize_pos hD hg1) k + +/-- Proof of Theorem 17: "so that h ≤ ⌈ng/s⌉". -/ +theorem numPieces_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) : + numPieces n D g ≤ ⌈(n : ℝ) * (g : ℝ) / (sOf D : ℝ)⌉₊ := by + have hsize : (0 : ℝ) < pieceSize D g := by exact_mod_cast pieceSize_pos hD hg1 + have hg : (0 : ℝ) < g := by exact_mod_cast hg1 + -- `s ≤ ⌈s/g⌉ g` + have hs' : (sOf D : ℝ) / (g : ℝ) ≤ pieceSize D g := by + rw [pieceSize_eq_ceil] + exact Nat.le_ceil _ + have hs := (div_le_iff₀ hg).mp hs' + rw [numPieces_eq_ceil] + refine Nat.ceil_mono ?_ + rw [div_le_div_iff₀ hsize (sOf_pos hD)] + calc (n : ℝ) * (sOf D : ℝ) ≤ (n : ℝ) * ((pieceSize D g : ℝ) * (g : ℝ)) := by gcongr + _ = (n : ℝ) * (g : ℝ) * (pieceSize D g : ℝ) := by ring + +/-! ### Residues -/ + +/-- The residue of the integer `x` modulo `p`, as an element of `Fin p`. -/ +def resFin (hp : p ≠ 0) (x : ℤ) : Fin p := + ⟨(x % (p : ℤ)).toNat, Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) x⟩ + +/-- The residue of `x` is congruent to `x`. -/ +theorem resFin_modEq (hp : p ≠ 0) (x : ℤ) : ((resFin hp x : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] := by + rw [resFin, Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] + exact Int.mod_modEq x p + +/-- An element of `Fin p` is congruent to `x` modulo `p` exactly if it is the residue of `x`. -/ +private theorem modEq_iff_eq_resFin (hp : p ≠ 0) (σ : Fin p) (x : ℤ) : + ((σ : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] ↔ σ = resFin hp x := by + refine ⟨fun h => ?_, fun h => h ▸ resFin_modEq hp x⟩ + -- Two congruent numbers in `{0, …, p − 1}` are equal. + have hmod : (σ : ℕ) ≡ (resFin hp x : ℕ) [MOD p] := + (Int.natCast_modEq_iff).mp (h.trans (resFin_modEq hp x).symm) + exact Fin.ext (Nat.ModEq.eq_of_lt_of_lt hmod σ.isLt (resFin hp x).isLt) + +/-! ### The arithmetic behind the two counts -/ + +/-- The number `⌈|S|/cap⌉` of chunks of `S` satisfies `⌈|S|/cap⌉ cap ≤ |S| + cap − 1`. -/ +private theorem numChunks_mul_le (S : Finset (Fin n × Fin n)) (cap : ℕ) (hcap : 1 ≤ cap) : + numChunks S cap * cap + 1 ≤ S.card + cap := by + rw [numChunks] + exact Nat.ceilDiv_mul_lt hcap + +/-- `s + 1` exceeds `√D` by at least `1/(2√D + 1)`: since `(s + 1)²` and `D` are integers, +`1 ≤ (s + 1)² − D = (s + 1 − √D)(s + 1 + √D)`. -/ +private theorem one_le_gap_mul (D : ℕ) : + 1 ≤ ((sOf D : ℝ) + 1 - Real.sqrt D) * (2 * Real.sqrt D + 1) := by + have hsq : Real.sqrt D ^ 2 = D := Real.sq_sqrt (Nat.cast_nonneg D) + have hle := sOf_le_sqrt D + have hlt := sqrt_lt_sOf_add_one D + have hinteger : (D : ℝ) + 1 ≤ ((sOf D : ℝ) + 1) ^ 2 := by + have hreal : (D : ℝ) < ((sOf D : ℝ) + 1) ^ 2 := + hsq ▸ pow_lt_pow_left₀ hlt (Real.sqrt_nonneg _) two_ne_zero + have hnat : D < (sOf D + 1) ^ 2 := by exact_mod_cast hreal + exact_mod_cast hnat + calc (1 : ℝ) ≤ ((sOf D : ℝ) + 1) ^ 2 - Real.sqrt D ^ 2 := by linarith [hinteger, hsq] + _ = ((sOf D : ℝ) + 1 - Real.sqrt D) * ((sOf D : ℝ) + 1 + Real.sqrt D) := by ring + _ ≤ ((sOf D : ℝ) + 1 - Real.sqrt D) * (2 * Real.sqrt D + 1) := + mul_le_mul_of_nonneg_left (by linarith) (by linarith) + +/-- The inequality behind "at most p + √D [...] chunks": `n² < (s + 1) ⌊n²/√D⌋ + p`. + +Write `r = √D`, `x = n²/r` and `δ = s + 1 − r`. Then +`(s + 1)⌊x⌋ > (s + 1)(x − 1) = n² + xδ − (s + 1)`, so it is enough that `xδ ≥ r/2 + 1 ≥ s + 1 − p`. +This holds because `x ≥ r³` (as `D ≤ n`), `δ ≥ 1/(2r + 1)` (as `D` is an integer) and `r ≥ 4`. -/ +private theorem sq_lt_mul_queryCap_add (hD : 16 ≤ D) (hDn : D ≤ n) + (hp : Real.sqrt D / 2 ≤ (p : ℝ)) : n ^ 2 < (sOf D + 1) * queryCap n D + p := by + have hgap := one_le_gap_mul D + have hs := sOf_le_sqrt D + set r := Real.sqrt D with hr + set δ := (sOf D : ℝ) + 1 - r with hδ + set x := (n : ℝ) ^ 2 / r with hx + have hr4 : 4 ≤ r := four_le_sqrt hD + have hxr : x * r = (n : ℝ) ^ 2 := div_mul_cancel₀ _ (by linarith) + have hx3 : r ^ 3 ≤ x := by + rw [hx, le_div_iff₀ (by linarith)] + calc r ^ 3 * r = (r ^ 2) ^ 2 := by ring + _ = (D : ℝ) ^ 2 := by rw [hr, Real.sq_sqrt (Nat.cast_nonneg D)] + _ ≤ (n : ℝ) ^ 2 := by gcongr + have hxδ : r / 2 + 1 ≤ x * δ := by + refine le_of_mul_le_mul_right ?_ (show 0 < 2 * r + 1 by linarith) + calc (r / 2 + 1) * (2 * r + 1) ≤ r ^ 3 := by + -- `r³ ≥ 4r²` and `r² ≥ 4r` + linarith [mul_le_mul_of_nonneg_right hr4 (sq_nonneg r), + mul_le_mul_of_nonneg_right hr4 (show 0 ≤ r by linarith)] + _ ≤ x * 1 := by rw [mul_one]; exact hx3 + _ ≤ x * (δ * (2 * r + 1)) := + mul_le_mul_of_nonneg_left hgap (le_trans (by positivity) hx3) + _ = x * δ * (2 * r + 1) := by ring + have hfloor : x < (queryCap n D : ℝ) + 1 := by + rw [queryCap_eq_floor] + exact Nat.lt_floor_add_one _ + have hmain : (n : ℝ) ^ 2 < ((sOf D : ℝ) + 1) * (queryCap n D : ℝ) + p := + calc (n : ℝ) ^ 2 = x * r := hxr.symm + _ ≤ x * r + x * δ - ((sOf D : ℝ) + 1) + r / 2 := by + -- `s + 1 ≤ r + 1 ≤ xδ + r/2` + linarith [hxδ, hs] + _ = ((sOf D : ℝ) + 1) * (x - 1) + r / 2 := by rw [hδ]; ring + _ < ((sOf D : ℝ) + 1) * (queryCap n D : ℝ) + p := + add_lt_add_of_lt_of_le (mul_lt_mul_of_pos_left (by linarith [hfloor]) (by positivity)) hp + exact_mod_cast hmain + +/-- Proof of Theorem 17: the two facts used for the count of the instances, "s ≥ √D − 1 ≥ 3√D/4 +because D ≥ 16, and √D ≤ √n ≤ 2n/3 because D ≤ n and we may assume n ≥ 3". -/ +theorem le_sOf_and_sqrt_le (hD : 16 ≤ D) (hDn : D ≤ n) : + 3 * Real.sqrt D / 4 ≤ (sOf D : ℝ) ∧ Real.sqrt D ≤ 2 * (n : ℝ) / 3 := by + have hsqrtD := four_le_sqrt hD + have hsqrtn := four_le_sqrt (hD.trans hDn) + constructor + · calc 3 * Real.sqrt D / 4 ≤ Real.sqrt D - 1 := by linarith + _ ≤ (sOf D : ℝ) := by linarith [sqrt_lt_sOf_add_one D] + · calc Real.sqrt D ≤ Real.sqrt n := Real.sqrt_le_sqrt (by exact_mod_cast hDn) + _ ≤ 2 * (n : ℝ) / 3 := by + -- `n = √n √n ≥ 4√n` + linarith [mul_le_mul_of_nonneg_right hsqrtn (Real.sqrt_nonneg n), + Real.mul_self_sqrt (Nat.cast_nonneg n : (0 : ℝ) ≤ n)] + +section Matrices + +variable (wAB wBC wAC : Fin n → Fin n → ℤ) + +/-! ### The sets `W_ϱ` -/ + +/-- A pair lies in `W_ϱ` exactly if `ϱ` is the residue of its weight. -/ +theorem mem_residueClass (hp : p ≠ 0) (ϱ : Fin p) (q : Fin n × Fin n) : + q ∈ residueClass wAB p ϱ ↔ ϱ = resFin hp (wAB q.1 q.2) := by + rw [← modEq_iff_eq_resFin] + simp only [residueClass, Finset.mem_filter, Finset.mem_univ, true_and] + exact ⟨Int.ModEq.symm, Int.ModEq.symm⟩ + +/-- Proof of Theorem 17, the sets `W_ϱ` for `ϱ ∈ ℤ_p`: every pair `(a,b) ∈ A × B` lies in exactly +one of them. -/ +theorem existsUnique_mem_residueClass (hp : p ≠ 0) (q : Fin n × Fin n) : + ∃! ϱ : Fin p, q ∈ residueClass wAB p ϱ := + ⟨resFin hp (wAB q.1 q.2), (mem_residueClass wAB hp _ q).mpr rfl, + fun ϱ hϱ => (mem_residueClass wAB hp ϱ q).mp hϱ⟩ + +/-- The sets `W_ϱ` have `n²` pairs in all. -/ +private theorem sum_card_residueClass (hp : p ≠ 0) : + ∑ ϱ : Fin p, (residueClass wAB p ϱ).card = n ^ 2 := by + calc ∑ ϱ : Fin p, (residueClass wAB p ϱ).card + = ∑ ϱ : Fin p, ((Finset.univ : Finset (Fin n × Fin n)).filter + fun q => resFin hp (wAB q.1 q.2) = ϱ).card := by + refine Finset.sum_congr rfl fun ϱ _ => congrArg Finset.card ?_ + ext q + rw [mem_residueClass wAB hp] + simp [eq_comm] + _ = (Finset.univ : Finset (Fin n × Fin n)).card := + (Finset.card_eq_sum_card_fiberwise fun _ _ => Finset.mem_coe.mpr (Finset.mem_univ _)).symm + _ = n ^ 2 := by simp [sq] + +/-! ### The chunks -/ + +/-- Proof of Theorem 17: "cut it into chunks of at most n²/√D query pairs". (The chunks of `W_ϱ` +cover it: `biUnion_chunk`.) -/ +theorem card_chunkOf_le (hD : 1 ≤ D) (hDn : D ≤ n) (ϱ : Fin p) (j : ℕ) : + ((chunkOf wAB D p ϱ j).card : ℝ) ≤ (n : ℝ) ^ 2 / Real.sqrt D := + calc ((chunkOf wAB D p ϱ j).card : ℝ) ≤ (queryCap n D : ℝ) := by + exact_mod_cast card_chunk_le _ (one_le_queryCap hD hDn) j + _ ≤ (n : ℝ) ^ 2 / Real.sqrt D := by + rw [queryCap_eq_floor] + exact Nat.floor_le (by positivity) + +/-- There are at most `p + s` chunks in all. Each `W_ϱ` has at most `(|W_ϱ| + cap − 1)/cap` chunks, +where `cap = ⌊n²/√D⌋`, so `p + s + 1` chunks or more would need `n² ≥ (s + 1) cap + p` pairs. -/ +private theorem totalChunks_le_add_sOf (hD : 16 ≤ D) (hDn : D ≤ n) (hp0 : p ≠ 0) + (hp : Real.sqrt D / 2 ≤ (p : ℝ)) : totalChunks wAB D p ≤ p + sOf D := by + have hsum : totalChunks wAB D p * queryCap n D + p ≤ n ^ 2 + p * queryCap n D := by + have h := Finset.sum_le_sum fun ϱ (_ : ϱ ∈ (Finset.univ : Finset (Fin p))) => + numChunks_mul_le (residueClass wAB p ϱ) (queryCap n D) (one_le_queryCap (by omega) hDn) + simp only [Finset.sum_add_distrib, ← Finset.sum_mul, Finset.sum_const, Finset.card_univ, + Fintype.card_fin, smul_eq_mul, mul_one] at h + rwa [sum_card_residueClass wAB hp0] at h + have hpairs := sq_lt_mul_queryCap_add hD hDn hp + by_contra hmany + have hmul : (p + sOf D + 1) * queryCap n D ≤ totalChunks wAB D p * queryCap n D := + Nat.mul_le_mul_right _ (by omega) + rw [show (p + sOf D + 1) * queryCap n D = p * queryCap n D + (sOf D + 1) * queryCap n D by + ring] at hmul + -- `hsum` and `hmul` give `(s + 1) cap + p ≤ n²`, against `hpairs`. + omega + +/-- Proof of Theorem 17: "There are at most p + √D ≤ 2√D chunks in all." + +NOTE. A chunk holds a whole number of pairs, at most `⌊n²/√D⌋`, so the count is at most +`p + (n² − p)/⌊n²/√D⌋`, and the second term can exceed `√D` slightly. The printed bound is still +true, because the count is an integer, `D` is an integer and `D ≤ n` (`sq_lt_mul_queryCap_add`). -/ +theorem totalChunks_le (hD : 16 ≤ D) (hDn : D ≤ n) (hp : p ∈ primesInRange D) : + (totalChunks wAB D p : ℝ) ≤ (p : ℝ) + Real.sqrt D ∧ + (p : ℝ) + Real.sqrt D ≤ 2 * Real.sqrt D := by + obtain ⟨hprime, hge, hlt⟩ := mem_primesInRange.mp hp + have hnat : (totalChunks wAB D p : ℝ) ≤ (p : ℝ) + (sOf D : ℝ) := by + exact_mod_cast totalChunks_le_add_sOf wAB hD hDn hprime.ne_zero hge + exact ⟨by linarith [sOf_le_sqrt D], by linarith⟩ + +/-! ### The instances -/ + +/-- Proof of Theorem 17: "middle part C_k × ℤ_p, of size at most sp ≤ D". So each instance is an +instance of Lop-AE-SparseTri(n, D). -/ +theorem card_middle_lopInstance (hD : 16 ≤ D) (hg1 : 1 ≤ g) (hp : p ∈ primesInRange D) + (ι : InstanceIndex p) : Fintype.card (lopInstance wAB wBC wAC D g p ι).M ≤ D := by + obtain ⟨-, -, hlt⟩ := mem_primesInRange.mp hp + have hsp : (sOf D : ℝ) * (p : ℝ) ≤ D := + calc (sOf D : ℝ) * (p : ℝ) ≤ Real.sqrt D * Real.sqrt D := + mul_le_mul (sOf_le_sqrt D) hlt.le (Nat.cast_nonneg p) (Real.sqrt_nonneg _) + _ = D := Real.mul_self_sqrt (Nat.cast_nonneg D) + calc Fintype.card ({c : Fin n // c ∈ piece n D g ι.2.2} × Fin p) + = (piece n D g ι.2.2).card * p := by + rw [Fintype.card_prod, Fintype.card_coe, Fintype.card_fin] + _ ≤ sOf D * p := Nat.mul_le_mul_right p + ((card_piece_le hD hg1 n ι.2.2).trans (pieceSize_le_sOf hg1)) + _ ≤ D := by exact_mod_cast hsp + +/-- A query pair of the instance with index `(ϱ, j, k)` lies in `W_ϱ`, and `j` is the number of its +chunk. -/ +theorem mem_lopInstance_W {wAB wBC wAC : Fin n → Fin n → ℤ} + {ι : InstanceIndex p} {q : Fin n × Fin n} + (hq : q ∈ (lopInstance wAB wBC wAC D g p ι).W) : + q ∈ residueClass wAB p ι.1 ∧ rankIn (residueClass wAB p ι.1) q / queryCap n D = ι.2.1 := + Finset.mem_filter.mp hq + +/-- Proof of Theorem 17: "Within the chunk, the condition S(a,b,c) ≡ 0 (mod p) has become the +equality w(a,c) + ϱ ≡ −w(b,c) of a label of (a,c) and a label of (b,c)". -/ +theorem weightSum_modEq_zero_iff {p ϱ : ℤ} {a b : Fin n} (hϱ : wAB a b ≡ ϱ [ZMOD p]) (c : Fin n) : + weightSum wAB wBC wAC a b c ≡ 0 [ZMOD p] ↔ wAC a c + ϱ ≡ -wBC b c [ZMOD p] := by + rw [Int.modEq_iff_dvd, Int.modEq_iff_dvd, + show 0 - weightSum wAB wBC wAC a b c = -wBC b c - (wAC a c + ϱ) + (ϱ - wAB a b) by + simp only [weightSum]; ring] + exact dvd_add_left (Int.modEq_iff_dvd.mp hϱ) + +/-- Proof of Theorem 17: "a query pair (a,b) ∈ 𝒬 has a common neighbor if and only if some c ∈ C_k +has S(a,b,c) ≡ 0 (mod p)". -/ +theorem hasCommonNeighbor_lopInstance_iff (hp : p ≠ 0) + {ι : InstanceIndex p} {q : Fin n × Fin n} + (hq : q ∈ (lopInstance wAB wBC wAC D g p ι).W) : + (lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2 ↔ + ∃ c ∈ piece n D g ι.2.2, weightSum wAB wBC wAC q.1 q.2 c ≡ 0 [ZMOD (p : ℤ)] := by + have hϱ : wAB q.1 q.2 ≡ ((ι.1 : ℕ) : ℤ) [ZMOD (p : ℤ)] := + (Finset.mem_filter.mp (mem_lopInstance_W hq).1).2 + constructor + · -- A common neighbor `(c, σ)` carries the label `σ` of `(a,c)` and of `(b,c)`. + rintro ⟨⟨c, σ⟩, hA, hB⟩ + exact ⟨c, c.2, (weightSum_modEq_zero_iff wAB wBC wAC hϱ c).mpr (hA.symm.trans hB)⟩ + · -- If the two labels agree, the vertex `c` with that label is a common neighbor. + rintro ⟨c, hc, hS⟩ + have hB := resFin_modEq hp (-wBC q.2 c) + exact ⟨(⟨c, hc⟩, resFin hp (-wBC q.2 c)), + hB.trans ((weightSum_modEq_zero_iff wAB wBC wAC hϱ c).mp hS).symm, hB⟩ + +/-- Proof of Theorem 17, "For each chunk 𝒬 ⊆ W_ϱ and each piece C_k": `(ϱ, j, k)` is the index of an +instance exactly if `j` is the number of a chunk of `W_ϱ` and `k` the number of a piece. -/ +theorem mem_instanceIndices (ι : InstanceIndex p) : + ι ∈ instanceIndices wAB D g p ↔ + ι.2.1 < numChunks (residueClass wAB p ι.1) (queryCap n D) ∧ ι.2.2 < numPieces n D g := by + obtain ⟨ϱ, j, k⟩ := ι + simp only [instanceIndices, Finset.mem_biUnion, Finset.mem_univ, true_and, + Finset.mem_image, Finset.mem_product, Finset.mem_range, Prod.mk.injEq, Prod.exists] + exact ⟨fun ⟨_, _, _, h, h1, h2, h3⟩ => by subst h1 h2 h3; exact h, + fun h => ⟨ϱ, j, k, h, rfl, rfl, rfl⟩⟩ + +/-- There are as many instances as chunks times pieces. -/ +theorem card_instanceIndices (D g p : ℕ) : + (instanceIndices wAB D g p).card = totalChunks wAB D p * numPieces n D g := by + unfold instanceIndices totalChunks + rw [Finset.card_biUnion, Finset.sum_mul] + · refine Finset.sum_congr rfl fun ϱ _ => ?_ + rw [Finset.card_image_of_injective _ (Prod.mk_right_injective ϱ), Finset.card_product, + Finset.card_range, Finset.card_range] + · intro ϱ _ ϱ' _ hne + rw [Function.onFun, Finset.disjoint_left] + intro x hx hx' + obtain ⟨_, _, rfl⟩ := Finset.mem_image.mp hx + obtain ⟨_, _, h⟩ := Finset.mem_image.mp hx' + exact hne (congrArg Prod.fst h).symm + +/-- Proof of Theorem 17: "There are at most 2√D h ≤ 2√D(ng/s + 1) ≤ 4ng instances". -/ +theorem card_instanceIndices_le (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (hp : p ∈ primesInRange D) : + ((instanceIndices wAB D g p).card : ℝ) ≤ 4 * (n : ℝ) * (g : ℝ) := by + obtain ⟨hs, hsqrt⟩ := le_sOf_and_sqrt_le hD hDn + obtain ⟨hchunks, hchunks'⟩ := totalChunks_le wAB hD hDn hp + have hs0 := sOf_pos hD + have hpieces : (numPieces n D g : ℝ) ≤ (n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1 := + calc (numPieces n D g : ℝ) ≤ (⌈(n : ℝ) * (g : ℝ) / (sOf D : ℝ)⌉₊ : ℝ) := by + exact_mod_cast numPieces_le hD hg1 + _ ≤ (n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1 := (Nat.ceil_lt_add_one (by positivity)).le + -- `2√D · ng/s ≤ 8ng/3` because `s ≥ 3√D/4`, and `2√D ≤ 4n/3 ≤ 4ng/3`. + have hmain : + Real.sqrt D * ((n : ℝ) * (g : ℝ) / (sOf D : ℝ)) ≤ 4 / 3 * ((n : ℝ) * (g : ℝ)) := by + rw [← mul_div_assoc, div_le_iff₀ hs0] + calc Real.sqrt D * ((n : ℝ) * (g : ℝ)) + = 4 / 3 * ((n : ℝ) * (g : ℝ)) * (3 * Real.sqrt D / 4) := by ring + _ ≤ 4 / 3 * ((n : ℝ) * (g : ℝ)) * (sOf D : ℝ) := by gcongr + have hng : (n : ℝ) ≤ (n : ℝ) * (g : ℝ) := + le_mul_of_one_le_right (Nat.cast_nonneg n) (by exact_mod_cast hg1) + calc ((instanceIndices wAB D g p).card : ℝ) + = (totalChunks wAB D p : ℝ) * (numPieces n D g : ℝ) := by + rw [card_instanceIndices, Nat.cast_mul] + _ ≤ 2 * Real.sqrt D * (numPieces n D g : ℝ) := by gcongr; exact hchunks.trans hchunks' + _ ≤ 2 * Real.sqrt D * ((n : ℝ) * (g : ℝ) / (sOf D : ℝ) + 1) := by gcongr + _ ≤ 4 * (n : ℝ) * (g : ℝ) := by linarith [hmain, hng, hsqrt] + +end Matrices + +/-! ### The neighborhoods in an instance -/ + +/-- The graph of a function into `Fin p` has as many points as the domain. -/ +private theorem ncard_graph {α : Type} [Fintype α] (f : α → Fin p) : + {v : α × Fin p | v.2 = f v.1}.ncard = Fintype.card α := by + have h : {v : α × Fin p | v.2 = f v.1} = Set.univ.graphOn f := by + ext v + simp [Set.mem_graphOn, eq_comm] + rw [h, Set.ncard_graphOn, Set.ncard_univ, Nat.card_eq_fintype_card] + +/-- Section 3.1, "The reductions below [...] produce at most n²/√D query pairs and sets of at most +√D elements", and Remark 20, "every vertex of A has O(√D/g) neighbors in the middle part": in an +instance of the reduction, a vertex of `A` or of `B` has exactly one neighbor `(c, σ)` for every +`c ∈ C_k`, hence at most `⌈s/g⌉` neighbors. (And `⌈s/g⌉ ≤ s ≤ √D`; for `g ≤ √D` also +`⌈s/g⌉ ≤ 2√D/g`, `pieceSize_le_two_mul_sqrt_div`.) -/ +theorem ncard_nbr_lopInstance_le (wAB wBC wAC : Fin n → Fin n → ℤ) (hD : 16 ≤ D) + (hg1 : 1 ≤ g) (hp : p ≠ 0) (ι : InstanceIndex p) : + (∀ a, {v | (lopInstance wAB wBC wAC D g p ι).adjA a v}.ncard ≤ pieceSize D g) ∧ + (∀ b, {v | (lopInstance wAB wBC wAC D g p ι).adjB v b}.ncard ≤ pieceSize D g) := by + -- The neighbors of a vertex are the graph of the function that sends `c` to its label. + have hgraph : ∀ (f : {c : Fin n // c ∈ piece n D g ι.2.2} → ℤ), + {v : {c : Fin n // c ∈ piece n D g ι.2.2} × Fin p | + ((v.2 : ℕ) : ℤ) ≡ f v.1 [ZMOD (p : ℤ)]}.ncard ≤ pieceSize D g := by + intro f + refine le_trans (le_of_eq ?_) (card_piece_le hD hg1 n ι.2.2) + rw [← Fintype.card_coe, ← ncard_graph fun c => resFin hp (f c)] + congr 1 + ext v + exact modEq_iff_eq_resFin hp v.2 _ + exact ⟨fun a => hgraph fun c => wAC a c + ((ι.1 : ℕ) : ℤ), fun b => hgraph fun c => -wBC b c⟩ + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean new file mode 100644 index 0000000000..0b5e39cb90 --- /dev/null +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -0,0 +1,398 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Splitting +public import Mathlib.Combinatorics.Enumerative.DoubleCounting +import Mathlib.Analysis.Complex.ExponentialBounds + +/-! +# Finite witness recovery from modular lopsided answers + +Correct finite answers are ordinary `Set.EqOn` premises on the actual lopsided graphs. +The recovery scans each accepted piece until it finds an exact zero. Distinct failed scans +contain distinct modular false positives. No solver, native program or time oracle is assumed. + +## Main definitions + +* `scanPiece`, `runScans`: finite witness search and number of scans. +* `AnswersCorrect`, `IsScanOrder`: correctness on queried pairs and a duplicate-free scan order. + +## Main results + +* `recovery_correct`: sound and complete recovery with at most `falsePositiveCount + 1` scans. +* `card_failedScans_le`: false-positive accounting. +* `falsePositiveCount_add_one_mul_pieceSize_le`: bound on the number of vertices scanned. + +## References + +Adapted from [Witnesses.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Witnesses.lean) and the minimal scan definitions in +`PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +Context: [AlmanWilliams2026, Section 3]. Source-derived prose is paraphrased. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ExactTriangle + +open Cslib.Matrix + +/-- A finite search result returns a zero triangle, or certifies that no zero triangle exists. +This specifies mathematical correctness only. -/ +def IsSearchAnswer {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) + (r : Option (Fin n × Fin n × Fin n)) : Prop := + (∀ a b c, r = some (a, b, c) → weightSum wAB wBC wAC a b c = 0) ∧ + (r = none → ¬ HasZeroTriangle wAB wBC wAC) + +/-- Proof of Theorem 17: "scan the piece C_k of its instance for a c with S(a,b,c) = 0". The +vertices of the piece are tried in increasing order; the result is the first `c` found, or `none` if +the scan fails. -/ +def scanPiece {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g k : ℕ) (a b : Fin n) : + Option (Fin n) := + ((List.finRange n).filter fun c => c ∈ piece n D g k).find? fun c => + weightSum wAB wBC wAC a b c = 0 + +/-- Proof of Theorem 17: "For every query pair that the oracle accepts". `ans ι` is the oracle's +answer to the instance with index `ι`. The result is the set of all (instance, query pair of that +instance) that the oracle accepts; each of them calls for one scan. -/ +def acceptedPairs {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Finset (InstanceIndex p × (Fin n × Fin n)) := + ((instanceIndices wAB D g p) ×ˢ Finset.univ).filter fun (ι, q) => + q ∈ (lopInstance wAB wBC wAC D g p ι).W ∧ ans ι q = true + +/-- Proof of Theorem 17: the scans, one after the other, over a list of accepted pairs: "We stop as +soon as a zero triangle is found." The result is the triangle found (or `none`) together with the +number of scans that were carried out. -/ +def runScans {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) {p : ℕ} : + List (InstanceIndex p × (Fin n × Fin n)) → Option (Fin n × Fin n × Fin n) × ℕ + | [] => (none, 0) + | ((_, _, k), a, b) :: rest => + match scanPiece wAB wBC wAC D g k a b with + | some c => (some (a, b, c), 1) + | none => ((runScans wAB wBC wAC D g rest).1, (runScans wAB wBC wAC D g rest).2 + 1) + +/-- Proof of Theorem 17: "For every query pair that the oracle accepts, scan [...]". The paper does +not say in which order. `L` is an order of the scans: a list in which every accepted pair occurs +exactly once. -/ +def IsScanOrder {n : ℕ} (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) (L : List (InstanceIndex p × (Fin n × Fin n))) : + Prop := + L.Nodup ∧ ∀ x, x ∈ L ↔ x ∈ acceptedPairs wAB wBC wAC D g p ans + + +variable {n D g p : ℕ} + +section Matrices + +variable {wAB wBC wAC : Fin n → Fin n → ℤ} + +/-! ### Scans and accepted pairs -/ + +/-- Proof of Theorem 17, "scan the piece C_k of its instance for a c with S(a,b,c) = 0": a `c` that +a scan returns lies in the piece and has `S(a,b,c) = 0`. -/ +theorem scanPiece_eq_some {k : ℕ} {a b c : Fin n} (h : scanPiece wAB wBC wAC D g k a b = some c) : + c ∈ piece n D g k ∧ weightSum wAB wBC wAC a b c = 0 := by + have hmem := List.mem_of_find?_eq_some h + rw [List.mem_filter] at hmem + exact ⟨by simpa using hmem.2, by simpa using List.find?_some h⟩ + +/-- A scan fails exactly if the piece has no `c` with `S(a,b,c) = 0`. -/ +theorem scanPiece_eq_none_iff {k : ℕ} {a b : Fin n} : + scanPiece wAB wBC wAC D g k a b = none ↔ + ∀ c ∈ piece n D g k, weightSum wAB wBC wAC a b c ≠ 0 := by + unfold scanPiece + rw [List.find?_eq_none] + simp [List.mem_filter] + +variable (wAB wBC wAC) (D g) + +/-- The scan that belongs to `x = ((ϱ, j, k), (a, b))`, a query pair `(a, b)` of the instance with +index `(ϱ, j, k)`: the piece `C_k` is scanned for the pair `(a, b)`. -/ +abbrev scanOf (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) + (x : InstanceIndex p × (Fin n × Fin n)) : Option (Fin n) := + scanPiece wAB wBC wAC D g x.1.2.2 x.2.1 x.2.2 + +/-- What the scans over a list of pairs return: a triangle that is returned is a zero triangle; if +none is returned, then every scan failed; and all the scans that are carried out, except possibly +the last one, fail. -/ +private theorem runScans_spec (wAB wBC wAC : Fin n → Fin n → ℤ) (D g : ℕ) + (l : List (InstanceIndex p × (Fin n × Fin n))) : + (∀ a b c, (runScans wAB wBC wAC D g l).1 = some (a, b, c) → weightSum wAB wBC wAC a b c = 0) ∧ + ((runScans wAB wBC wAC D g l).1 = none → ∀ x ∈ l, scanOf wAB wBC wAC D g x = none) ∧ + (runScans wAB wBC wAC D g l).2 ≤ + (l.filter fun x => decide (scanOf wAB wBC wAC D g x = none)).length + 1 := by + induction l with + | nil => simp [runScans] + | cons x rest ih => + obtain ⟨ihzero, ihnone, ihcount⟩ := ih + cases hscan : scanOf wAB wBC wAC D g x with + | some c => + -- The first scan finds `c`, and the scans stop. + have hrun : runScans wAB wBC wAC D g (x :: rest) = (some (x.2.1, x.2.2, c), 1) := by + simp only [runScans, hscan] + rw [hrun] + refine ⟨fun a b c' h => ?_, fun h => by simp at h, by simp⟩ + simp only [Option.some.injEq, Prod.mk.injEq] at h + obtain ⟨rfl, rfl, rfl⟩ := h + exact (scanPiece_eq_some hscan).2 + | none => + -- The first scan fails, and the scans go on with the rest of the list. + have hrun : runScans wAB wBC wAC D g (x :: rest) + = ((runScans wAB wBC wAC D g rest).1, (runScans wAB wBC wAC D g rest).2 + 1) := by + simp only [runScans, hscan] + rw [hrun, List.filter_cons_of_pos (by simpa using hscan), List.length_cons] + refine ⟨ihzero, fun h y hy => ?_, by omega⟩ + rcases List.mem_cons.mp hy with rfl | hy + · exact hscan + · exact ihnone h y hy + +variable (p) (ans : InstanceIndex p → Fin n × Fin n → Bool) + +/-- The hypothesis on the oracle: `ans ι` is a correct answer to the instance with index `ι`, for +every instance of the reduction. -/ +def AnswersCorrect (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Prop := + ∀ ι ∈ instanceIndices wAB D g p, Set.EqOn (fun q => ans ι q = true) + (fun q => (lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2) + ((lopInstance wAB wBC wAC D g p ι).W : Set _) + +/-- Proof of Theorem 17: a "failed scan, of a piece C_k for a pair (a,b)": the oracle accepted the +pair in the instance, and the piece contains no `c` with `S(a,b,c) = 0`. -/ +def failedScans (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : Finset (InstanceIndex p × (Fin n × Fin n)) := + (acceptedPairs wAB wBC wAC D g p ans).filter fun x => scanOf wAB wBC wAC D g x = none + +variable {wAB wBC wAC D g p ans} + +/-- The accepted pairs: an index of an instance, a query pair of that instance, and the answer +"yes". -/ +theorem mem_acceptedPairs {ι : InstanceIndex p} {q : Fin n × Fin n} : + (ι, q) ∈ acceptedPairs wAB wBC wAC D g p ans ↔ + ι ∈ instanceIndices wAB D g p ∧ q ∈ (lopInstance wAB wBC wAC D g p ι).W ∧ ans ι q = true := by + simp [acceptedPairs] + +/-! ### The sentences of the paragraph "Witnesses" -/ + +/-- Proof of Theorem 17: "A zero triangle is always found, since its c lies in some piece and makes +the oracle accept its pair." -/ +theorem exists_mem_acceptedPairs (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) {a b c : Fin n} + (h0 : weightSum wAB wBC wAC a b c = 0) : + ∃ ϱ j k, ((ϱ, j, k), (a, b)) ∈ acceptedPairs wAB wBC wAC D g p ans ∧ c ∈ piece n D g k := by + -- The piece of `c`, the residue class of `(a, b)` and the chunk of `(a, b)` in it. + obtain ⟨k, ⟨hk, hck⟩, -⟩ := existsUnique_mem_piece hD hg1 c + obtain ⟨ϱ, hab, -⟩ := existsUnique_mem_residueClass wAB hp (a, b) + obtain ⟨j, hj, hW⟩ := Finset.mem_biUnion.mp + ((biUnion_chunk (residueClass wAB p ϱ) (one_le_queryCap (by omega) hDn)).symm ▸ hab) + have hι : ((ϱ, j, k) : InstanceIndex p) ∈ instanceIndices wAB D g p := + (mem_instanceIndices wAB _).mpr ⟨Finset.mem_range.mp hj, hk⟩ + have hquery : (a, b) ∈ (lopInstance wAB wBC wAC D g p (ϱ, j, k)).W := hW + refine ⟨ϱ, j, k, mem_acceptedPairs.mpr ⟨hι, hquery, ?_⟩, hck⟩ + -- The vertex `c` makes the oracle accept the pair. + apply (Iff.of_eq (hans _ hι hquery)).mpr + rw [hasCommonNeighbor_lopInstance_iff wAB wBC wAC hp hquery] + exact ⟨c, hck, by rw [show weightSum wAB wBC wAC a b c = 0 from h0]⟩ + +/-- Proof of Theorem 17: "A failed scan, of a piece C_k for a pair (a,b), contains a c ∈ C_k with +S(a,b,c) ≡ 0 (mod p) but S(a,b,c) ≠ 0, that is, a false positive (a,b,c) of p". -/ +theorem exists_isFalsePositive_of_mem_failedScans (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) + {ϱ : Fin p} {j k : ℕ} {a b : Fin n} + (hx : ((ϱ, j, k), (a, b)) ∈ failedScans wAB wBC wAC D g p ans) : + ∃ c ∈ piece n D g k, IsFalsePositive wAB wBC wAC p (a, b, c) := by + obtain ⟨hacc, hnone⟩ := Finset.mem_filter.mp hx + obtain ⟨hι, hquery, htrue⟩ := mem_acceptedPairs.mp hacc + obtain ⟨c, hc, hS⟩ := (hasCommonNeighbor_lopInstance_iff wAB wBC wAC hp hquery).mp + ((Iff.of_eq (hans _ hι hquery)).mp htrue) + exact ⟨c, hc, scanPiece_eq_none_iff.mp hnone c hc, Int.modEq_zero_iff_dvd.mp hS⟩ + +/-- Proof of Theorem 17: "distinct scans contain distinct false positives": a triple `(a,b,c)` +belongs to the scan of at most one accepted pair. -/ +theorem eq_of_mem_acceptedPairs {ϱ ϱ' : Fin p} {j j' k k' : ℕ} {q : Fin n × Fin n} + (hx : ((ϱ, j, k), q) ∈ acceptedPairs wAB wBC wAC D g p ans) + (hy : ((ϱ', j', k'), q) ∈ acceptedPairs wAB wBC wAC D g p ans) {c : Fin n} + (hc : c ∈ piece n D g k) + (hc' : c ∈ piece n D g k') : (ϱ, j, k) = (ϱ', j', k') := by + have hp : p ≠ 0 := by + rintro rfl + exact ϱ.elim0 + obtain ⟨hclass, hchunk⟩ := mem_lopInstance_W (mem_acceptedPairs.mp hx).2.1 + obtain ⟨hclass', hchunk'⟩ := mem_lopInstance_W (mem_acceptedPairs.mp hy).2.1 + -- The pair determines its residue class, then its chunk; the vertex `c` determines the piece. + obtain rfl : ϱ = ϱ' := (existsUnique_mem_residueClass wAB hp q).unique hclass hclass' + obtain rfl : j = j' := hchunk.symm.trans hchunk' + obtain rfl : k = k' := ((mem_piece c).mp hc).symm.trans ((mem_piece c).mp hc') + rfl + +/-- Proof of Theorem 17: "distinct scans contain distinct false positives, so there are at most F(p) +failed scans", for any finite family `s` of scans and any notion "the scan `x` contains the triple +`t`". This applies to the accepted-pair scans in `card_failedScans_le`. -/ +theorem card_le_falsePositiveCount_of_distinct_scans + (wAB wBC wAC : Fin n → Fin n → ℤ) {σ : Type*} (s : Finset σ) + (contains : σ → Fin n × Fin n × Fin n → Prop) + (hfalse : ∀ x ∈ s, ∃ t, contains x t ∧ IsFalsePositive wAB wBC wAC p t) + (hdistinct : ∀ t, ∀ x ∈ s, ∀ y ∈ s, contains x t → contains y t → x = y) : + s.card ≤ falsePositiveCount wAB wBC wAC p := by + classical + refine Finset.card_le_card_of_forall_subsingleton contains (fun x hx => ?_) + fun t _ x hx y hy => hdistinct t x hx.1 y hy.1 hx.2 hy.2 + obtain ⟨t, ht, hfp⟩ := hfalse x hx + exact ⟨t, Finset.mem_filter.mpr ⟨Finset.mem_univ _, hfp⟩, ht⟩ + +/-- Proof of Theorem 17: "so there are at most F(p) failed scans". -/ +theorem card_failedScans_le (hp : p ≠ 0) + (hans : AnswersCorrect wAB wBC wAC D g p ans) : + (failedScans wAB wBC wAC D g p ans).card ≤ falsePositiveCount wAB wBC wAC p := by + -- The scan of the piece `C_k` for the pair `(a, b)` contains the triples `(a, b, c)`, `c ∈ C_k`. + refine card_le_falsePositiveCount_of_distinct_scans wAB wBC wAC _ + (fun x t => x.2 = (t.1, t.2.1) ∧ t.2.2 ∈ piece n D g x.1.2.2) ?_ ?_ + · rintro ⟨⟨ϱ, j, k⟩, a, b⟩ hx + obtain ⟨c, hc, hfalse⟩ := exists_isFalsePositive_of_mem_failedScans hp hans hx + exact ⟨(a, b, c), ⟨rfl, hc⟩, hfalse⟩ + · rintro ⟨a, b, c⟩ ⟨⟨ϱ, j, k⟩, q⟩ hx ⟨⟨ϱ', j', k'⟩, q'⟩ hy ⟨rfl, hc⟩ ⟨rfl, hc'⟩ + rw [eq_of_mem_acceptedPairs (Finset.mem_filter.mp hx).1 (Finset.mem_filter.mp hy).1 hc hc'] + +end Matrices + +/-- Proof of Theorem 17: the length of a scan, the `O(√D/g)` in "the scans cost O((F(p) + 1)√D/g)": +a piece has at most `⌈s/g⌉ ≤ 2√D/g` vertices. -/ +theorem pieceSize_le_two_mul_sqrt_div (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) : + (pieceSize D g : ℝ) ≤ 2 * Real.sqrt D / (g : ℝ) := by + have hg0 : (0 : ℝ) < g := by exact_mod_cast hg1 + calc (pieceSize D g : ℝ) ≤ (sOf D : ℝ) / (g : ℝ) + 1 := by + rw [pieceSize_eq_ceil] + exact (Nat.ceil_lt_add_one (by positivity)).le + _ ≤ Real.sqrt D / (g : ℝ) + Real.sqrt D / (g : ℝ) := + add_le_add (div_le_div_of_nonneg_right (sOf_le_sqrt D) hg0.le) + ((one_le_div hg0).mpr hg) + _ = 2 * Real.sqrt D / (g : ℝ) := by ring + +/-- Proof of Theorem 17: "the scans cost O((F(p) + 1)√D/g) = O(ν n³ log n/g)". -/ +theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) + (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : IsSelectedPrime wAB wBC wAC D p) : + ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) + ≤ (2 * Hashing.falsePositiveConst + 2) * (κ * (n : ℝ) ^ 3 * Real.log n / (g : ℝ)) := by + have hsqrt : 4 ≤ Real.sqrt (D : ℝ) := + Real.le_sqrt_of_sq_le (by norm_num; exact_mod_cast hD) + have hn : (16 : ℝ) ≤ n := by exact_mod_cast hD.trans hDn + have hlog : 1 ≤ Real.log n := + (Real.le_log_iff_exp_le (by linarith)).2 (Real.exp_one_lt_three.le.trans (by linarith)) + set B := κ * (n : ℝ) ^ 3 * Real.log n with hB + -- The two terms: `F(p) √D = O(B)` by the bound on `F(p)`, and `√D ≤ n ≤ n³ ≤ B`. + have hfalse : (falsePositiveCount wAB wBC wAC p : ℝ) * Real.sqrt D ≤ + Hashing.falsePositiveConst * B := by + have hF := Hashing.falsePositiveCount_le_const_mul hD hDn hκ wAB wBC wAC hT hp + rwa [← mul_div_assoc, le_div_iff₀ (by linarith)] at hF + have hone : Real.sqrt D ≤ B := + calc Real.sqrt D ≤ n := sqrt_le_natCast (by omega) hDn + _ ≤ (n : ℝ) ^ 3 := le_self_pow₀ (by linarith) (by norm_num) + _ ≤ κ * (n : ℝ) ^ 3 := le_mul_of_one_le_left (by positivity) hκ + _ ≤ B := le_mul_of_one_le_right (by positivity) hlog + calc ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (pieceSize D g : ℝ) + ≤ ((falsePositiveCount wAB wBC wAC p : ℝ) + 1) * (2 * Real.sqrt D / (g : ℝ)) := by + gcongr + exact pieceSize_le_two_mul_sqrt_div hg1 hg + _ = (2 * ((falsePositiveCount wAB wBC wAC p : ℝ) * Real.sqrt D) + + 2 * Real.sqrt D) / (g : ℝ) := by ring + _ ≤ (2 * (Hashing.falsePositiveConst * B) + 2 * B) / (g : ℝ) := by gcongr + _ = (2 * Hashing.falsePositiveConst + 2) * (B / (g : ℝ)) := by ring + +/-! ### The output of the reduction -/ + +/-- **Theorem 17, correctness.** Given any correct answers of an oracle for +Lop-AE-SparseTri to the instances of the reduction, and whatever the order `L` of the scans, the +reduction's output is correct: it is a zero triangle if one exists, and `none` otherwise. It +carries out at most `F(p) + 1` scans ("We stop as soon as a zero triangle is found"). -/ +theorem recovery_correct (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (wAB wBC wAC : Fin n → Fin n → ℤ) (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + IsSearchAnswer wAB wBC wAC (runScans wAB wBC wAC D g L).1 ∧ + (runScans wAB wBC wAC D g L).2 ≤ falsePositiveCount wAB wBC wAC p + 1 := by + have hp0 : p ≠ 0 := (mem_primesInRange.mp hp.1).1.ne_zero + obtain ⟨hzero, hnone, hcount⟩ := runScans_spec wAB wBC wAC D g L + refine ⟨⟨hzero, fun hout ⟨a, b, c, h0⟩ => ?_⟩, ?_⟩ + · -- "A zero triangle is always found": its scan cannot have failed. + obtain ⟨ϱ, j, k, hx, hc⟩ := exists_mem_acceptedPairs hD hDn hg1 hp0 hans h0 + exact scanPiece_eq_none_iff.mp (hnone hout _ ((hL.2 _).mpr hx)) c hc h0 + · -- All the scans except possibly the last one fail, and there are at most `F(p)` failed scans. + have hfailed : (L.filter fun x => decide (scanOf wAB wBC wAC D g x = none)).length + ≤ (failedScans wAB wBC wAC D g p ans).card := by + rw [← List.toFinset_card_of_nodup (hL.1.filter _)] + refine Finset.card_le_card fun x hx => ?_ + obtain ⟨hxL, hxnone⟩ := List.mem_filter.mp (List.mem_toFinset.mp hx) + exact Finset.mem_filter.mpr ⟨(hL.2 x).mp hxL, by simpa using hxnone⟩ + have hF := card_failedScans_le hp0 hans + omega + + +/-- Every finite set of accepted queries admits a duplicate-free scan order. -/ +theorem exists_isScanOrder (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (ans : InstanceIndex p → Fin n × Fin n → Bool) : + ∃ L, IsScanOrder wAB wBC wAC D g p ans L := + ⟨(acceptedPairs wAB wBC wAC D g p ans).toList, + Finset.nodup_toList _, fun _ => Finset.mem_toList⟩ + +/-- Correct finite answer tables exist. This is a finite specification, with no solver or +complexity claim. Values outside the queried edges remain unconstrained. -/ +theorem exists_answersCorrect (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) : + ∃ ans, AnswersCorrect wAB wBC wAC D g p ans := by + classical + refine ⟨fun ι q => decide ((lopInstance wAB wBC wAC D g p ι).HasCommonNeighbor q.1 q.2), ?_⟩ + intro ι _ q hq + simp + +/-- Correct finite common-neighbor counts supply the Boolean answers used by recovery. +This connects directly to the lopsided matrix/counting interface. -/ +theorem answersCorrect_of_counting (wAB wBC wAC : Fin n → Fin n → ℤ) (D g p : ℕ) + (counts : InstanceIndex p → Fin n × Fin n → ℕ) + (hcounts : ∀ ι ∈ instanceIndices wAB D g p, + Set.EqOn (counts ι) (fun q => (lopInstance wAB wBC wAC D g p ι).commonNeighborCount q.1 q.2) + ((lopInstance wAB wBC wAC D g p ι).W : Set _)) : + AnswersCorrect wAB wBC wAC D g p (fun ι q => decide (counts ι q ≠ 0)) := by + intro ι hι q hq + apply propext + simp only [decide_eq_true_eq] + exact Iff.of_eq ((lopInstance wAB wBC wAC D g p ι).detection_eqOn_of_counting + (counts ι) (hcounts ι hι) hq) + +/-- The recovery scan examines at most the uniform false-positive bound times the piece size. +This counts candidate vertices only; it is not a charged native running-time theorem. -/ +theorem recovery_vertexBound {κ : ℝ} (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) + (wAB wBC wAC : Fin n → Fin n → ℤ) + (hw : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) + (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + IsSearchAnswer wAB wBC wAC (runScans wAB wBC wAC D g L).1 ∧ + ((runScans wAB wBC wAC D g L).2 : ℝ) * pieceSize D g ≤ + (2 * Hashing.falsePositiveConst + 2) * (κ * (n : ℝ) ^ 3 * Real.log n / (g : ℝ)) := by + obtain ⟨hcorrect, hcount⟩ := recovery_correct hD hDn hg1 wAB wBC wAC hp hans hL + refine ⟨hcorrect, le_trans ?_ (falsePositiveCount_add_one_mul_pieceSize_le + wAB wBC wAC hD hDn hg1 hg hκ hw hp)⟩ + gcongr + exact_mod_cast hcount + +/-- Finite reduction equivalence: scanning all accepted queries returns `none` exactly when the +original three matrices have no zero triangle. Any duplicate-free scan order works. -/ +theorem recovery_eq_none_iff (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) + (wAB wBC wAC : Fin n → Fin n → ℤ) (hp : IsSelectedPrime wAB wBC wAC D p) + {ans : InstanceIndex p → Fin n × Fin n → Bool} (hans : AnswersCorrect wAB wBC wAC D g p ans) + {L : List (InstanceIndex p × (Fin n × Fin n))} (hL : IsScanOrder wAB wBC wAC D g p ans L) : + (runScans wAB wBC wAC D g L).1 = none ↔ ¬ HasZeroTriangle wAB wBC wAC := by + obtain ⟨hsound, hcomplete⟩ := (recovery_correct hD hDn hg1 wAB wBC wAC hp hans hL).1 + refine ⟨hcomplete, fun hn => ?_⟩ + cases h : (runScans wAB wBC wAC D g L).1 with + | none => rfl + | some t => + obtain ⟨a, b, c⟩ := t + exact False.elim (hn ⟨a, b, c, hsound a b c h⟩) + +end Cslib.Algorithms.ExactTriangle diff --git a/Complexity/Foundations/Data/Finset/Quotient.lean b/Complexity/Foundations/Data/Finset/Quotient.lean new file mode 100644 index 0000000000..875f7e52d7 --- /dev/null +++ b/Complexity/Foundations/Data/Finset/Quotient.lean @@ -0,0 +1,44 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Order.Interval.Finset.Nat + +/-! +# Cardinality of a quotient fiber + +## Main results + +* `Finset.card_filter_div_eq_le`: an injectively indexed quotient fiber has at most the divisor + many members. + +## References + +Adapted from [Counting.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Counting.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Finset + +/-- At most `c` elements `x` of `s` have `⌊f x / c⌋ = k`, if `f` is injective on `s`: their values +lie in `[kc, kc + c)`. -/ +theorem card_filter_div_eq_le {α : Type*} (s : Finset α) {f : α → ℕ} (hf : Set.InjOn f s) + {c : ℕ} (hc : 0 < c) (k : ℕ) : (s.filter fun x => f x / c = k).card ≤ c := by + have hmaps : ∀ x ∈ s.filter fun x => f x / c = k, f x ∈ Finset.Ico (k * c) (k * c + c) := by + intro x hx + obtain ⟨-, rfl⟩ := Finset.mem_filter.mp hx + rw [Finset.mem_Ico, ← Nat.succ_mul] + exact ⟨Nat.div_mul_le_self _ _, (Nat.div_lt_iff_lt_mul hc).mp (Nat.lt_succ_self _)⟩ + calc (s.filter fun x => f x / c = k).card ≤ (Finset.Ico (k * c) (k * c + c)).card := + Finset.card_le_card_of_injOn f hmaps (hf.mono fun x hx => (Finset.mem_filter.mp hx).1) + _ = c := by rw [Nat.card_Ico, Nat.add_sub_cancel_left] + +end Finset diff --git a/Complexity/Foundations/Data/Int/Residue.lean b/Complexity/Foundations/Data/Int/Residue.lean new file mode 100644 index 0000000000..f6622c6a71 --- /dev/null +++ b/Complexity/Foundations/Data/Int/Residue.lean @@ -0,0 +1,41 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Ring.Int + +/-! +# Integer residues as natural numbers + +## Main results + +* `Int.natCast_toNat_emod`: the nonnegative integer residue survives a round trip through `Nat`. +* `Int.toNat_emod_lt`: the natural residue is below a positive modulus. + +## References + +Adapted from [Index.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Index.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The existing Exact Triangle hashing client now uses these shared facts. +-/ + +@[expose] public section + +namespace Int + +theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : + ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by + have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) + have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) + omega + +end Int diff --git a/Complexity/Foundations/Data/Matrix/PairChunks.lean b/Complexity/Foundations/Data/Matrix/PairChunks.lean new file mode 100644 index 0000000000..3f3c74fee3 --- /dev/null +++ b/Complexity/Foundations/Data/Matrix/PairChunks.lean @@ -0,0 +1,114 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Nat.CeilDiv +public import Complexity.Foundations.Data.Finset.Quotient +public import Mathlib.Logic.Equiv.Fin.Basic +public import Mathlib.Data.Fintype.Prod +import Mathlib.Order.Interval.Finset.Fin + +/-! +# Chunks of matrix index pairs + +## Main definitions + +* `pairIndex`, `rankIn`: row-major index and rank in a finite set. +* `chunk`, `numChunks`: consecutive chunks and their number. + +## Main results + +* `card_chunk_le`: each chunk has at most the capacity many pairs. +* `biUnion_chunk`: all chunks cover the input finite set. + +## References + +Adapted from [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and the minimal definitions in +`PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +There are no graph or weight assumptions. +-/ + +@[expose] public section + +namespace Cslib.Matrix + +/-- The position of the pair `(a, b)` in the row-major order of `A × B`. -/ +def pairIndex {n : ℕ} (q : Fin n × Fin n) : ℕ := q.1.val * n + q.2.val + +/-- The number of pairs of `S` that come before `q` in row-major order. -/ +def rankIn {n : ℕ} (S : Finset (Fin n × Fin n)) (q : Fin n × Fin n) : ℕ := + (S.filter fun r => pairIndex r < pairIndex q).card + +/-- The `j`-th chunk (`j = 0, 1, …`) of `S` when `S` is cut, in row-major order, into chunks of +`cap` pairs each (the last one may be smaller). -/ +def chunk {n : ℕ} (S : Finset (Fin n × Fin n)) (cap j : ℕ) : Finset (Fin n × Fin n) := + S.filter fun q => rankIn S q / cap = j + +/-- The number `⌈|S| / cap⌉` of nonempty chunks of `S` (for `cap ≥ 1`). -/ +def numChunks {n : ℕ} (S : Finset (Fin n × Fin n)) (cap : ℕ) : ℕ := + S.card ⌈/⌉ cap + +variable {n : ℕ} {S : Finset (Fin n × Fin n)} {cap : ℕ} + +/-! ### Ranks -/ + +/-- The position in row-major order determines the pair. -/ +theorem pairIndex_injective : Function.Injective (pairIndex (n := n)) := fun q r h => + finProdFinEquiv.injective (Fin.ext (by simpa [pairIndex, add_comm, mul_comm] using h)) + +/-- Among the pairs of `S`, a pair that comes earlier in row-major order has a smaller rank. -/ +theorem rankIn_lt_rankIn {q r : Fin n × Fin n} (hq : q ∈ S) + (h : pairIndex q < pairIndex r) : rankIn S q < rankIn S r := by + refine Finset.card_lt_card ⟨fun x hx => ?_, fun hsub => ?_⟩ + · rw [Finset.mem_filter] at hx ⊢ + exact ⟨hx.1, hx.2.trans h⟩ + · exact lt_irrefl _ (Finset.mem_filter.mp (hsub (Finset.mem_filter.mpr ⟨hq, h⟩))).2 + +/-- Different pairs of `S` have different ranks. -/ +theorem rankIn_injOn (S : Finset (Fin n × Fin n)) : Set.InjOn (rankIn S) S := by + intro q hq r hr h + rcases lt_trichotomy (pairIndex q) (pairIndex r) with hlt | heq | hgt + · exact absurd h (rankIn_lt_rankIn hq hlt).ne + · exact pairIndex_injective heq + · exact absurd h (rankIn_lt_rankIn hr hgt).ne' + +/-- The rank of a pair of `S` is less than the number of pairs of `S`. -/ +theorem rankIn_lt_card {q : Fin n × Fin n} (hq : q ∈ S) : rankIn S q < S.card := + Finset.card_lt_card ⟨Finset.filter_subset _ _, + fun hsub => lt_irrefl _ (Finset.mem_filter.mp (hsub hq)).2⟩ + +/-- In the full matrix, row-major rank is the row-major index itself. This avoids enumerating +all preceding pairs when applying the chunk API to a full matrix. -/ +theorem rankIn_univ (q : Fin n × Fin n) : + rankIn (Finset.univ : Finset (Fin n × Fin n)) q = pairIndex q := by + calc rankIn (Finset.univ : Finset (Fin n × Fin n)) q + = (Finset.Iio (finProdFinEquiv q)).card := by + apply Finset.card_equiv finProdFinEquiv + intro r + simp [pairIndex, finProdFinEquiv, add_comm, mul_comm, Fin.lt_def] + _ = pairIndex q := by simp [pairIndex, finProdFinEquiv, add_comm, mul_comm] + +/-! ### Chunks -/ + +/-- A chunk has at most `cap` pairs: their ranks are different numbers in `[j cap, (j + 1) cap)`. -/ +theorem card_chunk_le (S : Finset (Fin n × Fin n)) (hcap : 1 ≤ cap) (j : ℕ) : + (chunk S cap j).card ≤ cap := + S.card_filter_div_eq_le (rankIn_injOn S) hcap j + +/-- The first `⌈|S|/cap⌉` chunks cover `S`, because `|S| ≤ ⌈|S|/cap⌉ cap`. -/ +theorem biUnion_chunk (S : Finset (Fin n × Fin n)) (hcap : 1 ≤ cap) : + (Finset.range (numChunks S cap)).biUnion (fun j => chunk S cap j) = S := by + have hcard : S.card ≤ numChunks S cap * cap := by + rw [numChunks] + exact Nat.le_ceilDiv_mul hcap + ext q + simp only [Finset.mem_biUnion, Finset.mem_range, chunk, Finset.mem_filter] + refine ⟨fun ⟨_, _, hq, _⟩ => hq, fun hq => ⟨rankIn S q / cap, ?_, hq, rfl⟩⟩ + exact (Nat.div_lt_iff_lt_mul hcap).mpr ((rankIn_lt_card hq).trans_le hcard) + +end Cslib.Matrix diff --git a/Complexity/Foundations/Data/Nat/CeilDiv.lean b/Complexity/Foundations/Data/Nat/CeilDiv.lean new file mode 100644 index 0000000000..c88317bc1b --- /dev/null +++ b/Complexity/Foundations/Data/Nat/CeilDiv.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Algebra.Order.Floor.Div +public import Mathlib.Algebra.Order.Archimedean.Real.Basic + +/-! +# Natural ceilings of quotients + +## Main results + +* `Nat.ceil_div_eq_ceilDiv`: real quotient ceilings equal natural ceiling division. +* `Nat.le_ceilDiv_mul`, `Nat.ceilDiv_mul_lt`: covering and overshoot bounds. + +## References + +Adapted from [Ceil.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Util/Ceil.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +-/ + +@[expose] public section + +namespace Nat + +/-! ## The ceiling of a quotient of natural numbers -/ + +/-- `a ⌈/⌉ b ≤ k` says that `k` pieces of size `b` cover `a`. Mathlib's `ceilDiv_le_iff_le_mul` has +`b * k` on the right. -/ +theorem ceilDiv_le_iff {a b k : ℕ} (hb : 0 < b) : a ⌈/⌉ b ≤ k ↔ a ≤ k * b := by + rw [ceilDiv_le_iff_le_mul hb, Nat.mul_comm] + +/-- `a ⌈/⌉ b` pieces of size `b` cover `a`. -/ +theorem le_ceilDiv_mul {a b : ℕ} (hb : 0 < b) : a ≤ a ⌈/⌉ b * b := + (ceilDiv_le_iff hb).1 le_rfl + +/-- `a ⌈/⌉ b` pieces of size `b` overshoot `a` by less than one piece. -/ +theorem ceilDiv_mul_lt {a b : ℕ} (hb : 0 < b) : a ⌈/⌉ b * b < a + b := by + have := Nat.div_mul_le_self (a + b - 1) b + rw [Nat.ceilDiv_eq_add_pred_div] + omega + +/-- The ceiling of the real quotient of two natural numbers, in natural numbers. -/ +theorem ceil_div_eq_ceilDiv (a : ℕ) {b : ℕ} (hb : 0 < b) : ⌈(a : ℝ) / (b : ℝ)⌉₊ = a ⌈/⌉ b := by + refine eq_of_forall_ge_iff fun k => ?_ + rw [Nat.ceil_le, div_le_iff₀ (Nat.cast_pos.2 hb), ceilDiv_le_iff hb] + exact_mod_cast Iff.rfl + +end Nat diff --git a/Complexity/Foundations/Data/Nat/SqrtRatio.lean b/Complexity/Foundations/Data/Nat/SqrtRatio.lean new file mode 100644 index 0000000000..602c737373 --- /dev/null +++ b/Complexity/Foundations/Data/Nat/SqrtRatio.lean @@ -0,0 +1,47 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Analysis.Real.Sqrt +import Mathlib.Tactic.Positivity +import Mathlib.Tactic.NormNum + +/-! +# Computing a floor involving a square root + +## Main results + +* `Nat.sqrt_div_eq_floor_div_sqrt`: the floor of `a / sqrt b` is the natural square root + of `a² / b`, including the zero-denominator convention. + +This elementary arithmetic bridge is original adaptation code. It lets finite algorithms use +integer arithmetic while retaining their real-valued capacity specifications. +-/ + +public section + +namespace Nat + +/-- A computable integer formula for the floor of a quotient by a square root. -/ +theorem sqrt_div_eq_floor_div_sqrt (a b : ℕ) : + Nat.sqrt (a ^ 2 / b) = ⌊(a : ℝ) / Real.sqrt b⌋₊ := by + by_cases hb : b = 0 + · simp [hb] + have hb0 : 0 < b := Nat.pos_of_ne_zero hb + have hs : 0 < Real.sqrt (b : ℝ) := Real.sqrt_pos.mpr (by exact_mod_cast hb0) + apply eq_of_forall_le_iff + intro k + rw [Nat.le_sqrt', Nat.le_div_iff_mul_le hb0, Nat.le_floor_iff (by positivity), + le_div_iff₀ hs] + have hcast : k ^ 2 * b ≤ a ^ 2 ↔ (k : ℝ) ^ 2 * (b : ℝ) ≤ (a : ℝ) ^ 2 := by + norm_cast + rw [hcast, ← sq_le_sq₀ (show (0 : ℝ) ≤ k * Real.sqrt b by positivity) (Nat.cast_nonneg a)] + rw [mul_pow, Real.sq_sqrt (Nat.cast_nonneg b)] + +end Nat diff --git a/ComplexityTests/ExactTriangleSplitting.lean b/ComplexityTests/ExactTriangleSplitting.lean new file mode 100644 index 0000000000..1768d67f64 --- /dev/null +++ b/ComplexityTests/ExactTriangleSplitting.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.ExactTriangle.Witnesses +import Mathlib.Tactic.NormNum.NatSqrt + +open Cslib.Algorithms.ExactTriangle Cslib.Matrix + +-- Row-major chunks, including the short last chunk and the first empty chunk. +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 0 = + {(0, 0), (0, 1), (1, 0)} := by decide +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 1 = {(1, 1)} := by decide +example : chunk (Finset.univ : Finset (Fin 2 × Fin 2)) 3 2 = ∅ := by decide +example : chunk (∅ : Finset (Fin 2 × Fin 2)) 3 0 = ∅ := by decide +example : numChunks (Finset.univ : Finset (Fin 2 × Fin 2)) 3 = 2 := by + rw [numChunks, Nat.ceilDiv_eq_add_pred_div] + decide + +private theorem pieceSize16 : pieceSize 16 2 = 2 := by + rw [pieceSize, sOf] + norm_num [Nat.ceilDiv_eq_add_pred_div] +private theorem queryCap16 : queryCap 16 16 = 64 := by norm_num [queryCap] +example : queryCap 17 17 = 70 := by norm_num [queryCap] +example : queryCap 0 16 = 0 := by norm_num [queryCap] +example : queryCap 16 0 = 0 := by norm_num [queryCap] + +-- Boundary indices belong to adjacent pieces; out-of-range pieces are empty. +example : (1 : Fin 16) ∈ piece 16 16 2 0 ∧ (2 : Fin 16) ∈ piece 16 16 2 1 := by + simp [piece, pieceSize16] +example : piece 16 16 2 8 = ∅ := by + ext c + simp only [piece, pieceSize16, Finset.mem_filter, Finset.mem_univ, true_and, + Finset.notMem_empty, iff_false] + omega +example : piece 0 16 2 0 = ∅ := by simp [piece] +example : numPieces 16 16 2 = 8 := by + rw [numPieces, pieceSize16, Nat.ceilDiv_eq_add_pred_div] + +private def zeroWeight : Fin 16 → Fin 16 → ℤ := fun _ _ => 0 +private def falseWeight : Fin 16 → Fin 16 → ℤ := fun _ _ => 6 + +-- The prime window admits p = 3 at the smallest supported D. +private theorem prime3 : 3 ∈ primesInRange 16 := by + rw [mem_primesInRange_iff_sq] + decide + +-- All-zero matrices supply an admitted, nonempty splitting with the promised bounds. +example : (totalChunks zeroWeight 16 3 : ℝ) ≤ 3 + Real.sqrt 16 := + (totalChunks_le zeroWeight (by decide) (by decide) prime3).1 +example : ((instanceIndices zeroWeight 16 2 3).card : ℝ) ≤ 4 * 16 * 2 := + card_instanceIndices_le zeroWeight (by decide) (by decide) (by decide) prime3 +example : Fintype.card (lopInstance zeroWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).M ≤ 16 := + card_middle_lopInstance zeroWeight zeroWeight zeroWeight (by decide) (by decide) prime3 _ + +-- Residues include negative weights, and are independent of exact zero sums. +example : (0, 0) ∈ residueClass (fun _ _ : Fin 1 => -1) 3 2 := by decide +example : IsFalsePositive falseWeight zeroWeight zeroWeight 3 (0, 0, 0) := by decide +example : ¬ HasZeroTriangle falseWeight zeroWeight zeroWeight := by + simp [HasZeroTriangle, falseWeight, zeroWeight] +example : HasZeroTriangle zeroWeight zeroWeight zeroWeight := ⟨0, 0, 0, rfl⟩ + +-- Recovery rejects a modular false positive; a true zero returns a witness on its first scan. +example : scanPiece falseWeight zeroWeight zeroWeight 16 2 0 0 0 = none := by + rw [scanPiece_eq_none_iff] + intro c _ + norm_num [weightSum, falseWeight, zeroWeight] +example : scanPiece zeroWeight zeroWeight zeroWeight 16 2 0 0 0 = some 0 := by + simp [scanPiece, piece, pieceSize16, weightSum, zeroWeight, List.finRange_succ] +example : runScans falseWeight zeroWeight zeroWeight 16 2 + [(((0 : Fin 3), 0, 0), (0, 0))] = (none, 1) := by + have hscan : scanPiece falseWeight zeroWeight zeroWeight 16 2 0 0 0 = none := by + rw [scanPiece_eq_none_iff] + intro c _ + norm_num [weightSum, falseWeight, zeroWeight] + simp [runScans, hscan] +example : runScans zeroWeight zeroWeight zeroWeight 16 2 + [(((0 : Fin 3), 0, 0), (0, 0)), ((0, 0, 1), (1, 1))] = (some (0, 0, 0), 1) := by + simp [runScans, scanPiece, piece, pieceSize16, weightSum, zeroWeight, List.finRange_succ] + +-- The query at rank 63 ends chunk 0; rank 64 starts chunk 1. +example : (3, 15) ∈ chunkOf zeroWeight 16 3 0 0 := by + have hclass : residueClass zeroWeight 3 0 = Finset.univ := by + ext q + simp [residueClass, zeroWeight] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] +example : (4, 0) ∈ chunkOf zeroWeight 16 3 0 1 := by + have hclass : residueClass zeroWeight 3 0 = Finset.univ := by + ext q + simp [residueClass, zeroWeight] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] + +private theorem falseQuery : (0, 0) ∈ + (lopInstance falseWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).W := by + change (0, 0) ∈ chunkOf falseWeight 16 3 0 0 + have hclass : residueClass falseWeight 3 0 = Finset.univ := by + ext q + norm_num [residueClass, falseWeight, Int.ModEq] + simp [chunkOf, queryCap16, hclass, chunk, rankIn_univ, pairIndex] + +-- A modular query really is accepted despite the absence of any exact zero triangle. +example : + (lopInstance falseWeight zeroWeight zeroWeight 16 2 3 (0, 0, 0)).HasCommonNeighbor 0 0 := by + rw [hasCommonNeighbor_lopInstance_iff falseWeight zeroWeight zeroWeight (by decide) falseQuery] + refine ⟨0, ?_, ?_⟩ + · simp [piece, pieceSize16] + · decide + +-- Supply the actual finite modular answers, rather than an abstract unconstrained answer premise. +private def modularAnswers (wAB wBC wAC : Fin 16 → Fin 16 → ℤ) + (ι : InstanceIndex 3) (q : Fin 16 × Fin 16) : Bool := + decide (∃ c ∈ piece 16 16 2 ι.2.2, weightSum wAB wBC wAC q.1 q.2 c ≡ 0 [ZMOD (3 : ℤ)]) + +private theorem modularAnswers_correct (wAB wBC wAC : Fin 16 → Fin 16 → ℤ) : + AnswersCorrect wAB wBC wAC 16 2 3 (modularAnswers wAB wBC wAC) := by + intro ι _ q hq + apply propext + simp only [modularAnswers, decide_eq_true_eq] + exact (hasCommonNeighbor_lopInstance_iff wAB wBC wAC (by decide) hq).symm + +private theorem zeroSelected : IsSelectedPrime zeroWeight zeroWeight zeroWeight 16 3 := by + refine ⟨prime3, fun q _ => ?_⟩ + simp [countZeroMod, weightSum, zeroWeight] + +-- The complete recovery theorem is inhabited at its smallest admitted n and D. +example : ∃ L, IsScanOrder zeroWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers zeroWeight zeroWeight zeroWeight) L ∧ + (runScans zeroWeight zeroWeight zeroWeight 16 2 L).1 ≠ none ∧ + (runScans zeroWeight zeroWeight zeroWeight 16 2 L).2 ≤ 1 := by + obtain ⟨L, hL⟩ := exists_isScanOrder zeroWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers zeroWeight zeroWeight zeroWeight) + have hc := modularAnswers_correct zeroWeight zeroWeight zeroWeight + obtain ⟨hout, hcount⟩ := recovery_correct (by decide) (by decide) (by decide) + zeroWeight zeroWeight zeroWeight zeroSelected hc hL + refine ⟨L, hL, fun hn => hout.2 hn ⟨0, 0, 0, rfl⟩, ?_⟩ + simpa [falsePositiveCount, IsFalsePositive, weightSum, zeroWeight] using hcount + +private theorem falseSelected : IsSelectedPrime falseWeight zeroWeight zeroWeight 16 3 := by + refine ⟨prime3, fun q hq => ?_⟩ + obtain ⟨hprime, -, hsq⟩ := mem_primesInRange_iff_sq.mp hq + have hq : q = 2 ∨ q = 3 := by + have := hprime.two_le + have : q < 4 := by nlinarith + omega + rcases hq with rfl | rfl <;> + norm_num [countZeroMod, weightSum, falseWeight, zeroWeight, Int.ModEq] + +example : ∃ L, IsScanOrder falseWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers falseWeight zeroWeight zeroWeight) L ∧ + (runScans falseWeight zeroWeight zeroWeight 16 2 L).1 = none := by + obtain ⟨L, hL⟩ := exists_isScanOrder falseWeight zeroWeight zeroWeight 16 2 3 + (modularAnswers falseWeight zeroWeight zeroWeight) + refine ⟨L, hL, (recovery_eq_none_iff (by decide) (by decide) (by decide) + falseWeight zeroWeight zeroWeight falseSelected + (modularAnswers_correct falseWeight zeroWeight zeroWeight) hL).mpr ?_⟩ + simp [HasZeroTriangle, falseWeight, zeroWeight] diff --git a/NOTICE b/NOTICE index 95b9c8d57a..e48c2ecb6c 100644 --- a/NOTICE +++ b/NOTICE @@ -1,9 +1,11 @@ The 3SUM/Exact Triangle reductions, their finite counting lemmas and native matrix builder in -Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing}.lean, +Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, Complexity/Algorithms/ThreeSum/, Complexity/Algorithms/WordRAM/ThreeSum/, -Complexity/Foundations/Data/Finset/Average.lean, -Complexity/Foundations/Data/Matrix/IntegerBits.lean and +Complexity/Foundations/Data/Finset/{Average,Quotient}.lean, +Complexity/Foundations/Data/Int/Residue.lean, +Complexity/Foundations/Data/Nat/CeilDiv.lean, +Complexity/Foundations/Data/Matrix/{IntegerBits,PairChunks}.lean and Complexity/Foundations/NumberTheory/{PrimeCounting,PrimeWindow}.lean and Complexity/Foundations/RingTheory/CyclicIntRing.lean are adapted from anthropics/formal-math, 3sum-apsp, revision From 2cae26c953e11a21b980198ab983f6c52a5a5062 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:21:36 -0400 Subject: [PATCH 07/20] doc(threeSum): record finite splitting and recovery milestone Address the nonblocking review finding on #1455: finite residue/chunk/piece splitting and witness recovery are implemented. Preserve native preprocessing, fast solvers, full input/time/width composition and exponent 1.9992 as open work under #1394. Documentation only. Lean and test files are byte-identical to independently reviewed e41e1dda62a10c8f89917e2ccab54fbbedad1ad4, whose six strict gates pass. Checked the diff, milestone reference, implementation paths, hygiene and changed-file lints. Abstracts and rendering are unaffected. --- ROADMAP.md | 10 ++++++---- UPSTREAM.md | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d31b9ede12..044ec8d475 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -423,10 +423,11 @@ each statement against the numbered result. Organize modules by subject, not cha matrix builder and the complete finite mathematical Chan–He reduction ([#1435]), including ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting - and the selected-prime false-positive bound ([#1442]) are proved mathematically; it still needs - residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, native - Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full input/time/width/ - asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width + and the selected-prime false-positive bound ([#1442]), finite residue/chunk/piece splitting and + witness recovery ([#1447]) are proved mathematically. It still needs tensor/sparse-query programs, + native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full + input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; + size-based width selection with an input-fit guarantee and explicit overhead; expected-time rules for unbounded almost-surely terminating structured loops; random-tape equivalence and BPP/RP/ZPP simulation bridges for the randomized word RAM ([#1124]); transfer of cell-probe lower bounds; and the @@ -552,4 +553,5 @@ not planned, not as proved. [#1435]: https://github.com/cslib-community/complexitylib/issues/1435 [#1438]: https://github.com/cslib-community/complexitylib/issues/1438 [#1442]: https://github.com/cslib-community/complexitylib/issues/1442 +[#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index 8b49547dae..abb96bb7ad 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -189,8 +189,12 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he all-window prime-count bound lives in `Foundations/NumberTheory/PrimeWindow.lean`, retaining the source credit to Mathlib, Patrick Stevens and Bolton Bailey. These are mathematical statements, not native instructions or an assumed solver. See - [#1442](https://github.com/cslib-community/complexitylib/issues/1442). Residue/chunk/piece splitting, - witness recovery and tensor/sparse query programs remain in #1394, alongside native composition. + [#1442](https://github.com/cslib-community/complexitylib/issues/1442). + `Algorithms/ExactTriangle/{Splitting,Witnesses}.lean` adapts the complete finite splitting and + witness recovery component ([#1447](https://github.com/cslib-community/complexitylib/issues/1447)), + using the existing matrices, lopsided graph and `Set.EqOn` answer API. Computable integer parameters + retain the source's exact real floor/ceiling specifications through checked bridges. Tensor/sparse + query programs, native preprocessing/solvers and full input/time/width composition remain in #1394. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`. From dc4206a314c9623894f6db04178ea0ecbb019bdf Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:23:13 -0400 Subject: [PATCH 08/20] refactor(exactTriangle): reuse the natural residue bound and clarify source prose --- Complexity/Algorithms/ExactTriangle/Hashing.lean | 3 ++- Complexity/Algorithms/ExactTriangle/Splitting.lean | 8 +++++--- Complexity/Algorithms/ExactTriangle/Witnesses.lean | 8 ++++++-- Complexity/Foundations/Data/Int/Residue.lean | 10 ++++------ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Complexity/Algorithms/ExactTriangle/Hashing.lean b/Complexity/Algorithms/ExactTriangle/Hashing.lean index 86aba7824b..17ee536865 100644 --- a/Complexity/Algorithms/ExactTriangle/Hashing.lean +++ b/Complexity/Algorithms/ExactTriangle/Hashing.lean @@ -226,7 +226,8 @@ theorem falsePositiveCount_add_zeroCount_eq_sum_coeff {p : ℕ} (hp : p ≠ 0) : rw [Fintype.sum_prod_type] refine Finset.sum_congr rfl fun b _ => ?_ have hcast := Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp) (-wAB a b) - rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ (Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) _), + have hr : ((-wAB a b) % (p : ℤ)).toNat < p := Int.natMod_lt (m := -wAB a b) hp + rw [(coeff_matP_mul_matQ wBC wAC) hp a b _ hr, Finset.card_filter] push_cast refine Finset.sum_congr rfl fun c _ => ?_ diff --git a/Complexity/Algorithms/ExactTriangle/Splitting.lean b/Complexity/Algorithms/ExactTriangle/Splitting.lean index 5015daf5e1..4f6f435c3a 100644 --- a/Complexity/Algorithms/ExactTriangle/Splitting.lean +++ b/Complexity/Algorithms/ExactTriangle/Splitting.lean @@ -40,7 +40,8 @@ The reduction is part of [AlmanWilliams2026, Section 3]. Direct adaptation of [Instances.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Instances.lean), with only required definitions from `PaperStatements.lean`, [Chunks.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Chunks.lean) and [Parameters.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Parameters.lean), at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -Source-derived prose below is paraphrased; quotations used for review are in the PR record. +Quoted docstring fragments reproduce comments in the pinned formal source; other prose describes +the local Lean API. -/ @[expose] public section @@ -215,11 +216,12 @@ theorem numPieces_le (hD : 16 ≤ D) (hg1 : 1 ≤ g) : /-- The residue of the integer `x` modulo `p`, as an element of `Fin p`. -/ def resFin (hp : p ≠ 0) (x : ℤ) : Fin p := - ⟨(x % (p : ℤ)).toNat, Int.toNat_emod_lt (Nat.pos_of_ne_zero hp) x⟩ + ⟨x.natMod p, Int.natMod_lt hp⟩ /-- The residue of `x` is congruent to `x`. -/ theorem resFin_modEq (hp : p ≠ 0) (x : ℤ) : ((resFin hp x : ℕ) : ℤ) ≡ x [ZMOD (p : ℤ)] := by - rw [resFin, Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] + change ((x % (p : ℤ)).toNat : ℤ) ≡ x [ZMOD (p : ℤ)] + rw [Int.natCast_toNat_emod (Nat.pos_of_ne_zero hp)] exact Int.mod_modEq x p /-- An element of `Fin p` is congruent to `x` modulo `p` exactly if it is the residue of `x`. -/ diff --git a/Complexity/Algorithms/ExactTriangle/Witnesses.lean b/Complexity/Algorithms/ExactTriangle/Witnesses.lean index 0b5e39cb90..dfaeebb6aa 100644 --- a/Complexity/Algorithms/ExactTriangle/Witnesses.lean +++ b/Complexity/Algorithms/ExactTriangle/Witnesses.lean @@ -33,7 +33,8 @@ contain distinct modular false positives. No solver, native program or time orac Adapted from [Witnesses.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec3/Theorem17/Witnesses.lean) and the minimal scan definitions in `PaperStatements.lean` at `anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. -Context: [AlmanWilliams2026, Section 3]. Source-derived prose is paraphrased. +Context: [AlmanWilliams2026, Section 3]. Quoted docstring fragments reproduce comments in the pinned +formal source; other prose describes the local Lean API. -/ @[expose] public section @@ -272,7 +273,10 @@ theorem pieceSize_le_two_mul_sqrt_div (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.s ((one_le_div hg0).mpr hg) _ = 2 * Real.sqrt D / (g : ℝ) := by ring -/-- Proof of Theorem 17: "the scans cost O((F(p) + 1)√D/g) = O(ν n³ log n/g)". -/ +/-- For `16 ≤ D ≤ n`, `1 ≤ g ≤ sqrt D`, `κ ≥ 1` and edge weights bounded by `n^κ`, bound +`(falsePositiveCount + 1) * pieceSize` at any selected prime by +`(2 * Hashing.falsePositiveConst + 2) * κ * n^3 * log n / g`. +This arithmetic estimate counts candidate vertices rather than native instructions. -/ theorem falsePositiveCount_add_one_mul_pieceSize_le {κ : ℝ} (wAB wBC wAC : Fin n → Fin n → ℤ) (hD : 16 ≤ D) (hDn : D ≤ n) (hg1 : 1 ≤ g) (hg : (g : ℝ) ≤ Real.sqrt D) (hκ : 1 ≤ κ) (hT : WeightsBoundedBy wAB wBC wAC ((n : ℝ) ^ κ)) (hp : IsSelectedPrime wAB wBC wAC D p) : diff --git a/Complexity/Foundations/Data/Int/Residue.lean b/Complexity/Foundations/Data/Int/Residue.lean index f6622c6a71..eee6215f92 100644 --- a/Complexity/Foundations/Data/Int/Residue.lean +++ b/Complexity/Foundations/Data/Int/Residue.lean @@ -16,7 +16,8 @@ public import Mathlib.Algebra.Order.Ring.Int ## Main results * `Int.natCast_toNat_emod`: the nonnegative integer residue survives a round trip through `Nat`. -* `Int.toNat_emod_lt`: the natural residue is below a positive modulus. + +Bounds on natural residues use Mathlib's existing `Int.natMod_lt`. ## References @@ -29,13 +30,10 @@ The existing Exact Triangle hashing client now uses these shared facts. namespace Int +/-- Casting the natural Euclidean residue back to `Int` preserves its value for a positive +modulus. -/ theorem natCast_toNat_emod {p : ℕ} (hp : 0 < p) (u : ℤ) : ((u % (p : ℤ)).toNat : ℤ) = u % (p : ℤ) := Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) -theorem toNat_emod_lt {p : ℕ} (hp : 0 < p) (u : ℤ) : (u % (p : ℤ)).toNat < p := by - have := Int.emod_nonneg u (by omega : (p : ℤ) ≠ 0) - have := Int.emod_lt_of_pos u (by omega : (0 : ℤ) < p) - omega - end Int From 19ee9423823818c8a69e038755ae99648c9a30dc Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:17:03 -0400 Subject: [PATCH 09/20] =?UTF-8?q?feat(wordram):=20count=20Chan=E2=80=93He?= =?UTF-8?q?=20collisions=20with=20native=20histograms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement fixed native residue, histogram and composed collision-counting programs with explicit frames, local word-fit contracts, exact mathematical collision semantics and charged bound 34n + 5M + 18 (halt excluded). Implementation layer for #1454 under #1446/#1394, not full preprocessing. Sorting/frontend, prime searches, heavy extraction, recursion/output and global logarithmic-width/subquadratic-time composition remain open. Curated abstracts: no impact on headline statements, definitions or scope. Validated on base 8e182b091199b323c7e6271acebbfcee2e4cdd18 plus this exact diff: LEAN_NUM_THREADS=1 COMPLEXITYLIB_CACHE_AUTO=0 scripts/check.sh: all six gates passed, including strict test/global axiom audit. Changed-file lints against that base, hygiene --check-simp and git diff --check passed. Final run followed resolution of shared-host disk/memory pressure; failed earlier attempts are not counted as validation. Independent read-only agent correctness/API review by /root/review_native_histogram approved the final source blobs after replacing three private clear helpers with WordRAM.clear. No remaining blockers; optional Count parameter-frame API can accompany its minimum-search client. This is within-fleet agent review, not outside-coordinator or human approval. Outside coordinator review is required before integration. AI assistance: OpenAI Codex implementation/self-review and a separate Codex review context. Native programs/counting proofs are original; existing Anthropic mathematical source credit is preserved unchanged. --- Complexity.lean | 4 + Complexity/Algorithms/WordRAM/Histogram.lean | 402 ++++++++++++++++++ .../WordRAM/ThreeSum/ChanHe/Collisions.lean | 77 ++++ .../WordRAM/ThreeSum/ChanHe/Count.lean | 206 +++++++++ .../WordRAM/ThreeSum/ChanHe/Residues.lean | 211 +++++++++ ComplexityTests/ThreeSumHistogram.lean | 149 +++++++ 6 files changed, 1049 insertions(+) create mode 100644 Complexity/Algorithms/WordRAM/Histogram.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean create mode 100644 ComplexityTests/ThreeSumHistogram.lean diff --git a/Complexity.lean b/Complexity.lean index e6ac945545..10dee5c44a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -88,6 +88,7 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Query.Traversal public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static +public import Complexity.Algorithms.WordRAM.Histogram public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree @@ -100,6 +101,9 @@ public import Complexity.Algorithms.WordRAM.Quicksort public import Complexity.Algorithms.WordRAM.Stack public import Complexity.Algorithms.WordRAM.Stack.Controller public import Complexity.Algorithms.WordRAM.ThreeSum.Cell +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Memory public import Complexity.Algorithms.WordRAM.ThreeSum.Weight diff --git a/Complexity/Algorithms/WordRAM/Histogram.lean b/Complexity/Algorithms/WordRAM/Histogram.lean new file mode 100644 index 0000000000..da7669c784 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/Histogram.lean @@ -0,0 +1,402 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Copy +public import Mathlib.Algebra.BigOperators.Group.Finset.Basic +public import Mathlib.Data.Finset.Prod +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.NormNum + +/-! +# Charged word-RAM histograms + +A fixed program clears a histogram and inserts the keys of a disjoint input array. +An insertion into a bucket of size `c` creates `2c` ordered collisions. Every address +calculation, load, increment, store and loop test is charged. + +## Main definitions + +* `Histogram.count`: bucket sizes in an input prefix. +* `Histogram.pairs`: ordered collisions accumulated by prefix insertion. +* `Histogram.program`: clear the buckets, then insert the input keys. + +## Main results + +* `Histogram.exec`: histogram construction and collision counting with a linear instruction bound. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.Histogram + +open Cslib.WordRAM +open Finset + +/-- Number of occurrences of a key among the first `k` input positions. -/ +def count (keys : ℕ → ℕ) (k b : ℕ) : ℕ := #{i ∈ range k | keys i = b} + +@[simp] theorem count_zero (keys : ℕ → ℕ) (b : ℕ) : count keys 0 b = 0 := by + simp [count] + +/-- Inserting one key changes just its bucket. -/ +theorem count_succ (keys : ℕ → ℕ) (k b : ℕ) : + count keys (k + 1) b = count keys k b + if keys k = b then 1 else 0 := by + by_cases h : keys k = b <;> simp [count, range_add_one, filter_insert, h] + +/-- A bucket cannot contain more positions than the prefix. -/ +theorem count_le (keys : ℕ → ℕ) (k b : ℕ) : count keys k b ≤ k := + (card_filter_le _ _).trans_eq (card_range k) + +/-- Ordered collisions accumulated while inserting successive keys. -/ +def pairs (keys : ℕ → ℕ) : ℕ → ℕ + | 0 => 0 + | k + 1 => pairs keys k + 2 * count keys k (keys k) + +/-- The prefix collision counter is at most the square of the prefix length. -/ +theorem pairs_le_sq (keys : ℕ → ℕ) (k : ℕ) : pairs keys k ≤ k * k := by + induction k with + | zero => simp [pairs] + | succ k ih => + have := count_le keys k (keys k) + dsimp only [pairs] + nlinarith + +/-- The insertion counter counts exactly the ordered pairs of different positions +with the same key, including when equal keys occur at different input positions. -/ +theorem pairs_eq_card (keys : ℕ → ℕ) (k : ℕ) : + pairs keys k = #{p ∈ (range k).offDiag | keys p.1 = keys p.2} := by + induction k with + | zero => simp [pairs] + | succ k ih => + let S := range k + let A := {p ∈ S.offDiag | keys p.1 = keys p.2} + let B := {p ∈ {k} ×ˢ S | keys p.1 = keys p.2} + let C := {p ∈ S ×ˢ {k} | keys p.1 = keys p.2} + have hrow : B = {k} ×ˢ {i ∈ S | keys i = keys k} := by + ext p + simp only [B, mem_filter, mem_product, mem_singleton] + aesop + have hcol : C = {i ∈ S | keys i = keys k} ×ˢ {k} := by + ext p + simp only [C, mem_filter, mem_product, mem_singleton] + aesop + have hAB : Disjoint A B := by + rw [disjoint_left] + intro p hp hq + have hp' := (mem_offDiag.mp (mem_filter.mp hp).1).1 + have hq' := mem_singleton.mp (mem_product.mp (mem_filter.mp hq).1).1 + have : p.1 < k := mem_range.mp hp' + omega + have hAC : Disjoint (A ∪ B) C := by + rw [disjoint_left] + intro p hp hq + have hq' := mem_singleton.mp (mem_product.mp (mem_filter.mp hq).1).2 + rcases mem_union.mp hp with hp | hp + · have hp' := (mem_offDiag.mp (mem_filter.mp hp).1).2.1 + have : p.2 < k := mem_range.mp hp' + omega + · have hp' := (mem_product.mp (mem_filter.mp hp).1).2 + have : p.2 < k := mem_range.mp hp' + omega + have hsplit : {p ∈ (range (k + 1)).offDiag | keys p.1 = keys p.2} = A ∪ B ∪ C := by + rw [range_add_one, offDiag_insert (notMem_range_self : k ∉ range k), + filter_union, filter_union] + rw [hsplit, card_union_of_disjoint hAC, card_union_of_disjoint hAB, hrow, hcol, + card_product, card_product, card_singleton, one_mul, mul_one] + change pairs keys k + 2 * count keys k (keys k) = + #{p ∈ (range k).offDiag | keys p.1 = keys p.2} + count keys k (keys k) + + count keys k (keys k) + rw [ih] + omega + +/-- The original memory, with the histogram block replaced by prefix bucket counts. -/ +def memory (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k : ℕ) : ℕ → ℕ := + fun a => if dst ≤ a ∧ a < dst + bins then count keys k (a - dst) else mem a + +/-- Reading a histogram bucket. -/ +theorem memory_read (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k b : ℕ) + (hb : b < bins) : memory mem keys dst bins k (dst + b) = count keys k b := by + simp [memory, hb] + +/-- Histogram writes preserve all other cells. -/ +theorem memory_outside (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k a : ℕ) + (ha : ¬(dst ≤ a ∧ a < dst + bins)) : memory mem keys dst bins k a = mem a := by + simp [memory, ha] + +/-- One insertion is a single histogram-cell update. -/ +theorem memory_succ (mem : ℕ → ℕ) (keys : ℕ → ℕ) (dst bins k : ℕ) + (hk : keys k < bins) : + memory mem keys dst bins (k + 1) = Function.update (memory mem keys dst bins k) + (dst + keys k) (count keys k (keys k) + 1) := by + funext a + by_cases ha : a = dst + keys k + · subst a + simp [memory_read, hk, count_succ] + · rw [Function.update_of_ne ha] + simp only [memory] + split_ifs with h + · have hne : keys k ≠ a - dst := by omega + simp [count_succ, hne] + · rfl + +/-- Input base. -/ +abbrev src : Fin 11 := 0 +/-- Histogram base. -/ +abbrev dst : Fin 11 := 1 +/-- Number of input keys. -/ +abbrev len : Fin 11 := 2 +/-- Number of buckets. -/ +abbrev bins : Fin 11 := 3 +/-- Prefix length. -/ +abbrev idx : Fin 11 := 4 +/-- Temporary address. -/ +abbrev addr : Fin 11 := 5 +/-- Current key. -/ +abbrev key : Fin 11 := 6 +/-- Current bucket count. -/ +abbrev bucketSize : Fin 11 := 7 +/-- Number of ordered collisions. -/ +abbrev total : Fin 11 := 8 +/-- Constant one. -/ +abbrev one : Fin 11 := 9 +/-- Constant zero. -/ +abbrev zero : Fin 11 := 10 + +/-- Clear one histogram cell and advance the cursor. -/ +def clearBody : Code .standard 11 := ram[ + addr ← dst + idx; *addr ← zero; idx ← idx + one ] + +/-- Insert one key and add twice its old bucket count. -/ +def body : Code .standard 11 := ram[ + addr ← src + idx; key ← *addr; addr ← dst + key; + bucketSize ← *addr; total ← total + bucketSize; total ← total + bucketSize; + bucketSize ← bucketSize + one; *addr ← bucketSize; idx ← idx + one ] + +/-- Clear the histogram, retaining the input and all four parameters. -/ +def clear : Code .standard 11 := + .seq (ram[ idx ← #0; one ← #1; zero ← #0 ]) (.loop (.lt idx bins) clearBody) + +/-- Insert all keys into a cleared histogram. -/ +def insert : Code .standard 11 := + .seq (ram[ idx ← #0; total ← #0 ]) (.loop (.lt idx len) body) + +/-- Fixed code, with all bases and lengths supplied in registers. -/ +def program : Code .standard 11 := .seq clear insert + +private theorem exec_clear {w : ℕ} (x : Data 11) (h1 : Fits w 1) + (hd : Fits w (x.regs dst + x.regs bins)) : + ∃ y, ExecWithin w clear x y (5 * x.regs bins + 5) ∧ + y.mem = Cslib.WordRAM.clear x.mem (x.regs dst) (x.regs bins) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 := by + let I (y : Data 11) : Prop := + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs zero = 0 ∧ + y.regs idx ≤ x.regs bins ∧ y.mem = Cslib.WordRAM.clear x.mem (x.regs dst) (y.regs idx) + have hbody (y : Data 11) (hy : I y) (hc : (Cond.lt idx bins).Holds y) : + ∃ z, ExecWithin w clearBody y z 3 ∧ I z ∧ + x.regs bins - z.regs idx < x.regs bins - y.regs idx := by + obtain ⟨hr, ho, hz, hi, hm⟩ := hy + have hb := hr bins (by decide) + have hdst := hr dst (by decide) + have hlt : y.regs idx < x.regs bins := by simpa only [Cond.holds_lt, hb] using hc + have ha : Fits w (y.regs dst + y.regs idx) := hd.of_le (by rw [hdst]; omega) + have hn : Fits w (y.regs idx + 1) := hd.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [clearBody, ha, hn, ho] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hii] using hr i hi + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using hz + · simpa [ho] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem, Data.set_regs, Function.update_apply] + rw [hz, hm, hdst, ← Cslib.WordRAM.clear_succ] + rfl + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := ((x.set idx 0).set one 1).set zero 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hiz : i ≠ zero := by intro h; subst i; norm_num [zero] at hi + have hio : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hiz, Function.update_of_ne hio, + Function.update_of_ne hii] + · simp +decide [x₀, one, zero, idx] + · simp [x₀] + · simp +decide [x₀, one, zero, idx] + · simp +decide [x₀, one, zero, idx, Cslib.WordRAM.clear_zero] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs bins - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hz, hi, hm⟩ := hy + have hb := hr bins (by decide) + have hend : y.regs idx = x.regs bins := by + simp only [Cond.holds_lt, hb] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, hr, ho⟩ + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; one ← #1; zero ← #0 ]) x x₀ 3 := by + ram_eval [x₀, h1] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, one, zero, ↓reduceIte, + Nat.sub_zero] + omega + +private theorem exec_insert {w : ℕ} (x : Data 11) (keys : ℕ → ℕ) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs bins)) + (ht : Fits w (x.regs len * x.regs len + x.regs len + 1)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hkeys : ∀ i < x.regs len, keys i < x.regs bins) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = keys i) + (hzero : ∀ b < x.regs bins, x.mem (x.regs dst + b) = 0) + (ho : x.regs one = 1) : + ∃ y, ExecWithin w insert x y (11 * x.regs len + 4) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (x.regs len) ∧ + y.regs total = pairs keys (x.regs len) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 := by + let I (y : Data 11) : Prop := + (∀ i, i.val < 4 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ + y.regs idx ≤ x.regs len ∧ y.regs total = pairs keys (y.regs idx) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (y.regs idx) + have hbody (y : Data 11) (hy : I y) (hc : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 9 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, ho, hi, hp, hm⟩ := hy + have hl := hr len (by decide) + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hc + have hkey := hkeys _ hlt + have hread : y.mem (y.regs src + y.regs idx) = keys (y.regs idx) := by + rw [hm, hsrc, memory_outside] + · exact hinput _ hlt + · omega + have hbucket : y.mem (y.regs dst + keys (y.regs idx)) = + count keys (y.regs idx) (keys (y.regs idx)) := by + rw [hm, hdst, memory_read _ _ _ _ _ _ hkey] + have ha : Fits w (y.regs src + y.regs idx) := hs.of_le (by rw [hsrc]; omega) + have hb : Fits w (y.regs dst + keys (y.regs idx)) := hd.of_le (by rw [hdst]; omega) + have hn : Fits w (y.regs idx + 1) := ht.of_le (by omega) + have hc := count_le keys (y.regs idx) (keys (y.regs idx)) + have hpnext := pairs_le_sq keys (y.regs idx + 1) + have hsquare : (y.regs idx + 1) * (y.regs idx + 1) ≤ x.regs len * x.regs len := + Nat.mul_self_le_mul_self (by omega) + have ht₂ : Fits w (pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx)) + + count keys (y.regs idx) (keys (y.regs idx))) := ht.of_le (by + dsimp only [pairs] at hpnext + omega) + have ht₁ : Fits w (pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx))) := + ht₂.of_le (by omega) + have hcount : Fits w (count keys (y.regs idx) (keys (y.regs idx)) + 1) := + ht.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ha, hb, hn, ho, hread, hbucket, hp, ht₁, ht₂, hcount] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hik : i ≠ key := by intro h; subst i; norm_num [key] at hi + have hic : i ≠ bucketSize := by intro h; subst i; norm_num [bucketSize] at hi + have hit : i ≠ total := by intro h; subst i; norm_num [total] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hik, Function.update_of_ne hic, Function.update_of_ne hit, + Function.update_of_ne hii] using hr i hi + · simpa +decide [Function.update_apply] using ho + · simpa [ho] using Nat.succ_le_of_lt hlt + · change pairs keys (y.regs idx) + count keys (y.regs idx) (keys (y.regs idx)) + + count keys (y.regs idx) (keys (y.regs idx)) = pairs keys (y.regs idx + 1) + simp only [pairs, two_mul, Nat.add_assoc] + · change Function.update y.mem (y.regs dst + keys (y.regs idx)) + (count keys (y.regs idx) (keys (y.regs idx)) + 1) = + memory x.mem keys (x.regs dst) (x.regs bins) (y.regs idx + 1) + rw [hm, hdst, ← memory_succ _ _ _ _ _ hkey] + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := (x.set idx 0).set total 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hit : i ≠ total := by intro h; subst i; norm_num [total] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hit, Function.update_of_ne hii] + · simpa +decide [x₀, one, total, idx] using ho + · simp +decide [x₀, total, idx] + · simp +decide [x₀, total, idx, pairs] + · change x.mem = memory x.mem keys (x.regs dst) (x.regs bins) 0 + funext a + by_cases h : x.regs dst ≤ a ∧ a < x.regs dst + x.regs bins + · have ha : x.regs dst + (a - x.regs dst) = a := by omega + simpa [memory, h, ha] using hzero (a - x.regs dst) (by omega) + · simp only [memory, h, ↓reduceIte] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hi, hp, hm⟩ := hy + have hl := hr len (by decide) + have hend : y.regs idx = x.regs len := by + simp only [Cond.holds_lt, hl] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, by simpa only [hend] using hp, hr, ho⟩ + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; total ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, total, ↓reduceIte, + Nat.sub_zero] + omega + +/-- Build a histogram and count ordered collisions within `5M + 11n + 9` native instructions. +The input keys lie in `[0,M)`. The input precedes the output block. Both one-past-end +addresses and the quadratic counter bound must fit. These are explicit local contracts; +constructing the keys and deriving a logarithmic width are separate client obligations. -/ +theorem exec {w : ℕ} (x : Data 11) (keys : ℕ → ℕ) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs bins)) + (ht : Fits w (x.regs len * x.regs len + x.regs len + 1)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hkeys : ∀ i < x.regs len, keys i < x.regs bins) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = keys i) : + ∃ y, ExecWithin w program x y (5 * x.regs bins + 11 * x.regs len + 9) ∧ + y.mem = memory x.mem keys (x.regs dst) (x.regs bins) (x.regs len) ∧ + y.regs total = pairs keys (x.regs len) ∧ + (∀ i, i.val < 4 → y.regs i = x.regs i) := by + obtain ⟨y, he, hm, hr, ho⟩ := exec_clear x h1 hd + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hlen := hr len (by decide) + have hbins := hr bins (by decide) + obtain ⟨z, hz, hzm, hzt, hzr, _⟩ := exec_insert y keys + (by simpa only [hsrc, hlen] using hs) (by simpa only [hdst, hbins] using hd) + (by simpa only [hlen] using ht) (by simpa only [hsrc, hlen, hdst] using hdisj) + (by simpa only [hlen, hbins] using hkeys) (by + intro i hi + rw [hsrc, hm] + have hlt : i < x.regs len := by simpa only [hlen] using hi + simp only [Cslib.WordRAM.clear, show ¬(x.regs dst ≤ x.regs src + i ∧ + x.regs src + i < x.regs dst + x.regs bins) from by omega, ↓reduceIte] + exact hinput i hlt) (by + intro b hb + rw [hm, hdst] + have hb' : b < x.regs bins := by simpa only [hbins] using hb + simp [Cslib.WordRAM.clear, hb']) ho + refine ⟨z, ?_, ?_, by simpa only [hlen] using hzt, fun i hi => (hzr i hi).trans (hr i hi)⟩ + · exact (he.seq hz).mono (by rw [hlen]; omega) + · rw [hzm, hm, hdst, hbins, hlen] + funext a + simp only [memory, Cslib.WordRAM.clear] + split_ifs <;> rfl + +end Cslib.Algorithms.WordRAM.Histogram diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean new file mode 100644 index 0000000000..6b18f5850d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Collisions.lean @@ -0,0 +1,77 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.Histogram +public import Complexity.Algorithms.ThreeSum.ChanHe.Collisions + +/-! +# Histogram collision counts for Chan–He + +The native histogram counts ordered pairs of different input positions. For an injective +array of integers and its Euclidean residue keys, this is exactly the existing Chan–He +collision count. Injectivity is required because the mathematical reduction operates on sets. +Equal input values must first be handled by the distinct-position frontend. + +## Main results + +* `coll_eq_histogramPairs`: the existing `coll` is the counter computed by the histogram. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +open Finset +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- For distinct array values, histogramming Euclidean residues computes exactly the +ordered distinct-element collision count of the existing Chan–He reduction. -/ +theorem coll_eq_histogramPairs (values : ℕ → ℤ) (n M : ℕ) (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) : + coll ((range n).image values) M = + Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) n := by + rw [Histogram.pairs_eq_card] + symm + refine card_bij (fun p _ => (values p.1, values p.2)) ?_ ?_ ?_ + · intro p hp + obtain ⟨hp, heq⟩ := mem_filter.mp hp + obtain ⟨hp₁, hp₂, hne⟩ := mem_offDiag.mp hp + refine mem_filter.mpr ⟨mem_offDiag.mpr ⟨mem_image.mpr ⟨p.1, hp₁, rfl⟩, + mem_image.mpr ⟨p.2, hp₂, rfl⟩, fun h => hne (hinj hp₁ hp₂ h)⟩, ?_⟩ + apply (natCast_dvd_sub_iff_emod_eq M _ _).mpr + change values p.1 % (M : ℤ) = values p.2 % (M : ℤ) + have hpos : (0 : ℤ) < M := by exact_mod_cast hM + have h₁ := Int.emod_nonneg (values p.1) (ne_of_gt hpos) + have h₂ := Int.emod_nonneg (values p.2) (ne_of_gt hpos) + omega + · intro p hp q hq heq + have hp' := mem_offDiag.mp (mem_filter.mp hp).1 + have hq' := mem_offDiag.mp (mem_filter.mp hq).1 + exact Prod.ext (hinj hp'.1 hq'.1 (congrArg Prod.fst heq)) + (hinj hp'.2.1 hq'.2.1 (congrArg Prod.snd heq)) + · intro p hp + obtain ⟨hp, hdvd⟩ := mem_filter.mp hp + obtain ⟨hp₁, hp₂, hne⟩ := mem_offDiag.mp hp + obtain ⟨i, hi, hival⟩ := mem_image.mp hp₁ + obtain ⟨j, hj, hjval⟩ := mem_image.mp hp₂ + refine ⟨(i, j), mem_filter.mpr ⟨mem_offDiag.mpr ⟨hi, hj, ?_⟩, ?_⟩, ?_⟩ + · intro hij + change i = j at hij + apply hne + rw [← hival, ← hjval, hij] + · have heq := (natCast_dvd_sub_iff_emod_eq M _ _).mp hdvd + change (values i % (M : ℤ)).toNat = (values j % (M : ℤ)).toNat + rw [hival, hjval, heq] + · exact Prod.ext hival hjval + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean new file mode 100644 index 0000000000..677b2c2152 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -0,0 +1,206 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues + +/-! +# Native Chan–He collision counting + +This fixed native program first decodes signed Euclidean residue keys, then builds a +histogram and counts ordered collisions. Register setup between the two loops is charged. +For injective input arrays its counter is exactly the existing Chan–He `coll`. + +## Main definitions + +* `Count.program`: the composed native residue/histogram program. +* `Count.memory`: the complete output memory, including the residue scratch array. + +## Main results + +* `Count.exec`: execution in at most `34n + 5M + 18` native instructions. +* `Count.exec_coll`: the counter equals the existing mathematical collision count. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Residue routine registers occupy the first sixteen registers. -/ +def residuesRegisters (i : Fin 16) : Fin 28 := ⟨i.val, by omega⟩ + +/-- Histogram registers occupy registers sixteen through twenty-six. -/ +def histogramRegisters (i : Fin 11) : Fin 28 := ⟨16 + i.val, by omega⟩ + +private theorem residues_injective : Function.Injective residuesRegisters := by + intro i j h + apply Fin.ext + exact congrArg (fun i : Fin 28 => i.val) h + +private theorem histogram_injective : Function.Injective histogramRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp [histogramRegisters] at this + omega + +/-- Encoded integer input base. -/ +abbrev src : Fin 28 := 9 +/-- Residue scratch base. -/ +abbrev scratch : Fin 28 := 10 +/-- Number of input values. -/ +abbrev len : Fin 28 := 11 +/-- Encoded positive modulus. -/ +abbrev modulus : Fin 28 := 12 +/-- Histogram destination base. -/ +abbrev dst : Fin 28 := 27 +/-- Output collision count, an ordinary unsigned word. -/ +abbrev total : Fin 28 := 24 + +/-- Histogram input base register. -/ +abbrev hsrc : Fin 28 := histogramRegisters Histogram.src +/-- Histogram output base register. -/ +abbrev hdst : Fin 28 := histogramRegisters Histogram.dst +/-- Histogram length register. -/ +abbrev hlen : Fin 28 := histogramRegisters Histogram.len +/-- Histogram modulus register. -/ +abbrev hbins : Fin 28 := histogramRegisters Histogram.bins +/-- Histogram constant register, also used to decode the encoded modulus. -/ +abbrev hone : Fin 28 := histogramRegisters Histogram.one + +/-- Set up the histogram parameters, explicitly decoding the positive modulus. -/ +def prepare : Code .standard 28 := ram[ + hsrc ← scratch; hdst ← dst; hlen ← len; hone ← #2; hbins ← modulus / hone ] + +/-- A single finite native program, independent of input length, modulus and word width. -/ +def program : Code .standard 28 := + .seq (Residues.program.rename residuesRegisters) + (.seq prepare (Histogram.program.rename histogramRegisters)) + +/-- The residue scratch array and the complete histogram, framed by the original memory. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst : ℕ) : ℕ → ℕ := + Histogram.memory (Residues.memory mem values M scratch n) + (fun i => (values i % (M : ℤ)).toNat) dst M n + +/-- Complete native collision counting, including residue computation, clearing the histogram +and register setup. Input, scratch and histogram blocks occur in that order and are disjoint. +The final halt is excluded. Fit hypotheses are local; a caller must derive them from its +input and width contract. -/ +theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hk : Fits (w + 2) (x.regs scratch + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + M)) + (ht : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1)) + (hsk : x.regs src + x.regs len ≤ x.regs scratch) + (hkd : x.regs scratch + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : + ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ + y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) := by + obtain ⟨a, ha, ham, har⟩ := Residues.exec (x.comap residuesRegisters) values hM hmod + hmodreg hfit hs hk hsk hinput + let y := a.extend residuesRegisters x + have hy : ExecWithin (w + 2) (Residues.program.rename residuesRegisters) x y + (23 * x.regs len + 4) := ha.rename residues_injective + have hyr (i : Fin 16) : y.regs (residuesRegisters i) = a.regs i := + residues_injective.extend_apply _ _ i + have hys : y.regs scratch = x.regs scratch := + (hyr Residues.dst).trans (har Residues.dst (by decide)) + have hyl : y.regs len = x.regs len := (hyr Residues.len).trans (har Residues.len (by decide)) + have hym : y.regs modulus = encode (M : ℤ) := + (hyr Residues.modulus).trans ((har Residues.modulus (by decide)).trans hmodreg) + have hyd : y.regs dst = x.regs dst := by + change Function.extend residuesRegisters a.regs x.regs dst = x.regs dst + apply Function.extend_apply' + rintro ⟨i, hi⟩ + have := congrArg Fin.val hi + dsimp [residuesRegisters, dst] at this + omega + let q := ((((y.set (histogramRegisters Histogram.src) (x.regs scratch)).set + (histogramRegisters Histogram.dst) (x.regs dst)).set + (histogramRegisters Histogram.len) (x.regs len)).set + (histogramRegisters Histogram.one) 2).set (histogramRegisters Histogram.bins) M + have htwo : Fits (w + 2) 2 := (show Fits 2 2 from by decide).mono (by omega) + have hraw : Fits (w + 2) M := + ((encode_fits hmod).mono (by omega)).of_le (by + simp only [encode, Int.natAbs_natCast] + omega) + have hprep : ExecWithin (w + 2) prepare y q 5 := by + ram_eval [prepare, q, hys, hyl, hym, hyd, htwo, hraw, histogramRegisters, + Histogram.src, Histogram.dst, Histogram.len, Histogram.one, Histogram.bins, + hsrc, hdst, hlen, hbins, hone, scratch, len, modulus, dst, Fin.mk.injEq, + encode_div_two, Int.natAbs_natCast] + have hqs : (q.comap histogramRegisters).regs Histogram.src = x.regs scratch := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hqd : (q.comap histogramRegisters).regs Histogram.dst = x.regs dst := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hql : (q.comap histogramRegisters).regs Histogram.len = x.regs len := by + simp +decide [q, histogramRegisters, Histogram.src, Histogram.dst, Histogram.len, + Histogram.one, Histogram.bins] + have hqb : (q.comap histogramRegisters).regs Histogram.bins = M := by + simp [q] + have hqm : q.mem = Residues.memory x.mem values M (x.regs scratch) (x.regs len) := ham + have h1 : Fits (w + 2) 1 := (show Fits 2 1 from by decide).mono (by omega) + obtain ⟨b, hb, hbm, hbt, _⟩ := Histogram.exec (q.comap histogramRegisters) + (fun i => (values i % (M : ℤ)).toNat) h1 + (by simpa only [hqs, hql] using hk) (by simpa only [hqd, hqb] using hd) + (by simpa only [hql] using ht) (by simpa only [hqs, hql, hqd] using hkd) + (by + intro i hi + rw [hqb] + have hnonneg := Int.emod_nonneg (values i) (show (M : ℤ) ≠ 0 by omega) + have hlt := Int.emod_lt_of_pos (values i) (show (0 : ℤ) < M by omega) + omega) + (by + intro i hi + have hi' : i < x.regs len := by simpa only [hql] using hi + rw [Data.comap_mem, hqm, hqs] + simp [Residues.memory, hi']) + refine ⟨b.extend histogramRegisters q, ?_, ?_, ?_⟩ + · exact (hy.seq (hprep.seq (hb.rename histogram_injective))).mono (by rw [hql, hqb]; omega) + · change b.mem = _ + rw [hbm, Data.comap_mem, hqm, hqd, hqb, hql] + rfl + · change Function.extend histogramRegisters b.regs q.regs + (histogramRegisters Histogram.total) = _ + rw [histogram_injective.extend_apply, hbt, hql] + +/-- For an injective integer array, the composed program returns exactly the collision +count used by the existing Chan–He prime searches. -/ +theorem exec_coll {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hk : Fits (w + 2) (x.regs scratch + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + M)) + (ht : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1)) + (hsk : x.regs src + x.regs len ≤ x.regs scratch) + (hkd : x.regs scratch + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) + (hinj : Set.InjOn values (↑(Finset.range (x.regs len)) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ + y.regs total = Cslib.Algorithms.ThreeSum.ChanHe.coll + ((Finset.range (x.regs len)).image values) M := by + obtain ⟨y, hy, hm, ht⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput + exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean new file mode 100644 index 0000000000..4cdbd3eef3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Residues.lean @@ -0,0 +1,211 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Signed.Arithmetic +public import Complexity.Computability.WordRAM.Copy +import Mathlib.Tactic.NormNum + +/-! +# Native Euclidean residue arrays + +The input holds signed encodings. The output holds ordinary unsigned residue keys, ready +for histogram construction. In particular a nonnegative encoded integer is explicitly decoded +by division by two before it is used as a histogram index. + +## Main definitions + +* `Residues.program`: a fixed loop producing Euclidean residue keys. + +## Main results + +* `Residues.exec`: charged execution preserving the input and all cells outside the output. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Residues + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Encoded integer input base. -/ +abbrev src : Fin 16 := 9 +/-- Unsigned residue output base. -/ +abbrev dst : Fin 16 := 10 +/-- Input length. -/ +abbrev len : Fin 16 := 11 +/-- Encoded modulus. -/ +abbrev modulus : Fin 16 := 12 +/-- Loop counter. -/ +abbrev idx : Fin 16 := 13 +/-- Address scratch. -/ +abbrev addr : Fin 16 := 14 +/-- Constant one. -/ +abbrev one : Fin 16 := 15 + +/-- The signed remainder block followed by explicit unsigned decoding and a store. -/ +def body : Code .standard 16 := + .seq (ram[ addr ← src + idx; left ← *addr; right ← modulus ]) + (.seq Signed.modulo (ram[ two ← #2; result ← result / two; + addr ← dst + idx; *addr ← result; idx ← idx + one ])) + +/-- All parameters are supplied at runtime; registers and code are independent +of length and width. -/ +def program : Code .standard 16 := + .seq (ram[ idx ← #0; one ← #1 ]) (.loop (.lt idx len) body) + +/-- Memory after writing a prefix of unsigned residue keys. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst k : ℕ) : ℕ → ℕ := + fun a => if dst ≤ a ∧ a < dst + k then (values (a - dst) % (M : ℤ)).toNat else mem a + +@[simp] theorem memory_zero (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst : ℕ) : + memory mem values M dst 0 = mem := by + funext a + simp [memory] + +private theorem memory_succ (mem : ℕ → ℕ) (values : ℕ → ℤ) (M dst k : ℕ) : + memory mem values M dst (k + 1) = Function.update (memory mem values M dst k) + (dst + k) (values k % (M : ℤ)).toNat := by + funext a + by_cases ha : a = dst + k + · subst a + simp [memory] + · rw [Function.update_of_ne ha] + simp only [memory] + have h : (dst ≤ a ∧ a < dst + (k + 1)) ↔ (dst ≤ a ∧ a < dst + k) := by omega + simp only [h] + +/-- Convert `n` encoded signed integers to unsigned Euclidean residue keys in at most +`23n + 4` native instructions. The modulus is positive and encoded in its parameter register; +addresses are ordinary unsigned words. The final halt is excluded. -/ +theorem exec {w M : ℕ} (x : Data 16) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs src + x.regs len)) + (hd : Fits (w + 2) (x.regs dst + x.regs len)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : + ∃ y, ExecWithin (w + 2) program x y (23 * x.regs len + 4) ∧ + y.mem = memory x.mem values M (x.regs dst) (x.regs len) ∧ + (∀ i, 9 ≤ i.val ∧ i.val ≤ 12 → y.regs i = x.regs i) := by + let I (y : Data 16) : Prop := + (∀ i, 9 ≤ i.val ∧ i.val ≤ 12 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ + y.regs idx ≤ x.regs len ∧ + y.mem = memory x.mem values M (x.regs dst) (y.regs idx) + have hbody (y : Data 16) (hy : I y) (hc : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin (w + 2) body y z 21 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, ho, hi, hm⟩ := hy + have hl := hr len (by decide) + have hsrc := hr src (by decide) + have hdst := hr dst (by decide) + have hmod' := hr modulus (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hc + have hread : y.mem (y.regs src + y.regs idx) = encode (values (y.regs idx)) := by + rw [hm, hsrc] + simp only [memory, show ¬(x.regs dst ≤ x.regs src + y.regs idx ∧ + x.regs src + y.regs idx < x.regs dst + y.regs idx) from by omega, ↓reduceIte] + exact hinput _ hlt + have ha : Fits (w + 2) (y.regs src + y.regs idx) := hs.of_le (by rw [hsrc]; omega) + let p := ((y.set addr (y.regs src + y.regs idx)).set left + (encode (values (y.regs idx)))).set right (encode (M : ℤ)) + have hp : ExecWithin (w + 2) + (ram[ addr ← src + idx; left ← *addr; right ← modulus ]) y p 3 := by + ram_eval [p, ha, hread, hmod', hmodreg, addr, src, idx, left, right, modulus, Fin.mk.injEq] + obtain ⟨z, hz, hzval, hzmem, hzreg⟩ := Signed.exec_modulo (hfit _ hlt) hmod p + (by simp +decide [p, left, right]) (by simp [p]) + have hzr (i : Fin 16) (hi : 9 ≤ i.val) : z.regs i = p.regs i := hzreg i (Or.inr hi) + have hzi : z.regs idx = y.regs idx := by + simpa +decide [p, idx, addr, left, right] using hzr idx (by decide) + have hzo : z.regs one = 1 := by + simpa +decide [p, one, addr, left, right] using (hzr one (by decide)).trans + (show p.regs one = 1 by simpa +decide [p, one, addr, left, right] using ho) + have hzd : z.regs dst = x.regs dst := by + simpa +decide [p, dst, addr, left, right] using (hzr dst (by decide)).trans + (show p.regs dst = x.regs dst by simpa +decide [p, dst, addr, left, right] using hdst) + have hnonneg := Int.emod_nonneg (values (y.regs idx)) (show (M : ℤ) ≠ 0 by omega) + have hdecode : z.regs result / 2 = (values (y.regs idx) % (M : ℤ)).toNat := by + rw [hzval, GenericRAM.Signed.Arithmetic.eval, encode_div_two] + omega + have hb : Fits (w + 2) (z.regs dst + z.regs idx) := hd.of_le (by rw [hzd, hzi]; omega) + have hn : Fits (w + 2) (z.regs idx + 1) := hs.of_le (by rw [hzi]; omega) + have htwo : Fits (w + 2) 2 := (show Fits 2 2 from by decide).mono (by omega) + have hrem : Fits (w + 2) (z.regs result / 2) := by + rw [hdecode] + have hlt' := Int.emod_lt_of_pos (values (y.regs idx)) (show (0 : ℤ) < M by omega) + exact ((encode_fits hmod).mono (by omega)).of_le (by + simp only [encode, Int.natAbs_natCast, show ¬(M : ℤ) < 0 from by omega, ↓reduceIte] + omega) + refine ⟨?q, ?run, ?inv, ?decrease⟩ + case run => + have htail : ExecWithin (w + 2) + (ram[ two ← #2; result ← result / two; addr ← dst + idx; + *addr ← result; idx ← idx + one ]) z ?q 5 := by + ram_eval [htwo, hrem, hb, hn, hzo, two, result, addr, dst, idx, one, Fin.mk.injEq] + exact (hp.seq (hz.seq htail)) + case inv => + refine ⟨?_, ?_, ?_, ?_⟩ + · intro i hi + have hip : p.regs i = y.regs i := by + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hil : i ≠ left := by intro h; subst i; norm_num [left] at hi + have hir : i ≠ right := by intro h; subst i; norm_num [right] at hi + simp only [p, Data.set_regs, Function.update_of_ne hia, Function.update_of_ne hil, + Function.update_of_ne hir] + have hia : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + have hit : i ≠ two := by intro h; subst i; norm_num [two] at hi + have hir : i ≠ result := by intro h; subst i; norm_num [result] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne hia, + Function.update_of_ne hii, Function.update_of_ne hit, Function.update_of_ne hir] + using (hzr i hi.1).trans (hip.trans (hr i hi)) + · simpa +decide [Function.update_apply] using hzo + · simpa +decide [Function.update_apply, hzi, hzo] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem, Data.set_regs, Function.update_apply] + rw [hdecode, hzmem] + change Function.update y.mem (z.regs dst + z.regs idx) + (values (y.regs idx) % (M : ℤ)).toNat = _ + rw [hm, hzd, hzi, ← memory_succ] + rfl + case decrease => + change x.regs len - (z.regs idx + 1) < x.regs len - y.regs idx + rw [hzi] + omega + let x₀ := (x.set idx 0).set one 1 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_⟩ + · intro i hi + have hio : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hii : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hio, Function.update_of_ne hii] + · simp [x₀] + · simp +decide [x₀, idx, one] + · simp +decide [x₀, idx, one] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, ho, hi, hm⟩ := hy + have hl := hr len (by decide) + have hend : y.regs idx = x.regs len := by + simp only [Cond.holds_lt, hl] at hc + omega + refine ⟨y, ?_, by simpa only [hend] using hm, hr⟩ + have h1 : Fits (w + 2) 1 := (show Fits 2 1 from by decide).mono (by omega) + have hinit : ExecWithin (B := .standard) (w + 2) (ram[ idx ← #0; one ← #1 ]) x x₀ 2 := by + ram_eval [x₀, h1] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, one, ↓reduceIte, + Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Residues diff --git a/ComplexityTests/ThreeSumHistogram.lean b/ComplexityTests/ThreeSumHistogram.lean new file mode 100644 index 0000000000..23081d4413 --- /dev/null +++ b/ComplexityTests/ThreeSumHistogram.lean @@ -0,0 +1,149 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count + +/-! # Executed histogram and residue-array regressions -/ + +namespace ComplexityTests.ThreeSumHistogram + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM + +private def keys (i : ℕ) : ℕ := [0, 1, 0, 0].getD i 0 + +private def input : Data 11 := + (((Data.init [0, 1, 0, 0]).set Histogram.src 1).set Histogram.dst 10).set Histogram.len 4 + |>.set Histogram.bins 3 + +/-- Clearing ignores the old contents, including nonzero words in the output block. -/ +example : (Code.eval 8 4 Histogram.program (input.write 10 77)).map + (fun yn => (yn.1.mem 10, yn.1.mem 11, yn.1.mem 12, yn.1.regs Histogram.total, yn.2)) = + some (3, 1, 0, 6, 68) := by decide + +/-- Read the same result through the generic charged theorem, checking both frames. -/ +example : ∃ y, ExecWithin 8 Histogram.program input y 68 ∧ + y.mem 10 = 3 ∧ y.mem 11 = 1 ∧ y.regs Histogram.total = 6 ∧ + y.mem 0 = 4 ∧ y.mem 13 = 0 := by + obtain ⟨y, hy, hm, ht, _⟩ := Histogram.exec (w := 8) input keys + (by decide) (by decide) (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_, ?_, ?_⟩ + · rw [hm]; decide + · rw [hm]; decide + · rw [ht]; decide + · rw [hm]; decide + · rw [hm]; decide + +/-- Empty inputs and empty histograms pay only setup and two failed loop tests. -/ +example : (Code.eval 8 0 Histogram.program + ((input.set Histogram.len 0).set Histogram.bins 0)).map + (fun yn => (yn.1.regs Histogram.total, yn.2)) = some (0, 9) := by decide + +/-- A singleton has no collision; two equal keys at different positions create two. -/ +example : Histogram.pairs (fun _ => 0) 0 = 0 ∧ + Histogram.pairs (fun _ => 0) 1 = 0 ∧ Histogram.pairs (fun _ => 0) 2 = 2 := by decide + +/-- Different keys create no collision. -/ +example : Histogram.pairs id 4 = 0 := by decide + +private def values (i : ℕ) : ℤ := [-5, -2, 1, 4].getD i 0 + +private def signedInput : Data 16 := + (((Data.init ([-5, -2, 1, 4].map encode)).set ThreeSum.ChanHe.Residues.src 1).set + ThreeSum.ChanHe.Residues.dst 10).set ThreeSum.ChanHe.Residues.len 4 + |>.set ThreeSum.ChanHe.Residues.modulus (encode 3) + +/-- Negative dividends use Euclidean residues, and the outputs are decoded unsigned keys. -/ +example : (Code.eval 8 4 ThreeSum.ChanHe.Residues.program signedInput).map + (fun yn => (List.range 4).map fun i => yn.1.mem (10 + i)) = some [1, 1, 1, 1] := by decide + +/-- The signed-array contract has satisfiable hypotheses on the executed negative input. -/ +example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Residues.program signedInput y 96 ∧ + y.mem 10 = 1 ∧ y.mem 13 = 1 ∧ y.mem 1 = encode (-5) ∧ y.mem 14 = 0 := by + obtain ⟨y, hy, hm, _⟩ := ThreeSum.ChanHe.Residues.exec (w := 6) (M := 3) + signedInput values (by decide) (by decide) rfl + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_, ?_⟩ <;> rw [hm] <;> decide + +/-- The existing mathematical collision count agrees with the histogram's counter. -/ +example : Cslib.Algorithms.ThreeSum.ChanHe.coll {-5, -2, 1, 4} 3 = 12 ∧ + Histogram.pairs (fun i => (values i % 3).toNat) 4 = 12 := by decide + +private def countInput : Data 28 := + (((((Data.init ([-5, -2, 1, 4].map encode)).set ThreeSum.ChanHe.Count.src 1).set + ThreeSum.ChanHe.Count.scratch 10).set ThreeSum.ChanHe.Count.len 4).set + ThreeSum.ChanHe.Count.modulus (encode 3)).set ThreeSum.ChanHe.Count.dst 20 + +-- Evaluating the three renamed loops unfolds over 150 native instructions and their register maps. +set_option maxRecDepth 4096 in +/-- Execute the composed signed-input program and check its unsigned count and both arrays. -/ +example : (Code.eval 8 4 ThreeSum.ChanHe.Count.program countInput).map + (fun yn => [yn.1.mem 1, yn.1.mem 10, yn.1.mem 13, yn.1.mem 20, yn.1.mem 21, + yn.1.mem 22, yn.1.regs ThreeSum.ChanHe.Count.total]) = + some [encode (-5), 1, 1, 0, 4, 0, 12] := by decide + +/-- The full composed contract is admissible for the same negative-input example. -/ +example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ + y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) := by + obtain ⟨y, hy, hm, ht⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) + countInput values (by decide) (by decide) rfl + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + (by decide) (by decide) (by decide) (by decide) (by decide) (by decide) + (by + intro i hi + change i < 4 at hi + have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega + rcases this with rfl | rfl | rfl | rfl <;> decide) + refine ⟨y, hy, ?_, ?_, ?_⟩ + · rw [ht]; decide + · rw [hm]; decide + · rw [hm]; decide + +-- The three complete program evaluations unfold the renamed loops and register maps. +set_option maxRecDepth 4096 in +/-- The composed program handles lengths zero, one and two, including an unused modulus block. -/ +example : ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 0)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 0 ∧ + ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 1)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 0 ∧ + ((Code.eval 8 3 ThreeSum.ChanHe.Count.program + (countInput.set ThreeSum.ChanHe.Count.len 2)).map + (fun yn => yn.1.regs ThreeSum.ChanHe.Count.total)) = some 2 := by decide + +/-- Equal integer values violate the set-enumeration premise, even at different positions. -/ +example : ¬Set.InjOn (fun _ : ℕ => (-1 : ℤ)) (↑(Finset.range 2) : Set ℕ) := by + intro h + have := h (by decide : 0 ∈ (↑(Finset.range 2) : Set ℕ)) + (by decide : 1 ∈ (↑(Finset.range 2) : Set ℕ)) rfl + omega + +end ComplexityTests.ThreeSumHistogram From a7122053713cab3e45b505306b5d03bcd01cc737 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:16:00 -0400 Subject: [PATCH 10/20] fix(wordram): preserve collision-counting parameters explicitly --- .../WordRAM/ThreeSum/ChanHe/Count.lean | 26 ++++++++++++++----- ComplexityTests/ThreeSumHistogram.lean | 8 +++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean index 677b2c2152..5a8a697325 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -9,6 +9,8 @@ module public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.NormNum /-! # Native Chan–He collision counting @@ -89,6 +91,12 @@ def program : Code .standard 28 := .seq (Residues.program.rename residuesRegisters) (.seq prepare (Histogram.program.rename histogramRegisters)) +/-- The complete counting program preserves its input base, scratch base, length, +encoded modulus and histogram base registers. -/ +theorem preserves_parameters (i : Fin 28) + (hi : (9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) : program.Preserves i := by + fin_cases i <;> norm_num [dst] at hi <;> decide + /-- The residue scratch array and the complete histogram, framed by the original memory. -/ def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst : ℕ) : ℕ → ℕ := Histogram.memory (Residues.memory mem values M scratch n) @@ -111,7 +119,8 @@ theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) : ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ - y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) := by + y.regs total = Histogram.pairs (fun i => (values i % (M : ℤ)).toNat) (x.regs len) ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) → y.regs i = x.regs i := by obtain ⟨a, ha, ham, har⟩ := Residues.exec (x.comap residuesRegisters) values hM hmod hmodreg hfit hs hk hsk hinput let y := a.extend residuesRegisters x @@ -173,8 +182,12 @@ theorem exec {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) have hi' : i < x.regs len := by simpa only [hql] using hi rw [Data.comap_mem, hqm, hqs] simp [Residues.memory, hi']) - refine ⟨b.extend histogramRegisters q, ?_, ?_, ?_⟩ - · exact (hy.seq (hprep.seq (hb.rename histogram_injective))).mono (by rw [hql, hqb]; omega) + have he := (hy.seq (hprep.seq (hb.rename histogram_injective))).mono + (show (23 * x.regs len + 4) + (5 + (5 * (q.comap histogramRegisters).regs Histogram.bins + + 11 * (q.comap histogramRegisters).regs Histogram.len + 9)) ≤ + 34 * x.regs len + 5 * M + 18 by rw [hql, hqb]; omega) + refine ⟨b.extend histogramRegisters q, he, ?_, ?_, fun i hi => + ExecWithin.regs_eq he (preserves_parameters i hi)⟩ · change b.mem = _ rw [hbm, Data.comap_mem, hqm, hqd, hqb, hql] rfl @@ -199,8 +212,9 @@ theorem exec_coll {w M : ℕ} (x : Data 28) (values : ℕ → ℤ) (hM : 0 < M) ∃ y, ExecWithin (w + 2) program x y (34 * x.regs len + 5 * M + 18) ∧ y.mem = memory x.mem values (x.regs len) M (x.regs scratch) (x.regs dst) ∧ y.regs total = Cslib.Algorithms.ThreeSum.ChanHe.coll - ((Finset.range (x.regs len)).image values) M := by - obtain ⟨y, hy, hm, ht⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput - exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm⟩ + ((Finset.range (x.regs len)).image values) M ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = dst) → y.regs i = x.regs i := by + obtain ⟨y, hy, hm, ht, hr⟩ := exec x values hM hmod hmodreg hfit hs hk hd ht hsk hkd hinput + exact ⟨y, hy, hm, ht.trans (coll_eq_histogramPairs values _ M hM hinj).symm, hr⟩ end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/ComplexityTests/ThreeSumHistogram.lean b/ComplexityTests/ThreeSumHistogram.lean index 23081d4413..d51a5340f6 100644 --- a/ComplexityTests/ThreeSumHistogram.lean +++ b/ComplexityTests/ThreeSumHistogram.lean @@ -107,8 +107,10 @@ example : (Code.eval 8 4 ThreeSum.ChanHe.Count.program countInput).map /-- The full composed contract is admissible for the same negative-input example. -/ example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ - y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) := by - obtain ⟨y, hy, hm, ht⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) + y.regs ThreeSum.ChanHe.Count.total = 12 ∧ y.mem 21 = 4 ∧ y.mem 1 = encode (-5) ∧ + ∀ i, ((9 ≤ i.val ∧ i.val ≤ 12) ∨ i = ThreeSum.ChanHe.Count.dst) → + y.regs i = countInput.regs i := by + obtain ⟨y, hy, hm, ht, hframe⟩ := ThreeSum.ChanHe.Count.exec (w := 6) (M := 3) countInput values (by decide) (by decide) rfl (by intro i hi @@ -121,7 +123,7 @@ example : ∃ y, ExecWithin 8 ThreeSum.ChanHe.Count.program countInput y 169 ∧ change i < 4 at hi have : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by omega rcases this with rfl | rfl | rfl | rfl <;> decide) - refine ⟨y, hy, ?_, ?_, ?_⟩ + refine ⟨y, hy, ?_, ?_, ?_, hframe⟩ · rw [ht]; decide · rw [hm]; decide · rw [hm]; decide From 5cbb93d5ddc946364292740e2d85e334f4bfa649 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:21:35 -0400 Subject: [PATCH 11/20] =?UTF-8?q?feat(wordram):=20compute=20Chan=E2=80=93H?= =?UTF-8?q?e=20parameters=20from=20native=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement fixed 24-register parameter code with charged numeric helpers, exact existing Chan–He outputs, preserved input memory, derived logarithmic width and subquadratic native time. Add executed boundary and input-init contract examples. Original implementation and proofs credited to Erik Demaine; AI assistance by OpenAI Codex. Part of #1446; independent complete parameter subtask #1458. --- Complexity.lean | 4 + Complexity/Algorithms/WordRAM/NatClog.lean | 116 +++ Complexity/Algorithms/WordRAM/NatLog.lean | 111 +++ Complexity/Algorithms/WordRAM/NatSqrt.lean | 129 +++ .../WordRAM/ThreeSum/ChanHe/Parameters.lean | 891 ++++++++++++++++++ ComplexityTests/WordRAMChanHeParameters.lean | 64 ++ 6 files changed, 1315 insertions(+) create mode 100644 Complexity/Algorithms/WordRAM/NatClog.lean create mode 100644 Complexity/Algorithms/WordRAM/NatLog.lean create mode 100644 Complexity/Algorithms/WordRAM/NatSqrt.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean create mode 100644 ComplexityTests/WordRAMChanHeParameters.lean diff --git a/Complexity.lean b/Complexity.lean index 10dee5c44a..af010bc30b 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -89,6 +89,9 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static public import Complexity.Algorithms.WordRAM.Histogram +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree @@ -103,6 +106,7 @@ public import Complexity.Algorithms.WordRAM.Stack.Controller public import Complexity.Algorithms.WordRAM.ThreeSum.Cell public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Memory diff --git a/Complexity/Algorithms/WordRAM/NatClog.lean b/Complexity/Algorithms/WordRAM/NatClog.lean new file mode 100644 index 0000000000..6feabd6b67 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatClog.lean @@ -0,0 +1,116 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Log + +/-! +# A charged native binary logarithm + +## Main definitions + +* `NatClog.program`: successive ceiling division by two. + +## Main results + +* `NatClog.exec`: the ceiling logarithm with a logarithmic charged instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatClog + +open Cslib.WordRAM + +/-- Quotient, initially the input. -/ +abbrev arg : Fin 4 := 0 +/-- Floor logarithm output. -/ +abbrev result : Fin 4 := 1 +/-- Constant one. -/ +abbrev one : Fin 4 := 2 +/-- Constant two. -/ +abbrev two : Fin 4 := 3 + +/-- Divide the quotient by two and increment the answer. -/ +def body : Code .standard 4 := ram[ arg ← arg + one; arg ← arg / two; result ← result + one ] + +/-- Fixed code for `Nat.clog 2`, including its value zero at input zero. -/ +def program : Code .standard 4 := + .seq (ram[ result ← #0; one ← #1; two ← #2 ]) (.loop (.lt one arg) body) + +/-- Compute the ceiling binary logarithm in at most `5 ⌈log₂ n⌉ + 5` instructions. +Memory is untouched and the input quotient is consumed. -/ +theorem exec {w : ℕ} (x : Data 4) (h2 : Fits w 2) (hn : Fits w (x.regs arg + 1)) + (hout : Fits w (Nat.clog 2 (x.regs arg) + 1)) : + ∃ y, ExecWithin w program x y (5 * Nat.clog 2 (x.regs arg) + 5) ∧ + y.regs result = Nat.clog 2 (x.regs arg) ∧ y.mem = x.mem ∧ + y.regs one = 1 ∧ y.regs two = 2 := by + let I (y : Data 4) : Prop := y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs arg ≤ x.regs arg ∧ + y.regs result + Nat.clog 2 (y.regs arg) = Nat.clog 2 (x.regs arg) ∧ y.mem = x.mem + have hbody (y : Data 4) (hy : I y) (hc : (Cond.lt one arg).Holds y) : + ∃ z, ExecWithin w body y z 3 ∧ I z ∧ + Nat.clog 2 (z.regs arg) < Nat.clog 2 (y.regs arg) := by + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hgt : 1 < y.regs arg := by simpa only [Cond.holds_lt, ho] using hc + have hpos : 0 < Nat.clog 2 (y.regs arg) := Nat.clog_pos (by decide) (by omega) + have hadd : Fits w (y.regs arg + 1) := hn.of_le (by omega) + have hdiv : Fits w ((y.regs arg + 1) / 2) := + hn.of_le ((Nat.div_le_self _ _).trans (by omega)) + have hinc : Fits w (y.regs result + 1) := hout.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, ht, hadd, hdiv, hinc, arg, result, one, two, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using ht + · simpa +decide [Function.update_apply] using + (show (y.regs arg + 1) / 2 ≤ x.regs arg from by omega) + · change y.regs result + 1 + Nat.clog 2 ((y.regs arg + 1) / 2) = Nat.clog 2 (x.regs arg) + have h := Nat.clog_of_two_le (b := 2) (by decide) (by omega : 2 ≤ y.regs arg) + simp only [show y.regs arg + 2 - 1 = y.regs arg + 1 by omega] at h + omega + · exact hm + case decrease => + change Nat.clog 2 ((y.regs arg + 1) / 2) < Nat.clog 2 (y.regs arg) + have h := Nat.clog_of_two_le (b := 2) (by decide) (by omega : 2 ≤ y.regs arg) + simp only [show y.regs arg + 2 - 1 = y.regs arg + 1 by omega] at h + omega + let x₀ := ((x.set result 0).set one 1).set two 2 + have hx₀ : I x₀ := by simp +decide [I, x₀, arg, result, one, two] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.clog 2 (y.regs arg)) hbody hx₀ + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hzero : Nat.clog 2 (y.regs arg) = 0 := by + have hle : y.regs arg ≤ 1 := by simpa only [Cond.holds_lt, ho, not_lt] using hc + exact Nat.clog_of_right_le_one hle 2 + refine ⟨y, ?_, by omega, hm, ho, ht⟩ + have h1 : Fits w 1 := h2.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ result ← #0; one ← #1; two ← #2 ]) x x₀ 3 := by + ram_eval [x₀, h1, h2] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, arg, result, one, two, + ↓reduceIte] + omega + +/-- Run the routine on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 4 → Fin r) (hf : f.Injective) (x : Data r) + (h2 : Fits w 2) (hn : Fits w (x.regs (f arg) + 1)) + (hout : Fits w (Nat.clog 2 (x.regs (f arg)) + 1)) : + ∃ y, ExecWithin w (program.rename f) x y (5 * Nat.clog 2 (x.regs (f arg)) + 5) ∧ + y.regs (f result) = Nat.clog 2 (x.regs (f arg)) ∧ y.mem = x.mem ∧ + y.regs (f one) = 1 ∧ y.regs (f two) = 2 := by + obtain ⟨a, ha, hr, hm, ho, ht⟩ := exec (x.comap f) h2 hn hout + refine ⟨a.extend f x, ha.rename hf, ?_, hm, ?_, ?_⟩ + · exact (hf.extend_apply _ _ result).trans hr + · exact (hf.extend_apply _ _ one).trans ho + · exact (hf.extend_apply _ _ two).trans ht + +end Cslib.Algorithms.WordRAM.NatClog diff --git a/Complexity/Algorithms/WordRAM/NatLog.lean b/Complexity/Algorithms/WordRAM/NatLog.lean new file mode 100644 index 0000000000..eb229076e6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatLog.lean @@ -0,0 +1,111 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Log + +/-! +# A charged native binary logarithm + +## Main definitions + +* `NatLog.program`: successive division by two. + +## Main results + +* `NatLog.exec`: the floor logarithm with a logarithmic charged instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatLog + +open Cslib.WordRAM + +/-- Quotient, initially the input. -/ +abbrev arg : Fin 4 := 0 +/-- Floor logarithm output. -/ +abbrev result : Fin 4 := 1 +/-- Constant one. -/ +abbrev one : Fin 4 := 2 +/-- Constant two. -/ +abbrev two : Fin 4 := 3 + +/-- Divide the quotient by two and increment the answer. -/ +def body : Code .standard 4 := ram[ arg ← arg / two; result ← result + one ] + +/-- Fixed code for `Nat.log 2`, including its value zero at input zero. -/ +def program : Code .standard 4 := + .seq (ram[ result ← #0; one ← #1; two ← #2 ]) (.loop (.lt one arg) body) + +/-- Compute the floor binary logarithm in at most `4 log₂ n + 5` instructions. +Memory is untouched and the input quotient is consumed. -/ +theorem exec {w : ℕ} (x : Data 4) (h2 : Fits w 2) (hn : Fits w (x.regs arg)) + (hout : Fits w (Nat.log 2 (x.regs arg) + 1)) : + ∃ y, ExecWithin w program x y (4 * Nat.log 2 (x.regs arg) + 5) ∧ + y.regs result = Nat.log 2 (x.regs arg) ∧ y.mem = x.mem ∧ + y.regs one = 1 ∧ y.regs two = 2 := by + let I (y : Data 4) : Prop := y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs arg ≤ x.regs arg ∧ + y.regs result + Nat.log 2 (y.regs arg) = Nat.log 2 (x.regs arg) ∧ y.mem = x.mem + have hbody (y : Data 4) (hy : I y) (hc : (Cond.lt one arg).Holds y) : + ∃ z, ExecWithin w body y z 2 ∧ I z ∧ + Nat.log 2 (z.regs arg) < Nat.log 2 (y.regs arg) := by + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hgt : 1 < y.regs arg := by simpa only [Cond.holds_lt, ho] using hc + have hpos : 0 < Nat.log 2 (y.regs arg) := Nat.log_pos (by decide) (by omega) + have hdiv : Fits w (y.regs arg / 2) := hn.of_le ((Nat.div_le_self _ _).trans harg) + have hinc : Fits w (y.regs result + 1) := hout.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, ht, hdiv, hinc, arg, result, one, two, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using ht + · simpa +decide [Function.update_apply] using (Nat.div_le_self (y.regs arg) 2).trans harg + · change y.regs result + 1 + Nat.log 2 (y.regs arg / 2) = Nat.log 2 (x.regs arg) + rw [Nat.log_div_base] + omega + · exact hm + case decrease => + change Nat.log 2 (y.regs arg / 2) < Nat.log 2 (y.regs arg) + rw [Nat.log_div_base] + omega + let x₀ := ((x.set result 0).set one 1).set two 2 + have hx₀ : I x₀ := by simp +decide [I, x₀, arg, result, one, two] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.log 2 (y.regs arg)) hbody hx₀ + obtain ⟨ho, ht, harg, hsum, hm⟩ := hy + have hzero : Nat.log 2 (y.regs arg) = 0 := by + have hle : y.regs arg ≤ 1 := by simpa only [Cond.holds_lt, ho, not_lt] using hc + exact Nat.log_eq_zero_iff.mpr (Or.inl (by omega)) + refine ⟨y, ?_, by omega, hm, ho, ht⟩ + have h1 : Fits w 1 := h2.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ result ← #0; one ← #1; two ← #2 ]) x x₀ 3 := by + ram_eval [x₀, h1, h2] + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, arg, result, one, two, + ↓reduceIte] + omega + +/-- Run the routine on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 4 → Fin r) (hf : f.Injective) (x : Data r) + (h2 : Fits w 2) (hn : Fits w (x.regs (f arg))) + (hout : Fits w (Nat.log 2 (x.regs (f arg)) + 1)) : + ∃ y, ExecWithin w (program.rename f) x y (4 * Nat.log 2 (x.regs (f arg)) + 5) ∧ + y.regs (f result) = Nat.log 2 (x.regs (f arg)) ∧ y.mem = x.mem ∧ + y.regs (f one) = 1 ∧ y.regs (f two) = 2 := by + obtain ⟨a, ha, hr, hm, ho, ht⟩ := exec (x.comap f) h2 hn hout + refine ⟨a.extend f x, ha.rename hf, ?_, hm, ?_, ?_⟩ + · exact (hf.extend_apply _ _ result).trans hr + · exact (hf.extend_apply _ _ one).trans ho + · exact (hf.extend_apply _ _ two).trans ht + +end Cslib.Algorithms.WordRAM.NatLog diff --git a/Complexity/Algorithms/WordRAM/NatSqrt.lean b/Complexity/Algorithms/WordRAM/NatSqrt.lean new file mode 100644 index 0000000000..98b3ad67ed --- /dev/null +++ b/Complexity/Algorithms/WordRAM/NatSqrt.lean @@ -0,0 +1,129 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Sqrt +import Mathlib.Tactic.Linarith + +/-! +# A charged native integer square root + +## Main definitions + +* `NatSqrt.program`: successive squares. + +## Main results + +* `NatSqrt.exec`: integer square root in a square-root number of native instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.NatSqrt + +open Cslib.WordRAM + +/-- Input integer, preserved. -/ +abbrev arg : Fin 6 := 0 +/-- Current root and output. -/ +abbrev root : Fin 6 := 1 +/-- Successor of the current root. -/ +abbrev next : Fin 6 := 2 +/-- Square of the successor. -/ +abbrev square : Fin 6 := 3 +/-- Constant one. -/ +abbrev one : Fin 6 := 4 +/-- Input plus one. -/ +abbrev bound : Fin 6 := 5 + +/-- Advance the root and compute the next square. -/ +def body : Code .standard 6 := ram[ + root ← next; next ← next + one; square ← next * next ] + +/-- A fixed program for the integer square root, with all square computations charged. -/ +def program : Code .standard 6 := + .seq (ram[ root ← #0; next ← #1; square ← #1; one ← #1; bound ← arg + one ]) + (.loop (.lt square bound) body) + +/-- Compute `Nat.sqrt n` in at most `5 sqrt(n) + 7` instructions, preserving input and memory. +The linear bound `4n + 4` covers the final unsuccessful square as well as all counters. -/ +theorem exec {w : ℕ} (x : Data 6) (hfit : Fits w (4 * x.regs arg + 4)) : + ∃ y, ExecWithin w program x y (5 * Nat.sqrt (x.regs arg) + 7) ∧ + y.regs root = Nat.sqrt (x.regs arg) ∧ y.regs arg = x.regs arg ∧ y.mem = x.mem ∧ + y.regs one = 1 := by + let n := x.regs arg + let I (y : Data 6) : Prop := y.regs arg = n ∧ y.regs one = 1 ∧ + y.regs bound = n + 1 ∧ y.regs next = y.regs root + 1 ∧ + y.regs square = y.regs next * y.regs next ∧ + y.regs root ≤ Nat.sqrt n ∧ y.mem = x.mem + have hbody (y : Data 6) (hy : I y) (hc : (Cond.lt square bound).Holds y) : + ∃ z, ExecWithin w body y z 3 ∧ I z ∧ + Nat.sqrt n - z.regs root < Nat.sqrt n - y.regs root := by + obtain ⟨ha, ho, hb, hn, hsq, hr, hm⟩ := hy + have hsmall : y.regs next * y.regs next ≤ n := by + simp only [Cond.holds_lt, hsq, hb] at hc + omega + have hnext : y.regs next ≤ Nat.sqrt n := Nat.le_sqrt.mpr hsmall + have hsl := Nat.sqrt_le n + have hsle := Nat.sqrt_le_self n + have hstep : Fits w (y.regs next + 1) := hfit.of_le (by dsimp [n] at *; omega) + have hsquare : Fits w ((y.regs next + 1) * (y.regs next + 1)) := hfit.of_le (by + change (y.regs next + 1) * (y.regs next + 1) ≤ 4 * n + 4 + have hnn : y.regs next ≤ n := hnext.trans hsle + nlinarith) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, ho, hstep, hsquare, root, next, square, one, Fin.mk.injEq] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using ha + · simpa +decide [Function.update_apply] using ho + · simpa +decide [Function.update_apply] using hb + · rfl + · rfl + · exact hnext + · exact hm + case decrease => + change Nat.sqrt n - y.regs next < Nat.sqrt n - y.regs root + omega + let x₀ := ((((x.set root 0).set next 1).set square 1).set one 1).set bound (n + 1) + have hx₀ : I x₀ := by simp +decide [I, x₀, n, root, next, square, one, bound, arg] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.sqrt n - y.regs root) hbody hx₀ + obtain ⟨ha, ho, hb, hn, hsq, hr, hm⟩ := hy + have hlarge : n < (y.regs root + 1) * (y.regs root + 1) := by + simp only [Cond.holds_lt, hsq, hn, hb] at hc + omega + have hout : y.regs root = Nat.sqrt n := by + have h := Nat.sqrt_lt.mpr hlarge + omega + have h1 : Fits w 1 := hfit.of_le (by omega) + have hbfit : Fits w (x.regs arg + 1) := hfit.of_le (by omega) + have hinit : ExecWithin (B := .standard) w + (ram[ root ← #0; next ← #1; square ← #1; one ← #1; bound ← arg + one ]) x x₀ 5 := by + ram_eval [x₀, n, h1, hbfit, root, next, square, one, bound, arg, Fin.mk.injEq] + refine ⟨y, ?_, hout, ha, hm, ho⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, root, next, square, one, + bound, arg, n, ↓reduceIte, Nat.sub_zero] + omega + +/-- Run successive squares on any injectively chosen registers. -/ +theorem exec_rename {r w : ℕ} (f : Fin 6 → Fin r) (hf : f.Injective) (x : Data r) + (hfit : Fits w (4 * x.regs (f arg) + 4)) : + ∃ y, ExecWithin w (program.rename f) x y (5 * Nat.sqrt (x.regs (f arg)) + 7) ∧ + y.regs (f root) = Nat.sqrt (x.regs (f arg)) ∧ + y.regs (f arg) = x.regs (f arg) ∧ y.mem = x.mem ∧ y.regs (f one) = 1 := by + obtain ⟨a, ha, hr, hn, hm, ho⟩ := exec (x.comap f) hfit + refine ⟨a.extend f x, ha.rename hf, ?_, ?_, hm, ?_⟩ + · exact (hf.extend_apply _ _ root).trans hr + · exact (hf.extend_apply _ _ arg).trans hn + · exact (hf.extend_apply _ _ one).trans ho + +end Cslib.Algorithms.WordRAM.NatSqrt diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean new file mode 100644 index 0000000000..3ddc4b7cc4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -0,0 +1,891 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Defs +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatSqrt +public import Complexity.Computability.WordRAM.Compile +public import Complexity.Computability.WordRAM.Signed.Encoding +import Mathlib.Tactic.Linarith + +/-! +# Native parameters for Chan–He preprocessing + +Original native programs and proofs. The parameter meanings are the existing finite mathematical +Chan–He definitions, with magnitude bound `2U` after the distinct-position frontend. +All loops and arithmetic are instructions; the program is fixed for each exponent `κ`. + +## Main definitions + +* `Parameters.program`: read the input header and compute the exact parameters. +* `Parameters.Output`: identify the unsigned output registers. + +## Main results + +* `Parameters.runs_input`: native execution from signed-encoded input with derived fits. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +open Cslib.WordRAM + +namespace Math +open Cslib.Algorithms.ThreeSum + +/-- A bound covering arithmetic, unsuccessful loop tests, and all output words. -/ +def budget (n U : ℕ) : ℕ := 8 * (ChanHe.mPar n (2 * U) + 1) ^ 2 + 8 * U + 4 * n + 64 + +private theorem want_le (n U : ℕ) : ChanHe.wPar n (2 * U) + 1 ≤ ChanHe.mPar n (2 * U) := + Nat.le_mul_of_pos_right _ (by omega) + +private theorem digits_le (n U : ℕ) : 5 * labelBits (2 * U) ≤ ChanHe.wPar n (2 * U) := + Nat.le_mul_of_pos_right _ (Nat.succ_pos _) + +private theorem factor_le (n U : ℕ) : + 2 * Nat.log 2 (ChanHe.wPar n (2 * U) + 1) + 4 ≤ ChanHe.mPar n (2 * U) := + Nat.le_mul_of_pos_left _ (Nat.succ_pos _) + +private theorem height_le (n : ℕ) : ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + +end Math + +/-- Unsigned input length. -/ +abbrev len : Fin 24 := 0 +/-- Original magnitude bound U. -/ +abbrev magnitude : Fin 24 := 1 +/-- Label bits for magnitude bound 2U. -/ +abbrev digits : Fin 24 := 2 +/-- Recursion height. -/ +abbrev height : Fin 24 := 3 +/-- Recursion fuel. -/ +abbrev fuel : Fin 24 := 4 +/-- Number of desired primes. -/ +abbrev want : Fin 24 := 5 +/-- Upper bound on candidate primes. -/ +abbrev primeBound : Fin 24 := 6 +/-- Common packed convolution length, 8m². -/ +abbrev packedLen : Fin 24 := 7 +/-- Magnitude of the padding for the doubled frontend bound, 4U+1. -/ +abbrev padding : Fin 24 := 8 +/-- Constant one. -/ +abbrev one : Fin 24 := 9 +/-- Constant two. -/ +abbrev two : Fin 24 := 10 +/-- Constant three. -/ +abbrev three : Fin 24 := 11 +/-- Constant four. -/ +abbrev four : Fin 24 := 12 +/-- Constant five. -/ +abbrev five : Fin 24 := 13 +/-- Constant eight. -/ +abbrev eight : Fin 24 := 14 +/-- Input length plus two. -/ +abbrev base : Fin 24 := 15 +/-- Consumed logarithm argument. -/ +abbrev quotient : Fin 24 := 16 +/-- Floor logarithm of input length. -/ +abbrev logLen : Fin 24 := 17 +/-- Integer square root of input length. -/ +abbrev root : Fin 24 := 18 +/-- Number of desired primes plus one. -/ +abbrev wantSucc : Fin 24 := 19 +/-- Arithmetic scratch. -/ +abbrev temp : Fin 24 := 20 +/-- Successor square-root counter. -/ +abbrev nextRoot : Fin 24 := 21 +/-- Square of that counter. -/ +abbrev square : Fin 24 := 22 +/-- Square-root test bound. -/ +abbrev rootBound : Fin 24 := 23 + +/-- Exact mathematical outputs; every output is an ordinary unsigned word. -/ +structure Output (n U : ℕ) (x : Data 24) : Prop where + len : x.regs len = n + magnitude : x.regs magnitude = U + digits : x.regs digits = Cslib.Algorithms.ThreeSum.labelBits (2 * U) + height : x.regs height = Cslib.Algorithms.ThreeSum.ChanHe.height n + fuel : x.regs fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + want : x.regs want = Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + primeBound : x.regs primeBound = Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + packedLen : x.regs packedLen = 8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) ^ 2 + padding : x.regs padding = 4 * U + 1 + +/-- One header load, constants, and the polynomial base. -/ +def setup : Code .standard 24 := ram[ + quotient ← #0; len ← *quotient; one ← #1; two ← #2; three ← #3; + four ← #4; five ← #5; eight ← #8; base ← len + two ] + +/-- Unroll only the fixed exponent, never the input length or word width. -/ +def power : ℕ → Code .standard 24 + | 0 => ram[ magnitude ← #1 ] + | κ + 1 => .seq (power κ) (ram[ magnitude ← magnitude * base ]) + +/-- Register selection for a floor/ceiling logarithm with output `dst`. -/ +def logRegisters (dst : Fin 24) : Fin 4 → Fin 24 := ![quotient, dst, one, two] + +/-- Register selection for successive squares. -/ +def sqrtRegisters : Fin 6 → Fin 24 := ![len, root, nextRoot, square, one, rootBound] + +private theorem sqrt_injective : sqrtRegisters.Injective := by decide + +/-- The instruction sequence after the power computation. -/ +def calculate : Code .standard 24 := + .seq (ram[ quotient ← four * magnitude; padding ← quotient + one ]) + (.seq (NatLog.program.rename (logRegisters digits)) + (.seq (ram[ digits ← digits + one ]) + (.seq (NatSqrt.program.rename sqrtRegisters) + (.seq (ram[ temp ← root + one; want ← five * digits; want ← want * temp; + wantSucc ← want + one; quotient ← wantSucc ]) + (.seq (NatLog.program.rename (logRegisters temp)) + (.seq (ram[ temp ← two * temp; temp ← temp + four; primeBound ← wantSucc * temp ]) + (.seq (ram[ quotient ← len ]) + (.seq (NatLog.program.rename (logRegisters logLen)) + (.seq (ram[ quotient ← logLen + two ]) + (.seq (NatClog.program.rename (logRegisters height)) + (ram[ fuel ← three * height; fuel ← fuel - two; packedLen ← primeBound * primeBound; + packedLen ← eight * packedLen ]))))))))))) + +/-- Fixed 24-register native code for each fixed polynomial magnitude exponent. -/ +def program (κ : ℕ) : Code .standard 24 := .seq setup (.seq (power κ) calculate) + +private theorem exec_power {w : ℕ} (κ : ℕ) (x : Data 24) (hb : 1 ≤ x.regs base) + (hfit : Fits w (x.regs base ^ κ)) : + ExecWithin w (power κ) x (x.set magnitude (x.regs base ^ κ)) (κ + 1) := by + induction κ with + | zero => simpa only [power, pow_zero] using ExecWithin.const hfit + | succ κ ih => + have hprev : Fits w (x.regs base ^ κ) := + hfit.of_le (Nat.pow_le_pow_right hb (by omega)) + have he := ih hprev + have hm : ExecWithin w (ram[ magnitude ← magnitude * base ]) + (x.set magnitude (x.regs base ^ κ)) + (x.set magnitude (x.regs base ^ (κ + 1))) 1 := by + have hp : Fits w (x.regs base ^ κ * x.regs base) := by + simpa only [pow_succ] using hfit + apply ExecWithin.of_eval (fuel := 0) (n := 1) ?_ (by decide) + simp +decide [Code.eval, Op.eval_bin, Data.put, + BinOp.eval, hp, magnitude, base, pow_succ, Data.set, Function.update_idem] + exact (he.seq hm).mono (by omega) + +/-- An explicit charged bound, including all header reads, copies and setup. Halt adds one. -/ +def time (κ n U : ℕ) : ℕ := κ + 64 + 5 * Nat.sqrt n + 4 * Nat.log 2 (4 * U) + + 4 * Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) + + 4 * Nat.log 2 n + 5 * Cslib.Algorithms.ThreeSum.ChanHe.height n + +private theorem time_zero (n U : ℕ) : time 0 n U - 20 = 44 + 5 * Nat.sqrt n + + 4 * Nat.log 2 (4 * U) + + 4 * Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) + + 4 * Nat.log 2 n + 5 * Cslib.Algorithms.ThreeSum.ChanHe.height n := by + dsimp [time] + omega + +set_option maxHeartbeats 800000 in +-- Elaborating twelve native segments and their explicit register frames exceeds the default budget. +private theorem exec_calculate {w n U : ℕ} (x : Data 24) + (hlen : x.regs len = n) (hU : x.regs magnitude = U) + (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h3 : x.regs three = 3) + (h4 : x.regs four = 4) (h5 : x.regs five = 5) (h8 : x.regs eight = 8) + (hfit : Fits w (Math.budget n U)) : + ∃ y, ExecWithin w calculate x y (time 0 n U - 20) ∧ Output n U y ∧ y.mem = x.mem := by + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + let h := Cslib.Algorithms.ThreeSum.ChanHe.height n + let wp := Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + have eΛ : Λ = Nat.log 2 (4 * U) + 1 := by + dsimp [Λ, Cslib.Algorithms.ThreeSum.labelBits] + congr 2 + omega + have ew : wp = 5 * Λ * (Nat.sqrt n + 1) := rfl + have em : m = (wp + 1) * (2 * Nat.log 2 (wp + 1) + 4) := rfl + have eh : Cslib.Algorithms.ThreeSum.ChanHe.fuel n = 3 * h - 2 := rfl + have hd : 5 * Λ ≤ wp := Math.digits_le n U + have hw : wp + 1 ≤ m := Math.want_le n U + have hf : 2 * Nat.log 2 (wp + 1) + 4 ≤ m := Math.factor_le n U + have hh : h ≤ Nat.log 2 n + 2 := Math.height_le n + have hl := Nat.log_le_self 2 n + have hroot := Nat.sqrt_le_self n + have hml : Fits w m := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fitm (v : ℕ) (hv : v ≤ m) : Fits w v := hml.of_le hv + have fitu (v : ℕ) (hv : v ≤ 8 * U + 64) : Fits w v := + hfit.of_le (by dsimp [Math.budget]; omega) + have fitn (v : ℕ) (hv : v ≤ 4 * n + 64) : Fits w v := + hfit.of_le (by dsimp [Math.budget]; omega) + have f1 : Fits w 1 := fitu _ (by omega) + have f2 : Fits w 2 := fitu _ (by omega) + have f4U : Fits w (4 * U) := fitu _ (by omega) + have fpad : Fits w (4 * U + 1) := fitu _ (by omega) + have fΛ : Fits w Λ := fitm _ (by omega) + have flogU : Fits w (Nat.log 2 (4 * U) + 1) := + fitu _ (by have := Nat.log_le_self 2 (4 * U); omega) + have fsqrt : Fits w (4 * n + 4) := fitn _ (by omega) + have fr : Fits w (Nat.sqrt n + 1) := fitn _ (by omega) + have f5Λ : Fits w (5 * Λ) := fitm _ (by omega) + have fwp : Fits w wp := fitm _ (by omega) + have fwps : Fits w (wp + 1) := fitm _ hw + have flogwp : Fits w (Nat.log 2 (wp + 1) + 1) := fitm _ (by omega) + have f2log : Fits w (2 * Nat.log 2 (wp + 1)) := fitm _ (by omega) + have ff : Fits w (2 * Nat.log 2 (wp + 1) + 4) := fitm _ hf + have fn : Fits w n := fitn _ (by omega) + have flogn : Fits w (Nat.log 2 n + 1) := fitn _ (by omega) + have flog2 : Fits w (Nat.log 2 n + 2) := fitn _ (by omega) + have flog3 : Fits w (Nat.log 2 n + 2 + 1) := fitn _ (by omega) + have fh : Fits w (h + 1) := fitn _ (by omega) + have f3h : Fits w (3 * h) := fitn _ (by omega) + have ffuel : Fits w (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) := + fitn _ (by dsimp [Cslib.Algorithms.ThreeSum.ChanHe.fuel]; omega) + have fm2 : Fits w (m * m) := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fN : Fits w (8 * m ^ 2) := hfit.of_le (by dsimp [Math.budget]; nlinarith) + have fNm : Fits w (8 * (m * m)) := by simpa only [pow_two] using fN + let a := ((x.set quotient (4 * U)).set padding (4 * U + 1)) + have ea : ExecWithin w (ram[ quotient ← four * magnitude; padding ← quotient + one ]) x a 2 := by + ram_eval [a, h4, hU, h1, f4U, fpad, four, magnitude, one, quotient, padding, + Fin.mk.injEq] + have a_len : a.regs len = n := by + simpa +decide [a, quotient, padding, len] using hlen + have a_magnitude : a.regs magnitude = U := by + simpa +decide [a, quotient, padding, magnitude] using hU + have a_one : a.regs one = 1 := by + simpa +decide [a, quotient, padding, one] using h1 + have a_two : a.regs two = 2 := by + simpa +decide [a, quotient, padding, two] using h2 + have a_three : a.regs three = 3 := by + simpa +decide [a, quotient, padding, three] using h3 + have a_four : a.regs four = 4 := by + simpa +decide [a, quotient, padding, four] using h4 + have a_five : a.regs five = 5 := by + simpa +decide [a, quotient, padding, five] using h5 + have a_eight : a.regs eight = 8 := by + simpa +decide [a, quotient, padding, eight] using h8 + have a_quotient : a.regs quotient = 4 * U := by + simp +decide [a, quotient, padding] + have a_padding : a.regs padding = 4 * U + 1 := by + simp +decide [a, quotient, padding] + have a_mem : a.mem = x.mem := rfl + obtain ⟨b, eb, b_digits, b_mem, b_one, b_two⟩ := + NatLog.exec_rename (logRegisters digits) (by decide) a f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, a_quotient] using f4U) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, a_quotient, h] using flogU) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + a_quotient] at eb b_digits b_one b_two + have b_len : b.regs len = n := (eb.regs_eq (by decide)).trans a_len + have b_magnitude : b.regs magnitude = U := (eb.regs_eq (by decide)).trans a_magnitude + have b_three : b.regs three = 3 := (eb.regs_eq (by decide)).trans a_three + have b_four : b.regs four = 4 := (eb.regs_eq (by decide)).trans a_four + have b_five : b.regs five = 5 := (eb.regs_eq (by decide)).trans a_five + have b_eight : b.regs eight = 8 := (eb.regs_eq (by decide)).trans a_eight + have b_padding : b.regs padding = 4 * U + 1 := (eb.regs_eq (by decide)).trans a_padding + have b_memory : b.mem = x.mem := b_mem.trans a_mem + let c := (b.set digits (Λ)) + have ec : ExecWithin w (ram[ digits ← digits + one ]) b c 1 := by + ram_eval [c, b_digits, b_one, ← eΛ, fΛ, digits, one, Fin.mk.injEq] + have c_len : c.regs len = n := by + simpa +decide [c, digits, len] using b_len + have c_magnitude : c.regs magnitude = U := by + simpa +decide [c, digits, magnitude] using b_magnitude + have c_three : c.regs three = 3 := by + simpa +decide [c, digits, three] using b_three + have c_four : c.regs four = 4 := by + simpa +decide [c, digits, four] using b_four + have c_five : c.regs five = 5 := by + simpa +decide [c, digits, five] using b_five + have c_eight : c.regs eight = 8 := by + simpa +decide [c, digits, eight] using b_eight + have c_padding : c.regs padding = 4 * U + 1 := by + simpa +decide [c, digits, padding] using b_padding + have c_digits : c.regs digits = Λ := by + simp +decide [c, digits] + have c_one : c.regs one = 1 := by + simpa +decide [c, digits, one] using b_one + have c_two : c.regs two = 2 := by + simpa +decide [c, digits, two] using b_two + have c_mem : c.mem = x.mem := b_memory + obtain ⟨d, ed, d_root, d_len, d_mem, d_one⟩ := NatSqrt.exec_rename + sqrtRegisters sqrt_injective c (by simpa [sqrtRegisters, NatSqrt.arg, c_len] using fsqrt) + simp only [sqrtRegisters, NatSqrt.arg, NatSqrt.root, NatSqrt.one, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, c_len] at ed d_root d_len d_one + have d_magnitude : d.regs magnitude = U := (ed.regs_eq (by decide)).trans c_magnitude + have d_three : d.regs three = 3 := (ed.regs_eq (by decide)).trans c_three + have d_four : d.regs four = 4 := (ed.regs_eq (by decide)).trans c_four + have d_five : d.regs five = 5 := (ed.regs_eq (by decide)).trans c_five + have d_eight : d.regs eight = 8 := (ed.regs_eq (by decide)).trans c_eight + have d_padding : d.regs padding = 4 * U + 1 := (ed.regs_eq (by decide)).trans c_padding + have d_digits : d.regs digits = Λ := (ed.regs_eq (by decide)).trans c_digits + have d_two : d.regs two = 2 := (ed.regs_eq (by decide)).trans c_two + have d_memory : d.mem = x.mem := d_mem.trans c_mem + let e := (((((d.set temp (Nat.sqrt n + 1)).set want (5 * Λ)).set want (wp)).set wantSucc + (wp + 1)).set quotient (wp + 1)) + have ee : ExecWithin w (ram[ temp ← root + one; want ← five * digits; want ← want * temp; + wantSucc ← want + one; quotient ← wantSucc ]) d e 5 := by + ram_eval [e, d_root, d_one, d_five, d_digits, ← ew, fr, f5Λ, fwp, fwps, temp, root, one, + want, five, digits, wantSucc, quotient, Fin.mk.injEq, Data.set, Function.update_idem] + have e_magnitude : e.regs magnitude = U := by + simpa +decide [e, temp, want, wantSucc, quotient, magnitude] using d_magnitude + have e_three : e.regs three = 3 := by + simpa +decide [e, temp, want, wantSucc, quotient, three] using d_three + have e_four : e.regs four = 4 := by + simpa +decide [e, temp, want, wantSucc, quotient, four] using d_four + have e_five : e.regs five = 5 := by + simpa +decide [e, temp, want, wantSucc, quotient, five] using d_five + have e_eight : e.regs eight = 8 := by + simpa +decide [e, temp, want, wantSucc, quotient, eight] using d_eight + have e_padding : e.regs padding = 4 * U + 1 := by + simpa +decide [e, temp, want, wantSucc, quotient, padding] using d_padding + have e_digits : e.regs digits = Λ := by + simpa +decide [e, temp, want, wantSucc, quotient, digits] using d_digits + have e_two : e.regs two = 2 := by + simpa +decide [e, temp, want, wantSucc, quotient, two] using d_two + have e_root : e.regs root = Nat.sqrt n := by + simpa +decide [e, temp, want, wantSucc, quotient, root] using d_root + have e_one : e.regs one = 1 := by + simpa +decide [e, temp, want, wantSucc, quotient, one] using d_one + have e_len : e.regs len = n := by + simpa +decide [e, temp, want, wantSucc, quotient, len] using d_len + have e_temp : e.regs temp = Nat.sqrt n + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_want : e.regs want = wp := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_wantSucc : e.regs wantSucc = wp + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_quotient : e.regs quotient = wp + 1 := by + simp +decide [e, temp, want, wantSucc, quotient] + have e_mem : e.mem = x.mem := d_memory + obtain ⟨f, ef, f_temp, f_mem, f_one, f_two⟩ := + NatLog.exec_rename (logRegisters temp) (by decide) e f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, e_quotient] using fwps) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, e_quotient, h] using flogwp) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, e_quotient] at ef f_temp f_one f_two + have f_magnitude : f.regs magnitude = U := (ef.regs_eq (by decide)).trans e_magnitude + have f_three : f.regs three = 3 := (ef.regs_eq (by decide)).trans e_three + have f_four : f.regs four = 4 := (ef.regs_eq (by decide)).trans e_four + have f_five : f.regs five = 5 := (ef.regs_eq (by decide)).trans e_five + have f_eight : f.regs eight = 8 := (ef.regs_eq (by decide)).trans e_eight + have f_padding : f.regs padding = 4 * U + 1 := (ef.regs_eq (by decide)).trans e_padding + have f_digits : f.regs digits = Λ := (ef.regs_eq (by decide)).trans e_digits + have f_root : f.regs root = Nat.sqrt n := (ef.regs_eq (by decide)).trans e_root + have f_len : f.regs len = n := (ef.regs_eq (by decide)).trans e_len + have f_want : f.regs want = wp := (ef.regs_eq (by decide)).trans e_want + have f_wantSucc : f.regs wantSucc = wp + 1 := (ef.regs_eq (by decide)).trans e_wantSucc + have f_memory : f.mem = x.mem := f_mem.trans e_mem + let g := (((f.set temp (2 * Nat.log 2 (wp + 1))).set temp + (2 * Nat.log 2 (wp + 1) + 4)).set primeBound m) + have eg : ExecWithin w (ram[ temp ← two * temp; temp ← temp + four; + primeBound ← wantSucc * temp ]) f g 3 := by + ram_eval [g, f_two, f_temp, f_four, f_wantSucc, ← em, f2log, ff, hml, temp, two, four, + primeBound, wantSucc, Fin.mk.injEq, Data.set, Function.update_idem] + have g_magnitude : g.regs magnitude = U := by + simpa +decide [g, temp, primeBound, magnitude] using f_magnitude + have g_three : g.regs three = 3 := by + simpa +decide [g, temp, primeBound, three] using f_three + have g_four : g.regs four = 4 := by + simpa +decide [g, temp, primeBound, four] using f_four + have g_five : g.regs five = 5 := by + simpa +decide [g, temp, primeBound, five] using f_five + have g_eight : g.regs eight = 8 := by + simpa +decide [g, temp, primeBound, eight] using f_eight + have g_padding : g.regs padding = 4 * U + 1 := by + simpa +decide [g, temp, primeBound, padding] using f_padding + have g_digits : g.regs digits = Λ := by + simpa +decide [g, temp, primeBound, digits] using f_digits + have g_root : g.regs root = Nat.sqrt n := by + simpa +decide [g, temp, primeBound, root] using f_root + have g_len : g.regs len = n := by + simpa +decide [g, temp, primeBound, len] using f_len + have g_want : g.regs want = wp := by + simpa +decide [g, temp, primeBound, want] using f_want + have g_wantSucc : g.regs wantSucc = wp + 1 := by + simpa +decide [g, temp, primeBound, wantSucc] using f_wantSucc + have g_temp : g.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simp +decide [g, temp, primeBound] + have g_one : g.regs one = 1 := by + simpa +decide [g, temp, primeBound, one] using f_one + have g_two : g.regs two = 2 := by + simpa +decide [g, temp, primeBound, two] using f_two + have g_primeBound : g.regs primeBound = m := by + simp +decide [g, temp, primeBound] + have g_mem : g.mem = x.mem := f_memory + let j := (g.set quotient (n)) + have ej : ExecWithin (B := .standard) w (ram[ quotient ← len ]) g j 1 := by + ram_eval [j, g_len] + have j_magnitude : j.regs magnitude = U := by + simpa +decide [j, quotient, magnitude] using g_magnitude + have j_three : j.regs three = 3 := by + simpa +decide [j, quotient, three] using g_three + have j_four : j.regs four = 4 := by + simpa +decide [j, quotient, four] using g_four + have j_five : j.regs five = 5 := by + simpa +decide [j, quotient, five] using g_five + have j_eight : j.regs eight = 8 := by + simpa +decide [j, quotient, eight] using g_eight + have j_padding : j.regs padding = 4 * U + 1 := by + simpa +decide [j, quotient, padding] using g_padding + have j_digits : j.regs digits = Λ := by + simpa +decide [j, quotient, digits] using g_digits + have j_root : j.regs root = Nat.sqrt n := by + simpa +decide [j, quotient, root] using g_root + have j_len : j.regs len = n := by + simpa +decide [j, quotient, len] using g_len + have j_want : j.regs want = wp := by + simpa +decide [j, quotient, want] using g_want + have j_wantSucc : j.regs wantSucc = wp + 1 := by + simpa +decide [j, quotient, wantSucc] using g_wantSucc + have j_temp : j.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [j, quotient, temp] using g_temp + have j_one : j.regs one = 1 := by + simpa +decide [j, quotient, one] using g_one + have j_two : j.regs two = 2 := by + simpa +decide [j, quotient, two] using g_two + have j_primeBound : j.regs primeBound = m := by + simpa +decide [j, quotient, primeBound] using g_primeBound + have j_quotient : j.regs quotient = n := by + simp +decide [j, quotient] + have j_mem : j.mem = x.mem := g_mem + obtain ⟨k, ek, k_logLen, k_mem, k_one, k_two⟩ := + NatLog.exec_rename (logRegisters logLen) (by decide) j f2 + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, j_quotient] using fn) + (by simpa only [logRegisters, NatLog.arg, Matrix.cons_val_zero, j_quotient, h] using flogn) + simp only [logRegisters, NatLog.arg, NatLog.result, NatLog.one, NatLog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + j_quotient] at ek k_logLen k_one k_two + have k_magnitude : k.regs magnitude = U := (ek.regs_eq (by decide)).trans j_magnitude + have k_three : k.regs three = 3 := (ek.regs_eq (by decide)).trans j_three + have k_four : k.regs four = 4 := (ek.regs_eq (by decide)).trans j_four + have k_five : k.regs five = 5 := (ek.regs_eq (by decide)).trans j_five + have k_eight : k.regs eight = 8 := (ek.regs_eq (by decide)).trans j_eight + have k_padding : k.regs padding = 4 * U + 1 := (ek.regs_eq (by decide)).trans j_padding + have k_digits : k.regs digits = Λ := (ek.regs_eq (by decide)).trans j_digits + have k_root : k.regs root = Nat.sqrt n := (ek.regs_eq (by decide)).trans j_root + have k_len : k.regs len = n := (ek.regs_eq (by decide)).trans j_len + have k_want : k.regs want = wp := (ek.regs_eq (by decide)).trans j_want + have k_wantSucc : k.regs wantSucc = wp + 1 := (ek.regs_eq (by decide)).trans j_wantSucc + have k_temp : k.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := (ek.regs_eq (by decide)).trans j_temp + have k_primeBound : k.regs primeBound = m := (ek.regs_eq (by decide)).trans j_primeBound + have k_memory : k.mem = x.mem := k_mem.trans j_mem + let l := (k.set quotient (Nat.log 2 n + 2)) + have el : ExecWithin w (ram[ quotient ← logLen + two ]) k l 1 := by + ram_eval [l, k_logLen, k_two, flog2, quotient, logLen, two, Fin.mk.injEq] + have l_magnitude : l.regs magnitude = U := by + simpa +decide [l, quotient, magnitude] using k_magnitude + have l_three : l.regs three = 3 := by + simpa +decide [l, quotient, three] using k_three + have l_four : l.regs four = 4 := by + simpa +decide [l, quotient, four] using k_four + have l_five : l.regs five = 5 := by + simpa +decide [l, quotient, five] using k_five + have l_eight : l.regs eight = 8 := by + simpa +decide [l, quotient, eight] using k_eight + have l_padding : l.regs padding = 4 * U + 1 := by + simpa +decide [l, quotient, padding] using k_padding + have l_digits : l.regs digits = Λ := by + simpa +decide [l, quotient, digits] using k_digits + have l_root : l.regs root = Nat.sqrt n := by + simpa +decide [l, quotient, root] using k_root + have l_len : l.regs len = n := by + simpa +decide [l, quotient, len] using k_len + have l_want : l.regs want = wp := by + simpa +decide [l, quotient, want] using k_want + have l_wantSucc : l.regs wantSucc = wp + 1 := by + simpa +decide [l, quotient, wantSucc] using k_wantSucc + have l_temp : l.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [l, quotient, temp] using k_temp + have l_primeBound : l.regs primeBound = m := by + simpa +decide [l, quotient, primeBound] using k_primeBound + have l_logLen : l.regs logLen = Nat.log 2 n := by + simpa +decide [l, quotient, logLen] using k_logLen + have l_one : l.regs one = 1 := by + simpa +decide [l, quotient, one] using k_one + have l_two : l.regs two = 2 := by + simpa +decide [l, quotient, two] using k_two + have l_quotient : l.regs quotient = Nat.log 2 n + 2 := by + simp +decide [l, quotient] + have l_mem : l.mem = x.mem := k_memory + obtain ⟨o, eo, o_height, o_mem, o_one, o_two⟩ := + NatClog.exec_rename (logRegisters height) (by decide) l f2 + (by simpa only [logRegisters, NatClog.arg, Matrix.cons_val_zero, l_quotient] using flog3) + (by simpa only [logRegisters, NatClog.arg, Matrix.cons_val_zero, l_quotient, h, + Cslib.Algorithms.ThreeSum.ChanHe.height] using fh) + simp only [logRegisters, NatClog.arg, NatClog.result, NatClog.one, NatClog.two, + Matrix.cons_val_zero, Matrix.cons_val_one, Matrix.cons_val, + l_quotient] at eo o_height o_one o_two + change o.regs height = h at o_height + change ExecWithin w _ l o (5 * h + 5) at eo + have o_magnitude : o.regs magnitude = U := (eo.regs_eq (by decide)).trans l_magnitude + have o_three : o.regs three = 3 := (eo.regs_eq (by decide)).trans l_three + have o_four : o.regs four = 4 := (eo.regs_eq (by decide)).trans l_four + have o_five : o.regs five = 5 := (eo.regs_eq (by decide)).trans l_five + have o_eight : o.regs eight = 8 := (eo.regs_eq (by decide)).trans l_eight + have o_padding : o.regs padding = 4 * U + 1 := (eo.regs_eq (by decide)).trans l_padding + have o_digits : o.regs digits = Λ := (eo.regs_eq (by decide)).trans l_digits + have o_root : o.regs root = Nat.sqrt n := (eo.regs_eq (by decide)).trans l_root + have o_len : o.regs len = n := (eo.regs_eq (by decide)).trans l_len + have o_want : o.regs want = wp := (eo.regs_eq (by decide)).trans l_want + have o_wantSucc : o.regs wantSucc = wp + 1 := (eo.regs_eq (by decide)).trans l_wantSucc + have o_temp : o.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := (eo.regs_eq (by decide)).trans l_temp + have o_primeBound : o.regs primeBound = m := (eo.regs_eq (by decide)).trans l_primeBound + have o_logLen : o.regs logLen = Nat.log 2 n := (eo.regs_eq (by decide)).trans l_logLen + have o_memory : o.mem = x.mem := o_mem.trans l_mem + let p := ((((o.set fuel (3 * h)).set fuel (Cslib.Algorithms.ThreeSum.ChanHe.fuel n)).set packedLen + (m * m)).set packedLen (8 * m ^ 2)) + have ep : ExecWithin w (ram[ fuel ← three * height; fuel ← fuel - two; + packedLen ← primeBound * primeBound; packedLen ← eight * packedLen ]) o p 4 := by + ram_eval [p, o_three, o_height, o_two, o_primeBound, o_eight, ← eh, f3h, ffuel, fm2, fNm, + fuel, three, height, two, packedLen, primeBound, eight, Fin.mk.injEq, pow_two, Data.set, + Function.update_idem] + have p_magnitude : p.regs magnitude = U := by + simpa +decide [p, fuel, packedLen, magnitude] using o_magnitude + have p_three : p.regs three = 3 := by + simpa +decide [p, fuel, packedLen, three] using o_three + have p_four : p.regs four = 4 := by + simpa +decide [p, fuel, packedLen, four] using o_four + have p_five : p.regs five = 5 := by + simpa +decide [p, fuel, packedLen, five] using o_five + have p_eight : p.regs eight = 8 := by + simpa +decide [p, fuel, packedLen, eight] using o_eight + have p_padding : p.regs padding = 4 * U + 1 := by + simpa +decide [p, fuel, packedLen, padding] using o_padding + have p_digits : p.regs digits = Λ := by + simpa +decide [p, fuel, packedLen, digits] using o_digits + have p_root : p.regs root = Nat.sqrt n := by + simpa +decide [p, fuel, packedLen, root] using o_root + have p_len : p.regs len = n := by + simpa +decide [p, fuel, packedLen, len] using o_len + have p_want : p.regs want = wp := by + simpa +decide [p, fuel, packedLen, want] using o_want + have p_wantSucc : p.regs wantSucc = wp + 1 := by + simpa +decide [p, fuel, packedLen, wantSucc] using o_wantSucc + have p_temp : p.regs temp = 2 * Nat.log 2 (wp + 1) + 4 := by + simpa +decide [p, fuel, packedLen, temp] using o_temp + have p_primeBound : p.regs primeBound = m := by + simpa +decide [p, fuel, packedLen, primeBound] using o_primeBound + have p_logLen : p.regs logLen = Nat.log 2 n := by + simpa +decide [p, fuel, packedLen, logLen] using o_logLen + have p_height : p.regs height = h := by + simpa +decide [p, fuel, packedLen, height] using o_height + have p_one : p.regs one = 1 := by + simpa +decide [p, fuel, packedLen, one] using o_one + have p_two : p.regs two = 2 := by + simpa +decide [p, fuel, packedLen, two] using o_two + have p_fuel : p.regs fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel n := by + simp +decide [p, fuel, packedLen] + have p_packedLen : p.regs packedLen = 8 * m ^ 2 := by + simp +decide [p, fuel, packedLen] + have p_mem : p.mem = x.mem := o_memory + refine ⟨p, ?_, ?_, p_mem⟩ + · exact (ea.seq + (eb.seq + (ec.seq + (ed.seq + (ee.seq + (ef.seq + (eg.seq + (ej.seq + (ek.seq + (el.seq + (eo.seq + (ep)))))))))))).mono (by + rw [time_zero] + dsimp only [wp, h] + exact le_of_eq (by ring)) + · exact ⟨p_len, p_magnitude, p_digits, p_height, p_fuel, p_want, p_primeBound, p_packedLen, + p_padding⟩ + +/-- Read the native unsigned header and compute all parameters, preserving every memory cell. +This local theorem is discharged from a logarithmic width in `runs_input`. -/ +theorem exec {w : ℕ} (κ : ℕ) (x : Data 24) + (hfit : Fits w (Math.budget (x.mem 0) ((x.mem 0 + 2) ^ κ))) : + ∃ y, ExecWithin w (program κ) x y (time κ (x.mem 0) ((x.mem 0 + 2) ^ κ)) ∧ + Output (x.mem 0) ((x.mem 0 + 2) ^ κ) y ∧ y.mem = x.mem := by + let n := x.mem 0 + let U := (n + 2) ^ κ + have fit (v : ℕ) (hv : v ≤ 8 * U + 4 * n + 64) : Fits w v := + hfit.of_le (by change v ≤ Math.budget n U; dsimp [Math.budget]; omega) + have f1 : Fits w 1 := fit _ (by omega) + have f2 : Fits w 2 := fit _ (by omega) + have f3 : Fits w 3 := fit _ (by omega) + have f4 : Fits w 4 := fit _ (by omega) + have f5 : Fits w 5 := fit _ (by omega) + have f8 : Fits w 8 := fit _ (by omega) + have fb : Fits w (n + 2) := fit _ (by omega) + have fU : Fits w U := fit _ (by omega) + let a := (((((((((x.set quotient 0).set len n).set one 1).set two 2).set three 3).set + four 4).set five 5).set eight 8).set base (n + 2)) + have ha : ExecWithin w setup x a 9 := by + ram_eval [setup, a, n, f1, f2, f3, f4, f5, f8, fb, + quotient, len, one, two, three, four, five, eight, base, Fin.mk.injEq] + have hab : a.regs base = n + 2 := by simp [a] + have hp := exec_power κ a (by rw [hab]; omega) (by simpa only [hab] using fU) + let b := a.set magnitude U + have hpb : ExecWithin w (power κ) a b (κ + 1) := by simpa only [hab] using hp + obtain ⟨y, hy, hout, hm⟩ := exec_calculate b + (by simp +decide [b, a, n, magnitude, len, base, one, two, three, four, five, eight, quotient]) + (by simp [b, U, n]) + (by simp +decide [b, a, magnitude, one, base, two, three, four, five, eight]) + (by simp +decide [b, a, magnitude, two, base, three, four, five, eight]) + (by simp +decide [b, a, magnitude, three, base, four, five, eight]) + (by simp +decide [b, a, magnitude, four, base, five, eight]) + (by simp +decide [b, a, magnitude, five, base, eight]) + (by simp +decide [b, a, magnitude, eight, base]) hfit + refine ⟨y, (ha.seq (hpb.seq hy)).mono ?_, hout, hm⟩ + dsimp only [time] + omega + +namespace Math + +open Cslib.Algorithms.ThreeSum + +/-- A polynomial scale for bounding every parameter intermediate. -/ +def scale (n U : ℕ) : ℕ := 16 * (n + 2) * (U + 1) + +private theorem scale_bounds (n U : ℕ) : + 16 ≤ scale n U ∧ n + 2 ≤ scale n U ∧ 4 * U + 2 ≤ scale n U := by + have h := Nat.mul_le_mul_left 16 (Nat.mul_le_mul + (show 1 ≤ n + 2 by omega) (show 1 ≤ U + 1 by omega)) + have hn := Nat.le_mul_of_pos_right (n + 2) (show 0 < 16 * (U + 1) by positivity) + have hU := Nat.le_mul_of_pos_right (U + 1) (show 0 < n + 2 by omega) + dsimp [scale] + have hnu := Nat.zero_le (n * U) + refine ⟨?_, ?_, ?_⟩ <;> nlinarith + +private theorem scale_pow_mono {b : ℕ} (hb : 1 ≤ b) {i j : ℕ} (hij : i ≤ j) : + b ^ i ≤ b ^ j := Nat.pow_le_pow_right hb hij + +/-- A coarse polynomial bound; it also covers final unsuccessful root/log iterations. -/ +theorem budget_le_scale (n U : ℕ) : budget n U ≤ scale n U ^ 20 := by + let b := scale n U + obtain ⟨hb, hn, hU⟩ := scale_bounds n U + change 16 ≤ b at hb + change n + 2 ≤ b at hn + change 4 * U + 2 ≤ b at hU + have hb1 : 1 ≤ b := by omega + have hΛ : labelBits (2 * U) ≤ b := by + have hl := Nat.log_le_self 2 (4 * U) + dsimp [labelBits] + have he : 2 * (2 * U) = 4 * U := by omega + rw [he] + omega + have hr : Nat.sqrt n + 1 ≤ b := by have := Nat.sqrt_le_self n; omega + have hw : ChanHe.wPar n (2 * U) ≤ b ^ 3 := by + calc + _ ≤ b * b * b := by + dsimp [ChanHe.wPar] + exact Nat.mul_le_mul (Nat.mul_le_mul (by omega) hΛ) hr + _ = b ^ 3 := by ring + have hws : ChanHe.wPar n (2 * U) + 1 ≤ b ^ 4 := by + have hp : 1 ≤ b ^ 3 := Nat.one_le_pow _ _ hb1 + calc + _ ≤ 2 * b ^ 3 := by omega + _ ≤ b * b ^ 3 := Nat.mul_le_mul_right _ (by omega) + _ = b ^ 4 := by ring + have hfac : 2 * Nat.log 2 (ChanHe.wPar n (2 * U) + 1) + 4 ≤ b ^ 5 := by + have hl := Nat.log_le_self 2 (ChanHe.wPar n (2 * U) + 1) + have hp : 1 ≤ b ^ 4 := Nat.one_le_pow _ _ hb1 + calc + _ ≤ 6 * b ^ 4 := by omega + _ ≤ b * b ^ 4 := Nat.mul_le_mul_right _ (by omega) + _ = b ^ 5 := by ring + have hm : ChanHe.mPar n (2 * U) ≤ b ^ 9 := by + calc + _ ≤ b ^ 4 * b ^ 5 := Nat.mul_le_mul hws hfac + _ = b ^ 9 := by ring + have hms : ChanHe.mPar n (2 * U) + 1 ≤ 2 * b ^ 9 := by + have hp : 1 ≤ b ^ 9 := Nat.one_le_pow _ _ hb1 + omega + have hsq : (ChanHe.mPar n (2 * U) + 1) ^ 2 ≤ (2 * b ^ 9) ^ 2 := + Nat.pow_le_pow_left hms 2 + have hsmall : 8 * U + 4 * n + 64 ≤ 16 * b := by omega + have hp18 : b ≤ b ^ 18 := by + simpa only [pow_one] using scale_pow_mono hb1 (show 1 ≤ 18 by decide) + have hbudget : budget n U ≤ 48 * b ^ 18 := by + dsimp only [budget] + nlinarith + calc + _ ≤ 48 * b ^ 18 := hbudget + _ ≤ b ^ 2 * b ^ 18 := Nat.mul_le_mul_right _ (by nlinarith) + _ = b ^ 20 := by ring + +/-- A single polynomial bound, with exponent affine in the fixed magnitude exponent. -/ +theorem budget_polynomial (κ n : ℕ) : budget n ((n + 2) ^ κ) ≤ (n + 2) ^ (20 * κ + 120) := by + let a := n + 2 + have ha : 2 ≤ a := by dsimp [a]; omega + have hpow : 1 ≤ a ^ κ := Nat.one_le_pow _ _ (by omega) + have hs : scale n (a ^ κ) ≤ a ^ (κ + 6) := by + calc + _ = 16 * a * (a ^ κ + 1) := rfl + _ ≤ 32 * a * a ^ κ := by nlinarith + _ ≤ a ^ 5 * a * a ^ κ := by + exact Nat.mul_le_mul_right _ (Nat.mul_le_mul_right _ (by + have := Nat.pow_le_pow_left ha 5 + norm_num at this ⊢ + omega)) + _ = a ^ (κ + 6) := by ring + calc + _ ≤ scale n (a ^ κ) ^ 20 := budget_le_scale n (a ^ κ) + _ ≤ (a ^ (κ + 6)) ^ 20 := Nat.pow_le_pow_left hs 20 + _ = a ^ (20 * κ + 120) := by ring + +end Math +/-- Positive logarithmic word-width threshold for the complete parameter computation. -/ +def width (κ n : ℕ) : ℕ := (20 * κ + 121) * Nat.clog 2 (n + 2) + +/-- The width requirement is satisfiable even at empty input and exponent zero. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := Nat.clog_pos (b := 2) (by decide) (show 1 < n + 2 by omega) + dsimp [width] + positivity + +private theorem budget_le_two_pow (κ n : ℕ) : + Math.budget n ((n + 2) ^ κ) ≤ 2 ^ ((20 * κ + 120) * Nat.clog 2 (n + 2)) := by + calc + _ ≤ (n + 2) ^ (20 * κ + 120) := Math.budget_polynomial κ n + _ ≤ (2 ^ Nat.clog 2 (n + 2)) ^ (20 * κ + 120) := + Nat.pow_le_pow_left (Nat.le_pow_clog (by decide) _) _ + _ = _ := by rw [← pow_mul]; congr 1; ring + +/-- Every local fit premise follows from the positive logarithmic width. -/ +theorem budget_fits {κ n w : ℕ} (hw : width κ n ≤ w) : + Fits w (Math.budget n ((n + 2) ^ κ)) := by + have hl := Nat.clog_pos (b := 2) (by decide) (show 1 < n + 2 by omega) + apply Fits.mono (w := width κ n) _ hw + apply (budget_le_two_pow κ n).trans_lt + apply Nat.pow_lt_pow_right (by decide) + dsimp [width] + nlinarith + +/-- Signed-encoded inputs and the unsigned length header fit at the same width. -/ +theorem inputFits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := by + have hf := budget_fits hw + refine ⟨?_, ?_⟩ + · simp only [List.length_map] + apply hf.of_le + dsimp [Math.budget] + omega + · intro v hv + obtain ⟨z, hz, rfl⟩ := List.mem_map.mp hv + have habs : z.natAbs ≤ (input.length + 2) ^ κ := by + have hc : (z.natAbs : ℤ) ≤ ((input.length + 2) ^ κ : ℕ) := by + simpa only [Int.natCast_natAbs] using hvalues z hz + exact_mod_cast hc + apply hf.of_le + dsimp [Math.budget, Signed.encode] + split_ifs <;> omega + +/-- Explicit `Oκ(√n + log(n+2))` instruction bound, valid also at `n = 0,1,2`. -/ +theorem time_bound (κ n : ℕ) : + time κ n ((n + 2) ^ κ) ≤ + 5 * Nat.sqrt n + (400 * κ + 3000) * Nat.clog 2 (n + 2) := by + let U := (n + 2) ^ κ + let L := Nat.clog 2 (n + 2) + let E := 20 * κ + 120 + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hB : Nat.log 2 (Math.budget n U) ≤ E * L := by + calc + _ ≤ Nat.log 2 (2 ^ (E * L)) := Nat.log_mono_right (budget_le_two_pow κ n) + _ = _ := Nat.log_pow (by decide) _ + have hlogU : Nat.log 2 (4 * U) ≤ E * L := + (Nat.log_mono_right (show 4 * U ≤ Math.budget n U by dsimp [Math.budget]; omega)).trans hB + have hw := Math.want_le n U + have hlogw : Nat.log 2 (Cslib.Algorithms.ThreeSum.ChanHe.wPar n (2 * U) + 1) ≤ E * L := by + apply (Nat.log_mono_right ?_).trans hB + dsimp [Math.budget] + nlinarith + have hlogn : Nat.log 2 n ≤ E * L := + (Nat.log_mono_right (show n ≤ Math.budget n U by dsimp [Math.budget]; omega)).trans hB + have hh := Math.height_le n + change time κ n U ≤ _ + dsimp only [time] + dsimp only [E, L] at * + nlinarith + +/-- Native run from actual signed input initialization. No parameters or arithmetic routines +are supplied as advice. The returned state identifies all outputs and keeps *all* input memory. +The flat program has 24 registers and depends only on `κ`; halting adds one instruction. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ y t, t ≤ 5 * Nat.sqrt input.length + + (400 * κ + 3000) * Nat.clog 2 (input.length + 2) ∧ + Runs w ((program κ).program packedLen) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ t ∧ + Output input.length ((input.length + 2) ^ κ) y ∧ + y.mem = (Data.init (input.map Signed.encode) : Data 24).mem ∧ y.Bounded w := by + have hi := inputFits input hw hvalues + obtain ⟨y, hy, ho, hm⟩ := exec κ (Data.init (input.map Signed.encode)) + (by simpa only [Data.init_mem_zero, List.length_map] using budget_fits hw) + simp only [Data.init_mem_zero, List.length_map] at hy ho + obtain ⟨t, ht, he⟩ := hy + have hr := he.runs (pc := 0) (p := (program κ).program packedLen) + (Program.Contains.self _).append_left + simp only [Nat.zero_add] at hr + refine ⟨y, t, ht.trans (time_bound κ input.length), hr, ho, hm, ?_⟩ + exact hr.bounded_init hi + +/-- The number of native instructions in the finite structured code is affine in `κ`. -/ +theorem size_program (κ : ℕ) : (program κ).size = κ + 71 := by + have hp : (power κ).size = κ + 1 := by + induction κ with + | zero => rfl + | succ κ ih => simp [power, ih] + simp [program, setup, calculate, hp, NatLog.program, NatLog.body, + NatClog.program, NatClog.body, NatSqrt.program, NatSqrt.body] + omega + +/-- The code is the same at every word width. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by + have hs : ¬setup.ReadsWidth := by decide + have hc : ¬calculate.ReadsWidth := by decide + have hp : ¬(power κ).ReadsWidth := by + induction κ with + | zero => decide + | succ κ ih => + change ¬((power κ).ReadsWidth ∨ + (Code.op (Op.bin BinOp.mul magnitude magnitude base) : Code .standard 24).ReadsWidth) + exact not_or.mpr ⟨ih, by decide⟩ + simpa only [program, Code.ReadsWidth, not_or] using And.intro hs (And.intro hp hc) + +/-- Canonical-width executions use only polynomially bounded integers, including memory +addresses, loop counters, and final unsuccessful tests. -/ +theorem intermediates_polynomial {κ : ℕ} (input : List ℤ) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 24} {t : ℕ} + (hr : Runs (width κ input.length) ((program κ).program packedLen) + (State.init (input.map Signed.encode)) s t) : + (∀ i, s.data.regs i < (input.length + 2) ^ (40 * κ + 242)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (40 * κ + 242) := by + let n := input.length + let L := Nat.clog 2 (n + 2) + have hL : 0 < L := Nat.clog_pos (by decide) (by omega) + have hpred : 2 ^ (L - 1) < n + 2 := + Nat.pow_pred_clog_lt_self (b := 2) (x := n + 2) (by decide) (by omega) + have he : 2 ^ L = 2 * 2 ^ (L - 1) := by + conv_lhs => rw [show L = (L - 1) + 1 by omega] + rw [pow_succ, Nat.mul_comm] + have hceil : 2 ^ L ≤ (n + 2) ^ 2 := by rw [he]; nlinarith + have hpoly : 2 ^ width κ n ≤ (n + 2) ^ (40 * κ + 242) := by + calc + _ = (2 ^ L) ^ (20 * κ + 121) := by + dsimp only [width, L] + rw [← pow_mul] + congr 1 + ring + _ ≤ ((n + 2) ^ 2) ^ (20 * κ + 121) := Nat.pow_le_pow_left hceil _ + _ = _ := by rw [← pow_mul]; congr 1; omega + have hb := hr.bounded_init (inputFits input le_rfl hvalues) + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters diff --git a/ComplexityTests/WordRAMChanHeParameters.lean b/ComplexityTests/WordRAMChanHeParameters.lean new file mode 100644 index 0000000000..f76dec9c15 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeParameters.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +/-! +# Executed boundary cases for native Chan–He parameters + +The observations include all nine outputs, the instruction count, and input memory cells. +The generic run clients below discharge the actual input magnitude and width hypotheses. +-/ + +-- This module contains only private execution checks. +set_option linter.privateModule false + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters + +private def observe (κ : ℕ) (input : List ℤ) : Option (List ℕ × ℕ × List ℕ) := + ((program κ).eval 64 32 (Data.init (input.map Signed.encode))).map fun (x, t) => + ([x.regs len, x.regs magnitude, x.regs digits, x.regs height, x.regs fuel, + x.regs want, x.regs primeBound, x.regs packedLen, x.regs padding], t, + [x.mem 0, x.mem 1, x.mem 2]) + +-- Empty input, including the fixed exponent zero. +example : observe 0 [] = some ([0, 1, 3, 1, 1, 15, 192, 294912, 5], 83, [0, 0, 0]) := by + decide + +-- One position; the signed encoding of -1 remains in memory. +example : observe 0 [-1] = some ([1, 1, 3, 1, 1, 30, 372, 1107072, 5], 88, [1, 3, 0]) := by + decide + +-- Two positions at exponent zero: the ceiling logarithm and fuel increase. +example : observe 0 [-1, 1] = + some ([2, 1, 3, 2, 4, 30, 372, 1107072, 5], 97, [2, 3, 2]) := by + decide + +-- Nonconstant polynomial magnitude, with both signed boundary values. +example : observe 1 [-4, 4] = + some ([2, 4, 5, 2, 4, 50, 714, 4078368, 17], 110, [2, 9, 8]) := by + decide + +-- A native contract client, including all width/magnitude hypotheses and all memory framing. +example : ∃ y t, t ≤ 5 * Nat.sqrt 2 + 3400 * Nat.clog 2 4 ∧ + Runs 512 ((program 1).program packedLen) (State.init ([(-4 : ℤ), 4].map Signed.encode)) + ⟨(program 1).size, y⟩ t ∧ Output 2 4 y ∧ + y.mem = (Data.init ([(-4 : ℤ), 4].map Signed.encode) : Data 24).mem ∧ y.Bounded 512 := by + apply runs_input (κ := 1) [(-4 : ℤ), 4] (by decide) + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false] at hz + rcases hz with rfl | rfl <;> decide + +-- The empty-input width remains positive and the final input-init theorem is satisfiable. +example : 0 < width 0 0 ∧ InputFits 512 (([] : List ℤ).map Signed.encode) := by + constructor + · decide + · apply inputFits (κ := 0) [] (by decide) + simp From 0505439ad73e746828ba9e5ade7ec8bb4577456e Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 10:47:17 -0400 Subject: [PATCH 12/20] fix(wordram): repair parameter proof hygiene and ceiling-log docs --- Complexity/Algorithms/WordRAM/NatClog.lean | 4 ++-- .../Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/NatClog.lean b/Complexity/Algorithms/WordRAM/NatClog.lean index 6feabd6b67..215117b054 100644 --- a/Complexity/Algorithms/WordRAM/NatClog.lean +++ b/Complexity/Algorithms/WordRAM/NatClog.lean @@ -11,7 +11,7 @@ public import Complexity.Computability.WordRAM.Notation public import Mathlib.Data.Nat.Log /-! -# A charged native binary logarithm +# A charged native ceiling binary logarithm ## Main definitions @@ -30,7 +30,7 @@ open Cslib.WordRAM /-- Quotient, initially the input. -/ abbrev arg : Fin 4 := 0 -/-- Floor logarithm output. -/ +/-- Ceiling logarithm output. -/ abbrev result : Fin 4 := 1 /-- Constant one. -/ abbrev one : Fin 4 := 2 diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean index 3ddc4b7cc4..f26ccbe183 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -841,8 +841,9 @@ theorem size_program (κ : ℕ) : (program κ).size = κ + 71 := by induction κ with | zero => rfl | succ κ ih => simp [power, ih] - simp [program, setup, calculate, hp, NatLog.program, NatLog.body, - NatClog.program, NatClog.body, NatSqrt.program, NatSqrt.body] + simp only [program, setup, calculate, NatLog.program, NatLog.body, NatSqrt.program, + NatSqrt.body, NatClog.program, NatClog.body, Code.size.eq_2, Code.size, Nat.reduceAdd, hp, + Code.size_rename] omega /-- The code is the same at every word width. -/ From e4d2fa0348cd1a64a24f75f58889aed06877ec5f Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 14:37:44 -0400 Subject: [PATCH 13/20] feat(wordram): implement the complete native convolution host Implement milestone #1524 above native Params/Fill. Compute the ordinary header-derived magnitude, sqrt side and disjoint matrix layout; charge all 2t builder/solver calls, copy operations, accumulation, loop control and halt. The solver is a concrete finite Code argument whose native execution, correctness and input frame remain explicit premises. This does not finish #1394's fast Exact Triangle or subquadratic ordinary-3SUM endpoint. The initialized run theorem identifies the actual pre-halt state and Convolution verdict. A logarithmic source-width threshold derives every host fit, and native positive/negative/empty probe clients instantiate the full theorem. Reuse Params, NatSqrt, Fill.Args/exec and the standard compiler. Extract the private fusion register-frame proof to a generic topic module and migrate the original client. Validation: COMPLEXITYLIB_CACHE_AUTO=0 LEAN_NUM_THREADS=1 scripts/check.sh passed all six gates together, including strict tests/global Axioms. Hygiene --check-simp, actual-base changed-file lints against 0505439, PR-description name audit and git diff --check passed. Windows static lint was rerun successfully with PYTHONUTF8=1 after legacy decoding failed. Nonauthoring technical review: /root/host_review found no correctness or API blockers on tree a3eb6a3639705a0f294a29da2f2ac4caa25cf6fe against 0505439ad73e746828ba9e5ade7ec8bb4577456e. Final tree 95825c5f0f3367549f12af40fcc94f38d4c11c41 differs only in six new-file modes (100755 -> 100644), with every blob identical. Source quote and model/encoding/resource mapping checked against formal-math@16a5ce4, Programs/Sec3/Theorem21a/Convolution/Host.lean:60-87. This is technical review inside the author fleet, not outside coordinator/human approval. Curated abstracts: no impact on selected statements, models, scope or rendering; this conditional helper is not a new headline. Outside coordinator review is required. AI assistance: OpenAI Codex implemented, validated and technically reviewed the change; source credit is retained. --- Complexity.lean | 5 + .../WordRAM/FusionTree/Node/Registers.lean | 16 +- .../WordRAM/ThreeSum/Host/Loop.lean | 457 ++++++++++++++++++ .../WordRAM/ThreeSum/Host/Prepare.lean | 249 ++++++++++ .../WordRAM/ThreeSum/Host/Program.lean | 158 ++++++ .../WordRAM/ThreeSum/Host/Width.lean | 123 +++++ .../Computability/WordRAM/RegisterFrame.lean | 39 ++ ComplexityTests/ThreeSumHost.lean | 141 ++++++ ROADMAP.md | 9 +- UPSTREAM.md | 8 +- 10 files changed, 1187 insertions(+), 18 deletions(-) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Host/Loop.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Host/Prepare.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Host/Program.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Host/Width.lean create mode 100644 Complexity/Computability/WordRAM/RegisterFrame.lean create mode 100644 ComplexityTests/ThreeSumHost.lean diff --git a/Complexity.lean b/Complexity.lean index af010bc30b..2ac0695cc4 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -109,6 +109,10 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.Fill +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Loop +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Prepare +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Program +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Width public import Complexity.Algorithms.WordRAM.ThreeSum.Memory public import Complexity.Algorithms.WordRAM.ThreeSum.Weight public import Complexity.Algorithms.WordRAM.VanEmdeBoas.Call @@ -1636,6 +1640,7 @@ public import Complexity.Computability.WordRAM.RAMInput public import Complexity.Computability.WordRAM.Randomized.Defs public import Complexity.Computability.WordRAM.Randomized.Expected public import Complexity.Computability.WordRAM.Randomized.Structured +public import Complexity.Computability.WordRAM.RegisterFrame public import Complexity.Computability.WordRAM.Signed.Arithmetic public import Complexity.Computability.WordRAM.Signed.Comparison public import Complexity.Computability.WordRAM.Signed.Compile diff --git a/Complexity/Algorithms/WordRAM/FusionTree/Node/Registers.lean b/Complexity/Algorithms/WordRAM/FusionTree/Node/Registers.lean index f5903b8169..30b798fbaa 100644 --- a/Complexity/Algorithms/WordRAM/FusionTree/Node/Registers.lean +++ b/Complexity/Algorithms/WordRAM/FusionTree/Node/Registers.lean @@ -9,6 +9,7 @@ module public import Complexity.Algorithms.WordRAM.FusionTree.Parameters public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader +public import Complexity.Computability.WordRAM.RegisterFrame /-! # Register banks for fusion-node queries @@ -75,25 +76,12 @@ theorem bank_injective {k offset : ℕ} (hk : offset + k ≤ registers) : dsimp only [bank] at this omega -/-- A renamed subroutine cannot write a register outside its embedding. This proof treats -the subroutine structurally and does not expand its instruction graph. -/ -private theorem preserves_rename {k : ℕ} {f : Fin k → Fin registers} (c : Code .standard k) - (i : Fin registers) (hi : ∀ j, f j ≠ i) : (c.rename f).Preserves i := by - induction c with - | op o => - cases o <;> simp only [Code.rename, Code.Preserves, Op.rename, Op.dest, - ne_eq, Option.some.injEq, reduceCtorEq, not_false_eq_true] <;> apply hi - | seq a b iha ihb => exact ⟨iha, ihb⟩ - | branch cond pos neg ihp ihn => exact ⟨ihp, ihn⟩ - | loop cond body ih => exact ih - | rand dst src => simpa only [Code.rename, Code.Preserves] using hi dst - /-- In particular, a contiguous bank frames every physical register outside its interval. -/ theorem preserves_bank {k offset : ℕ} (hk : offset + k ≤ registers) (c : Code .standard k) (i : Fin registers) (hi : i.val < offset ∨ offset + k ≤ i.val) : (c.rename (bank offset hk)).Preserves i := by - apply preserves_rename + apply Code.preserves_rename_of_forall_ne intro j he have := congrArg Fin.val he dsimp only [bank] at this diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Host/Loop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Loop.lean new file mode 100644 index 0000000000..853b3a1b32 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Loop.lean @@ -0,0 +1,457 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Prepare + +/-! +# The native Convolution-3SUM instance loop + +The supplied solver is an actual finite structured program. Its six input registers hold the +side, weight bound, three matrix pointers and workspace pointer; its seventh register returns +an unsigned Boolean. Solver registers are disjoint from the host bank. The host writes each +instance using `Fill.program`, copies its arguments, executes the solver and accumulates the +verdict. The execution theorem requires the concrete solver's execution/correctness/frame proof; +it does not establish a fast solver by assuming one. + +The construction directly adapts Anthropic's pinned +[`c3Round` and `c3Body`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/Convolution/Host.lean). + +## Main definitions + +* `Host.instanceArgs`: the concrete generated instance and layout. +* `Host.hit`: the mathematical verdict for one instance. +* `Host.round`: fill, invoke the solver and advance one instance. +* `Host.loop`: the complete instance loop. + +## References + +* [V. Vassilevska Williams and R. Williams, *Finding, minimizing, and counting weighted + subgraphs*][VassilevskaWilliamsWilliams2013] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Host + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.ThreeSum + +/-- The generated matrices follow the input and precede the solver's workspace. -/ +def instanceArgs (N U s : ℕ) (μ : ℕ → ℤ) : Fill.Args := + ⟨N, sideLength N, s, 1, N + 1, + N + 1 + sideLength N * sideLength N, + N + 1 + 2 * (sideLength N * sideLength N), μ, 2 * U + 1⟩ + +/-- The exact-triangle answer for one instance of the explicit reduction. -/ +def hit (N U s : ℕ) (μ : ℕ → ℤ) : Prop := + Cslib.Algorithms.ExactTriangle.HasZeroTriangle + (weightAB (t := sideLength N) N μ (2 * U + 1)) + (weightBC N s μ (2 * U + 1)) (weightAC N s μ (2 * U + 1)) + +/-- Embed the fixed matrix-builder register bank. -/ +def fillRegisters (i : Fin 28) : Fin 64 := ⟨i.val, by omega⟩ + +/-- Copy every matrix-builder argument; the builder bank occupies the first 28 registers. -/ +def fillSetup : Code .standard 64 := + let fLen := fillRegisters Cell.len + let fBase := fillRegisters Cell.base + let fFiller := fillRegisters Cell.filler + let fSide := fillRegisters Cell.side + let fInst := fillRegisters Cell.inst + let fAB := fillRegisters Cell.matAB + let fBC := fillRegisters Cell.matBC + let fAC := fillRegisters Cell.matAC + ram[ fLen ← len; fBase ← base; fFiller ← encodedFiller; + fSide ← side; fInst ← inst; fAB ← matAB; fBC ← matBC; fAC ← matAC ] + +/-- Matrix construction, including its argument copies. -/ +def fill : Code .standard 64 := .seq fillSetup (Fill.program.rename fillRegisters) + +/-- Host registers precede the solver bank. -/ +def hostRegisters {r : ℕ} (i : Fin 64) : Fin (64 + r) := ⟨i.val, by omega⟩ + +/-- The solver bank is disjoint from every host register. -/ +def solverRegisters {r : ℕ} (i : Fin r) : Fin (64 + r) := ⟨64 + i.val, by omega⟩ + +/-- One of the seven fixed protocol registers in the solver bank. -/ +def solverArg {r : ℕ} (i : Fin 7) : Fin (r + 7) := ⟨i.val, by omega⟩ + +/-- Protocol registers after placing the solver after the host. -/ +def callRegisters {r : ℕ} (i : Fin 7) : Fin (64 + (r + 7)) := + solverRegisters (solverArg i) + +/-- Prepare the solver's side, unsigned bound, AB/BC/AC pointers and free pointer. -/ +def solverSetup {r : ℕ} : Code .standard (64 + (r + 7)) := + .seq (.op (.copy (callRegisters 0) (hostRegisters side))) + (.seq (.op (.copy (callRegisters 1) (hostRegisters filler))) + (.seq (.op (.copy (callRegisters 2) (hostRegisters matAB))) + (.seq (.op (.copy (callRegisters 3) (hostRegisters matBC))) + (.seq (.op (.copy (callRegisters 4) (hostRegisters matAC))) + (.op (.copy (callRegisters 5) (hostRegisters free))))))) + +/-- Accumulate the unsigned Boolean result and advance the instance counter. -/ +def finish {r : ℕ} : Code .standard (64 + (r + 7)) := + .seq (.branch (.eq (callRegisters 6) (hostRegisters one)) + (.op (.copy (hostRegisters found) (hostRegisters one))) + (.op (.copy (hostRegisters found) (hostRegisters found)))) + (.op (.bin .add (hostRegisters inst) (hostRegisters inst) (hostRegisters one))) + +/-- One complete instance, invoking the supplied finite solver in its own register bank. -/ +def round {r : ℕ} (solver : Code .standard (r + 7)) : Code .standard (64 + (r + 7)) := + .seq (fill.rename hostRegisters) + (.seq solverSetup (.seq (solver.rename solverRegisters) finish)) + +/-- Every one of the `2t` instances is examined, without an uncharged early-exit operation. -/ +def loop {r : ℕ} (solver : Code .standard (r + 7)) : Code .standard (64 + (r + 7)) := + .loop (.lt (hostRegisters inst) (hostRegisters stop)) (round solver) + +private theorem fill_injective : fillRegisters.Injective := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 64 => k.val) h) + +private theorem parameters_of_regs {N U : ℕ} {x y : Data 64} + (hx : Parameters N U x) + (h : ∀ i : Fin 64, 34 ≤ i.val → i ≠ inst → i ≠ found → y.regs i = x.regs i) : + Parameters N U y := by + exact ⟨(h len (by decide) (by decide) (by decide)).trans hx.len, + (h upper (by decide) (by decide) (by decide)).trans hx.upper, + (h base (by decide) (by decide) (by decide)).trans hx.base, + (h matAB (by decide) (by decide) (by decide)).trans hx.ab, + (h matBC (by decide) (by decide) (by decide)).trans hx.bc, + (h matAC (by decide) (by decide) (by decide)).trans hx.ac, + (h free (by decide) (by decide) (by decide)).trans hx.free, + (h side (by decide) (by decide) (by decide)).trans hx.side, + (h stop (by decide) (by decide) (by decide)).trans hx.stop, + (h filler (by decide) (by decide) (by decide)).trans hx.filler, + (h encodedFiller (by decide) (by decide) (by decide)).trans hx.encodedFiller, + (h one (by decide) (by decide) (by decide)).trans hx.one⟩ + +/-- All eight argument copies and the complete matrix builder are charged. Memory before +the matrix blocks is preserved; the result contains the three actual serialized matrices. -/ +theorem exec_fill {w N U s : ℕ} (μ : ℕ → ℤ) (x : Data 64) + (hp : Parameters N U x) (hs : x.regs inst = s) + (hmem : ∀ i < N, x.mem (1 + i) = encode (μ i)) + (hidx : GenericRAM.Signed.Fits w ((2 * (sideLength N * sideLength N) + s + 1 : ℕ) : ℤ)) + (hF : GenericRAM.Signed.Fits w (2 * (U : ℤ) + 1)) + (hnF : GenericRAM.Signed.Fits w (-(2 * (U : ℤ) + 1))) + (hμ : ∀ i < N, GenericRAM.Signed.Fits w (μ i)) + (hsize : Fits (w + 2) (N + 1 + 3 * (sideLength N * sideLength N))) : + ∃ y, ExecWithin (w + 2) fill x y (131 * (sideLength N * sideLength N) + 13) ∧ + Parameters N U y ∧ y.regs inst = s ∧ y.regs found = x.regs found ∧ + y.mem = (instanceArgs N U s μ).filled x.mem (sideLength N * sideLength N) ∧ + (∀ a < N + 1, y.mem a = x.mem a) := by + let b := x.set (fillRegisters Cell.len) N |>.set (fillRegisters Cell.base) 1 + |>.set (fillRegisters Cell.filler) (4 * U + 2) + |>.set (fillRegisters Cell.side) (sideLength N) |>.set (fillRegisters Cell.inst) s + |>.set (fillRegisters Cell.matAB) (N + 1) + |>.set (fillRegisters Cell.matBC) (N + 1 + sideLength N * sideLength N) + |>.set (fillRegisters Cell.matAC) (N + 1 + 2 * (sideLength N * sideLength N)) + have hb : ExecWithin (w + 2) fillSetup x b 8 := by + ram_eval [fillSetup, b, hp.len, hp.base, hp.encodedFiller, hp.side, hs, + hp.ab, hp.bc, hp.ac, fillRegisters, Cell.len, Cell.base, Cell.filler, Cell.side, + Cell.inst, Cell.matAB, Cell.matBC, Cell.matAC, len, base, encodedFiller, side, inst, + matAB, matBC, matAC, Fin.mk.injEq] + have hbregs (i : Fin 64) (hi : 34 ≤ i.val) : b.regs i = x.regs i := by + have hne (j : Fin 28) : i ≠ fillRegisters j := by + intro he + have hv := congrArg Fin.val he + dsimp only [fillRegisters] at hv + have := j.isLt + omega + simp only [b, Data.set_regs, Function.update_of_ne (hne _)] + have hbf : (b.comap fillRegisters).regs Cell.filler = encode (instanceArgs N U s μ).filler := by + simp +decide [b, instanceArgs, fillRegisters, Cell.filler, Cell.side, Cell.inst, + Cell.matAB, Cell.matBC, Cell.matAC, encode, + show ¬(2 * (U : ℤ) + 1 < 0) from by omega] + omega + obtain ⟨p, he, hm, -⟩ := Fill.exec (instanceArgs N U s μ) (b.comap fillRegisters) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.side, Cell.inst, + Cell.matAB, Cell.matBC, Cell.matAC]) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.inst, + Cell.matAB, Cell.matBC, Cell.matAC]) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.matAB, Cell.matBC, Cell.matAC]) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.matBC, Cell.matAC]) + (by simp [b, instanceArgs]) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.len, Cell.base, Cell.filler, + Cell.side, Cell.inst, Cell.matAB, Cell.matBC, Cell.matAC]) + (by simp +decide [b, instanceArgs, fillRegisters, Cell.base, Cell.filler, + Cell.side, Cell.inst, Cell.matAB, Cell.matBC, Cell.matAC]) hbf + hmem hidx hF hnF hμ + (hsize.of_le (by dsimp [instanceArgs]; omega)) + (hsize.of_le (by dsimp [instanceArgs]; omega)) + (hsize.of_le (by dsimp [instanceArgs]; omega)) + (hsize.of_le (by dsimp [instanceArgs]; omega)) + (by dsimp [instanceArgs]; omega) (by dsimp [instanceArgs]; omega) + (by dsimp [instanceArgs]; omega) + let y := p.extend fillRegisters b + have hy : ExecWithin (w + 2) (Fill.program.rename fillRegisters) b y + (131 * (sideLength N * sideLength N) + 5) := he.rename fill_injective + have hyr (i : Fin 64) (hi : 34 ≤ i.val) : y.regs i = x.regs i := by + refine (hy.regs_eq ?_).trans (hbregs i hi) + apply Code.preserves_rename_of_forall_ne + intro j heq + have hv := congrArg Fin.val heq + dsimp only [fillRegisters] at hv + have := j.isLt + omega + refine ⟨y, ?_, parameters_of_regs hp (fun i hi _ _ => hyr i hi), (hyr inst (by decide)).trans hs, + hyr found (by decide), hm, ?_⟩ + · convert hb.seq hy using 1 <;> first | rfl | omega + · intro a ha + change p.mem a = x.mem a + rw [hm] + apply Fill.Args.outside_filled + all_goals dsimp [instanceArgs]; omega + +/-- The host register bank is embedded injectively. -/ +theorem hostRegisters_injective {r : ℕ} : (hostRegisters (r := r)).Injective := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin (64 + r) => k.val) h) + +private theorem solver_injective {r : ℕ} : (solverRegisters (r := r)).Injective := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [solverRegisters] at this + omega + +private theorem exec_solverSetup {w r : ℕ} (x : Data (64 + (r + 7))) : + ∃ y, ExecWithin w solverSetup x y 6 ∧ + (∀ i, y.regs (hostRegisters i) = x.regs (hostRegisters i)) ∧ y.mem = x.mem ∧ + y.regs (callRegisters 0) = x.regs (hostRegisters side) ∧ + y.regs (callRegisters 1) = x.regs (hostRegisters filler) ∧ + y.regs (callRegisters 2) = x.regs (hostRegisters matAB) ∧ + y.regs (callRegisters 3) = x.regs (hostRegisters matBC) ∧ + y.regs (callRegisters 4) = x.regs (hostRegisters matAC) ∧ + y.regs (callRegisters 5) = x.regs (hostRegisters free) := by + refine ⟨?y, ?exec, ?frame, ?mem, ?side, ?bound, ?ab, ?bc, ?ac, ?free⟩ + case exec => ram_eval [solverSetup, callRegisters, solverArg, solverRegisters, + hostRegisters, side, filler, + matAB, matBC, matAC, free, Fin.mk.injEq] + case frame => + intro i + have hlt := i.isLt + simp only [Data.set_regs, Function.update_apply, hostRegisters, Fin.mk.injEq] + split_ifs <;> omega + all_goals simp +decide [Data.set_regs, callRegisters, solverArg, solverRegisters, hostRegisters] + +private theorem exec_finish {w r s : ℕ} (x : Data (64 + (r + 7))) + (ho : x.regs (hostRegisters one) = 1) (hi : x.regs (hostRegisters inst) = s) + (hfit : Fits w (s + 1)) : + ∃ y, ExecWithin w finish x y 4 ∧ + y.regs (hostRegisters inst) = s + 1 ∧ + y.regs (hostRegisters found) = + (if x.regs (callRegisters 6) = 1 then 1 else x.regs (hostRegisters found)) ∧ + (∀ i : Fin 64, i ≠ inst → i ≠ found → + y.regs (hostRegisters i) = x.regs (hostRegisters i)) ∧ y.mem = x.mem := by + change x.regs ⟨45, by omega⟩ = 1 at ho + change x.regs ⟨42, by omega⟩ = s at hi + by_cases ha : x.regs (callRegisters 6) = 1 + · change x.regs ⟨70, by omega⟩ = 1 at ha + refine ⟨(x.set (hostRegisters found) 1).set (hostRegisters inst) (s + 1), + ?exec, ?inst, ?found, ?frame, ?mem⟩ + case exec => + apply ExecWithin.of_eval (fuel := 0) (n := 3) + · simp +decide [finish, Code.eval, Op.eval_copy, Op.eval_bin, Data.put, + Data.set_regs, Cond.holds_eq, BinOp.eval, + callRegisters, solverArg, solverRegisters, hostRegisters, one, inst, found, + ho, hi, ha, hfit] + · omega + case inst => simp + case found => + simp +decide [ha, callRegisters, solverArg, solverRegisters, hostRegisters, inst, found] + case frame => + intro i hi hf + have hni := (hostRegisters_injective (r := r + 7)).ne hi + have hnf := (hostRegisters_injective (r := r + 7)).ne hf + simp only [Data.set_regs, Function.update_of_ne hni, Function.update_of_ne hnf] + case mem => rfl + · change ¬x.regs ⟨70, by omega⟩ = 1 at ha + refine ⟨(x.set (hostRegisters found) (x.regs (hostRegisters found))).set + (hostRegisters inst) (s + 1), ?exec, ?inst, ?found, ?frame, ?mem⟩ + case exec => + apply ExecWithin.of_eval (fuel := 0) (n := 4) + · simp +decide [finish, Code.eval, Op.eval_copy, Op.eval_bin, Data.put, + Data.set_regs, Cond.holds_eq, BinOp.eval, + callRegisters, solverArg, solverRegisters, hostRegisters, one, inst, found, + ho, hi, ha, hfit] + · omega + case inst => simp + case found => + simp +decide [ha, callRegisters, solverArg, solverRegisters, hostRegisters, inst, found] + case frame => + intro i hi hf + have hni := (hostRegisters_injective (r := r + 7)).ne hi + have hnf := (hostRegisters_injective (r := r + 7)).ne hf + simp only [Data.set_regs, Function.update_of_ne hni, Function.update_of_ne hnf] + case mem => rfl + +/-- One generated instance, with the concrete solver's native execution proof supplied by the +caller. The solver may overwrite matrices and scratch, but preserves the length header and input. +The matrices in its premise are produced by the native builder, with no supplied layout oracle. -/ +theorem exec_round {w r N U s T : ℕ} (μ : ℕ → ℤ) + (solver : Code .standard (r + 7)) (x : Data (64 + (r + 7))) + (hp : Parameters N U (x.comap hostRegisters)) + (hs : x.regs (hostRegisters inst) = s) (hslt : s < 2 * sideLength N) + (hf : x.regs (hostRegisters found) ≤ 1) + (hmem : ∀ i < N, x.mem (1 + i) = encode (μ i)) + (hidx : GenericRAM.Signed.Fits w ((2 * (sideLength N * sideLength N) + s + 1 : ℕ) : ℤ)) + (hF : GenericRAM.Signed.Fits w (2 * (U : ℤ) + 1)) + (hnF : GenericRAM.Signed.Fits w (-(2 * (U : ℤ) + 1))) + (hμ : ∀ i < N, GenericRAM.Signed.Fits w (μ i)) + (hsize : Fits (w + 2) (N + 1 + 3 * (sideLength N * sideLength N))) + (hsolver : ∀ (a : Data (r + 7)) (mem : ℕ → ℕ), + a.regs (solverArg 0) = sideLength N → a.regs (solverArg 1) = 2 * U + 1 → + a.regs (solverArg 2) = N + 1 → + a.regs (solverArg 3) = N + 1 + sideLength N * sideLength N → + a.regs (solverArg 4) = N + 1 + 2 * (sideLength N * sideLength N) → + a.regs (solverArg 5) = N + 1 + 3 * (sideLength N * sideLength N) → + a.mem = (instanceArgs N U s μ).filled mem (sideLength N * sideLength N) → + ∃ b, ExecWithin (w + 2) solver a b T ∧ b.regs (solverArg 6) ≤ 1 ∧ + (b.regs (solverArg 6) = 1 ↔ hit N U s μ) ∧ + (∀ a < N + 1, b.mem a = mem a)) : + ∃ y, ExecWithin (w + 2) (round solver) x y + (131 * (sideLength N * sideLength N) + T + 23) ∧ + Parameters N U (y.comap hostRegisters) ∧ y.regs (hostRegisters inst) = s + 1 ∧ + y.regs (hostRegisters found) ≤ 1 ∧ + (y.regs (hostRegisters found) = 1 ↔ x.regs (hostRegisters found) = 1 ∨ hit N U s μ) ∧ + (∀ a < N + 1, y.mem a = x.mem a) := by + obtain ⟨a, ha, hap, hasi, haf, ham, -⟩ := + exec_fill μ (x.comap hostRegisters) hp hs hmem hidx hF hnF hμ hsize + let y := a.extend hostRegisters x + have hy : ExecWithin (w + 2) (fill.rename hostRegisters) x y + (131 * (sideLength N * sideLength N) + 13) := ha.rename hostRegisters_injective + have hyc : y.comap hostRegisters = a := Data.comap_extend hostRegisters_injective _ _ + have hyp : Parameters N U (y.comap hostRegisters) := by rw [hyc]; exact hap + have hysi : y.regs (hostRegisters inst) = s := + (hostRegisters_injective.extend_apply _ _ inst).trans hasi + have hyf : y.regs (hostRegisters found) = x.regs (hostRegisters found) := + (hostRegisters_injective.extend_apply _ _ found).trans haf + obtain ⟨b, hb, hbr, hbm, hb0, hb1, hb2, hb3, hb4, hb5⟩ := exec_solverSetup (w := w + 2) y + have hbp : Parameters N U (b.comap hostRegisters) := + parameters_of_regs hyp (fun i _ _ _ => hbr i) + obtain ⟨c, hc, hcans, hchit, hcm⟩ := hsolver (b.comap solverRegisters) x.mem + (hb0.trans hyp.side) (hb1.trans hyp.filler) (hb2.trans hyp.ab) + (hb3.trans hyp.bc) (hb4.trans hyp.ac) (hb5.trans hyp.free) + (hbm.trans ham) + let z := c.extend solverRegisters b + have hz : ExecWithin (w + 2) (solver.rename solverRegisters) b z T := + hc.rename solver_injective + have hzr (i : Fin 64) : z.regs (hostRegisters i) = b.regs (hostRegisters i) := by + apply hz.regs_eq + apply Code.preserves_rename_of_forall_ne + intro j he + have hv := congrArg Fin.val he + dsimp only [solverRegisters, hostRegisters] at hv + have := i.isLt + omega + have hzp : Parameters N U (z.comap hostRegisters) := + parameters_of_regs hbp (fun i _ _ _ => hzr i) + have hzsi : z.regs (hostRegisters inst) = s := + (hzr inst).trans ((hbr inst).trans hysi) + have hzf : z.regs (hostRegisters found) = x.regs (hostRegisters found) := + (hzr found).trans ((hbr found).trans hyf) + have hza : z.regs (callRegisters 6) = c.regs (solverArg 6) := + solver_injective.extend_apply _ _ _ + have hinc : Fits (w + 2) (s + 1) := hsize.of_le (by + have := Nat.le_mul_self (sideLength N) + omega) + obtain ⟨q, hq, hqi, hqf, hqr, hqm⟩ := exec_finish z hzp.one hzsi hinc + have hqp : Parameters N U (q.comap hostRegisters) := + parameters_of_regs hzp (fun i _ hi hf => hqr i hi hf) + refine ⟨q, ?_, hqp, hqi, ?_, ?_, ?_⟩ + · exact (hy.seq (hb.seq (hz.seq hq))).mono (by omega) + · rw [hqf, hza, hzf] + split_ifs <;> omega + · rw [hqf, hza, hzf] + by_cases h : c.regs (solverArg 6) = 1 + · simp only [h, ↓reduceIte, hchit.mp h, or_true] + · simp only [h, ↓reduceIte, hchit.not.mp h, or_false] + · intro a ha + rw [hqm] + exact hcm a ha + +/-- The complete native instance loop. Its invariant records exactly the instances already +examined, retains the original input, and charges every solver call and loop instruction. -/ +theorem exec_loop {w r N U T : ℕ} (μ : ℕ → ℤ) + (solver : Code .standard (r + 7)) (x : Data (64 + (r + 7))) + (hp : Parameters N U (x.comap hostRegisters)) + (hi : x.regs (hostRegisters inst) = 0) (hf : x.regs (hostRegisters found) = 0) + (hmem : ∀ i < N, x.mem (1 + i) = encode (μ i)) + (hidx : ∀ s < 2 * sideLength N, + GenericRAM.Signed.Fits w ((2 * (sideLength N * sideLength N) + s + 1 : ℕ) : ℤ)) + (hF : GenericRAM.Signed.Fits w (2 * (U : ℤ) + 1)) + (hnF : GenericRAM.Signed.Fits w (-(2 * (U : ℤ) + 1))) + (hμ : ∀ i < N, GenericRAM.Signed.Fits w (μ i)) + (hsize : Fits (w + 2) (N + 1 + 3 * (sideLength N * sideLength N))) + (hsolver : ∀ s < 2 * sideLength N, ∀ (a : Data (r + 7)) (mem : ℕ → ℕ), + a.regs (solverArg 0) = sideLength N → a.regs (solverArg 1) = 2 * U + 1 → + a.regs (solverArg 2) = N + 1 → + a.regs (solverArg 3) = N + 1 + sideLength N * sideLength N → + a.regs (solverArg 4) = N + 1 + 2 * (sideLength N * sideLength N) → + a.regs (solverArg 5) = N + 1 + 3 * (sideLength N * sideLength N) → + a.mem = (instanceArgs N U s μ).filled mem (sideLength N * sideLength N) → + ∃ b, ExecWithin (w + 2) solver a b T ∧ b.regs (solverArg 6) ≤ 1 ∧ + (b.regs (solverArg 6) = 1 ↔ hit N U s μ) ∧ + (∀ a < N + 1, b.mem a = mem a)) : + ∃ y, ExecWithin (w + 2) (loop solver) x y + (2 * sideLength N * (131 * (sideLength N * sideLength N) + T + 25) + 2) ∧ + Parameters N U (y.comap hostRegisters) ∧ + y.regs (hostRegisters inst) = 2 * sideLength N ∧ + y.regs (hostRegisters found) ≤ 1 ∧ + (y.regs (hostRegisters found) = 1 ↔ ∃ s < 2 * sideLength N, hit N U s μ) ∧ + (∀ a < N + 1, y.mem a = x.mem a) := by + let I (y : Data (64 + (r + 7))) : Prop := + Parameters N U (y.comap hostRegisters) ∧ + y.regs (hostRegisters inst) ≤ 2 * sideLength N ∧ + y.regs (hostRegisters found) ≤ 1 ∧ + (y.regs (hostRegisters found) = 1 ↔ ∃ s < y.regs (hostRegisters inst), hit N U s μ) ∧ + (∀ a < N + 1, y.mem a = x.mem a) + have hbody (y : Data (64 + (r + 7))) (hy : I y) + (hc : (Cond.lt (hostRegisters inst) (hostRegisters stop)).Holds y) : + ∃ z, ExecWithin (w + 2) (round solver) y z + (131 * (sideLength N * sideLength N) + T + 23) ∧ I z ∧ + 2 * sideLength N - z.regs (hostRegisters inst) < + 2 * sideLength N - y.regs (hostRegisters inst) := by + obtain ⟨hyp, hys, hyf, hyans, hym⟩ := hy + have hlt : y.regs (hostRegisters inst) < 2 * sideLength N := by + change y.regs (hostRegisters inst) < y.regs (hostRegisters stop) at hc + exact hc.trans_eq hyp.stop + have hyinput : ∀ i < N, y.mem (1 + i) = encode (μ i) := by + intro i hi + rw [hym _ (by omega)] + exact hmem i hi + obtain ⟨z, hz, hzp, hzsi, hzf, hzans, hzm⟩ := exec_round μ solver y hyp rfl hlt + hyf hyinput (hidx _ hlt) hF hnF hμ hsize (hsolver _ hlt) + refine ⟨z, hz, ⟨hzp, by omega, hzf, ?_, ?_⟩, by omega⟩ + · rw [hzans, hyans, hzsi] + constructor + · rintro (⟨s, hs, hh⟩ | hh) + · exact ⟨s, by omega, hh⟩ + · exact ⟨_, by omega, hh⟩ + · rintro ⟨s, hs, hh⟩ + by_cases h : s < y.regs (hostRegisters inst) + · exact Or.inl ⟨s, h, hh⟩ + · have he : s = y.regs (hostRegisters inst) := by omega + exact Or.inr (he ▸ hh) + · intro a ha + exact (hzm a ha).trans (hym a ha) + have hx : I x := ⟨hp, by omega, by omega, by simp [hi, hf], fun _ _ => rfl⟩ + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => 2 * sideLength N - y.regs (hostRegisters inst)) hbody hx + obtain ⟨hyp, hyi, hyf, hyans, hym⟩ := hy + have hend : y.regs (hostRegisters inst) = 2 * sideLength N := by + change ¬y.regs (hostRegisters inst) < y.regs (hostRegisters stop) at hc + have hstop : y.regs (hostRegisters stop) = 2 * sideLength N := hyp.stop + omega + refine ⟨y, ?_, hyp, hend, hyf, ?_, hym⟩ + · simpa only [loop, hi, Nat.sub_zero, Nat.add_assoc, Nat.reduceAdd] using he + · simpa only [hend] using hyans + +end Cslib.Algorithms.WordRAM.ThreeSum.Host diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Host/Prepare.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Prepare.lean new file mode 100644 index 0000000000..4c1806cb83 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Prepare.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.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.ThreeSum.Fill +public import Complexity.Computability.WordRAM.RegisterFrame + +/-! +# Preparing the native Convolution-3SUM host + +The host reads the standard length header and computes a polynomial magnitude bound using the +existing native parameter program. It then computes the matrix side and consecutive matrix +addresses. The parameter program's additional computations are included in the instruction count. + +This directly adapts the setup of Anthropic's pinned +[`c3Body`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/Convolution/Host.lean). +The complete reduction must also fill every instance and invoke a concrete certified solver. + +## Main definitions + +* `Host.Parameters`: the prepared register interface. +* `Host.prepare`: native header, magnitude, square-root and layout computation. + +## Main results + +* `Host.exec_prepare`: exact prepared parameters and a charged setup bound. + +## References + +* [V. Vassilevska Williams and R. Williams, *Finding, minimizing, and counting weighted + subgraphs*][VassilevskaWilliamsWilliams2013] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Host + +open Cslib.WordRAM + +/-- Input length. -/ +abbrev len : Fin 64 := 34 +/-- Polynomial magnitude bound. -/ +abbrev upper : Fin 64 := 35 +/-- Input base address. -/ +abbrev base : Fin 64 := 36 +/-- AB matrix base address. -/ +abbrev matAB : Fin 64 := 37 +/-- BC matrix base address. -/ +abbrev matBC : Fin 64 := 38 +/-- AC matrix base address. -/ +abbrev matAC : Fin 64 := 39 +/-- Solver workspace starts after the three matrices. -/ +abbrev free : Fin 64 := 40 +/-- Number of vertices per part. -/ +abbrev side : Fin 64 := 41 +/-- Current instance number. -/ +abbrev inst : Fin 64 := 42 +/-- Number of instances. -/ +abbrev stop : Fin 64 := 43 +/-- Accumulated Boolean verdict. -/ +abbrev found : Fin 64 := 44 +/-- Constant one. -/ +abbrev one : Fin 64 := 45 +/-- Unsigned filler magnitude. -/ +abbrev filler : Fin 64 := 47 +/-- Signed-encoded positive filler. -/ +abbrev encodedFiller : Fin 64 := 48 +/-- Number of entries per matrix. -/ +abbrev area : Fin 64 := 49 +/-- Square-root argument. -/ +abbrev sqrtArg : Fin 64 := 28 +/-- Square-root output. -/ +abbrev sqrtRoot : Fin 64 := 29 + +/-- Register embedding for the existing parameter routine. -/ +def parameterRegisters (i : Fin 24) : Fin 64 := ⟨i.val, by omega⟩ + +/-- Register embedding for successive squares. -/ +def sqrtRegisters (i : Fin 6) : Fin 64 := ⟨28 + i.val, by omega⟩ + +private theorem parameter_injective : parameterRegisters.Injective := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 64 => k.val) h) + +private theorem sqrt_injective : sqrtRegisters.Injective := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [sqrtRegisters] at this + omega + +/-- The mathematical side length used by the reduction. -/ +abbrev sideLength (N : ℕ) : ℕ := Nat.sqrt N + 1 + +/-- Parameters retained throughout the instance loop. -/ +structure Parameters (N U : ℕ) (x : Data 64) : Prop where + /-- Input length. -/ + len : x.regs len = N + /-- Magnitude bound. -/ + upper : x.regs upper = U + /-- Input starts after the length header. -/ + base : x.regs base = 1 + /-- AB follows the input. -/ + ab : x.regs matAB = N + 1 + /-- BC follows AB. -/ + bc : x.regs matBC = N + 1 + sideLength N * sideLength N + /-- AC follows BC. -/ + ac : x.regs matAC = N + 1 + 2 * (sideLength N * sideLength N) + /-- Solver scratch follows AC. -/ + free : x.regs free = N + 1 + 3 * (sideLength N * sideLength N) + /-- Side length. -/ + side : x.regs side = sideLength N + /-- Two instances per possible low-index sum. -/ + stop : x.regs stop = 2 * sideLength N + /-- Positive invalid-index filler. -/ + filler : x.regs filler = 2 * U + 1 + /-- Sign-and-magnitude representation of that filler. -/ + encodedFiller : x.regs encodedFiller = 4 * U + 2 + /-- Loop increment. -/ + one : x.regs one = 1 + +/-- Input-length output of the parameter bank. -/ +abbrev parameterLen : Fin 64 := 0 +/-- Magnitude output of the parameter bank. -/ +abbrev parameterUpper : Fin 64 := 1 + +/-- Copy the two needed parameter outputs and initialize constants. -/ +def setup : Code .standard 64 := ram[ + len ← parameterLen; upper ← parameterUpper; + base ← #1; one ← #1; sqrtArg ← len ] + +/-- Consecutive output blocks and the two loop counters. -/ +def layout : Code .standard 64 := ram[ + side ← sqrtRoot + one; area ← side * side; stop ← side + side; + matAB ← base + len; matBC ← matAB + area; matAC ← matBC + area; free ← matAC + area; + filler ← upper + upper; filler ← filler + one; encodedFiller ← filler + filler; + inst ← #0; found ← #0 ] + +/-- All initialization consists of actual native subprograms and charged register operations. -/ +def prepare (κ : ℕ) : Code .standard 64 := + .seq ((ChanHe.Parameters.program κ).rename parameterRegisters) + (.seq setup (.seq (NatSqrt.program.rename sqrtRegisters) layout)) + +private theorem exec_layout {w N U : ℕ} (x : Data 64) + (hN : x.regs len = N) (hU : x.regs upper = U) (hb : x.regs base = 1) + (ho : x.regs one = 1) + (hr : x.regs sqrtRoot = Nat.sqrt N) + (hsize : Fits w (N + 1 + 3 * (sideLength N * sideLength N))) + (hfill : Fits w (4 * U + 2)) : + ∃ y, ExecWithin w layout x y 12 ∧ Parameters N U y ∧ + y.regs inst = 0 ∧ y.regs found = 0 ∧ y.mem = x.mem := by + let t := sideLength N + have ht : Fits w t := hsize.of_le (by have := Nat.le_mul_self t; dsimp [t] at *; omega) + have ha : Fits w (t * t) := hsize.of_le (by dsimp [t] at *; omega) + have hs : Fits w (t + t) := hsize.of_le (by have := Nat.le_mul_self t; dsimp [t] at *; omega) + have hab : Fits w (1 + N) := hsize.of_le (by omega) + have hbc : Fits w (1 + N + t * t) := hsize.of_le (by dsimp [t] at *; omega) + have hac : Fits w (1 + N + t * t + t * t) := hsize.of_le (by dsimp [t] at *; omega) + have hfree : Fits w (1 + N + t * t + t * t + t * t) := + hsize.of_le (by dsimp [t] at *; omega) + have hu2 : Fits w (U + U) := hfill.of_le (by omega) + have hu1 : Fits w (U + U + 1) := hfill.of_le (by omega) + have hu4 : Fits w (U + U + 1 + (U + U + 1)) := hfill.of_le (by omega) + refine ⟨?y, ?run, ?parameters, ?counter, ?found, ?memory⟩ + case run => + ram_eval [layout, hr, ho, hN, hU, hb, ht, ha, hs, hab, hbc, hac, hfree, + hu2, hu1, hu4, t, sideLength, len, upper, base, matAB, matBC, matAC, + free, side, inst, stop, found, one, filler, encodedFiller, area, sqrtRoot, Fin.mk.injEq] + case parameters => + constructor <;> + simp +decide [Data.set_regs, len, upper, base, matAB, + matBC, matAC, free, side, stop, filler, encodedFiller, one, + hN, hU, hb, ho, sideLength] <;> omega + case counter => simp [inst] + case found => simp + case memory => rfl + +/-- Exact initialization from the ordinary length header. The existing parameter program +computes `U = (N+2)^κ`; its entire execution is charged, including the unused extra parameters. +The additional fit premises cover matrix addresses and the encoded filler, and are independent +of any solver execution contract. -/ +theorem exec_prepare {w : ℕ} (κ : ℕ) (x : Data 64) + (hbudget : Fits w (ChanHe.Parameters.Math.budget (x.mem 0) ((x.mem 0 + 2) ^ κ))) + (hsize : Fits w (x.mem 0 + 1 + 3 * (sideLength (x.mem 0) * sideLength (x.mem 0)))) + (hfill : Fits w (4 * ((x.mem 0 + 2) ^ κ) + 2)) : + ∃ y, ExecWithin w (prepare κ) x y + (ChanHe.Parameters.time κ (x.mem 0) ((x.mem 0 + 2) ^ κ) + + 5 * Nat.sqrt (x.mem 0) + 24) ∧ + Parameters (x.mem 0) ((x.mem 0 + 2) ^ κ) y ∧ + y.regs inst = 0 ∧ y.regs found = 0 ∧ y.mem = x.mem := by + let N := x.mem 0 + let U := (N + 2) ^ κ + obtain ⟨p, hp, hout, hm⟩ := ChanHe.Parameters.exec κ (x.comap parameterRegisters) hbudget + let a := p.extend parameterRegisters x + have ha := hp.rename parameter_injective + have han : a.regs parameterLen = N := + (parameter_injective.extend_apply _ _ ChanHe.Parameters.len).trans hout.len + have hau : a.regs parameterUpper = U := + (parameter_injective.extend_apply _ _ ChanHe.Parameters.magnitude).trans hout.magnitude + have ham : a.mem = x.mem := hm + have h1 : Fits w 1 := hfill.of_le (by omega) + let b := a.set len N |>.set upper U |>.set base 1 |>.set one 1 + |>.set sqrtArg N + have hb : ExecWithin w setup a b 5 := by + ram_eval [setup, b, han, hau, h1, parameterLen, parameterUpper, len, upper, base, + one, sqrtArg, Fin.mk.injEq] + have hbn : b.regs sqrtArg = N := by simp [b] + have hsqrt : Fits w (4 * b.regs sqrtArg + 4) := hbudget.of_le (by + rw [hbn] + change 4 * N + 4 ≤ ChanHe.Parameters.Math.budget N U + dsimp [ChanHe.Parameters.Math.budget] + omega) + obtain ⟨c, hc, hcr, -, hcm, -⟩ := NatSqrt.exec_rename sqrtRegisters sqrt_injective b hsqrt + have hpres (i : Fin 64) (hi : 34 ≤ i.val) : c.regs i = b.regs i := by + apply hc.regs_eq + apply Code.preserves_rename_of_forall_ne + intro j he + have hv := congrArg Fin.val he + dsimp only [sqrtRegisters] at hv + have := j.isLt + omega + have hcr' : c.regs sqrtRoot = Nat.sqrt N := by + change c.regs sqrtRoot = Nat.sqrt (b.regs sqrtArg) at hcr + simpa only [hbn] using hcr + have hc' : ExecWithin w (NatSqrt.program.rename sqrtRegisters) b c + (5 * Nat.sqrt N + 7) := by + change ExecWithin w _ b c (5 * Nat.sqrt (b.regs sqrtArg) + 7) at hc + simpa only [hbn] using hc + obtain ⟨y, hy, hparams, hi, hf, hym⟩ := exec_layout (N := N) (U := U) c + ((hpres len (by decide)).trans (by + simp +decide [b, N, U, len, sqrtArg, one, base, upper])) + ((hpres upper (by decide)).trans (by + simp +decide [b, N, U, upper, sqrtArg, one, base])) + ((hpres base (by decide)).trans (by simp +decide [b, base, sqrtArg, one])) + ((hpres one (by decide)).trans (by simp +decide [b, one, sqrtArg])) + hcr' hsize hfill + refine ⟨y, ?_, hparams, hi, hf, hym.trans (hcm.trans ham)⟩ + convert ha.seq (hb.seq (hc'.seq hy)) using 1 + · rfl + · simp only [Data.comap_mem, N] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.Host diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Host/Program.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Program.lean new file mode 100644 index 0000000000..c712759ac9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Program.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Loop +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Width +public import Complexity.Computability.WordRAM.Compile + +/-! +# The complete native Convolution-3SUM host + +The host computes its parameters from the ordinary input header, constructs every instance, +executes the supplied finite solver program in a disjoint register bank, accumulates its answer, +and halts. The source word width is logarithmic for polynomially bounded integer inputs. + +This is a conditional native reduction: the caller must prove execution, correctness and input +preservation of the actual solver code at the stated width and charged budget. The host does not +supply a fast Exact Triangle solver or finish the subquadratic 3SUM endpoint. + +The construction directly adapts Anthropic's pinned +[`c3Body`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/Convolution/Host.lean). + +## Main definitions + +* `Host.body`: initialization and the complete native instance loop. +* `Host.time`: charged time, including the final halt. + +## Main results + +* `Host.runs_input`: actual initialized native execution, output and input preservation. + +## References + +* [V. Vassilevska Williams and R. Williams, *Finding, minimizing, and counting weighted + subgraphs*][VassilevskaWilliamsWilliams2013] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Host + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.ThreeSum + +/-- The code depends only on the fixed magnitude exponent and the supplied finite solver. -/ +def body {r : ℕ} (κ : ℕ) (solver : Code .standard (r + 7)) : + Code .standard (64 + (r + 7)) := + .seq ((prepare κ).rename hostRegisters) (loop solver) + +/-- Complete charged instruction budget, including the halt and every solver invocation. -/ +def time (κ N T : ℕ) : ℕ := + ChanHe.Parameters.time κ N ((N + 2) ^ κ) + 5 * Nat.sqrt N + 27 + + 2 * sideLength N * (131 * (sideLength N * sideLength N) + T + 25) + +/-- Solver time is multiplied by `2t`; all host work is `Oκ((N+1)t + log(N+2))`. -/ +theorem time_bound (κ N T : ℕ) : time κ N T ≤ + 2 * sideLength N * T + 1000 * (N + 1) * sideLength N + + (400 * κ + 3000) * Nat.clog 2 (N + 2) := by + have hp := ChanHe.Parameters.time_bound κ N + have ha := area_le N + have ht : 1 ≤ sideLength N := by dsimp [sideLength]; omega + have hmul := Nat.mul_le_mul_left (262 * sideLength N) ha + dsimp only [time] + dsimp only [sideLength] at * + nlinarith + +/-- Native initialized execution at target width `w+2`. The execution witness identifies the +actual pre-halt state containing the output. The sole solver premise is an execution theorem +for the concrete `solver` argument, on the matrices the host actually constructs. -/ +theorem runs_input {κ w r T : ℕ} (input : List ℤ) (solver : Code .standard (r + 7)) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + (hsolver : let N := input.length; let U := (N + 2) ^ κ; + let μ : ℕ → ℤ := fun i => input.getD i 0; + ∀ s < 2 * sideLength N, ∀ (a : Data (r + 7)) (mem : ℕ → ℕ), + a.regs (solverArg 0) = sideLength N → a.regs (solverArg 1) = 2 * U + 1 → + a.regs (solverArg 2) = N + 1 → + a.regs (solverArg 3) = N + 1 + sideLength N * sideLength N → + a.regs (solverArg 4) = N + 1 + 2 * (sideLength N * sideLength N) → + a.regs (solverArg 5) = N + 1 + 3 * (sideLength N * sideLength N) → + a.mem = (instanceArgs N U s μ).filled mem (sideLength N * sideLength N) → + ∃ b, ExecWithin (w + 2) solver a b T ∧ b.regs (solverArg 6) ≤ 1 ∧ + (b.regs (solverArg 6) = 1 ↔ hit N U s μ) ∧ + (∀ a < N + 1, b.mem a = mem a)) : + ∃ y t, t ≤ time κ input.length T ∧ + Runs (w + 2) ((body κ solver).program (hostRegisters found)) + (State.init (input.map encode)) ⟨(body κ solver).size, y⟩ (t - 1) ∧ + Executes (w + 2) ((body κ solver).program (hostRegisters found)) + (State.init (input.map encode)) (y.regs (hostRegisters found)) t ∧ + y.regs (hostRegisters found) ≤ 1 ∧ + (y.regs (hostRegisters found) = 1 ↔ + Convolution input.length (fun i => input.getD i 0)) ∧ + (∀ a < input.length + 1, + y.mem a = (Data.init (input.map encode) : Data (64 + (r + 7))).mem a) ∧ + y.Bounded (w + 2) := by + let N := input.length + let U := (N + 2) ^ κ + let μ : ℕ → ℤ := fun i => input.getD i 0 + let x : Data (64 + (r + 7)) := Data.init (input.map encode) + have hmem : ∀ i < N, x.mem (1 + i) = encode (μ i) := by + intro i hi + have hm : i < (input.map encode).length := by simpa only [List.length_map] using hi + simp only [x, μ, Nat.add_comm 1, Data.init_mem_succ, List.getD_eq_getElem?_getD, + List.getElem?_eq_getElem hm, + List.getElem?_eq_getElem hi, List.getElem_map, Option.getD_some] + have hμ : ∀ i < N, |μ i| ≤ (U : ℤ) := by + intro i hi + simpa only [μ, U, N, List.getD_eq_getElem?_getD, List.getElem?_eq_getElem hi, + Option.getD_some] using hvalues input[i] (List.getElem_mem hi) + have hpw : ChanHe.Parameters.width κ N ≤ w + 2 := + (Nat.le_add_right _ 4).trans ((parameters_width_le κ N).trans (hw.trans (by omega))) + have hf := layout_fits hw + have hbudget : Fits (w + 2) (ChanHe.Parameters.Math.budget N U) := + ChanHe.Parameters.budget_fits hpw + obtain ⟨p, hp, hpp, hpi, hpf, hpm⟩ := exec_prepare κ (x.comap hostRegisters) + (by simpa only [x, Data.comap_mem, Data.init_mem_zero, List.length_map] using hbudget) + (by simpa only [x, Data.comap_mem, Data.init_mem_zero, List.length_map] using + hf.1.mono (show w ≤ w + 2 by omega)) + (by simpa only [x, Data.comap_mem, Data.init_mem_zero, List.length_map] using + hf.2.mono (show w ≤ w + 2 by omega)) + simp only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] at hp hpp + let a := p.extend hostRegisters x + have ha := hp.rename (hostRegisters_injective (r := r + 7)) + have hac : a.comap hostRegisters = p := Data.comap_extend hostRegisters_injective _ _ + have hap : Parameters N U (a.comap hostRegisters) := by rw [hac]; exact hpp + have hai : a.regs (hostRegisters inst) = 0 := + hostRegisters_injective.extend_apply _ _ _ |>.trans hpi + have haf : a.regs (hostRegisters found) = 0 := + hostRegisters_injective.extend_apply _ _ _ |>.trans hpf + have ham : a.mem = x.mem := hpm + have hinput : ∀ i < N, a.mem (1 + i) = encode (μ i) := by + intro i hi + rw [ham] + exact hmem i hi + obtain ⟨y, hy, -, -, hyf, hyans, hym⟩ := exec_loop μ solver a hap hai haf hinput + (fun s hs => index_fits hw hs) (filler_fits hw).1 (filler_fits hw).2 + (fun i hi => value_fits hw (hμ i hi)) + (hf.1.mono (show w ≤ w + 2 by omega)) hsolver + have hans : y.regs (hostRegisters found) = 1 ↔ Convolution N μ := + hyans.trans (convolution_iff_sqrt (U := (U : ℤ)) (by positivity) hμ).symm + obtain ⟨n, hn, he⟩ := ha.seq hy + have hr := he.runs (pc := 0) (p := (body κ solver).program (hostRegisters found)) + (Program.Contains.self _).append_left + have hi := ChanHe.Parameters.inputFits input hpw hvalues + refine ⟨y, n + 1, ?_, ?_, he.executes (hostRegisters found), hyf, hans, ?_, ?_⟩ + · dsimp only [time, N] at hn ⊢ + omega + · simpa only [Nat.add_sub_cancel, Nat.zero_add, body, State.init] using hr + · intro a ha + exact (hym a ha).trans (congrFun ham a) + · exact hr.bounded_init hi + +end Cslib.Algorithms.WordRAM.ThreeSum.Host diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Host/Width.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Width.lean new file mode 100644 index 0000000000..6a9a537552 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Host/Width.lean @@ -0,0 +1,123 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Prepare + +/-! +# A logarithmic word width for the Convolution-3SUM host + +The existing parameter program's polynomial budget also bounds the host's matrix addresses, +signed indices and filler. Four extra source bits suffice. These bounds cover the host; +the concrete solver must separately prove its own execution at the target width. + +## Main definitions + +* `Host.width`: a positive logarithmic source-width threshold. + +## Main results + +* `Host.layout_fits`: all matrix addresses and the encoded filler fit. +* `Host.index_fits`, `Host.filler_fits`: the signed matrix-builder premises. +* `Host.value_fits`: polynomially bounded input values fit the signed source width. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Host + +open Cslib.WordRAM + +/-- Source width; the signed arithmetic kernels use unsigned target words of width `w+2`. -/ +def width (κ N : ℕ) : ℕ := (20 * κ + 125) * Nat.clog 2 (N + 2) + +/-- The threshold includes the empty input and magnitude exponent zero. -/ +theorem width_pos (κ N : ℕ) : 0 < width κ N := by + have := Nat.clog_pos (b := 2) (by decide) (show 1 < N + 2 by omega) + dsimp [width] + positivity + +/-- The host has at least four more source bits than the parameter routine requires. -/ +theorem parameters_width_le (κ N : ℕ) : ChanHe.Parameters.width κ N + 4 ≤ width κ N := by + have := Nat.clog_pos (b := 2) (by decide) (show 1 < N + 2 by omega) + dsimp [width, ChanHe.Parameters.width] + nlinarith + +private theorem margin_fits {κ N w : ℕ} (hw : width κ N ≤ w) : + Fits (w - 1) (8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ)) := by + have hf := ChanHe.Parameters.budget_fits (κ := κ) (n := N) (w := ChanHe.Parameters.width κ N) + (Nat.le_refl _) + have hwidth := (parameters_width_le κ N).trans hw + have hmul : 8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) < + 8 * 2 ^ ChanHe.Parameters.width κ N := Nat.mul_lt_mul_of_pos_left hf (by decide) + apply hmul.trans_le + calc + _ = 2 ^ (ChanHe.Parameters.width κ N + 3) := by rw [pow_add]; norm_num; omega + _ ≤ 2 ^ (w - 1) := Nat.pow_le_pow_right (by decide) (by omega) + +private theorem signed_fits {κ N w : ℕ} (hw : width κ N ≤ w) (z : ℤ) + (hz : |z| ≤ (8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) : ℕ)) : + GenericRAM.Signed.Fits w z := by + have hf := margin_fits hw + have hwpos := width_pos κ N + have hpow : (8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) : ℤ) < + 2 ^ (w - 1) := by exact_mod_cast hf + obtain ⟨hlo, hhi⟩ := abs_le.mp hz + exact ⟨by omega, by omega, by omega⟩ + +/-- A linear bound for the number of entries per generated matrix. -/ +theorem area_le (N : ℕ) : sideLength N * sideLength N ≤ 3 * N + 1 := by + have := Nat.sqrt_le N + have := Nat.sqrt_le_self N + dsimp [sideLength] + nlinarith + +/-- All three matrix blocks and their one-past-end addresses fit at the source width. -/ +theorem layout_fits {κ N w : ℕ} (hw : width κ N ≤ w) : + Fits w (N + 1 + 3 * (sideLength N * sideLength N)) ∧ + Fits w (4 * (N + 2) ^ κ + 2) := by + have hf := (margin_fits hw).mono (show w - 1 ≤ w by omega) + have ha := area_le N + constructor <;> apply hf.of_le <;> dsimp [ChanHe.Parameters.Math.budget] <;> omega + +/-- The builder's positive and negative filler fit the signed source range. -/ +theorem filler_fits {κ N w : ℕ} (hw : width κ N ≤ w) : + GenericRAM.Signed.Fits w (2 * ((N + 2) ^ κ : ℕ) + 1) ∧ + GenericRAM.Signed.Fits w (-(2 * ((N + 2) ^ κ : ℕ) + 1)) := by + have hbound : |(2 * ((N + 2) ^ κ : ℕ) + 1 : ℤ)| ≤ + (8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) : ℕ) := by + rw [abs_of_nonneg (by positivity)] + exact_mod_cast (show 2 * (N + 2) ^ κ + 1 ≤ + 8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) from by + dsimp [ChanHe.Parameters.Math.budget] + omega) + exact ⟨signed_fits hw _ hbound, signed_fits hw _ (by simpa only [abs_neg] using hbound)⟩ + +/-- Every signed input index computed in a generated instance fits. -/ +theorem index_fits {κ N w s : ℕ} (hw : width κ N ≤ w) (hs : s < 2 * sideLength N) : + GenericRAM.Signed.Fits w ((2 * (sideLength N * sideLength N) + s + 1 : ℕ) : ℤ) := by + apply signed_fits hw + rw [abs_of_nonneg (by positivity)] + have ha := area_le N + have ht := Nat.sqrt_le_self N + dsimp only [sideLength] at hs + exact_mod_cast (show 2 * (sideLength N * sideLength N) + s + 1 ≤ + 8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) from by + dsimp [ChanHe.Parameters.Math.budget] + omega) + +/-- A polynomial magnitude bound implies the signed source fit premise. -/ +theorem value_fits {κ N w : ℕ} (hw : width κ N ≤ w) {z : ℤ} + (hz : |z| ≤ ((N + 2) ^ κ : ℕ)) : GenericRAM.Signed.Fits w z := by + apply signed_fits hw z + have hb : (N + 2) ^ κ ≤ 8 * ChanHe.Parameters.Math.budget N ((N + 2) ^ κ) := by + dsimp [ChanHe.Parameters.Math.budget] + omega + exact hz.trans (by exact_mod_cast hb) + +end Cslib.Algorithms.WordRAM.ThreeSum.Host diff --git a/Complexity/Computability/WordRAM/RegisterFrame.lean b/Complexity/Computability/WordRAM/RegisterFrame.lean new file mode 100644 index 0000000000..54de876e2d --- /dev/null +++ b/Complexity/Computability/WordRAM/RegisterFrame.lean @@ -0,0 +1,39 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Structured + +/-! +# Register frames for renamed native programs + +## Main results + +* `Code.preserves_rename_of_forall_ne`: registers outside an embedded register bank are + preserved without unfolding the subroutine's instruction graph. +-/ + +public section + +namespace Cslib.WordRAM + +/-- A renamed subroutine cannot write a register outside the image of its register embedding. +This generalizes the former private framing rule of the fusion-node register banks. -/ +theorem Code.preserves_rename_of_forall_ne {B : Basis} {r r' : ℕ} + (c : Code B r) (f : Fin r → Fin r') (i : Fin r') (hi : ∀ j, f j ≠ i) : + (c.rename f).Preserves i := by + induction c with + | op o => + cases o <;> simp only [Code.rename, Code.Preserves, Op.rename, Op.dest, + ne_eq, Option.some.injEq, reduceCtorEq, not_false_eq_true] <;> apply hi + | seq a b iha ihb => exact ⟨iha, ihb⟩ + | branch cond pos neg ihp ihn => exact ⟨ihp, ihn⟩ + | loop cond body ih => exact ih + | rand dst src => simpa only [Code.rename, Code.Preserves] using hi dst + +end Cslib.WordRAM diff --git a/ComplexityTests/ThreeSumHost.lean b/ComplexityTests/ThreeSumHost.lean new file mode 100644 index 0000000000..28531cf054 --- /dev/null +++ b/ComplexityTests/ThreeSumHost.lean @@ -0,0 +1,141 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.Host.Program +import Mathlib.Tactic.IntervalCases + +/-! # Initialized native Convolution-3SUM host clients + +The small test solver inspects only the first cell of each matrix. Its correctness is certified +below only for the empty input and the listed singleton inputs. It is not a general Exact +Triangle solver. These clients check that the host's conditional execution theorem is usable. +-/ + +namespace ComplexityTests.ThreeSumHost + +open Cslib WordRAM WordRAM.Signed +open Algorithms.WordRAM.ThreeSum +open Algorithms.ThreeSum + +private abbrev ab : Fin 7 := 2 +private abbrev bc : Fin 7 := 3 +private abbrev ac : Fin 7 := 4 +private abbrev zero : Fin 7 := 5 +private abbrev out : Fin 7 := 6 + +private def probe : Code .standard 7 := ram[ + ab ← *ab; bc ← *bc; ac ← *ac; zero ← #0; + if ab = zero then [ + if bc = zero then [ + if ac = zero then [ out ← #1 ] else [ out ← #0 ] + ] else [ out ← #0 ] + ] else [ out ← #0 ] ] + +private theorem probe_exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) : + ∃ y, ExecWithin w probe x y 9 ∧ + y.regs out = (if x.mem (x.regs ab) = 0 ∧ x.mem (x.regs bc) = 0 ∧ + x.mem (x.regs ac) = 0 then 1 else 0) ∧ y.mem = x.mem := by + let b := if x.mem (x.regs ab) = 0 ∧ x.mem (x.regs bc) = 0 ∧ + x.mem (x.regs ac) = 0 then 1 else 0 + let y := x.set ab (x.mem (x.regs ab)) |>.set bc (x.mem (x.regs bc)) + |>.set ac (x.mem (x.regs ac)) |>.set zero 0 |>.set out b + refine ⟨y, ?_, by simp [y, b], rfl⟩ + by_cases hab : x.mem (x.regs ab) = 0 <;> + by_cases hbc : x.mem (x.regs bc) = 0 <;> + by_cases hac : x.mem (x.regs ac) = 0 <;> + ram_eval [probe, y, b, hab, hbc, hac, h1, ab, bc, ac, zero, out, + true_and, false_and, and_true, and_false, Fin.mk.injEq] + +/-- Every supplied first-cell answer is charged, and the probe writes no memory. -/ +example {w : ℕ} (x : Data 7) (h1 : Fits w 1) : + ∃ y, ExecWithin w probe x y 9 ∧ y.regs out ≤ 1 ∧ y.mem = x.mem := by + obtain ⟨y, he, ho, hm⟩ := probe_exec x h1 + refine ⟨y, he, ?_, hm⟩ + rw [ho] + split_ifs <;> decide + +private theorem probe_spec (input : List ℤ) + (hinput : input = [] ∨ input = [-1] ∨ input = [0] ∨ input = [1]) + (w : ℕ) (h1 : Fits (w + 2) 1) : + let N := input.length; let U := (N + 2) ^ 0; + let μ : ℕ → ℤ := fun i => input.getD i 0; + ∀ s < 2 * Host.sideLength N, ∀ (a : Data 7) (mem : ℕ → ℕ), + a.regs (Host.solverArg 0) = Host.sideLength N → a.regs (Host.solverArg 1) = 2 * U + 1 → + a.regs (Host.solverArg 2) = N + 1 → + a.regs (Host.solverArg 3) = N + 1 + Host.sideLength N * Host.sideLength N → + a.regs (Host.solverArg 4) = N + 1 + 2 * (Host.sideLength N * Host.sideLength N) → + a.regs (Host.solverArg 5) = N + 1 + 3 * (Host.sideLength N * Host.sideLength N) → + a.mem = (Host.instanceArgs N U s μ).filled mem (Host.sideLength N * Host.sideLength N) → + ∃ b, ExecWithin (w + 2) probe a b 9 ∧ b.regs (Host.solverArg 6) ≤ 1 ∧ + (b.regs (Host.solverArg 6) = 1 ↔ Host.hit N U s μ) ∧ + (∀ a < N + 1, b.mem a = mem a) := by + dsimp only [Nat.pow_zero] + intro s hs a mem ht hF hab hbc hac hfree hm + change a.regs ab = _ at hab + change a.regs bc = _ at hbc + change a.regs ac = _ at hac + obtain ⟨y, he, ho, hym⟩ := probe_exec a h1 + refine ⟨y, he, ?_, ?_, ?_⟩ + · change y.regs out ≤ 1 + rw [ho] + split_ifs <;> decide + · change y.regs out = 1 ↔ _ + rw [ho, hab, hbc, hac, hm] + rcases hinput with rfl | rfl | rfl | rfl + all_goals norm_num [Host.sideLength] at hs + all_goals interval_cases s <;> + norm_num [Host.instanceArgs, Fill.Args.filled, Fill.Args.valueAB, Fill.Args.valueBC, + Fill.Args.valueAC, pick, encode, Host.hit, Algorithms.ExactTriangle.HasZeroTriangle, + weightAB, weightBC, weightAC] <;> decide + · intro k hk + rw [hym, hm] + apply Fill.Args.outside_filled + all_goals dsimp [Host.instanceArgs]; omega + +/-- The full quantitative theorem is inhabited for empty, positive and negative singleton +inputs, and the output witness is the actual native pre-halt state. -/ +example (input : List ℤ) + (hinput : input = [] ∨ input = [-1] ∨ input = [0] ∨ input = [1]) : + ∃ y t, t ≤ Host.time 0 input.length 9 ∧ + Runs 252 ((Host.body 0 probe).program (Host.hostRegisters Host.found)) + (State.init (input.map encode)) ⟨(Host.body 0 probe).size, y⟩ (t - 1) ∧ + Executes 252 ((Host.body 0 probe).program (Host.hostRegisters Host.found)) + (State.init (input.map encode)) (y.regs (Host.hostRegisters Host.found)) t ∧ + (y.regs (Host.hostRegisters Host.found) = 1 ↔ + Convolution input.length (fun i => input.getD i 0)) ∧ y.mem 0 = input.length := by + have hw : Host.width 0 input.length ≤ 250 := by + rcases hinput with rfl | rfl | rfl | rfl <;> decide + have hv : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ 0 : ℕ) := by + rcases hinput with rfl | rfl | rfl | rfl <;> simp + obtain ⟨y, t, ht, hr, he, -, ha, hm, -⟩ := + Host.runs_input input probe hw hv (probe_spec input hinput 250 (by decide)) + exact ⟨y, t, ht, hr, he, ha, + (hm 0 (by omega)).trans (by simp only [Data.init_mem_zero, List.length_map])⟩ + +section Interpreter + +-- Kernel reduction traverses the parameter routine and sixteen fully expanded cell computations. +set_option maxRecDepth 16384 + +/-- Actual host execution sees the zero singleton solution. -/ +example : ((Host.body 0 probe).eval 252 10 (.init [encode 0])).map + (fun yn => yn.1.regs (Host.hostRegisters Host.found)) = some 1 := by decide + +/-- Actual host execution rejects a positive singleton. -/ +example : ((Host.body 0 probe).eval 252 10 (.init [encode 1])).map + (fun yn => yn.1.regs (Host.hostRegisters Host.found)) = some 0 := by decide + +/-- Actual host execution rejects a negative singleton. -/ +example : ((Host.body 0 probe).eval 252 10 (.init [encode (-1)])).map + (fun yn => yn.1.regs (Host.hostRegisters Host.found)) = some 0 := by decide + +/-- Empty input constructs only filler matrices and returns false. -/ +example : ((Host.body 0 probe).eval 127 10 (.init [])).map + (fun yn => yn.1.regs (Host.hostRegisters Host.found)) = some 0 := by decide + +end Interpreter + +end ComplexityTests.ThreeSumHost diff --git a/ROADMAP.md b/ROADMAP.md index d31b9ede12..908de8e447 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -423,9 +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/ + and the selected-prime false-positive bound ([#1442]) are proved mathematically. The charged + native Convolution host ([#1524]) initializes from the ordinary input and runs every generated + instance, conditional on a concrete solver's execution, correctness and frame theorem. Remaining + work includes residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, + native Chan–He preprocessing, 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 @@ -553,3 +555,4 @@ not planned, not as proved. [#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 +[#1524]: https://github.com/cslib-community/complexitylib/issues/1524 diff --git a/UPSTREAM.md b/UPSTREAM.md index 8b49547dae..9eaeddf0b4 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -159,7 +159,13 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he 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 + were copied. `Algorithms/WordRAM/ThreeSum/Host/` adds the complete charged native host: ordinary + header initialization, polynomial magnitude computation, square root, consecutive matrix layout, + all `2t` instances and a finite solver program in a disjoint register bank. Its initialized run + theorem connects the actual pre-halt state to the Convolution-3SUM answer and preserves the input. + The host's source-width threshold is `(20κ+125)clog₂(N+2)`; signed kernels use target width `w+2`. + This remains conditional on the supplied solver's native execution/correctness/frame theorem; + it does not establish a fast solver. 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 From c3acab1b0bbf1919cb9ab744337fb60b9b4d9024 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 18:40:23 -0400 Subject: [PATCH 14/20] style(three-sum): align native host test bindings Address the optional indentation finding in PR #1531's outside review at e4d2fa0348cd1a64a24f75f58889aed06877ec5f. Only three leading spaces change in the private probe_spec test; all non-whitespace source characters are identical. Native programs, public contracts, models and costs are unchanged. Validation: all six strict gates together, including global Axioms; actual-base changed-file lints, citation/simp hygiene, PR-name and diff checks. The unchanged native/correctness/source coverage remains the outside FINAL https://github.com/cslib-community/complexitylib/pull/1531#issuecomment-6480840994 The style delta was checked by coordinator codex-three-sum-stack-20261028. Curated abstracts: no impact on statements, scope or rendering. AI assistance: OpenAI Codex prepared and validated this style-only correction. --- ComplexityTests/ThreeSumHost.lean | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ComplexityTests/ThreeSumHost.lean b/ComplexityTests/ThreeSumHost.lean index 28531cf054..8d782fdeaa 100644 --- a/ComplexityTests/ThreeSumHost.lean +++ b/ComplexityTests/ThreeSumHost.lean @@ -60,7 +60,7 @@ example {w : ℕ} (x : Data 7) (h1 : Fits w 1) : private theorem probe_spec (input : List ℤ) (hinput : input = [] ∨ input = [-1] ∨ input = [0] ∨ input = [1]) (w : ℕ) (h1 : Fits (w + 2) 1) : - let N := input.length; let U := (N + 2) ^ 0; + let N := input.length; let U := (N + 2) ^ 0; let μ : ℕ → ℤ := fun i => input.getD i 0; ∀ s < 2 * Host.sideLength N, ∀ (a : Data 7) (mem : ℕ → ℕ), a.regs (Host.solverArg 0) = Host.sideLength N → a.regs (Host.solverArg 1) = 2 * U + 1 → From 4f6de797203eb5dac02b2cadd9e8fa33e7427d57 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:06 -0400 Subject: [PATCH 15/20] fix(imports): deduplicate aggregate after counting integration --- Complexity.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 432ee4c7f5..5c2c4eead3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -6,7 +6,6 @@ public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.ExactTriangle.Witnesses -public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction From 02f1b495322d8cfee81969186795670e3574defa Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:08 -0400 Subject: [PATCH 16/20] fix(imports): deduplicate aggregate after query-splitting integration --- Complexity.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/Complexity.lean b/Complexity.lean index 69abff1939..f12edfac12 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -6,7 +6,6 @@ public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.ExactTriangle.Splitting public import Complexity.Algorithms.ExactTriangle.Weights public import Complexity.Algorithms.ExactTriangle.Witnesses -public import Complexity.Algorithms.ExactTriangle.Lopsided public import Complexity.Algorithms.Lean.FusionTree.Capacity public import Complexity.Algorithms.Lean.FusionTree.Compression public import Complexity.Algorithms.Lean.FusionTree.Correction From b79ae8e407f9f0349d2c14beb0d3e1aac8187e04 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:53:41 -0400 Subject: [PATCH 17/20] fix(imports): regenerate aggregate after Chan-He parameter integration --- Complexity.lean | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index c31f3baadd..cd891a6997 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -90,14 +90,14 @@ public import Complexity.Algorithms.WordRAM.FusionTree.Rank public import Complexity.Algorithms.WordRAM.FusionTree.ReadHeader public import Complexity.Algorithms.WordRAM.FusionTree.Static public import Complexity.Algorithms.WordRAM.Histogram -public import Complexity.Algorithms.WordRAM.NatClog -public import Complexity.Algorithms.WordRAM.NatLog -public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.Karger.Contract public import Complexity.Algorithms.WordRAM.Karger.FastCut public import Complexity.Algorithms.WordRAM.Karger.Graph public import Complexity.Algorithms.WordRAM.KargerMinCut public import Complexity.Algorithms.WordRAM.KargerStein +public import Complexity.Algorithms.WordRAM.NatClog +public import Complexity.Algorithms.WordRAM.NatLog +public import Complexity.Algorithms.WordRAM.NatSqrt public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree From 9c8b9fcc3ccc5a6282bbd3b3e3cbcfc4c0070c62 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:31:21 -0400 Subject: [PATCH 18/20] perf(wordram): reduce native parameter proof heartbeat budget --- Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean index f26ccbe183..26812d6412 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean @@ -193,8 +193,8 @@ private theorem time_zero (n U : ℕ) : time 0 n U - 20 = 44 + 5 * Nat.sqrt n + dsimp [time] omega -set_option maxHeartbeats 800000 in --- Elaborating twelve native segments and their explicit register frames exceeds the default budget. +set_option maxHeartbeats 300000 in +-- Measured 256376 heartbeats for the twelve native segments and explicit register frames. private theorem exec_calculate {w n U : ℕ} (x : Data 24) (hlen : x.regs len = n) (hU : x.regs magnitude = U) (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h3 : x.regs three = 3) From 452924eae8db34ac5c3ae00dcad77e8b5e40dea7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:33:03 -0400 Subject: [PATCH 19/20] chore(wordram): scope measured parameter proof budget to reduction task --- scripts/heartbeat_allowlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/heartbeat_allowlist.txt b/scripts/heartbeat_allowlist.txt index fc443bbffa..02ca8b8a21 100644 --- a/scripts/heartbeat_allowlist.txt +++ b/scripts/heartbeat_allowlist.txt @@ -6,3 +6,6 @@ # #1528: closed incidence-table certificates from #1281. Complexity/Computability/Complexity/Problems/SAT/ExactOne/GeometryProfilesPrefix.lean 8000000 + +# #1583: split native parameter segment/frame proofs to reach the default budget. +Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Parameters.lean 300000 From 19df5d14a10468bd8f9717e847e4ab15125974d1 Mon Sep 17 00:00:00 2001 From: OpenAI Codex coordinator Date: Thu, 29 Oct 2026 02:06:36 -0400 Subject: [PATCH 20/20] doc(roadmap): separate conditional convolution host progress Restore the shared Word RAM/tensor paragraph from actual main c5cb0fdc and record the unchanged conditional host milestone separately, preserving the broader solver and full reduction objective. This avoids overlapping queued thin-product progress. Documentation only; all source, tests, abstracts, UPSTREAM records, allowances and imports unchanged. Queue group commits are read-only projection inputs, never author parents. --- ROADMAP.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 46fdb7eae9..93afea6258 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -434,17 +434,20 @@ 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. The charged native Convolution host - ([#1524]) initializes from the ordinary input and runs every generated instance, conditional on - a concrete solver's execution, correctness and frame theorem. Remaining work includes tiling - and sparse leaf bounds, native tensor/sparse-query programs, native Chan–He preprocessing, - the fast Exact Triangle solver and full + 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 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 Horn-SAT, 2SAT, matching and domino-tiling clients. +- **Conditional Convolution-3SUM host** ([#1524]): the charged native host initializes from the + ordinary input and runs every generated instance, conditional on a concrete solver's execution, + correctness and frame theorem. This completes the conditional host milestone. Integrating the + concrete fast Exact Triangle solver and the full input/time/width/asymptotic reduction remains + open under [#1394]. - **Stateful algorithms and data structures:** Word-RAM controllers for larger adaptive and iterative clients, including the two-stack queue and timeline-editing controllers; reusable allocation and deallocation, shrinking, and live and peak space accounting; ownership and