From 6094ec9cc975f8e361f8ea49146ac4bea36aa115 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 05:13:43 -0400 Subject: [PATCH 01/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] =?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/45] 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 4fc6a70f0da95833bedf57afcacd117b8700c22c Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 07:59:17 -0400 Subject: [PATCH 11/45] feat(wordram): add native radix sorting and multiplicity arrays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement complete fixed-code stable binary radix sorting, adjacent full run counts, and charged composition from ordinary native input. Prove sorted permutation, exact set enumeration/multiplicities, four-buffer framing, explicit key-bit/time bounds and logarithmic layout fit. The 81-instruction program uses 16 registers. Scope: #1452 under #1446/#1394; full Chan–He preprocessing remains open. No Histogram or ThreeSum/ChanHe changes. Original implementation/proofs; no source machine import. Curated abstracts and rendering: no impact. Independent review remains requested. Validation on the final staged sources above 8e182b091: all six scripts/check.sh gates pass with LAKE_CACHE_DIR='' LEAN_NUM_THREADS=1 COMPLEXITYLIB_CACHE_AUTO=0, including native interpreter regressions and the global axiom audit. PYTHONUTF8=1 hygiene --check-simp, changed-file lints --base 8e182b091 and git diff --cached --check pass. Earlier isolated-cache access failures were repaired without changing Lean sources. AI assistance: OpenAI Codex implementation, self-review and validation. --- Complexity.lean | 9 + .../Algorithms/WordRAM/Deduplicate/Count.lean | 148 +++++++++ .../Algorithms/WordRAM/Deduplicate/Model.lean | 146 +++++++++ .../WordRAM/Deduplicate/Program.lean | 284 ++++++++++++++++++ Complexity/Algorithms/WordRAM/RadixSort.lean | 106 +++++++ .../WordRAM/RadixSort/Deduplicate.lean | 135 +++++++++ .../Algorithms/WordRAM/RadixSort/Model.lean | 134 +++++++++ .../Algorithms/WordRAM/RadixSort/Program.lean | 204 +++++++++++++ .../Algorithms/WordRAM/RadixSort/Scan.lean | 254 ++++++++++++++++ .../Computability/WordRAM/ListArray.lean | 94 ++++++ ComplexityTests/WordRAMRadixSort.lean | 67 +++++ 11 files changed, 1581 insertions(+) create mode 100755 Complexity/Algorithms/WordRAM/Deduplicate/Count.lean create mode 100755 Complexity/Algorithms/WordRAM/Deduplicate/Model.lean create mode 100755 Complexity/Algorithms/WordRAM/Deduplicate/Program.lean create mode 100755 Complexity/Algorithms/WordRAM/RadixSort.lean create mode 100755 Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean create mode 100755 Complexity/Algorithms/WordRAM/RadixSort/Model.lean create mode 100755 Complexity/Algorithms/WordRAM/RadixSort/Program.lean create mode 100755 Complexity/Algorithms/WordRAM/RadixSort/Scan.lean create mode 100755 Complexity/Computability/WordRAM/ListArray.lean create mode 100755 ComplexityTests/WordRAMRadixSort.lean diff --git a/Complexity.lean b/Complexity.lean index e6ac945545..f7f030e1ba 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -49,6 +49,9 @@ public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes public import Complexity.Algorithms.ThreeSum.Convolution public import Complexity.Algorithms.ThreeSum.Defs public import Complexity.Algorithms.ThreeSum.ThreeSets +public import Complexity.Algorithms.WordRAM.Deduplicate.Count +public import Complexity.Algorithms.WordRAM.Deduplicate.Model +public import Complexity.Algorithms.WordRAM.Deduplicate.Program public import Complexity.Algorithms.WordRAM.Dijkstra.BinaryHeap public import Complexity.Algorithms.WordRAM.Dijkstra.ExactNotation public import Complexity.Algorithms.WordRAM.Dijkstra.HeapProgram @@ -97,6 +100,11 @@ public import Complexity.Algorithms.WordRAM.Queue.Dynamic public import Complexity.Algorithms.WordRAM.Queue.Model public import Complexity.Algorithms.WordRAM.Quickselect public import Complexity.Algorithms.WordRAM.Quicksort +public import Complexity.Algorithms.WordRAM.RadixSort +public import Complexity.Algorithms.WordRAM.RadixSort.Deduplicate +public import Complexity.Algorithms.WordRAM.RadixSort.Model +public import Complexity.Algorithms.WordRAM.RadixSort.Program +public import Complexity.Algorithms.WordRAM.RadixSort.Scan public import Complexity.Algorithms.WordRAM.Stack public import Complexity.Algorithms.WordRAM.Stack.Controller public import Complexity.Algorithms.WordRAM.ThreeSum.Cell @@ -1603,6 +1611,7 @@ public import Complexity.Computability.WordRAM.Dynamic public import Complexity.Computability.WordRAM.Function public import Complexity.Computability.WordRAM.Generic public import Complexity.Computability.WordRAM.Heap +public import Complexity.Computability.WordRAM.ListArray public import Complexity.Computability.WordRAM.MapOp public import Complexity.Computability.WordRAM.Multiword public import Complexity.Computability.WordRAM.Multiword.Arithmetic diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Count.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Count.lean new file mode 100755 index 0000000000..7ecf8a3c0a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/Deduplicate/Count.lean @@ -0,0 +1,148 @@ +/- +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.Deduplicate.Model +public import Complexity.Computability.WordRAM.ListArray + +/-! +# Native counting of one adjacent run + +The counter tests the array bound before every read and stops at the first different word. +No sorting or counting oracle is used. The mathematical end index is a proof parameter and +does not occur in the code or a register. + +## Main definitions + +* `Count.body`, `Count.code`: the native run-counting loop. + +## Main results + +* `Count.exec`: every matching input position advances the cursor, with linear charged time. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.Deduplicate + +open Cslib.WordRAM + +/-- Input length. -/ +abbrev len : Fin 16 := 0 +/-- Sorted input array. -/ +abbrev src : Fin 16 := 1 +/-- Unique-key output array. -/ +abbrev keys : Fin 16 := 2 +/-- Multiplicity output array. -/ +abbrev counts : Fin 16 := 3 +/-- Input cursor. -/ +abbrev idx : Fin 16 := 4 +/-- Number of completed output runs. -/ +abbrev out : Fin 16 := 5 +/-- Current run key. -/ +abbrev key : Fin 16 := 6 +/-- Current run multiplicity. -/ +abbrev count : Fin 16 := 7 +/-- Loaded input word. -/ +abbrev value : Fin 16 := 8 +/-- Address temporary. -/ +abbrev addr : Fin 16 := 9 +/-- Constant one. -/ +abbrev one : Fin 16 := 10 +/-- Run-counting loop flag. -/ +abbrev active : Fin 16 := 11 + +namespace Count + +/-- Read the next word only if the cursor is in bounds; count it or end the run. -/ +def body : Code .standard 16 := ram[ + if idx < len then [ + addr ← src + idx; value ← *addr; + if value = key then [ count ← count + one; idx ← idx + one ] + else [ active ← #0 ] + ] else [ active ← #0 ] ] + +/-- Count from the current cursor, starting with zero count and flag one. -/ +def code : Code .standard 16 := .loop (.eq active one) body + +/-- Count a maximal adjacent run in `8c+10` instructions, including the terminating test. +`stop` is a proof parameter: execution learns the boundary solely through charged comparisons +and loads. The source memory is unchanged. Empty runs are also admitted by this kernel. -/ +theorem exec {w s n start stop k : ℕ} (x : Data 16) + (hn : x.regs len = n) (hs : x.regs src = s) (hi : x.regs idx = start) + (hk : x.regs key = k) (hc : x.regs count = 0) + (ha : x.regs active = 1) (ho : x.regs one = 1) + (hle : start ≤ stop) (hstop : stop ≤ n) + (hmem : ∀ j, start ≤ j → j < stop → x.mem (s + j) = k) + (hnext : stop < n → x.mem (s + stop) ≠ k) + (hfit : Fits w (s + n)) : + ∃ y, ExecWithin w code x y (8 * (stop - start) + 10) ∧ + y.regs idx = stop ∧ y.regs count = stop - start ∧ y.mem = x.mem := by + let I (y : Data 16) : Prop := + y.regs len = n ∧ y.regs src = s ∧ y.regs key = k ∧ y.regs one = 1 ∧ + start ≤ y.regs idx ∧ y.regs idx ≤ stop ∧ y.regs count = y.regs idx - start ∧ + y.regs active ≤ 1 ∧ (y.regs active = 0 → y.regs idx = stop) ∧ y.mem = x.mem + have hbody (y : Data 16) (hy : I y) (hcond : (Cond.eq active one).Holds y) : + ∃ z, ExecWithin w body y z 6 ∧ I z ∧ + stop - z.regs idx + z.regs active < stop - y.regs idx + y.regs active := by + obtain ⟨hyn, hys, hyk, hyo, hlo, hhi, hyc, hya, hyend, hym⟩ := hy + have hyflag : y.regs active = 1 := by simpa only [Cond.holds_eq, hyo] using hcond + simp only [len, src, key, one, idx, count, active] at * + have hlenLink : y.regs len = y.regs 0 := rfl + have hidxLink : y.regs idx = y.regs 4 := rfl + have hcountLink : y.regs count = y.regs 7 := rfl + have hactiveLink : y.regs active = y.regs 11 := rfl + by_cases hlt : y.regs idx < stop + · have hidx : y.regs idx < y.regs len := by omega + have hread : y.mem (y.regs src + y.regs idx) = k := by + rw [hym, hys]; exact hmem _ hlo hlt + have had : Fits w (y.regs src + y.regs idx) := by + rw [hys]; exact hfit.of_le (by omega) + have hic : Fits w (y.regs idx + 1) := hfit.of_le (by omega) + have hcc : Fits w (y.regs count + 1) := hfit.of_le (by omega) + refine ⟨?_, ?run, ?_, ?_⟩ + case run => ram_eval [body, hidx, hread, hyk, hyo, had, hic, hcc] + · simp +decide only [I, Data.set_regs, Data.set_mem, Function.update_apply, + idx, count, value, addr, len, src, key, one, active, ↓reduceIte] + exact ⟨hyn, hys, hyk, hyo, by omega, by omega, by omega, by omega, by omega, hym⟩ + · simp +decide only [Data.set_regs, Function.update_apply, idx, count, value, addr, + ↓reduceIte] + omega + · have hend : y.regs idx = stop := by omega + by_cases hsn : stop < n + · have hidx : y.regs idx < y.regs len := by omega + have hread : y.mem (y.regs src + y.regs idx) ≠ y.regs key := by + simpa only [hym, hys, hend, hyk] using hnext hsn + have had : Fits w (y.regs src + y.regs idx) := by + rw [hys]; exact hfit.of_le (by omega) + refine ⟨?_, ?run, ?_, ?_⟩ + case run => ram_eval [body, hidx, hread, had] + · simp +decide only [I, Data.set_regs, Data.set_mem, Function.update_apply, + idx, count, value, addr, len, src, key, one, active, ↓reduceIte] + exact ⟨hyn, hys, hyk, hyo, hlo, hhi, hyc, by omega, fun _ => hend, hym⟩ + · simp +decide [Data.set_regs, hyflag, hend] + · have hidx : ¬y.regs idx < y.regs len := by omega + refine ⟨?_, ?run, ?_, ?_⟩ + case run => ram_eval [body, hidx] + · simp +decide only [I, Data.set_regs, Data.set_mem, Function.update_apply, + idx, count, len, src, key, one, active, ↓reduceIte] + exact ⟨hyn, hys, hyk, hyo, hlo, hhi, hyc, by omega, fun _ => hend, hym⟩ + · simp +decide [Data.set_regs, hyflag, hend] + have hx : I x := ⟨hn, hs, hk, ho, by omega, by omega, by omega, by omega, by omega, rfl⟩ + obtain ⟨y, he, hy, hnot⟩ := ExecWithin.loop_invariant I + (fun y => stop - y.regs idx + y.regs active) hbody hx + obtain ⟨_, _, _, hyo, _, _, hyc, hya, hyend, hym⟩ := hy + have hz : y.regs active = 0 := by + simp only [Cond.holds_eq, hyo] at hnot + omega + have hend := hyend hz + refine ⟨y, ?_, hend, by simpa only [hend] using hyc, hym⟩ + simpa only [code, hi, ha, Nat.add_mul, Nat.one_mul, Nat.mul_comm, Nat.add_assoc] using he + +end Count +end Cslib.Algorithms.WordRAM.Deduplicate diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Model.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Model.lean new file mode 100755 index 0000000000..fb6feb225b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/Deduplicate/Model.lean @@ -0,0 +1,146 @@ +/- +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.Data.List.Sort +public import Mathlib.Data.List.Nodup + +/-! +# Adjacent deduplication with full multiplicities + +The output is a list of key/count pairs with positive counts and strictly increasing keys. +Expanding those runs recovers the sorted input exactly. This specifies the whole finite set and +all multiplicities, rather than a one-way membership condition. These proofs are original. + +## Main definitions + +* `expand`: expand key/count pairs into their repeated keys. +* `RunEncoding`: positive, strictly ordered runs reconstructing the input. + +## Main results + +* `RunEncoding.keys_nodup`: each key occurs once. +* `RunEncoding.mem_iff`: the keys enumerate exactly the input set. +* `RunEncoding.multiplicity`: each count is the full input multiplicity. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.Deduplicate + +/-- Expand a list of key/count pairs into repeated keys. -/ +def expand (rs : List (ℕ × ℕ)) : List ℕ := rs.flatMap (fun p => List.replicate p.2 p.1) + +/-- Runs with positive counts and strictly increasing keys reconstruct exactly `xs`. -/ +structure RunEncoding (xs : List ℕ) (rs : List (ℕ × ℕ)) : Prop where + /-- Each emitted run is nonempty. -/ + positive : ∀ p ∈ rs, 0 < p.2 + /-- A key never reappears after a different key. -/ + ordered : rs.Pairwise (fun p q => p.1 < q.1) + /-- All input positions are accounted for, with their exact order and multiplicity. -/ + reconstruct : expand rs = xs + +/-- Expansion distributes over concatenation. -/ +theorem expand_append (rs ss : List (ℕ × ℕ)) : expand (rs ++ ss) = expand rs ++ expand ss := by + simp [expand] + +/-- Expansion of one run. -/ +theorem expand_singleton (k c : ℕ) : expand [(k, c)] = List.replicate c k := by + simp [expand] + +/-- Every key in an expansion occurs in some nonempty run. -/ +theorem mem_expand {rs : List (ℕ × ℕ)} {k : ℕ} : + k ∈ expand rs ↔ ∃ p ∈ rs, p.1 = k ∧ 0 < p.2 := by + simp only [expand, List.mem_flatMap, List.mem_replicate] + constructor + · rintro ⟨p, hp, hc, hk⟩ + exact ⟨p, hp, hk.symm, by omega⟩ + · rintro ⟨p, hp, hk, hc⟩ + exact ⟨p, hp, by omega, hk.symm⟩ + +/-- Positive runs contain at least one element each. -/ +theorem length_le_expand {rs : List (ℕ × ℕ)} (hp : ∀ p ∈ rs, 0 < p.2) : + rs.length ≤ (expand rs).length := by + induction rs with + | nil => simp [expand] + | cons p rs ih => + have hc := hp p (by simp) + have ht := ih (fun q hq => hp q (by simp [hq])) + simp only [expand, List.flatMap_cons, List.length_append, List.length_replicate, + List.length_cons] at * + omega + +private theorem count_expand {rs : List (ℕ × ℕ)} + (ho : rs.Pairwise (fun p q => p.1 < q.1)) {p : ℕ × ℕ} (hp : p ∈ rs) : + (expand rs).count p.1 = p.2 := by + induction rs with + | nil => simp at hp + | cons q rs ih => + obtain ⟨hq, ht⟩ := List.pairwise_cons.mp ho + rcases List.mem_cons.mp hp with rfl | hp + · have hnot : p.1 ∉ expand rs := by + rintro hk + obtain ⟨p, hp, he, _⟩ := mem_expand.mp hk + have := hq p hp + omega + change (List.replicate p.2 p.1 ++ expand rs).count p.1 = p.2 + rw [List.count_append, List.count_replicate_self, List.count_eq_zero_of_not_mem hnot, + Nat.add_zero] + · have hlt := hq p hp + have hc := ih ht hp + simp only [expand, List.flatMap_cons, List.count_append] + rw [show (List.replicate q.2 q.1).count p.1 = 0 by + simp [List.count_replicate, Nat.ne_of_lt hlt]] + simpa only [Nat.zero_add, expand] using hc + +/-- The key array contains no repeated key. -/ +theorem RunEncoding.keys_nodup {xs : List ℕ} {rs : List (ℕ × ℕ)} + (h : RunEncoding xs rs) : (rs.map Prod.fst).Nodup := by + rw [List.nodup_iff_pairwise_ne, List.pairwise_map] + exact h.ordered.imp (fun hlt => Nat.ne_of_lt hlt) + +/-- The key array enumerates precisely the input set. -/ +theorem RunEncoding.mem_iff {xs : List ℕ} {rs : List (ℕ × ℕ)} + (h : RunEncoding xs rs) (k : ℕ) : k ∈ rs.map Prod.fst ↔ k ∈ xs := by + rw [← h.reconstruct, mem_expand] + rw [List.mem_map] + constructor + · rintro ⟨p, hp, hk⟩ + exact ⟨p, hp, hk, h.positive p hp⟩ + · rintro ⟨p, hp, hk, _⟩ + exact ⟨p, hp, hk⟩ + +/-- Each emitted count equals the full multiplicity in the input list. -/ +theorem RunEncoding.multiplicity {xs : List ℕ} {rs : List (ℕ × ℕ)} + (h : RunEncoding xs rs) {p : ℕ × ℕ} (hp : p ∈ rs) : xs.count p.1 = p.2 := by + rw [← h.reconstruct] + exact count_expand h.ordered hp + +/-- A nonempty sorted list starts with a positive run of its head, followed only by larger +keys. This is a mathematical decomposition used to verify the adjacent scan, not an oracle +operation of the program. -/ +theorem first_run (k : ℕ) (xs : List ℕ) (hs : (k :: xs).Pairwise (· ≤ ·)) : + ∃ c rest, 0 < c ∧ k :: xs = List.replicate c k ++ rest ∧ ∀ v ∈ rest, k < v := by + induction xs generalizing k with + | nil => exact ⟨1, [], by decide, rfl, by simp⟩ + | cons j xs ih => + obtain ⟨hk, ht⟩ := List.pairwise_cons.mp hs + by_cases heq : k = j + · subst j + obtain ⟨c, rest, hc, he, hr⟩ := ih k ht + refine ⟨c + 1, rest, by omega, ?_, hr⟩ + simpa only [List.replicate_succ, List.cons_append] using congrArg (List.cons k) he + · have hkj : k < j := lt_of_le_of_ne (hk j (by simp)) heq + refine ⟨1, j :: xs, by decide, rfl, ?_⟩ + intro v hv + rcases List.mem_cons.mp hv with rfl | hv + · exact hkj + · exact lt_of_lt_of_le hkj ((List.pairwise_cons.mp ht).1 v hv) + +end Cslib.Algorithms.WordRAM.Deduplicate diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Program.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Program.lean new file mode 100755 index 0000000000..df4da01c3d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/Deduplicate/Program.lean @@ -0,0 +1,284 @@ +/- +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.Deduplicate.Count + +/-! +# Complete native adjacent deduplication and multiplicity arrays + +The input is a sorted array. The program counts one maximal adjacent run, emits its key and +full count, and repeats. The output consists of two adjacent arrays, each with capacity equal +to the input length. All run tests, loads, stores, address calculations and control instructions +are charged. The total time is linear, including repeated keys and empty inputs. + +## Main definitions + +* `flush`: store one key/count pair and advance the output cursor. +* `body`: count and emit one complete run. +* `code`: the full adjacent deduplication program. + +## Main results + +* `exec`: full unique-key and multiplicity output with a linear native instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.Deduplicate + +open Cslib.WordRAM + +/-- Store one key/count pair and advance the output cursor. -/ +def flush : Code .standard 16 := ram[ + addr ← keys + out; *addr ← key; + addr ← counts + out; *addr ← count; out ← out + one ] + +/-- Count and emit the next maximal run. -/ +def body : Code .standard 16 := + .seq (ram[ addr ← src + idx; key ← *addr; count ← #0; active ← #1 ]) + (.seq Count.code flush) + +/-- Initialize the two cursors and count all adjacent runs. -/ +def code : Code .standard 16 := + .seq (ram[ one ← #1; idx ← #0; out ← #0 ]) (.loop (.lt idx len) body) + +private theorem flush_exec {w : ℕ} (x : Data 16) (ho : x.regs one = 1) + (hk : Fits w (x.regs keys + x.regs out)) + (hc : Fits w (x.regs counts + x.regs out)) (hi : Fits w (x.regs out + 1)) : + ∃ y, ExecWithin w flush x y 5 ∧ y.regs out = x.regs out + 1 ∧ + y.mem = Function.update (Function.update x.mem (x.regs keys + x.regs out) (x.regs key)) + (x.regs counts + x.regs out) (x.regs count) := by + refine ⟨?_, ?run, ?_, ?_⟩ + case run => ram_eval [flush, ho, hk, hc, hi] + all_goals simp +decide [Data.set_regs, Data.write_mem] + +/-- Emit each distinct key once and its full multiplicity in at most `29n+5` instructions. +The input precedes the two output blocks; no input cells or cells outside the output blocks +change. Counts are ordinary unsigned words bounded by `n`. No restriction on key signs or +canonical encodings is needed: in particular canonical `Signed.encode` inputs are admitted. -/ +theorem exec {w s base : ℕ} (xs : List ℕ) (x : Data 16) + (hn : x.regs len = xs.length) (hs : x.regs src = s) + (hk : x.regs keys = base) (hc : x.regs counts = base + xs.length) + (hmem : Block.HoldsList s xs x.mem) (hsorted : xs.Pairwise (· ≤ ·)) + (hbelow : s + xs.length ≤ base) (hfit : Fits w (base + 2 * xs.length)) + (h1 : Fits w 1) : + ∃ y rs, ExecWithin w code x y (29 * xs.length + 5) ∧ + RunEncoding xs rs ∧ y.regs out = rs.length ∧ + Block.HoldsList base (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (base + xs.length) (rs.map Prod.snd) y.mem ∧ + Block.HoldsList s xs y.mem ∧ + Modifies (Block.mk base (2 * xs.length)).footprint x.mem y.mem := by + let n := xs.length + let I (y : Data 16) : Prop := + y.regs len = n ∧ y.regs src = s ∧ y.regs keys = base ∧ + y.regs counts = base + n ∧ y.regs one = 1 ∧ y.regs idx ≤ n ∧ + Block.HoldsList s xs y.mem ∧ + Modifies (Block.mk base (2 * n)).footprint x.mem y.mem ∧ + ∃ rs, RunEncoding (xs.take (y.regs idx)) rs ∧ y.regs out = rs.length ∧ + Block.HoldsList base (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (base + n) (rs.map Prod.snd) y.mem ∧ + ∀ p ∈ rs, ∀ v ∈ xs.drop (y.regs idx), p.1 < v + have hbody (y : Data 16) (hy : I y) (hcond : (Cond.lt idx len).Holds y) : + ∃ z T, ExecWithin w body y z T ∧ I z ∧ + T + 2 + 29 * (n - z.regs idx) ≤ 29 * (n - y.regs idx) := by + obtain ⟨hyn, hys, hyk, hyc, hyo, hiy, hym, hyf, rs, hrs, hyout, hyrk, hyrc, hrlt⟩ := hy + have hi : y.regs idx < xs.length := by simpa only [Cond.holds_lt, hyn] using hcond + have htail := hsorted.sublist (List.drop_sublist (y.regs idx) xs) + rw [List.drop_eq_getElem_cons hi] at htail + obtain ⟨c, rest, hcp, heq, hrest⟩ := first_run xs[y.regs idx] (xs.drop (y.regs idx + 1)) htail + have hdrop : xs.drop (y.regs idx) = List.replicate c xs[y.regs idx] ++ rest := by + rw [List.drop_eq_getElem_cons hi, heq] + have hlen := congrArg List.length hdrop + simp only [List.length_drop, List.length_append, List.length_replicate] at hlen + have hstop : y.regs idx + c ≤ n := by omega + have hdrop' : xs.drop (y.regs idx + c) = rest := by + rw [← List.drop_drop, hdrop] + exact List.drop_left' (by simp) + have htake : xs.take (y.regs idx + c) = + xs.take (y.regs idx) ++ List.replicate c xs[y.regs idx] := by + rw [List.take_add, hdrop, List.take_left' (by simp)] + have hrun (j : ℕ) (hlo : y.regs idx ≤ j) (hhi : j < y.regs idx + c) : + y.mem (s + j) = xs[y.regs idx] := by + rw [hym.get j (by omega)] + have hh : (xs.drop (y.regs idx))[j - y.regs idx]'(by simp only [List.length_drop]; omega) = + xs[y.regs idx] := by + simp [hdrop, show j - y.regs idx < c from by omega] + simpa only [List.getElem_drop, show y.regs idx + (j - y.regs idx) = j from by omega] using hh + have hnext (hlt : y.regs idx + c < n) : + y.mem (s + (y.regs idx + c)) ≠ xs[y.regs idx] := by + rw [hym.get _ hlt] + have hmember : xs[y.regs idx + c] ∈ rest := by + rw [← hdrop'] + have hh := List.getElem_mem (l := xs.drop (y.regs idx + c)) + (n := 0) (by simp only [List.length_drop]; omega) + simpa using hh + have := hrest _ hmember + omega + let y₀ := (((y.set addr (s + y.regs idx)).set key xs[y.regs idx]).set count 0).set active 1 + have hinit : ExecWithin (B := .standard) w + (ram[ addr ← src + idx; key ← *addr; count ← #0; active ← #1 ]) y y₀ 4 := by + have had : Fits w (y.regs src + y.regs idx) := by + rw [hys]; exact hfit.of_le (by omega) + have hread : y.mem (y.regs src + y.regs idx) = xs[y.regs idx] := by + rw [hys]; exact hym.get _ hi + have had' : Fits w (s + y.regs idx) := hfit.of_le (by omega) + have hread' : y.mem (s + y.regs idx) = xs[y.regs idx] := hym.get _ hi + ram_eval [y₀, had, had', hread, hread', hys, h1] + obtain ⟨u, hu, hui, huc, hum⟩ := Count.exec (k := xs[y.regs idx]) y₀ + (by simpa +decide [y₀] using hyn) (by simpa +decide [y₀] using hys) + (by simp +decide [y₀]) (by simp +decide [y₀]) + (by simp +decide [y₀]) (by simp +decide [y₀]) + (by simpa +decide [y₀] using hyo) (show y.regs idx ≤ y.regs idx + c by omega) + hstop (by simpa only [y₀, Data.set_mem] using hrun) + (by simpa only [y₀, Data.set_mem] using hnext) (hfit.of_le (by omega)) + have huc' : u.regs count = c := by simpa using huc + have hur (j : Fin 16) (hj : Count.code.Preserves j) : u.regs j = y₀.regs j := hu.regs_eq hj + have hun : u.regs len = n := by + simpa +decide [y₀] + using (hur len (by decide)).trans + (by simpa +decide [y₀] using hyn) + have hus : u.regs src = s := by + simpa +decide [y₀] + using (hur src (by decide)).trans + (by simpa +decide [y₀] using hys) + have huk : u.regs keys = base := by + simpa +decide [y₀] + using (hur keys (by decide)).trans + (by simpa +decide [y₀] using hyk) + have hucb : u.regs counts = base + n := by + simpa +decide [y₀] + using (hur counts (by decide)).trans + (by simpa +decide [y₀] using hyc) + have huo : u.regs one = 1 := by + simpa +decide [y₀] + using (hur one (by decide)).trans + (by simpa +decide [y₀] using hyo) + have huout : u.regs out = rs.length := by + simpa +decide [y₀] + using (hur out (by decide)).trans + (by simpa +decide [y₀] using hyout) + have hukey : u.regs key = xs[y.regs idx] := by simpa +decide [y₀] using hur key (by decide) + have hrl : rs.length ≤ y.regs idx := by + have hh := length_le_expand hrs.positive + rw [hrs.reconstruct, List.length_take_of_le hiy] at hh + exact hh + obtain ⟨z, hz, hzout, hzm⟩ := flush_exec u huo + (by rw [huk, huout]; exact hfit.of_le (by omega)) + (by rw [hucb, huout]; exact hfit.of_le (by omega)) + (by rw [huout]; exact hfit.of_le (by omega)) + have hzmem : z.mem = Function.update + (Function.update y.mem (base + rs.length) xs[y.regs idx]) (base + n + rs.length) c := by + simpa only [huk, hucb, huout, hukey, huc', hum, y₀, Data.set_mem] using hzm + have hzi : z.regs idx = y.regs idx + c := (hz.regs_eq (by decide)).trans hui + have hzf : Modifies (Block.mk base (2 * n)).footprint y.mem z.mem := by + intro a hnot + have ha : ¬(base ≤ a ∧ a < base + 2 * n) := by + simpa only [Block.mem_footprint, Block.Owns] using hnot + rw [hzmem, Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + have hkeyrep : Block.HoldsList base + ((rs ++ [(xs[y.regs idx], c)]).map Prod.fst) z.mem := by + rw [List.map_append] + simp only [List.map_cons, List.map_nil] + rw [hzmem] + have hkr : Block.HoldsList base (rs.map Prod.fst ++ [xs[y.regs idx]]) + (Function.update y.mem (base + rs.length) xs[y.regs idx]) := by + simpa only [List.length_map] using hyrk.append_one xs[y.regs idx] + apply hkr.frame + intro a hlo hhi + rw [Function.update_of_ne (by + simp only [List.length_append, List.length_map, List.length_singleton] at hhi + omega)] + have hcountrep : Block.HoldsList (base + n) + ((rs ++ [(xs[y.regs idx], c)]).map Prod.snd) z.mem := by + rw [List.map_append] + simp only [List.map_cons, List.map_nil] + rw [hzmem] + have hcr : Block.HoldsList (base + n) (rs.map Prod.snd) + (Function.update y.mem (base + rs.length) xs[y.regs idx]) := by + apply hyrc.frame + intro a hlo hhi + rw [Function.update_of_ne (by omega)] + simpa only [List.length_map] using hcr.append_one c + have hrs' : RunEncoding (xs.take (y.regs idx + c)) (rs ++ [(xs[y.regs idx], c)]) := by + refine ⟨?_, ?_, ?_⟩ + · intro p hp + rcases List.mem_append.mp hp with hp | hp + · exact hrs.positive p hp + · have hp' := List.mem_singleton.mp hp + subst p + exact hcp + · apply List.pairwise_append.mpr + refine ⟨hrs.ordered, by simp, ?_⟩ + intro p hp q hq + have hq' : q = (xs[y.regs idx], c) := List.mem_singleton.mp hq + subst q + apply hrlt p hp + rw [List.drop_eq_getElem_cons hi] + exact List.mem_cons_self + · rw [expand_append, expand_singleton, hrs.reconstruct, htake] + refine ⟨z, 8 * c + 19, ?_, ?_, ?_⟩ + · have hall := hinit.seq (hu.seq hz) + exact hall.mono (by omega) + · refine ⟨(hz.regs_eq (by decide)).trans hun, (hz.regs_eq (by decide)).trans hus, + (hz.regs_eq (by decide)).trans huk, (hz.regs_eq (by decide)).trans hucb, + (hz.regs_eq (by decide)).trans huo, by omega, ?_, ?_, + rs ++ [(xs[y.regs idx], c)], by simpa only [hzi] using hrs', ?_, hkeyrep, hcountrep, ?_⟩ + · apply hym.frame + intro a hlo hhi + apply hzf + rw [Block.mem_footprint] + change ¬(base ≤ a ∧ a < base + 2 * n) + omega + · intro a ha + exact (hzf a ha).trans (hyf a ha) + · rw [hzout, huout, List.length_append, List.length_singleton] + · intro p hp v hv + rw [hzi, hdrop'] at hv + rcases List.mem_append.mp hp with hp | hp + · apply hrlt p hp + rw [hdrop] + exact List.mem_append_right _ hv + · have hp' : p = (xs[y.regs idx], c) := List.mem_singleton.mp hp + subst p + exact hrest v hv + · rw [hzi] + omega + let x₀ := ((x.set one 1).set idx 0).set out 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, hmem, Modifies.refl _ _, [], ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [x₀] using hn + · simpa +decide [x₀] using hs + · simpa +decide [x₀] using hk + · simpa +decide [x₀] using hc + · simp +decide [x₀] + · simp +decide [x₀] + · refine ⟨by simp, by simp, ?_⟩ + simp +decide [x₀, expand] + · simp [x₀] + · exact Block.holdsList_nil _ _ + · exact Block.holdsList_nil _ _ + · simp + obtain ⟨y, he, hy, hcond⟩ := ExecWithin.loop_potential I + (fun y => 29 * (n - y.regs idx)) hbody hx₀ + obtain ⟨hyn, _, _, _, _, hiy, hym, hyf, rs, hrs, hyout, hyrk, hyrc, _⟩ := hy + have hend : y.regs idx = xs.length := by + simp only [Cond.holds_lt, hyn] at hcond + omega + have hinit : ExecWithin (B := .standard) w (ram[ one ← #1; idx ← #0; out ← #0 ]) x x₀ 3 := by + ram_eval [x₀, h1] + refine ⟨y, rs, ?_, by simpa only [hend, List.take_length] using hrs, + hyout, hyrk, hyrc, hym, hyf⟩ + have hall := hinit.seq he + convert hall using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, out, one, + ↓reduceIte, Nat.sub_zero, n] + omega + +end Cslib.Algorithms.WordRAM.Deduplicate diff --git a/Complexity/Algorithms/WordRAM/RadixSort.lean b/Complexity/Algorithms/WordRAM/RadixSort.lean new file mode 100755 index 0000000000..d3a1a6584c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/RadixSort.lean @@ -0,0 +1,106 @@ +/- +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.RadixSort.Deduplicate +public import Complexity.Computability.WordRAM.Time + +/-! +# Fixed-code native sorting and multiplicity construction from machine input + +The input is the ordinary native list of unsigned words. The code reads its length and the +physical word width, and uses that width as an explicit key-bit bound. The lower-level +`deduplicateCode` accepts a smaller caller-supplied bit bound when known. + +The key array starts at `1+2n`, the count array at `1+3n`; the return value is their common +logical length. The two sorting buffers occupy `[1,1+2n)`. The implementation accepts repeated +keys, zero keys and canonical signed integer encodings. Its order is unsigned encoding order. + +## Main definitions + +* `inputCode`, `program`: the complete structured and flat native programs. +* `layoutWidth`: a logarithmic width sufficient for all four array blocks. + +## Main results + +* `input_exec`: charged execution from ordinary native input, with exact key/count output. +* `layout_fits`: logarithmic-width layout fit, including empty input. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.RadixSort + +open Cslib.WordRAM + +/-- Read the native input header and width, then sort and construct both output arrays. -/ +def inputCode : Code .standard 16 := + .seq (ram[ addr ← #0; len ← *addr; src ← #1; dst ← src + len; bits ← width% ]) + deduplicateCode + +/-- The fixed native instruction list, returning the number of unique keys. -/ +def program : Program .standard 16 := inputCode.program Deduplicate.out + +/-- A logarithmic layout-width schedule; the key magnitude bound is independent. -/ +def layoutWidth (n : ℕ) : ℕ := logWidth 3 (n + 1) + +/-- Four array blocks and both initialization constants fit at the layout-width threshold. +This does not by itself bound input key magnitudes. -/ +theorem layout_fits (n : ℕ) : Fits (layoutWidth n) (1 + 4 * n) ∧ Fits (layoutWidth n) 2 := by + have hs : 4 ≤ (n + 2) ^ 2 := Nat.pow_le_pow_left (show 2 ≤ n + 2 by omega) 2 + have hm := Nat.mul_le_mul_right (n + 2) hs + have hp : 4 * (n + 2) ≤ (n + 2) ^ 3 := by simpa only [Nat.pow_succ] using hm + constructor + · apply fits_of_lt_pow + have he : n + 1 + 1 = n + 2 := by omega + rw [he] + omega + · apply fits_of_lt_pow + have he : n + 1 + 1 = n + 2 := by omega + rw [he] + omega + +/-- Execute the complete finite native program on the standard input convention. The bound +includes the final halt and every setup/address/control instruction. Input fit and the explicit +`4n+1` layout fit are independent requirements. Empty and singleton inputs need no exceptions. +The output words enumerate exactly the input set and each stored count is its full multiplicity. -/ +theorem input_exec {w : ℕ} (xs : List ℕ) (hinput : InputFits w xs) + (hfit : Fits w (1 + 4 * xs.length)) (h2 : Fits w 2) : + ∃ (y : Data 16) (rs : List (ℕ × ℕ)) (steps : ℕ), + Exec w inputCode (Data.init xs) y steps ∧ + Executes w program (State.init xs) rs.length (steps + 1) ∧ + steps + 1 ≤ deduplicateTime xs.length w + 6 ∧ + Block.HoldsList (1 + 2 * xs.length) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * xs.length) (rs.map Prod.snd) y.mem ∧ + (rs.map Prod.fst).Nodup ∧ + (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ xs) ∧ + (∀ p ∈ rs, 0 < p.2 ∧ xs.count p.1 = p.2) ∧ + Modifies (Block.mk 1 (4 * xs.length)).footprint (Data.init xs : Data 16).mem y.mem := by + let x := Data.init xs (r := 16) + have h1 := h2.of_le (show 1 ≤ 2 by omega) + have had : Fits w (1 + xs.length) := hfit.of_le (by omega) + let u := ((((x.set addr 0).set len xs.length).set src 1).set dst (1 + xs.length)).set bits w + have hi : ExecWithin w + (ram[ addr ← #0; len ← *addr; src ← #1; dst ← src + len; bits ← width% ]) x u 5 := by + ram_eval [u, x, Data.init_mem_zero, h1, had] + have hn : u.regs len = xs.length := by simp +decide [u] + have hs : u.regs src = 1 := by simp +decide [u] + have hd : u.regs dst = 1 + xs.length := by simp +decide [u] + have hb : u.regs bits = w := by simp [u] + have hm : Block.HoldsList 1 xs u.mem := by + intro i + change (Data.init xs : Data 16).mem (1 + i.val) = xs[i.val] + rw [Nat.add_comm, Data.init_mem_succ] + simp only [List.getD, List.getElem?_eq_getElem i.isLt, Option.getD_some] + obtain ⟨y, rs, he, hyout, hyrk, hyrc, hnodup, hset, hcount, hframe⟩ := + deduplicate_exec xs u hn hs hd hb hm hinput.2 hinput.2 hfit (fits_self w) h2 + obtain ⟨steps, hsteps, hexec⟩ := hi.seq he + refine ⟨y, rs, steps, hexec, ?_, by omega, hyrk, hyrc, hnodup, hset, hcount, hframe⟩ + simpa only [program, inputCode, hyout, State.init, x] using hexec.executes Deduplicate.out + +end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean b/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean new file mode 100755 index 0000000000..eed159b431 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean @@ -0,0 +1,135 @@ +/- +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.RadixSort.Program +public import Complexity.Algorithms.WordRAM.Deduplicate.Program +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Native radix sorting followed by unique-key/multiplicity array construction + +This composes the complete native sorter and adjacent-run builder. Two length-`n` sorting +buffers are followed by length-`n` key and count output buffers. All four blocks lie in one +explicit frame. The returned keys are in unsigned encoding order; decoding signed keys does +not turn that order into signed integer order. + +## Main definitions + +* `deduplicateCode`: fixed 16-register sorting and multiplicity program. +* `deduplicateTime`: complete structured instruction bound. + +## Main results + +* `deduplicate_exec`: exact set enumeration and full multiplicities, with no sorting premise. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.RadixSort + +open Cslib.WordRAM + +/-- Original base retained while radix sorting swaps its two pointers. -/ +abbrev origin : Fin 16 := 14 + +/-- Install the two output pointers after the two sorting buffers. -/ +def outputLayout : Code .standard 16 := ram[ + Deduplicate.keys ← len + len; Deduplicate.keys ← Deduplicate.keys + origin; + Deduplicate.counts ← Deduplicate.keys + len ] + +/-- Complete sorting and deduplication; `Deduplicate.out` returns the number of unique words. -/ +def deduplicateCode : Code .standard 16 := + .seq (ram[ origin ← src ]) (.seq code (.seq outputLayout Deduplicate.code)) + +/-- All instructions, including pointer construction and both complete algorithms, excluding +only the final halt. For positive `n` and key-bit bound this is `O(n * keyBits)`. -/ +def deduplicateTime (n keyBits : ℕ) : ℕ := keyBits * (22 * n + 15) + 29 * n + 14 + +/-- Complete fixed-code radix sorting and adjacent deduplication, with full multiplicities. +Keys below `2^keyBits` may repeat arbitrarily. Only the four length-`n` buffers change. The +conditions constrain arithmetic/address fit, not sorting or deduplication behavior. -/ +theorem deduplicate_exec {w base keyBits : ℕ} (xs : List ℕ) (x : Data 16) + (hn : x.regs len = xs.length) (hs : x.regs src = base) + (hd : x.regs dst = base + xs.length) (hb : x.regs bits = keyBits) + (hmem : Block.HoldsList base xs x.mem) + (hkeys : ∀ v ∈ xs, v < 2 ^ keyBits) (hwords : ∀ v ∈ xs, Fits w v) + (hfit : Fits w (base + 4 * xs.length)) (hbits : Fits w keyBits) (h2 : Fits w 2) : + ∃ (y : Data 16) (rs : List (ℕ × ℕ)), + ExecWithin w deduplicateCode x y (deduplicateTime xs.length keyBits) ∧ + y.regs Deduplicate.out = rs.length ∧ + Block.HoldsList (base + 2 * xs.length) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (base + 3 * xs.length) (rs.map Prod.snd) y.mem ∧ + (rs.map Prod.fst).Nodup ∧ + (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ xs) ∧ + (∀ p ∈ rs, 0 < p.2 ∧ xs.count p.1 = p.2) ∧ + Modifies (Block.mk base (4 * xs.length)).footprint x.mem y.mem := by + let x₀ := x.set origin base + have hinit : ExecWithin (B := .standard) w (ram[ origin ← src ]) x x₀ 1 := by ram_eval [x₀, hs] + obtain ⟨u, hu, hum, hp, hsorted, hptr, huf⟩ := exec xs x₀ + (by simpa +decide [x₀] using hn) (by simpa +decide [x₀] using hs) + (by simpa +decide [x₀] using hd) (by simpa +decide [x₀] using hb) + hmem hkeys hwords (hfit.of_le (by omega)) hbits h2 + have hun : u.regs len = xs.length := by + simpa +decide [x₀] using (hu.regs_eq (i := len) (by decide)).trans + (show x₀.regs len = xs.length by simpa +decide [x₀] using hn) + have huo : u.regs origin = base := by + simpa only [x₀, Data.set_regs, Function.update_self] using hu.regs_eq (i := origin) (by decide) + have htwice : Fits w (u.regs len + u.regs len) := hfit.of_le (by rw [hun]; omega) + have hkeyaddr : Fits w (u.regs len + u.regs len + base) := hfit.of_le (by rw [hun]; omega) + have hcntaddr : Fits w (u.regs len + u.regs len + base + u.regs len) := + hfit.of_le (by rw [hun]; omega) + let v := ((u.set Deduplicate.keys (u.regs len + u.regs len)).set + Deduplicate.keys (u.regs len + u.regs len + base)).set + Deduplicate.counts (u.regs len + u.regs len + base + u.regs len) + have hl : ExecWithin w outputLayout u v 3 := by + ram_eval [v, outputLayout, huo, htwice, hkeyaddr, hcntaddr] + have hvl : v.regs Deduplicate.len = (passes keyBits xs).length := by + change u.regs len = (passes keyBits xs).length + rw [hun, hp.length_eq] + have hvs : v.regs Deduplicate.src = u.regs src := rfl + have hvk : v.regs Deduplicate.keys = base + 2 * xs.length := by + change u.regs len + u.regs len + base = base + 2 * xs.length + omega + have hvc : v.regs Deduplicate.counts = base + 3 * xs.length := by + change u.regs len + u.regs len + base + u.regs len = base + 3 * xs.length + omega + have hlen : (passes keyBits xs).length = xs.length := hp.length_eq + obtain ⟨y, rs, hy, hrs, hyout, hyrk, hyrc, _, hyf⟩ := Deduplicate.exec (passes keyBits xs) v + hvl hvs hvk (by rw [hvc, hlen]; omega) hum hsorted + (by rw [hlen]; rcases hptr with hptr | hptr <;> omega) + (by rw [hlen]; exact hfit.of_le (by omega)) (h2.of_le (by omega)) + have heq : base + 2 * xs.length + xs.length = base + 3 * xs.length := by omega + refine ⟨y, rs, ?_, hyout, hyrk, by simpa only [hlen, heq] using hyrc, + hrs.keys_nodup, ?_, ?_, ?_⟩ + · have hall := hinit.seq (hu.seq (hl.seq hy)) + apply hall.mono + simp only [deduplicateTime, timeBound, hlen] + omega + · intro k + exact (hrs.mem_iff k).trans hp.mem_iff + · intro p hp' + exact ⟨hrs.positive p hp', (hp.count_eq p.1).symm.trans (hrs.multiplicity hp')⟩ + · intro a hnot + have hnotSort : a ∉ (Block.mk base (2 * xs.length)).footprint := by + intro ha + apply hnot + rw [Block.mem_footprint] at ha ⊢ + dsimp only [Block.Owns] at ha ⊢ + omega + have hnotOut : a ∉ (Block.mk (base + 2 * xs.length) + (2 * (passes keyBits xs).length)).footprint := by + intro ha + apply hnot + rw [Block.mem_footprint] at ha ⊢ + dsimp only [Block.Owns] at ha ⊢ + rw [hlen] at ha + omega + exact (hyf a hnotOut).trans (huf a hnotSort) + +end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Model.lean b/Complexity/Algorithms/WordRAM/RadixSort/Model.lean new file mode 100755 index 0000000000..c92aeef96c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/RadixSort/Model.lean @@ -0,0 +1,134 @@ +/- +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.Data.List.Sort + +/-! +# Stable binary radix sorting: mathematical specification + +The order is the unsigned order on encoded words. In particular this does not claim that +sign-and-magnitude integer encodings are ordered as integers. The construction and proofs here +are original; the executable native program is supplied separately. + +## Main definitions + +* `digit`: one binary digit, represented as zero or one. +* `select`: the stable subsequence with a specified digit. +* `partition`: zeros followed by ones, preserving the order within each part. +* `passes`: least-significant-digit radix passes. + +## Main results + +* `partition_perm`: a pass preserves the multiset. +* `passes_correct`: sufficiently many passes give a sorted permutation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.RadixSort + +/-- Binary digit `b` of unsigned word `v`. -/ +def digit (b v : ℕ) : ℕ := v / 2 ^ b % 2 + +/-- The stable subsequence of words whose digit `b` equals `t`. -/ +def select (b t : ℕ) (xs : List ℕ) : List ℕ := + xs.filter (fun v => decide (digit b v = t)) + +/-- Stable zero/one partition at digit `b`. -/ +def partition (b : ℕ) (xs : List ℕ) : List ℕ := select b 0 xs ++ select b 1 xs + +/-- Apply the first `bits` binary radix passes, starting with the least significant bit. -/ +def passes : ℕ → List ℕ → List ℕ + | 0, xs => xs + | b + 1, xs => partition b (passes b xs) + +/-- Every binary digit is below two. -/ +theorem digit_lt_two (b v : ℕ) : digit b v < 2 := Nat.mod_lt _ (by decide) + +/-- The native shift/remainder implementation reads precisely `digit`. -/ +theorem digit_eq_shift (b v : ℕ) : digit b v = (v >>> b) % 2 := by + rw [Nat.shiftRight_eq_div_pow, digit] + +/-- A selection never increases the length. -/ +theorem length_select_le (b t : ℕ) (xs : List ℕ) : (select b t xs).length ≤ xs.length := + List.length_filter_le _ _ + +/-- A selected word occurred in the original list and has the requested digit. -/ +theorem mem_select {b t v : ℕ} {xs : List ℕ} : + v ∈ select b t xs ↔ v ∈ xs ∧ digit b v = t := by + simp [select] + +/-- Appending an input word appends it to precisely its selected subsequence. -/ +theorem select_append (b t : ℕ) (xs ys : List ℕ) : + select b t (xs ++ ys) = select b t xs ++ select b t ys := by + simp [select] + +/-- A stable bit partition is a permutation of the input, including repeated words. -/ +theorem partition_perm (b : ℕ) (xs : List ℕ) : (partition b xs).Perm xs := by + have h (v : ℕ) : decide (digit b v = 1) = !decide (digit b v = 0) := by + have := digit_lt_two b v + by_cases hz : digit b v = 0 + · simp [hz] + · have ho : digit b v = 1 := by omega + simp [ho] + simpa only [partition, select, h] using + List.filter_append_perm (fun v => decide (digit b v = 0)) xs + +/-- Each pass keeps exactly the same number of words. -/ +theorem length_partition (b : ℕ) (xs : List ℕ) : (partition b xs).length = xs.length := + (partition_perm b xs).length_eq + +/-- All radix passes preserve the input multiset. -/ +theorem passes_perm (bits : ℕ) (xs : List ℕ) : (passes bits xs).Perm xs := by + induction bits with + | zero => exact .refl _ + | succ b ih => exact (partition_perm b _).trans ih + +/-- A pass extends sortedness modulo `2^b` to sortedness modulo `2^(b+1)`. -/ +theorem partition_pairwise {b : ℕ} {xs : List ℕ} + (hs : xs.Pairwise (fun u v => u % 2 ^ b ≤ v % 2 ^ b)) : + (partition b xs).Pairwise (fun u v => u % 2 ^ (b + 1) ≤ v % 2 ^ (b + 1)) := by + have he (v : ℕ) : v % 2 ^ (b + 1) = v % 2 ^ b + 2 ^ b * digit b v := by + rw [Nat.pow_succ, Nat.mod_mul, digit] + have hp (t : ℕ) : + (select b t xs).Pairwise (fun u v => u % 2 ^ (b + 1) ≤ v % 2 ^ (b + 1)) := by + apply (hs.filter _).imp_of_mem + intro u v hu hv huv + change u ∈ select b t xs at hu + change v ∈ select b t xs at hv + rw [mem_select] at hu hv + rw [he, he, hu.2, hv.2] + omega + apply List.pairwise_append.mpr + refine ⟨hp 0, hp 1, ?_⟩ + intro u hu v hv + rw [mem_select] at hu hv + rw [he, he, hu.2, hv.2] + have hmod := Nat.mod_lt u (Nat.two_pow_pos b) + omega + +/-- The output is sorted on the low `bits` bits. -/ +theorem passes_pairwise (bits : ℕ) (xs : List ℕ) : + (passes bits xs).Pairwise (fun u v => u % 2 ^ bits ≤ v % 2 ^ bits) := by + induction bits with + | zero => simp [passes, List.pairwise_iff_getElem, Nat.mod_one] + | succ b ih => exact partition_pairwise ih + +/-- Keys strictly below `2^bits` are sorted in unsigned order after `bits` passes. -/ +theorem passes_correct (bits : ℕ) (xs : List ℕ) + (hbound : ∀ v ∈ xs, v < 2 ^ bits) : + (passes bits xs).Perm xs ∧ (passes bits xs).Pairwise (· ≤ ·) := by + refine ⟨passes_perm bits xs, (passes_pairwise bits xs).imp_of_mem ?_⟩ + intro u v hu hv huv + have hu' := hbound u ((passes_perm bits xs).mem_iff.mp hu) + have hv' := hbound v ((passes_perm bits xs).mem_iff.mp hv) + simpa only [Nat.mod_eq_of_lt hu', Nat.mod_eq_of_lt hv'] using huv + +end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Program.lean b/Complexity/Algorithms/WordRAM/RadixSort/Program.lean new file mode 100755 index 0000000000..a4d8a6f851 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/RadixSort/Program.lean @@ -0,0 +1,204 @@ +/- +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.RadixSort.Scan +public import Complexity.Computability.WordRAM.Compile + +/-! +# Fixed-code native unsigned radix sorter + +Two stable scans per bit partition the source into zeros followed by ones in scratch. Swapping +the pointers avoids a copy-back scan. Code and registers are independent of length and key width. +The source pointer on exit identifies the sorted array; the other block is scratch. + +## Main definitions + +* `pass`: two digit scans and pointer swap. +* `code`: the complete radix sorter with a register-supplied bit bound. +* `timeBound`: explicit instruction bound, excluding halt. + +## Main results + +* `exec`: complete charged sorting, with permutation, sortedness and a two-array frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.RadixSort + +open Cslib.WordRAM + +/-- Exchange source and scratch, then advance to the next bit. -/ +def advance : Code .standard 16 := ram[ + swap ← src; src ← dst; dst ← swap; bit ← bit + one ] + +/-- Exactly two stable scans per bit. -/ +def pass : Code .standard 16 := + .seq (ram[ out ← #0; tag ← #0 ]) + (ram[ ⟪Scan.code⟫; tag ← #1; ⟪Scan.code⟫; ⟪advance⟫ ]) + +/-- Complete unsigned radix sorter. Parameters are length, two disjoint array bases, and bits. +The returned source register identifies the sorted block. -/ +def code : Code .standard 16 := + .seq (ram[ one ← #1; two ← #2; bit ← #0 ]) (.loop (.lt bit bits) pass) + +/-- All instructions of radix sorting, excluding a final halt. -/ +def timeBound (n keyBits : ℕ) : ℕ := keyBits * (22 * n + 15) + 5 + +private theorem pass_exec {w b s d : ℕ} (xs : List ℕ) (x : Data 16) + (hn : x.regs len = xs.length) (hs : x.regs src = s) (hd : x.regs dst = d) + (hb : x.regs bit = b) (ho : x.regs one = 1) (h2 : x.regs two = 2) + (hmem : Block.HoldsList s xs x.mem) (hwords : ∀ v ∈ xs, Fits w v) + (hdisj : s + xs.length ≤ d ∨ d + xs.length ≤ s) + (hsfit : Fits w (s + xs.length)) (hdfit : Fits w (d + xs.length)) + (hbit : Fits w (b + 1)) (h1 : Fits w 1) : + ∃ y, ExecWithin w pass x y (22 * xs.length + 13) ∧ + Block.HoldsList d (partition b xs) y.mem ∧ + y.regs src = d ∧ y.regs dst = s ∧ y.regs bit = b + 1 ∧ + Modifies (Block.mk d xs.length).footprint x.mem y.mem := by + let x₀ := (x.set out 0).set tag 0 + have hinit : ExecWithin (B := .standard) w (ram[ out ← #0; tag ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + obtain ⟨y, hy, hym, hysrc, hyout, hyf⟩ := Scan.exec (t := 0) xs [] x₀ + (by simpa +decide [x₀] using hn) (by simpa +decide [x₀] using hs) + (by simpa +decide [x₀] using hd) (by simpa +decide [x₀] using hb) + (by simp [x₀]) (by simpa +decide [x₀] using ho) + (by simpa +decide [x₀] using h2) (by simp +decide [x₀]) + hmem (Block.holdsList_nil _ _) hwords + (by simpa using length_select_le b 0 xs) hdisj hsfit hdfit + have hyr (j : Fin 16) (hj : Scan.code.Preserves j) : y.regs j = x₀.regs j := + hy.regs_eq hj + let y₀ := y.set tag 1 + have hn₀ : y₀.regs len = xs.length := by + simpa +decide [y₀, hyr len (by decide), x₀] using hn + have hs₀ : y₀.regs src = s := by + simpa +decide [y₀, hyr src (by decide), x₀] using hs + have hd₀ : y₀.regs dst = d := by + simpa +decide [y₀, hyr dst (by decide), x₀] using hd + have hb₀ : y₀.regs bit = b := by + simpa +decide [y₀, hyr bit (by decide), x₀] using hb + have ho₀ : y₀.regs one = 1 := by + simpa +decide [y₀, hyr one (by decide), x₀] using ho + have h2₀ : y₀.regs two = 2 := by + simpa +decide [y₀, hyr two (by decide), x₀] using h2 + obtain ⟨z, hz, hzm, _, _, hzf⟩ := Scan.exec (t := 1) xs (select b 0 xs) y₀ + hn₀ hs₀ hd₀ hb₀ (by simp [y₀]) ho₀ h2₀ + (by simpa +decide [y₀] using hyout) hysrc + (by simpa only [y₀, Data.set_mem, List.nil_append] using hym) hwords + (by rw [← partition, length_partition]) hdisj hsfit hdfit + have hzs : z.regs src = s := (hz.regs_eq (by decide)).trans hs₀ + have hzd : z.regs dst = d := (hz.regs_eq (by decide)).trans hd₀ + have hzb : z.regs bit = b := (hz.regs_eq (by decide)).trans hb₀ + have hzo : z.regs one = 1 := (hz.regs_eq (by decide)).trans ho₀ + refine ⟨?u, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => + have ha : ExecWithin w advance z ?u 4 := by + ram_eval [advance, hzs, hzd, hzb, hzo, hbit] + have hall := hinit.seq (hy.seq ((ExecWithin.const (d := tag) (x := y) h1).seq (hz.seq ha))) + exact hall.mono (by omega) + · exact hzm + · simp +decide [Data.set_regs] + · simp +decide [Data.set_regs] + · simp +decide [Data.set_regs] + · intro a hnot + exact (hzf a hnot).trans (hyf a hnot) + +/-- Sort all keys below `2^keyBits` in `keyBits * (22n+15)+5` instructions. The two arrays +have length `n`, are adjacent, and start at caller-supplied `base`. Only these `2n` cells may +change. The sorted block is returned in `src`; it is one of the two original blocks. This +includes empty lists and zero bit bounds. The key-bit bound and both one-past-end addresses +must fit; constant two must fit. No ordering contract is assumed. -/ +theorem exec {w base keyBits : ℕ} (xs : List ℕ) (x : Data 16) + (hn : x.regs len = xs.length) (hs : x.regs src = base) + (hd : x.regs dst = base + xs.length) (hb : x.regs bits = keyBits) + (hmem : Block.HoldsList base xs x.mem) + (hkeys : ∀ v ∈ xs, v < 2 ^ keyBits) + (hwords : ∀ v ∈ xs, Fits w v) + (hfit : Fits w (base + 2 * xs.length)) (hbits : Fits w keyBits) (h2 : Fits w 2) : + ∃ y, ExecWithin w code x y (timeBound xs.length keyBits) ∧ + Block.HoldsList (y.regs src) (passes keyBits xs) y.mem ∧ + (passes keyBits xs).Perm xs ∧ (passes keyBits xs).Pairwise (· ≤ ·) ∧ + (y.regs src = base ∨ y.regs src = base + xs.length) ∧ + Modifies (Block.mk base (2 * xs.length)).footprint x.mem y.mem := by + let n := xs.length + let I (y : Data 16) : Prop := + y.regs len = n ∧ y.regs bits = keyBits ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs bit ≤ keyBits ∧ + ((y.regs src = base ∧ y.regs dst = base + n) ∨ + (y.regs src = base + n ∧ y.regs dst = base)) ∧ + Block.HoldsList (y.regs src) (passes (y.regs bit) xs) y.mem ∧ + Modifies (Block.mk base (2 * n)).footprint x.mem y.mem + have hbody (y : Data 16) (hy : I y) (hc : (Cond.lt bit bits).Holds y) : + ∃ z, ExecWithin w pass y z (22 * n + 13) ∧ I z ∧ + keyBits - z.regs bit < keyBits - y.regs bit := by + obtain ⟨hyn, hyB, hyo, hy2, hyb, hyp, hym, hyf⟩ := hy + have hlt : y.regs bit < keyBits := by simpa only [Cond.holds_lt, hyB] using hc + have hl : (passes (y.regs bit) xs).length = n := (passes_perm _ _).length_eq + have hdis : y.regs src + n ≤ y.regs dst ∨ y.regs dst + n ≤ y.regs src := by + rcases hyp with ⟨ha, hb⟩ | ⟨ha, hb⟩ <;> omega + have hsf : Fits w (y.regs src + n) := hfit.of_le (by + rcases hyp with ⟨ha, hb⟩ | ⟨ha, hb⟩ <;> omega) + have hdf : Fits w (y.regs dst + n) := hfit.of_le (by + rcases hyp with ⟨ha, hb⟩ | ⟨ha, hb⟩ <;> omega) + have hwds : ∀ v ∈ passes (y.regs bit) xs, Fits w v := fun v hv => + hwords v ((passes_perm _ _).mem_iff.mp hv) + obtain ⟨z, he, hzm, hzs, hzd, hzb, hzf⟩ := pass_exec (passes (y.regs bit) xs) y + (hyn.trans hl.symm) rfl rfl rfl hyo hy2 hym hwds + (by simpa only [hl] using hdis) (by simpa only [hl] using hsf) + (by simpa only [hl] using hdf) (hbits.of_le (by omega)) (h2.of_le (by omega)) + refine ⟨z, by simpa only [hl] using he, ?_, by omega⟩ + refine ⟨(he.regs_eq (by decide)).trans hyn, (he.regs_eq (by decide)).trans hyB, + (he.regs_eq (by decide)).trans hyo, (he.regs_eq (by decide)).trans hy2, + by omega, ?_, ?_, ?_⟩ + · rw [hzs, hzd] + rcases hyp with ⟨ha, hb⟩ | ⟨ha, hb⟩ + · exact Or.inr ⟨hb, ha⟩ + · exact Or.inl ⟨hb, ha⟩ + · rw [hzs, hzb, passes] + exact hzm + · intro a hnot + have hnot' : a ∉ (Block.mk (y.regs dst) (passes (y.regs bit) xs).length).footprint := by + intro ha + apply hnot + rw [Block.mem_footprint] at ha ⊢ + change y.regs dst ≤ a ∧ + a < y.regs dst + (passes (y.regs bit) xs).length at ha + change base ≤ a ∧ a < base + 2 * n + rw [hl] at ha + rcases hyp with ⟨ha', hb'⟩ | ⟨ha', hb'⟩ <;> omega + exact (hzf a hnot').trans (hyf a hnot) + let x₀ := ((x.set one 1).set two 2).set bit 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, Modifies.refl _ _⟩ + · simpa +decide [x₀] using hn + · simpa +decide [x₀] using hb + · simp +decide [x₀] + · simp +decide [x₀] + · simp [x₀] + · left; simpa +decide [x₀, n] using And.intro hs hd + · simpa +decide [x₀, passes, hs] using hmem + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => keyBits - y.regs bit) hbody hx₀ + obtain ⟨hyn, hyB, hyo, hy2, hyb, hyp, hym, hyf⟩ := hy + have hend : y.regs bit = keyBits := by + simp only [Cond.holds_lt, hyB] at hc + omega + have hinit : ExecWithin (B := .standard) w (ram[ one ← #1; two ← #2; bit ← #0 ]) x x₀ 3 := by + ram_eval [x₀, h2, h2.of_le (show 1 ≤ 2 by omega)] + refine ⟨y, ?_, by simpa only [hend] using hym, + (passes_correct keyBits xs hkeys).1, (passes_correct keyBits xs hkeys).2, + hyp.imp And.left And.left, hyf⟩ + have hall := hinit.seq he + convert hall using 1 + · rfl + · simp +decide only [timeBound, n, x₀, Data.set_regs, Function.update_apply, + ↓reduceIte, Nat.sub_zero, Nat.add_assoc, Nat.reduceAdd] + omega + +end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Scan.lean b/Complexity/Algorithms/WordRAM/RadixSort/Scan.lean new file mode 100755 index 0000000000..8ebb617df9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/RadixSort/Scan.lean @@ -0,0 +1,254 @@ +/- +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.RadixSort.Model +public import Complexity.Computability.WordRAM.ListArray + +/-! +# Native stable digit-selection scan + +The scan appends a stable digit-selected subsequence to a disjoint destination array. Both +orders of the two disjoint blocks are allowed, so consecutive radix passes can swap pointers. +All loads, address calculations, stores and loop control are native instructions. + +## Main definitions + +* `Scan.body`: read, test, conditionally append, and advance. +* `Scan.code`: reset the input cursor and scan the array. + +## Main results + +* `Scan.exec`: stable selection with a linear instruction bound and memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.RadixSort + +open Cslib.WordRAM + +/-- Array length. -/ +abbrev len : Fin 16 := 0 +/-- Source pointer; the sorter returns the final sorted pointer here. -/ +abbrev src : Fin 16 := 1 +/-- Scratch pointer. -/ +abbrev dst : Fin 16 := 2 +/-- Current bit index. -/ +abbrev bit : Fin 16 := 3 +/-- Digit selected by the current scan. -/ +abbrev tag : Fin 16 := 4 +/-- Constant one. -/ +abbrev one : Fin 16 := 5 +/-- Constant two. -/ +abbrev two : Fin 16 := 6 +/-- Input cursor. -/ +abbrev idx : Fin 16 := 7 +/-- Output cursor. -/ +abbrev out : Fin 16 := 8 +/-- Address temporary. -/ +abbrev addr : Fin 16 := 9 +/-- Loaded key. -/ +abbrev value : Fin 16 := 10 +/-- Current key digit. -/ +abbrev keyDigit : Fin 16 := 11 +/-- Number of key bits, supplied by the caller. -/ +abbrev bits : Fin 16 := 12 +/-- Temporary used to swap the two pointers. -/ +abbrev swap : Fin 16 := 13 + +namespace Scan + +/-- One stable-selection iteration, with every address and counter operation charged. -/ +def body : Code .standard 16 := ram[ + addr ← src + idx; value ← *addr; + keyDigit ← value >>> bit; keyDigit ← keyDigit % two; + if keyDigit = tag then [ addr ← dst + out; *addr ← value; out ← out + one ] + else [ addr ← addr ]; + idx ← idx + one ] + +/-- Reset the input cursor, then perform one stable-selection scan. -/ +def code : Code .standard 16 := ram[ idx ← #0; while idx < len do [ ⟪body⟫ ] ] + +private theorem preserves_low (j : Fin 16) (hj : j.val ≤ 6) : body.Preserves j := by + have hne (k : Fin 16) (hk : 7 ≤ k.val) : (some k : Option (Fin 16)) ≠ some j := by + intro h + have hv := congrArg Fin.val (Option.some.inj h) + omega + change (some addr ≠ some j) ∧ (some value ≠ some j) ∧ + (some keyDigit ≠ some j) ∧ (some keyDigit ≠ some j) ∧ + (((some addr ≠ some j) ∧ (none ≠ some j) ∧ (some out ≠ some j)) ∧ + (some addr ≠ some j)) ∧ (some idx ≠ some j) + have hnone : (none : Option (Fin 16)) ≠ some j := by intro h; cases h + exact ⟨hne addr (by decide), hne value (by decide), hne keyDigit (by decide), + hne keyDigit (by decide), + ⟨⟨hne addr (by decide), hnone, hne out (by decide)⟩, + hne addr (by decide)⟩, + hne idx (by decide)⟩ + +private theorem step {w : ℕ} (x : Data 16) + (ho : x.regs one = 1) (ht : x.regs two = 2) + (hs : Fits w (x.regs src + x.regs idx)) + (hd : Fits w (x.regs dst + x.regs out)) + (hi : Fits w (x.regs idx + 1)) + (hu : digit (x.regs bit) (x.mem (x.regs src + x.regs idx)) = x.regs tag → + Fits w (x.regs out + 1)) + (hv : Fits w (x.mem (x.regs src + x.regs idx))) : + ∃ y, ExecWithin w body x y 9 ∧ + y.regs idx = x.regs idx + 1 ∧ + y.regs out = x.regs out + + (if digit (x.regs bit) (x.mem (x.regs src + x.regs idx)) = x.regs tag then 1 else 0) ∧ + y.mem = (if digit (x.regs bit) (x.mem (x.regs src + x.regs idx)) = x.regs tag then + Function.update x.mem (x.regs dst + x.regs out) (x.mem (x.regs src + x.regs idx)) + else x.mem) := by + have hshift := hv.of_le (Nat.shiftRight_le _ (x.regs bit)) + have hmod := hshift.of_le (Nat.mod_le _ 2) + by_cases h : digit (x.regs bit) (x.mem (x.regs src + x.regs idx)) = x.regs tag + · have heq : (x.mem (x.regs src + x.regs idx) >>> x.regs bit) % 2 = x.regs tag := + (digit_eq_shift _ _).symm.trans h + have hinc := hu h + have htagfit : Fits w (x.regs tag) := heq ▸ hmod + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [body, ho, ht, hs, hd, hi, hinc, hshift, hmod, heq, htagfit] + all_goals simp +decide [Data.set_regs, h] + · have hne : (x.mem (x.regs src + x.regs idx) >>> x.regs bit) % 2 ≠ x.regs tag := by + simpa only [← digit_eq_shift] using h + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [body, ho, ht, hs, hd, hi, hshift, hmod, hne] + all_goals simp +decide [Data.set_regs, h] + +/-- Append a stable digit-selected subsequence in `11n+3` instructions. The destination has +capacity `n`; source and destination blocks are disjoint, in either order. The caller's existing +destination prefix is retained. Only destination cells may change. -/ +theorem exec {w s d b t : ℕ} (xs acc : List ℕ) (x : Data 16) + (hn : x.regs len = xs.length) (hs : x.regs src = s) (hd : x.regs dst = d) + (hb : x.regs bit = b) (ht : x.regs tag = t) + (ho : x.regs one = 1) (h2 : x.regs two = 2) (hout : x.regs out = acc.length) + (hsrc : Block.HoldsList s xs x.mem) (hacc : Block.HoldsList d acc x.mem) + (hwords : ∀ v ∈ xs, Fits w v) + (hcap : (acc ++ select b t xs).length ≤ xs.length) + (hdisj : s + xs.length ≤ d ∨ d + xs.length ≤ s) + (hsfit : Fits w (s + xs.length)) (hdfit : Fits w (d + xs.length)) : + ∃ y, ExecWithin w code x y (11 * xs.length + 3) ∧ + Block.HoldsList d (acc ++ select b t xs) y.mem ∧ + Block.HoldsList s xs y.mem ∧ + y.regs out = (acc ++ select b t xs).length ∧ + Modifies (Block.mk d xs.length).footprint x.mem y.mem := by + let selected (i : ℕ) := acc ++ select b t (xs.take i) + have hselected (i : ℕ) : (selected i).length ≤ xs.length := by + have hsub : (select b t (xs.take i)).Sublist (select b t xs) := + (List.take_sublist i xs).filter _ + simp only [selected, List.length_append] at hcap ⊢ + have := hsub.length_le + omega + let I (y : Data 16) : Prop := + (∀ j : Fin 16, j.val ≤ 6 → y.regs j = x.regs j) ∧ + y.regs idx ≤ xs.length ∧ y.regs out = (selected (y.regs idx)).length ∧ + Block.HoldsList s xs y.mem ∧ Block.HoldsList d (selected (y.regs idx)) y.mem ∧ + Modifies (Block.mk d xs.length).footprint x.mem y.mem + have hbody (y : Data 16) (hy : I y) (hc : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 9 ∧ I z ∧ + xs.length - z.regs idx < xs.length - y.regs idx := by + obtain ⟨hr, hle, hu, hm, ha, hf⟩ := hy + have hyl : y.regs len = xs.length := (hr len (by decide)).trans hn + have hys : y.regs src = s := (hr src (by decide)).trans hs + have hyd : y.regs dst = d := (hr dst (by decide)).trans hd + have hyb : y.regs bit = b := (hr bit (by decide)).trans hb + have hyt : y.regs tag = t := (hr tag (by decide)).trans ht + have hyo : y.regs one = 1 := (hr one (by decide)).trans ho + have hy2 : y.regs two = 2 := (hr two (by decide)).trans h2 + have hi : y.regs idx < xs.length := by simpa only [Cond.holds_lt, hyl] using hc + have hread : y.mem (y.regs src + y.regs idx) = xs[y.regs idx] := by + rw [hys]; exact hm.get _ hi + have hv : Fits w (y.mem (y.regs src + y.regs idx)) := by + rw [hread]; exact hwords _ (List.getElem_mem hi) + have hsa : Fits w (y.regs src + y.regs idx) := by + rw [hys]; exact hsfit.of_le (by omega) + have hda : Fits w (y.regs dst + y.regs out) := by + rw [hyd, hu]; exact hdfit.of_le (by have := hselected (y.regs idx); omega) + have hic : Fits w (y.regs idx + 1) := hsfit.of_le (by omega) + have hsuc : selected (y.regs idx + 1) = selected (y.regs idx) ++ + (if digit b xs[y.regs idx] = t then [xs[y.regs idx]] else []) := by + dsimp only [selected] + rw [List.take_succ_eq_append_getElem hi, select_append] + simp only [select, List.filter_cons, List.filter_nil, decide_eq_true_eq] + split_ifs <;> simp only [List.append_assoc, List.append_nil] + by_cases heq : digit b xs[y.regs idx] = t + · have hlt : y.regs out < xs.length := by + have hlim := hselected (y.regs idx + 1) + rw [hsuc, ite_eq_left heq, List.length_append, List.length_singleton] at hlim + omega + obtain ⟨z, he, hzi, hzu, hzm⟩ := step y hyo hy2 hsa hda hic + (fun _ => hdfit.of_le (by omega)) hv + rw [hyb, hread, hyt, ite_eq_left heq] at hzu hzm + refine ⟨z, he, ?_, by omega⟩ + refine ⟨?_, by omega, ?_, ?_, ?_, ?_⟩ + · intro j hj + exact (he.regs_eq (preserves_low j hj)).trans (hr j hj) + · rw [hzi, hsuc, ite_eq_left heq, List.length_append, List.length_singleton] + omega + · apply hm.frame + intro a hlo hhi + rw [hzm, hyd, Function.update_of_ne (by rcases hdisj with h | h <;> omega)] + · rw [hzi, hsuc, ite_eq_left heq, hzm, hyd, hu] + exact ha.append_one _ + · intro a hnot + have hne : a ≠ d + y.regs out := by + intro heqa + apply hnot + apply Block.mem_footprint.mpr + change d ≤ a ∧ a < d + xs.length + omega + rw [hzm, hyd, Function.update_of_ne hne] + exact hf a hnot + · obtain ⟨z, he, hzi, hzu, hzm⟩ := step y hyo hy2 hsa hda hic + (by simpa only [hyb, hread, hyt] using fun h => (heq h).elim) hv + rw [hyb, hread, hyt, ite_eq_right heq] at hzu hzm + have hsel : selected (y.regs idx + 1) = selected (y.regs idx) := by + rw [hsuc, ite_eq_right heq, List.append_nil] + refine ⟨z, he, ?_, by omega⟩ + refine ⟨?_, by omega, ?_, ?_, ?_, ?_⟩ + · intro j hj + exact (he.regs_eq (preserves_low j hj)).trans (hr j hj) + · rw [hzi, hsel, hzu, Nat.add_zero, hu] + · simpa only [hzm] using hm + · simpa only [hzi, hsel, hzm] using ha + · simpa only [hzm] using hf + let x₀ := x.set idx 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro j hj + have hne : j ≠ idx := by + intro h + have hv := congrArg Fin.val h + change j.val = 7 at hv + omega + exact Function.update_of_ne hne _ _ + · simp [x₀] + · simpa +decide [x₀, selected, select] using hout + · exact hsrc + · simpa [x₀, selected, select] using hacc + · exact Modifies.refl _ _ + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => xs.length - y.regs idx) hbody hx₀ + obtain ⟨hr, hle, hu, hm, ha, hf⟩ := hy + have hend : y.regs idx = xs.length := by + have hyl : y.regs len = xs.length := (hr len (by decide)).trans hn + simp only [Cond.holds_lt, hyl] at hc + omega + have hi := (ExecWithin.const (d := idx) (x := x) (fits_zero w)).seq he + refine ⟨y, ?_, ?_, hm, ?_, hf⟩ + · convert hi using 1 + · rfl + · simp only [x₀, Data.set_regs, Function.update_self, Nat.sub_zero] + omega + · simpa only [hend, selected, List.take_length] using ha + · simpa only [hend, selected, List.take_length] using hu + +end Scan +end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Computability/WordRAM/ListArray.lean b/Complexity/Computability/WordRAM/ListArray.lean new file mode 100755 index 0000000000..5d90770831 --- /dev/null +++ b/Complexity/Computability/WordRAM/ListArray.lean @@ -0,0 +1,94 @@ +/- +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.Heap + +/-! +# List-valued block assertions + +These are specializations of `Block.Realizes`, not an additional memory model. + +## Main definitions + +* `Block.HoldsList`: a contiguous block realizes a list. + +## Main results + +* `Block.HoldsList.append_one`: a store immediately after a list appends one element. +* `Block.HoldsList.frame`: writes outside the block preserve its list. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.Block + +/-- The block starting at `base` realizes exactly the entries of `xs`. -/ +abbrev HoldsList (base : ℕ) (xs : List ℕ) (mem : ℕ → ℕ) : Prop := + (Block.mk base xs.length).Realizes mem (fun i => xs[i]) + +/-- Read an entry of a represented list. -/ +theorem HoldsList.get {base : ℕ} {xs : List ℕ} {mem : ℕ → ℕ} + (h : HoldsList base xs mem) (i : ℕ) (hi : i < xs.length) : + mem (base + i) = xs[i] := h ⟨i, hi⟩ + +/-- An empty list imposes no memory constraints. -/ +theorem holdsList_nil (base : ℕ) (mem : ℕ → ℕ) : HoldsList base [] mem := by + intro i + exact Fin.elim0 i + +/-- The prefix of a represented list is represented in the same memory. -/ +theorem HoldsList.take {base : ℕ} {xs : List ℕ} {mem : ℕ → ℕ} + (h : HoldsList base xs mem) (k : ℕ) : HoldsList base (xs.take k) mem := by + intro i + have hi : i.val < xs.length := lt_of_lt_of_le i.isLt (List.take_sublist k xs).length_le + change mem (base + i.val) = (xs.take k)[i.val] + rw [List.getElem_take] + exact h.get i.val hi + +/-- A store immediately following a represented list appends an element. -/ +theorem HoldsList.append_one {base : ℕ} {xs : List ℕ} {mem : ℕ → ℕ} + (h : HoldsList base xs mem) (v : ℕ) : + HoldsList base (xs ++ [v]) (Function.update mem (base + xs.length) v) := by + intro i + change Function.update mem (base + xs.length) v (base + i.val) = (xs ++ [v])[i.val] + by_cases hi : i.val < xs.length + · rw [Function.update_of_ne (by omega), List.getElem_append_left hi] + exact h.get i.val hi + · have hil := i.isLt + simp only [List.length_append, List.length_singleton] at hil + have he : i.val = xs.length := by omega + simp [he] + +/-- Writes outside the represented block preserve the list. -/ +theorem HoldsList.frame {base : ℕ} {xs : List ℕ} {mem mem' : ℕ → ℕ} + (h : HoldsList base xs mem) + (heq : ∀ a, base ≤ a → a < base + xs.length → mem' a = mem a) : + HoldsList base xs mem' := by + intro i + have hi : i.val < xs.length := i.isLt + change mem' (base + i.val) = xs[i.val] + rw [heq _ (by omega) (by omega)] + exact h.get i.val i.isLt + +/-- Append two adjacent represented blocks. -/ +theorem HoldsList.append {base : ℕ} {xs ys : List ℕ} {mem : ℕ → ℕ} + (h : HoldsList base xs mem) (h' : HoldsList (base + xs.length) ys mem) : + HoldsList base (xs ++ ys) mem := by + intro i + change mem (base + i.val) = (xs ++ ys)[i.val] + by_cases hi : i.val < xs.length + · rw [List.getElem_append_left hi] + exact h.get i.val hi + · rw [List.getElem_append_right (by omega)] + have hil := i.isLt + simp only [List.length_append] at hil + have he : base + xs.length + (i.val - xs.length) = base + i.val := by omega + simpa only [he] using h'.get (i.val - xs.length) (by omega) + +end Cslib.WordRAM.Block diff --git a/ComplexityTests/WordRAMRadixSort.lean b/ComplexityTests/WordRAMRadixSort.lean new file mode 100755 index 0000000000..55ddb07ef7 --- /dev/null +++ b/ComplexityTests/WordRAMRadixSort.lean @@ -0,0 +1,67 @@ +import Complexity.Algorithms.WordRAM.RadixSort + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM + +private def observe (w fuel : ℕ) (xs : List ℕ) : Option (List ℕ × List ℕ × ℕ) := + (Code.eval w fuel RadixSort.inputCode (Data.init xs)).map fun (y, _) => + let m := y.regs Deduplicate.out + ((List.range m).map (fun i => y.mem (1 + 2 * xs.length + i)), + (List.range m).map (fun i => y.mem (1 + 3 * xs.length + i)), m) + +-- Empty input still executes setup and exits both array scans safely. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 3 4 [] = some ([], [], 0)) + +-- The largest fitting four-bit canonical signed encoding occurs at the key boundary. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 4 5 [Signed.encode (-7)] = some ([15], [1], 1)) + +-- Zero is canonical; a repeated zero is one key with full multiplicity two. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 4 5 [0, 0] = some ([0], [2], 1)) + +-- Counts are full multiplicities, including values above three. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 5 6 (List.replicate 4 (Signed.encode (-1))) = some ([3], [4], 1)) + +-- Positive and negative input integers, repeated keys, and zero in unsigned encoding order. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 5 6 (([2, -1, 2, 0, -1] : List ℤ).map Signed.encode) = + some ([0, 3, 4], [1, 2, 2], 3)) + +-- Encoding order deliberately differs from signed integer order. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 4 5 (([-2, 1, -1] : List ℤ).map Signed.encode) = + some ([2, 3, 5], [1, 1, 1], 3)) + +-- Two physical bits cannot address this singleton's count output: layout fit matters. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 2 4 [2] = none) + +-- A raw unsigned input beyond the declared physical word bound faults as well. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 2 4 [4] = none) + +-- The assembled program is executed using the ordinary native initial state. +/-- info: true -/ +#guard_msgs in +#eval decide ((run 4 RadixSort.program 500 (State.init [4, 3, 3])).map Prod.fst = some 2) + +-- The array layout threshold includes zero and small input lengths. +example : RadixSort.layoutWidth 0 = 3 ∧ RadixSort.layoutWidth 2 = 6 := by decide +kernel + +-- A bit bound of zero is meaningful exactly when all unsigned keys are zero. +example : RadixSort.passes 0 [0, 0] = [0, 0] ∧ + ¬([2, 0] : List ℕ).Pairwise (· ≤ ·) := by decide +kernel + +-- One finite instruction list is used at every input length and word width. +example : RadixSort.program.length = 81 := by decide +kernel From 4edf0b8ac9bba87b0f282fec989670c49b8bb641 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:00:31 -0400 Subject: [PATCH 12/45] chore(wordram): keep Lean sources non-executable Normalize the new file modes. Lean source bytes are unchanged from the fully validated 25474f432. --- Complexity/Algorithms/WordRAM/Deduplicate/Count.lean | 0 Complexity/Algorithms/WordRAM/Deduplicate/Model.lean | 0 Complexity/Algorithms/WordRAM/Deduplicate/Program.lean | 0 Complexity/Algorithms/WordRAM/RadixSort.lean | 0 Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean | 0 Complexity/Algorithms/WordRAM/RadixSort/Model.lean | 0 Complexity/Algorithms/WordRAM/RadixSort/Program.lean | 0 Complexity/Algorithms/WordRAM/RadixSort/Scan.lean | 0 Complexity/Computability/WordRAM/ListArray.lean | 0 ComplexityTests/WordRAMRadixSort.lean | 0 10 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Complexity/Algorithms/WordRAM/Deduplicate/Count.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/Deduplicate/Model.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/Deduplicate/Program.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/RadixSort.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/RadixSort/Model.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/RadixSort/Program.lean mode change 100755 => 100644 Complexity/Algorithms/WordRAM/RadixSort/Scan.lean mode change 100755 => 100644 Complexity/Computability/WordRAM/ListArray.lean mode change 100755 => 100644 ComplexityTests/WordRAMRadixSort.lean diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Count.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Count.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Model.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Model.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/Deduplicate/Program.lean b/Complexity/Algorithms/WordRAM/Deduplicate/Program.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/RadixSort.lean b/Complexity/Algorithms/WordRAM/RadixSort.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean b/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Model.lean b/Complexity/Algorithms/WordRAM/RadixSort/Model.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Program.lean b/Complexity/Algorithms/WordRAM/RadixSort/Program.lean old mode 100755 new mode 100644 diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Scan.lean b/Complexity/Algorithms/WordRAM/RadixSort/Scan.lean old mode 100755 new mode 100644 diff --git a/Complexity/Computability/WordRAM/ListArray.lean b/Complexity/Computability/WordRAM/ListArray.lean old mode 100755 new mode 100644 diff --git a/ComplexityTests/WordRAMRadixSort.lean b/ComplexityTests/WordRAMRadixSort.lean old mode 100755 new mode 100644 From 5cbb93d5ddc946364292740e2d85e334f4bfa649 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 08:21:35 -0400 Subject: [PATCH 13/45] =?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 14/45] 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 0e05605fbe71bbcfce3d2bf247f651ec8e483b9d Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 09:37:59 -0400 Subject: [PATCH 15/45] feat(wordram): implement prime tables and collision minimum searches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #1446, tracked by #1480. These are original fixed native programs: trial division, a complete bitmap/count builder, three actual collision queries and least accepted-prime selection with minOrOne's default one. The private search-driver query contract is discharged by Query.exec; there is no assumed primality, counting or acceptance operation. Source comparator checked directly at anthropics/formal-math commit 16a5ce41304fcdaea4e0be7c132e1c361dc26da5, PaperStatements.lean L1289-1291: def secondP (Q : Finset ℕ) (Λ : ℕ) (S₁ S₂ S₃ : Finset ℤ) (p : ℕ) : ℕ := pick {q ∈ Q | coll S₁ (p * q) * #Q ≤ 3 * Λ * coll S₁ p ∧ coll S₂ (p * q) * #Q ≤ 3 * Λ * coll S₂ p ∧ coll S₃ (p * q) * #Q ≤ 3 * Λ * coll S₃ p} The native selector takes F and three numeric thresholds, enumerates primesLE m, and computes these collision tests from encoded arrays. Existing Cslib coll and minOrOne are reused; source pick is minOrOne. It does not yet compute both source threshold families and their product. No source program or machine simulation is imported. PrimeTable.exec costs (m+1)*(7m+15)+8; Search.exec costs (m+1)*(34*(n0+n1+n2)+15*F*m+102)+9, each excluding one halt. Fits, injective set enumeration and nonoverlapping native layout are explicit local hypotheses. Full frontend/heavy/recursion/packed output, State.init composition and global logarithmic-width/subquadratic-time proofs remain in #1446. The #1394 endpoint and exponent remain open. Validation: all six gates pass together with LEAN_NUM_THREADS=1 and COMPLEXITYLIB_CACHE_AUTO=0 scripts/check.sh. Hygiene --check-simp, changed-file lints against 0505439ad73e746828ba9e5ade7ec8bb4577456e, and git diff --check pass. Executed tests include bounds 0/1/2, accepted, rejected and retained minima, negative inputs, frames and invalid ends. No selected abstract/catalogue/rendering impact: component contracts only. Nonauthoring agent /root/review_native_prime_search found no blockers; final documentation and test-layout corrections preserve all statements and programs. This is within-fleet agent review, not outside/human approval. The base is repaired #1469 0505439ad73e746828ba9e5ade7ec8bb4577456e, above #1456 a7122053713cab3e45b505306b5d03bcd01cc737 and #1445 fd69be7bc. All eight implementation modules and three tests are byte-identical to 1a45c8b1fea51a29b252567814690f82fc5ed0ac. Outside PR review remains necessary. AI assistance: OpenAI Codex. --- Complexity.lean | 8 + Complexity/Algorithms/WordRAM/PrimeTable.lean | 233 ++++++++ Complexity/Algorithms/WordRAM/PrimeTrial.lean | 142 +++++ .../ThreeSum/ChanHe/CollisionTest.lean | 147 +++++ .../WordRAM/ThreeSum/ChanHe/CountCall.lean | 172 ++++++ .../WordRAM/ThreeSum/ChanHe/CountFrame.lean | 35 ++ .../WordRAM/ThreeSum/ChanHe/CountTriple.lean | 122 +++++ .../WordRAM/ThreeSum/ChanHe/Query.lean | 99 ++++ .../WordRAM/ThreeSum/ChanHe/Search.lean | 507 ++++++++++++++++++ ComplexityTests/WordRAMChanHeQuery.lean | 58 ++ ComplexityTests/WordRAMChanHeSearch.lean | 90 ++++ ComplexityTests/WordRAMPrimeTable.lean | 63 +++ ROADMAP.md | 6 +- UPSTREAM.md | 5 + 14 files changed, 1686 insertions(+), 1 deletion(-) create mode 100644 Complexity/Algorithms/WordRAM/PrimeTable.lean create mode 100644 Complexity/Algorithms/WordRAM/PrimeTrial.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CollisionTest.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountTriple.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Query.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Search.lean create mode 100644 ComplexityTests/WordRAMChanHeQuery.lean create mode 100644 ComplexityTests/WordRAMChanHeSearch.lean create mode 100644 ComplexityTests/WordRAMPrimeTable.lean diff --git a/Complexity.lean b/Complexity.lean index af010bc30b..091db68880 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -96,6 +96,8 @@ public import Complexity.Algorithms.WordRAM.PrefixSums.Dynamic public import Complexity.Algorithms.WordRAM.PrefixSums.Query public import Complexity.Algorithms.WordRAM.PrefixSums.Tree public import Complexity.Algorithms.WordRAM.PrefixSums.Update +public import Complexity.Algorithms.WordRAM.PrimeTable +public import Complexity.Algorithms.WordRAM.PrimeTrial public import Complexity.Algorithms.WordRAM.Queue.Basic public import Complexity.Algorithms.WordRAM.Queue.Dynamic public import Complexity.Algorithms.WordRAM.Queue.Model @@ -104,10 +106,16 @@ 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.CollisionTest public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search 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/PrimeTable.lean b/Complexity/Algorithms/WordRAM/PrimeTable.lean new file mode 100644 index 0000000000..09e9ce40a7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/PrimeTable.lean @@ -0,0 +1,233 @@ +/- +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.PrimeTrial +public import Mathlib.NumberTheory.PrimeCounting + +/-! +# Native prime tables + +This fixed program tests every candidate through the supplied bound, writes its primality +bit, and counts the primes. Trial division, table addresses, writes and loops are charged. +There is no primality oracle or assumed sieve. + +## Main definitions + +* `PrimeTable.program`: the complete native table builder. +* `PrimeTable.memory`: its output bitmap and memory frame. + +## Main results + +* `PrimeTable.exec`: exact bitmap and prime count in an explicit quadratic instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.PrimeTable + +open Cslib.WordRAM +open Finset + +/-- Registers of the trial-division subroutine. -/ +def trialRegisters (i : Fin 6) : Fin 15 := ⟨i.val, by omega⟩ + +private theorem trial_injective : Function.Injective trialRegisters := by + intro i j h + apply Fin.ext + exact congrArg (fun i : Fin 15 => i.val) h + +/-- Current trial-division candidate. -/ +abbrev arg : Fin 15 := 0 +/-- Current candidate's primality bit. -/ +abbrev flag : Fin 15 := 2 +/-- Largest candidate. -/ +abbrev limit : Fin 15 := 6 +/-- Output bitmap base. -/ +abbrev base : Fin 15 := 7 +/-- First candidate not yet written. -/ +abbrev cursor : Fin 15 := 8 +/-- Address scratch. -/ +abbrev addr : Fin 15 := 9 +/-- Number of primes written. -/ +abbrev count : Fin 15 := 10 +/-- One past the largest candidate. -/ +abbrev stop : Fin 15 := 11 +/-- Constant one. -/ +abbrev one : Fin 15 := 12 +/-- Constant zero. -/ +abbrev zero : Fin 15 := 13 +/-- Constant two. -/ +abbrev two : Fin 15 := 14 + +/-- Handle zero and one directly; test all larger candidates by charged native trial division. -/ +def test : Code .standard 15 := + .branch (.lt cursor two) (ram[ flag ← #0 ]) + (.seq (ram[ arg ← cursor ]) (PrimeTrial.program.rename trialRegisters)) + +/-- Test, write the bit, update the count, and advance the candidate. -/ +def body : Code .standard 15 := + .seq test (ram[ addr ← base + cursor; *addr ← flag; + count ← count + flag; cursor ← cursor + one ]) + +/-- A finite native prime-table program with its bound and base supplied in registers. -/ +def program : Code .standard 15 := + .seq (ram[ one ← #1; zero ← #0; two ← #2; cursor ← #0; count ← #0; + stop ← limit + one ]) (.loop (.lt cursor stop) body) + +/-- The first `k` primality bits, preserving all other memory cells. -/ +def memory (mem : ℕ → ℕ) (base k : ℕ) : ℕ → ℕ := + fun a => if base ≤ a ∧ a < base + k then if (a - base).Prime then 1 else 0 else mem a + +private theorem memory_zero (mem : ℕ → ℕ) (base : ℕ) : memory mem base 0 = mem := by + funext a + simp [memory] + +private theorem memory_succ (mem : ℕ → ℕ) (base k : ℕ) : + memory mem base (k + 1) = Function.update (memory mem base k) + (base + k) (if k.Prime then 1 else 0) := by + funext a + by_cases ha : a = base + k + · subst a + simp [memory] + · rw [Function.update_of_ne ha] + have h : (base ≤ a ∧ a < base + (k + 1)) ↔ (base ≤ a ∧ a < base + k) := by omega + simp only [memory, h] + +private def primesLT (k : ℕ) : ℕ := #{p ∈ range k | p.Prime} + +private theorem primesLT_zero : primesLT 0 = 0 := by simp [primesLT] + +private theorem primesLT_succ (k : ℕ) : + primesLT (k + 1) = primesLT k + if k.Prime then 1 else 0 := by + by_cases hp : k.Prime <;> simp [primesLT, range_add_one, filter_insert, hp] + +private theorem primesLT_le (k : ℕ) : primesLT k ≤ k := + (card_filter_le _ _).trans_eq (card_range k) + +private theorem exec_test {w m : ℕ} (x : Data 15) (htwo : x.regs two = 2) + (hbound : x.regs cursor ≤ m) (hfit : Fits w (m + 3)) : + ∃ y, ExecWithin w test x y (7 * m + 9) ∧ + y.regs flag = (if (x.regs cursor).Prime then 1 else 0) ∧ y.mem = x.mem ∧ + ∀ i, 6 ≤ i.val → y.regs i = x.regs i := by + by_cases hp : x.regs cursor < 2 + · have hnp : ¬(x.regs cursor).Prime := fun h => by have := h.two_le; omega + refine ⟨x.set flag 0, ?_, by simp [hnp], rfl, ?_⟩ + · exact (ExecWithin.branch_pos (by simpa only [Cond.holds_lt, htwo] using hp) + (ExecWithin.const (fits_zero w))).mono (by omega) + · intro i hi + have hne : i ≠ flag := by intro h; subst i; change 6 ≤ 2 at hi; omega + simp only [Data.set_regs, Function.update_of_ne hne] + · let p := x.set arg (x.regs cursor) + have hcopy : ExecWithin (B := .standard) w (ram[ arg ← cursor ]) x p 1 := + ExecWithin.copy + obtain ⟨a, ha, haf, haa, ham⟩ := PrimeTrial.exec (p.comap trialRegisters) + (by change 2 ≤ x.regs cursor; omega) + (hfit.of_le (by change x.regs cursor + 2 ≤ m + 3; omega)) + let y := a.extend trialRegisters p + have hf : y.regs flag = a.regs PrimeTrial.flag := by + change Function.extend trialRegisters a.regs p.regs (trialRegisters PrimeTrial.flag) = _ + exact trial_injective.extend_apply _ _ _ + refine ⟨y, ?_, hf.trans haf, ham, ?_⟩ + · exact (ExecWithin.branch_neg (by simpa only [Cond.holds_lt, htwo] using hp) + (hcopy.seq (ha.rename trial_injective))).mono (by + change 1 + (7 * (x.regs cursor - 2) + 6) + 2 ≤ 7 * m + 9 + omega) + · intro i hi + change Function.extend trialRegisters a.regs p.regs i = x.regs i + rw [Function.extend_apply'] + · have hne : i ≠ arg := by intro h; subst i; change 6 ≤ 0 at hi; omega + exact Function.update_of_ne hne _ _ + · rintro ⟨j, hj⟩ + have := congrArg Fin.val hj + dsimp [trialRegisters] at this + omega + + +/-- Write every primality bit through `m`, count exactly `Nat.primesLE m`, and preserve +all cells outside the bitmap. Trial division gives the explicit quadratic bound +`(m+1)(7m+15)+8`; the final halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 15) + (hfit : Fits w (x.regs limit + 3)) + (haddr : Fits w (x.regs base + (x.regs limit + 1))) : + ∃ y, ExecWithin w program x y ((x.regs limit + 1) * (7 * x.regs limit + 15) + 8) ∧ + y.mem = memory x.mem (x.regs base) (x.regs limit + 1) ∧ + y.regs count = #(Nat.primesLE (x.regs limit)) ∧ + y.regs limit = x.regs limit ∧ y.regs base = x.regs base := by + let m := x.regs limit + let I (y : Data 15) : Prop := y.regs limit = m ∧ y.regs base = x.regs base ∧ + y.regs stop = m + 1 ∧ y.regs one = 1 ∧ y.regs zero = 0 ∧ y.regs two = 2 ∧ + y.regs cursor ≤ m + 1 ∧ y.regs count = primesLT (y.regs cursor) ∧ + y.mem = memory x.mem (x.regs base) (y.regs cursor) + have hbody (y : Data 15) (hy : I y) (hc : (Cond.lt cursor stop).Holds y) : + ∃ q, ExecWithin w body y q (7 * m + 13) ∧ I q ∧ + m + 1 - q.regs cursor < m + 1 - y.regs cursor := by + obtain ⟨hl, hb, hs, ho, hz, ht, hi, hcount, hm⟩ := hy + have hlt : y.regs cursor < m + 1 := by simpa only [Cond.holds_lt, hs] using hc + obtain ⟨z, he, hzf, hzm, hzr⟩ := exec_test y ht (show y.regs cursor ≤ m by omega) hfit + have hzb : z.regs base = x.regs base := (hzr base (by decide)).trans hb + have hzi : z.regs cursor = y.regs cursor := hzr cursor (by decide) + have hzc : z.regs count = primesLT (y.regs cursor) := (hzr count (by decide)).trans hcount + have hzo : z.regs one = 1 := (hzr one (by decide)).trans ho + have hzf_le : z.regs flag ≤ 1 := by rw [hzf]; split_ifs <;> omega + have ha : Fits w (z.regs base + z.regs cursor) := haddr.of_le (by rw [hzb, hzi]; omega) + have hn : Fits w (z.regs cursor + 1) := hfit.of_le (by rw [hzi]; change _ ≤ m + 3; omega) + have hct : Fits w (z.regs count + z.regs flag) := hfit.of_le (by + rw [hzc] + have hle := primesLT_le (y.regs cursor) + change _ ≤ m + 3 + omega) + refine ⟨?q, ?run, ?inv, ?decrease⟩ + case run => + have htail : ExecWithin w (ram[ addr ← base + cursor; *addr ← flag; + count ← count + flag; cursor ← cursor + one ]) z ?q 4 := by + ram_eval [ha, hn, hct, hzo] + exact (he.seq htail).mono (by omega) + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Function.update_apply] using (hzr limit (by decide)).trans hl + · simpa +decide [Function.update_apply] using hzb + · simpa +decide [Function.update_apply] using (hzr stop (by decide)).trans hs + · simpa +decide [Function.update_apply] using hzo + · simpa +decide [Function.update_apply] using (hzr zero (by decide)).trans hz + · simpa +decide [Function.update_apply] using (hzr two (by decide)).trans ht + · simpa +decide [Function.update_apply, hzi] using Nat.succ_le_of_lt hlt + · change z.regs count + z.regs flag = primesLT (z.regs cursor + 1) + rw [hzc, hzf, hzi, primesLT_succ] + · change Function.update z.mem (z.regs base + z.regs cursor) (z.regs flag) = + memory x.mem (x.regs base) (z.regs cursor + 1) + rw [hzm, hm, hzb, hzi, hzf, ← memory_succ] + case decrease => + change m + 1 - (z.regs cursor + 1) < m + 1 - y.regs cursor + rw [hzi] + omega + let x₀ := (((((x.set one 1).set zero 0).set two 2).set cursor 0).set count 0).set stop (m + 1) + have hx₀ : I x₀ := by + simp +decide [I, x₀, m, limit, base, stop, one, zero, two, cursor, count, + primesLT_zero, memory_zero] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => m + 1 - y.regs cursor) hbody hx₀ + obtain ⟨hl, hb, hs, ho, hz, ht, hi, hcount, hm⟩ := hy + have hend : y.regs cursor = m + 1 := by + simp only [Cond.holds_lt, hs] at hc + omega + have h1 : Fits w 1 := hfit.of_le (by omega) + have h2 : Fits w 2 := hfit.of_le (by omega) + have hstop : Fits w (x.regs limit + 1) := hfit.of_le (by omega) + have hinit : ExecWithin (B := .standard) w + (ram[ one ← #1; zero ← #0; two ← #2; cursor ← #0; count ← #0; + stop ← limit + one ]) x x₀ 6 := by + ram_eval [x₀, m, h1, h2, hstop, limit, base, stop, one, zero, two, cursor, count, Fin.mk.injEq] + refine ⟨y, ?_, by simpa only [hend] using hm, ?_, hl, hb⟩ + · have hi₀ : x₀.regs cursor = 0 := rfl + have hcost : 7 * m + 13 + 2 = 7 * m + 15 := by omega + exact (hinit.seq he).mono (by rw [hi₀, Nat.sub_zero, hcost]; dsimp only [m]; omega) + · rw [hcount, hend, Nat.primesLE_eq_filter_range] + rfl + +end Cslib.Algorithms.WordRAM.PrimeTable diff --git a/Complexity/Algorithms/WordRAM/PrimeTrial.lean b/Complexity/Algorithms/WordRAM/PrimeTrial.lean new file mode 100644 index 0000000000..ec29efe117 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/PrimeTrial.lean @@ -0,0 +1,142 @@ +/- +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.Prime.Defs + +/-! +# Charged native trial division + +This is a finite instruction program, not an invocation of Lean's primality decision procedure. +The Chan–He prime table will call it only on candidates at least two. + +## Main definitions + +* `PrimeTrial.program`: trial divisors from two up to the candidate. + +## Main results + +* `PrimeTrial.exec`: native trial division with an explicit linear instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.PrimeTrial + +open Cslib.WordRAM + +/-- Candidate, preserved. -/ +abbrev arg : Fin 6 := 0 +/-- Current trial divisor. -/ +abbrev divisor : Fin 6 := 1 +/-- Primality flag. -/ +abbrev flag : Fin 6 := 2 +/-- Remainder scratch. -/ +abbrev remainder : Fin 6 := 3 +/-- Constant one. -/ +abbrev one : Fin 6 := 4 +/-- Constant zero. -/ +abbrev zero : Fin 6 := 5 + +/-- Test one trial divisor and advance. The conditional also charges its control instructions. -/ +def body : Code .standard 6 := ram[ + remainder ← arg % divisor; + if remainder = zero then [ flag ← zero ] else [ flag ← flag ]; + divisor ← divisor + one ] + +/-- Initialize and test every divisor strictly between one and the candidate. -/ +def program : Code .standard 6 := + .seq (ram[ divisor ← #2; flag ← #1; one ← #1; zero ← #0 ]) + (.loop (.lt divisor arg) body) + +private def Good (p k : ℕ) : Prop := ∀ d, 2 ≤ d → d < k → p % d ≠ 0 + +private theorem good_succ (p k : ℕ) (hk : 2 ≤ k) : + Good p (k + 1) ↔ Good p k ∧ p % k ≠ 0 := by + constructor + · intro h + exact ⟨fun d hd hdk => h d hd (by omega), h k hk (by omega)⟩ + · rintro ⟨h, hmod⟩ d hd hdk + by_cases hdk' : d = k + · simpa only [hdk'] using hmod + · exact h d hd (by omega) + +/-- Test a candidate at least two in at most `7(p-2)+6` native instructions. +All divisors are actual register values; the native remainder instruction is charged. -/ +theorem exec {w : ℕ} (x : Data 6) (hp : 2 ≤ x.regs arg) + (hfit : Fits w (x.regs arg + 2)) : + ∃ y, ExecWithin w program x y (7 * (x.regs arg - 2) + 6) ∧ + y.regs flag = (if (x.regs arg).Prime then 1 else 0) ∧ + y.regs arg = x.regs arg ∧ y.mem = x.mem := by + classical + let p := x.regs arg + let I (y : Data 6) : Prop := y.regs arg = p ∧ y.regs one = 1 ∧ y.regs zero = 0 ∧ + 2 ≤ y.regs divisor ∧ y.regs divisor ≤ p ∧ + y.regs flag = (if Good p (y.regs divisor) then 1 else 0) ∧ y.mem = x.mem + have hbody (y : Data 6) (hy : I y) (hc : (Cond.lt divisor arg).Holds y) : + ∃ z, ExecWithin w body y z 5 ∧ I z ∧ p - z.regs divisor < p - y.regs divisor := by + obtain ⟨ha, ho, hz, hd, hdp, hf, hm⟩ := hy + have hlt : y.regs divisor < p := by simpa only [Cond.holds_lt, ha] using hc + have hmod : Fits w (p % y.regs divisor) := hfit.of_le (by + have := Nat.mod_le p (y.regs divisor) + change p % y.regs divisor ≤ p + 2 + omega) + have hinc : Fits w (y.regs divisor + 1) := hfit.of_le (by change _ ≤ p + 2; omega) + let z := ((y.set remainder (p % y.regs divisor)).set flag + (if p % y.regs divisor = 0 then 0 else y.regs flag)).set divisor (y.regs divisor + 1) + have he : ExecWithin w body y z 5 := by + by_cases hrem : p % y.regs divisor = 0 <;> + ram_eval [body, z, ha, ho, hz, hmod, hinc, hrem, arg, divisor, flag, + remainder, one, zero, Fin.mk.injEq] + have hflag : (if p % y.regs divisor = 0 then 0 else y.regs flag) = + if Good p (y.regs divisor + 1) then 1 else 0 := by + rw [hf] + have hg := good_succ p (y.regs divisor) hd + by_cases hrem : p % y.regs divisor = 0 + · have hg' : ¬Good p (y.regs divisor + 1) := fun h => (hg.mp h).2 hrem + simp only [hrem, hg', ↓reduceIte] + · have hg' : Good p (y.regs divisor + 1) ↔ Good p (y.regs divisor) := by tauto + simp only [hrem, hg', ↓reduceIte] + refine ⟨z, he, ?_, ?_⟩ + · refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [z, Function.update_apply, arg, divisor, flag, remainder] using ha + · simpa +decide [z, Function.update_apply, one, divisor, flag, remainder] using ho + · simpa +decide [z, Function.update_apply, zero, divisor, flag, remainder] using hz + · simpa +decide [z, Function.update_apply, divisor] using + (show 2 ≤ y.regs divisor + 1 by omega) + · simpa +decide [z, Function.update_apply, divisor] using Nat.succ_le_of_lt hlt + · simpa +decide [z, Function.update_apply, divisor, flag, remainder] using hflag + · exact hm + · change p - (y.regs divisor + 1) < p - y.regs divisor + omega + let x₀ := (((x.set divisor 2).set flag 1).set one 1).set zero 0 + have hx₀ : I x₀ := by + have hgood : Good p 2 := by intro d hd hlt; omega + simp +decide [I, x₀, p, arg, divisor, flag, one, zero, hgood, hp] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => p - y.regs divisor) hbody hx₀ + obtain ⟨ha, ho, hz, hd, hdp, hf, hm⟩ := hy + have hend : y.regs divisor = p := by simp only [Cond.holds_lt, ha] at hc; omega + have hprime : Good p p ↔ p.Prime := by + rw [Nat.prime_def_lt'] + constructor + · intro h + refine ⟨hp, fun d hd hdp hdiv => ?_⟩ + exact h d hd hdp (Nat.mod_eq_zero_of_dvd hdiv) + · rintro ⟨_, h⟩ d hd hdp hzero + exact h d hd hdp (Nat.dvd_of_mod_eq_zero hzero) + have h1 : Fits w 1 := hfit.of_le (by omega) + have h2 : Fits w 2 := hfit.of_le (by omega) + have hinit : ExecWithin (B := .standard) w + (ram[ divisor ← #2; flag ← #1; one ← #1; zero ← #0 ]) x x₀ 4 := by + ram_eval [x₀, h1, h2] + refine ⟨y, ?_, by simpa only [hend, hprime] using hf, ha, hm⟩ + have hd₀ : x₀.regs divisor = 2 := rfl + exact (hinit.seq he).mono (by rw [hd₀]; dsimp only [p]; omega) + +end Cslib.Algorithms.WordRAM.PrimeTrial diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CollisionTest.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CollisionTest.lean new file mode 100644 index 0000000000..37545bd9a0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CollisionTest.lean @@ -0,0 +1,147 @@ +/- +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.CountCall +import Mathlib.Tactic.FinCases + +/-! +# Native tests of the three Chan–He collision inequalities + +Each inequality multiplies a computed collision count by the number of candidate primes, +then compares it with its supplied threshold. All multiplications, branches and flag updates +are ordinary charged instructions. No predicate is executed by an oracle. + +## Main definitions + +* `CollisionTest.program`: test all three inequalities. + +## Main results + +* `CollisionTest.exec`: the exact acceptance flag in at most thirteen instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CollisionTest + +open Cslib.WordRAM + +/-- Number of candidate primes. -/ +abbrev card : Fin 64 := 42 +/-- Threshold for one of the three sets. -/ +def threshold (j : Fin 3) : Fin 64 := ⟨44 + j.val, by omega⟩ +/-- Constant zero. -/ +abbrev zero : Fin 64 := 52 +/-- Collision count times the number of candidate primes. -/ +abbrev scaled : Fin 64 := 57 +/-- Acceptance flag. -/ +abbrev good : Fin 64 := 58 + +/-- One charged multiplication and comparison, retaining a previously false flag. -/ +def compare (j : Fin 3) : Code .standard 64 := + let value := CountCall.result j + let cutoff := threshold j + ram[ scaled ← value * card; + if cutoff < scaled then [ good ← zero ] else [ good ← good ] ] + +/-- Initialize a true flag and test all three supplied collision counts. -/ +def program : Code .standard 64 := + .seq (ram[ good ← #1 ]) (.seq (compare 0) (.seq (compare 1) (compare 2))) + +private theorem exec_compare {w : ℕ} (j : Fin 3) (x : Data 64) + (hz : x.regs zero = 0) + (hfit : Fits w (x.regs (CountCall.result j) * x.regs card)) : + ∃ y, ExecWithin w (compare j) x y 4 ∧ + y.regs good = (if x.regs (CountCall.result j) * x.regs card ≤ x.regs (threshold j) + then x.regs good else 0) ∧ y.mem = x.mem ∧ + ∀ i, i ≠ scaled → i ≠ good → y.regs i = x.regs i := by + let product := x.regs (CountCall.result j) * x.regs card + let y := (x.set scaled product).set good + (if x.regs (threshold j) < product then 0 else x.regs good) + have hm : ExecWithin w + (.op (.bin .mul scaled (CountCall.result j) card)) x (x.set scaled product) 1 := + ExecWithin.bin hfit + have hcut : (x.set scaled product).regs (threshold j) = x.regs (threshold j) := by + fin_cases j <;> rfl + have hz' : (x.set scaled product).regs zero = 0 := by + simpa +decide [zero, scaled] using hz + have hg' : (x.set scaled product).regs good = x.regs good := rfl + have hscaled : (x.set scaled product).regs scaled = product := rfl + have he : ExecWithin w (compare j) x y 4 := by + by_cases hlt : x.regs (threshold j) < product + · have hc : (Cond.lt (threshold j) scaled).Holds (x.set scaled product) := by + simpa only [Cond.holds_lt, hcut, hscaled] using hlt + have hb := ExecWithin.branch_pos (B := .standard) (w := w) (pos := ram[ good ← zero ]) + (neg := ram[ good ← good ]) hc + (ExecWithin.copy (x := x.set scaled product) (d := good) (s := zero)) + have hb' : ExecWithin (B := .standard) w + (.branch (.lt (threshold j) scaled) (ram[ good ← zero ]) (ram[ good ← good ])) + (x.set scaled product) y 2 := by + simpa only [y, hlt, ↓reduceIte, hz'] using hb + exact (hm.seq hb').mono (by decide) + · have hc : ¬(Cond.lt (threshold j) scaled).Holds (x.set scaled product) := by + simpa only [Cond.holds_lt, hcut, hscaled] using hlt + have hb := ExecWithin.branch_neg (B := .standard) (w := w) (pos := ram[ good ← zero ]) + (neg := ram[ good ← good ]) hc + (ExecWithin.copy (x := x.set scaled product) (d := good) (s := good)) + have hb' : ExecWithin (B := .standard) w + (.branch (.lt (threshold j) scaled) (ram[ good ← zero ]) (ram[ good ← good ])) + (x.set scaled product) y 3 := by + simpa only [y, hlt, ↓reduceIte, hg'] using hb + exact hm.seq hb' + refine ⟨y, he, ?_, rfl, ?_⟩ + · change (if x.regs (threshold j) < product then 0 else x.regs good) = + if product ≤ x.regs (threshold j) then x.regs good else 0 + by_cases hlt : x.regs (threshold j) < product + · have hnot : ¬product ≤ x.regs (threshold j) := by omega + simp only [hlt, hnot, ↓reduceIte] + · have hle : product ≤ x.regs (threshold j) := by omega + simp only [hlt, hle, ↓reduceIte] + · intro i hs hg + simp only [y, Data.set_regs, Function.update_of_ne hg, Function.update_of_ne hs] + +/-- Test all three inequalities with a shared acceptance flag. The only written registers +are `scaled` and `good`; memory and every other caller register are unchanged. -/ +theorem exec {w : ℕ} (x : Data 64) (h1 : Fits w 1) (hz : x.regs zero = 0) + (hfit : ∀ j, Fits w (x.regs (CountCall.result j) * x.regs card)) : + ∃ y, ExecWithin w program x y 13 ∧ + y.regs good = (if + x.regs (CountCall.result 0) * x.regs card ≤ x.regs (threshold 0) ∧ + x.regs (CountCall.result 1) * x.regs card ≤ x.regs (threshold 1) ∧ + x.regs (CountCall.result 2) * x.regs card ≤ x.regs (threshold 2) then 1 else 0) ∧ + y.mem = x.mem ∧ ∀ i, i ≠ scaled → i ≠ good → y.regs i = x.regs i := by + let R (y : Data 64) : Prop := y.mem = x.mem ∧ + ∀ i, i ≠ scaled → i ≠ good → y.regs i = x.regs i + have hcall (j : Fin 3) (y : Data 64) (hy : R y) : + ∃ z, ExecWithin w (compare j) y z 4 ∧ R z ∧ + z.regs good = (if x.regs (CountCall.result j) * x.regs card ≤ x.regs (threshold j) + then y.regs good else 0) := by + obtain ⟨hm, hr⟩ := hy + have hvalue : y.regs (CountCall.result j) = x.regs (CountCall.result j) := + hr _ (by fin_cases j <;> decide) (by fin_cases j <;> decide) + have hcard : y.regs card = x.regs card := hr _ (by decide) (by decide) + have hcut : y.regs (threshold j) = x.regs (threshold j) := + hr _ (by fin_cases j <;> decide) (by fin_cases j <;> decide) + obtain ⟨z, he, hf, hzm, hzr⟩ := exec_compare j y + ((hr _ (by decide) (by decide)).trans hz) + (by simpa only [hvalue, hcard] using hfit j) + exact ⟨z, he, ⟨hzm.trans hm, fun i hs hg => (hzr i hs hg).trans (hr i hs hg)⟩, + by simpa only [hvalue, hcard, hcut] using hf⟩ + let x₀ := x.set good 1 + have hx₀ : R x₀ := ⟨rfl, fun i _ hg => Function.update_of_ne hg _ _⟩ + obtain ⟨a, ha, hRa, hfa⟩ := hcall 0 x₀ hx₀ + obtain ⟨b, hb, hRb, hfb⟩ := hcall 1 a hRa + obtain ⟨c, hc, hRc, hfc⟩ := hcall 2 b hRb + refine ⟨c, ?_, ?_, hRc⟩ + · exact (ExecWithin.const h1).seq (ha.seq (hb.seq hc)) + · rw [hfc, hfb, hfa] + change (if _ then (if _ then (if _ then 1 else 0) else 0) else 0) = _ + split_ifs <;> omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CollisionTest diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean new file mode 100644 index 0000000000..de574e2935 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean @@ -0,0 +1,172 @@ +/- +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.CountFrame +import Mathlib.Tactic.FinCases + +/-! +# Charged calls to Chan–He collision counting + +The prime searches keep their parameters outside the counting workspace. Each of the three +set arrays has a fixed pair of base/length registers and its own result register. This module +charges argument setup and result delivery as well as the complete native counting routine. + +## Main definitions + +* `CountCall.program`: a counting call on one of the three input arrays. + +## Main results + +* `CountCall.exec`: exact collision count, memory frame and preserved caller registers. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Counting workspace in a larger, fixed register file. -/ +def registers (i : Fin 28) : Fin 64 := ⟨i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j h + apply Fin.ext + exact congrArg (fun i : Fin 64 => i.val) h + +/-- Length register for the selected set. -/ +def length (j : Fin 3) : Fin 64 := ⟨28 + 2 * j.val, by omega⟩ +/-- Base register for the selected set. -/ +def source (j : Fin 3) : Fin 64 := ⟨29 + 2 * j.val, by omega⟩ +/-- Residue scratch base. -/ +abbrev scratch : Fin 64 := 34 +/-- Histogram base. -/ +abbrev histogram : Fin 64 := 35 +/-- Encoded positive modulus. -/ +abbrev modulus : Fin 64 := 36 +/-- Collision-count output register for the selected set. -/ +def result (j : Fin 3) : Fin 64 := ⟨37 + j.val, by omega⟩ + +/-- Counting input base register. -/ +abbrev countSource : Fin 64 := registers Count.src +/-- Counting scratch base register. -/ +abbrev countScratch : Fin 64 := registers Count.scratch +/-- Counting input length register. -/ +abbrev countLength : Fin 64 := registers Count.len +/-- Counting encoded-modulus register. -/ +abbrev countModulus : Fin 64 := registers Count.modulus +/-- Counting histogram base register. -/ +abbrev countHistogram : Fin 64 := registers Count.dst +/-- Counting result register. -/ +abbrev countTotal : Fin 64 := registers Count.total + +/-- Five runtime register copies deliver the counting arguments. -/ +def prepare (j : Fin 3) : Code .standard 64 := + let input := source j + let n := length j + ram[ countSource ← input; countScratch ← scratch; countLength ← n; + countModulus ← modulus; countHistogram ← histogram ] + +/-- A complete native count call, including argument and result copies. -/ +def program (j : Fin 3) : Code .standard 64 := + .seq (prepare j) (.seq (Count.program.rename registers) (.op (.copy (result j) countTotal))) + +private theorem extend_outside (a : Data 28) (x : Data 64) (i : Fin 64) (hi : 28 ≤ i.val) : + (a.extend registers x).regs i = x.regs i := by + change Function.extend registers a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + have := congrArg Fin.val hj + dsimp [registers] at this + omega + +/-- A native count call costs `34n+5M+24`, including five argument copies and one result copy. +The mathematical input is an injective array enumeration, as required by the existing set-based +Chan–He collision count. All registers outside the workspace, except its chosen result, survive. -/ +theorem exec {w M : ℕ} (j : Fin 3) (x : Data 64) (values : ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs modulus = encode (M : ℤ)) + (hfit : ∀ i < x.regs (length j), GenericRAM.Signed.Fits w (values i)) + (hs : Fits (w + 2) (x.regs (source j) + x.regs (length j))) + (hk : Fits (w + 2) (x.regs scratch + x.regs (length j))) + (hd : Fits (w + 2) (x.regs histogram + M)) + (ht : Fits (w + 2) (x.regs (length j) * x.regs (length j) + x.regs (length j) + 1)) + (hsk : x.regs (source j) + x.regs (length j) ≤ x.regs scratch) + (hkd : x.regs scratch + x.regs (length j) ≤ x.regs histogram) + (hinput : ∀ i < x.regs (length j), + x.mem (x.regs (source j) + i) = encode (values i)) + (hinj : Set.InjOn values (↑(Finset.range (x.regs (length j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) (program j) x y (34 * x.regs (length j) + 5 * M + 24) ∧ + y.mem = Count.memory x.mem values (x.regs (length j)) M + (x.regs scratch) (x.regs histogram) ∧ + y.regs (result j) = Cslib.Algorithms.ThreeSum.ChanHe.coll + ((Finset.range (x.regs (length j))).image values) M ∧ + ∀ i, 28 ≤ i.val → i ≠ result j → y.regs i = x.regs i := by + let q := ((((x.set countSource (x.regs (source j))).set countScratch (x.regs scratch)).set + countLength (x.regs (length j))).set countModulus (x.regs modulus)).set + countHistogram (x.regs histogram) + have hprep : ExecWithin (w + 2) (prepare j) x q 5 := by + fin_cases j <;> + ram_eval [prepare, q, source, length, registers, countSource, countScratch, + countLength, countModulus, countHistogram, scratch, histogram, modulus, + Count.src, Count.scratch, Count.len, Count.modulus, Count.dst, Fin.mk.injEq] + have hq (i : Fin 64) (hi : 28 ≤ i.val) : q.regs i = x.regs i := by + have hne (k : Fin 64) (hk : k.val < 28) : i ≠ k := by + intro h + have := congrArg Fin.val h + omega + simp only [q, Data.set_regs, Function.update_of_ne (hne countSource (by decide)), + Function.update_of_ne (hne countScratch (by decide)), + Function.update_of_ne (hne countLength (by decide)), + Function.update_of_ne (hne countModulus (by decide)), + Function.update_of_ne (hne countHistogram (by decide))] + have hqs : (q.comap registers).regs Count.src = x.regs (source j) := by + simp +decide [q, registers, countSource, countScratch, countLength, countModulus, + countHistogram, Count.src, Count.scratch, Count.len, Count.modulus, Count.dst] + have hqn : (q.comap registers).regs Count.len = x.regs (length j) := by + simp +decide [q, registers, countSource, countScratch, countLength, countModulus, + countHistogram, Count.src, Count.scratch, Count.len, Count.modulus, Count.dst] + have hqk : (q.comap registers).regs Count.scratch = x.regs scratch := by + simp +decide [q, registers, countSource, countScratch, countLength, countModulus, + countHistogram, Count.src, Count.scratch, Count.len, Count.modulus, Count.dst] + have hqd : (q.comap registers).regs Count.dst = x.regs histogram := by + simp +decide [q, registers, countSource, countScratch, countLength, countModulus, + countHistogram, Count.src, Count.scratch, Count.len, Count.modulus, Count.dst] + have hqm : (q.comap registers).regs Count.modulus = x.regs modulus := by + simp +decide [q, registers, countSource, countScratch, countLength, countModulus, + countHistogram, Count.src, Count.scratch, Count.len, Count.modulus, Count.dst] + obtain ⟨a, ha, ham, hav, _⟩ := Count.exec_coll (q.comap registers) values hM hmod + (hqm.trans hmodreg) (by simpa only [hqn] using hfit) + (by simpa only [hqs, hqn] using hs) (by simpa only [hqk, hqn] using hk) + (by simpa only [hqd] using hd) (by simpa only [hqn] using ht) + (by simpa only [hqs, hqn, hqk] using hsk) + (by simpa only [hqk, hqn, hqd] using hkd) + (by + change ∀ i < (q.comap registers).regs Count.len, + x.mem ((q.comap registers).regs Count.src + i) = encode (values i) + simpa only [hqn, hqs] using hinput) + (by simpa only [hqn] using hinj) + let y := a.extend registers q + have hy : ExecWithin (w + 2) (Count.program.rename registers) q y + (34 * x.regs (length j) + 5 * M + 18) := by + simpa only [hqn] using ha.rename registers_injective + have hyt : y.regs countTotal = a.regs Count.total := by + change Function.extend registers a.regs q.regs (registers Count.total) = _ + exact registers_injective.extend_apply _ _ _ + refine ⟨y.set (result j) (y.regs countTotal), ?_, ?_, ?_, ?_⟩ + · exact (hprep.seq (hy.seq ExecWithin.copy)).mono (by omega) + · change a.mem = _ + have hqmem : q.mem = x.mem := rfl + simpa only [Data.comap_mem, hqmem, hqn, hqk, hqd] using ham + · simp only [Data.set_regs, Function.update_self, hyt] + simpa only [hqn] using hav + · intro i hi hne + rw [Data.set_regs, Function.update_of_ne hne, extend_outside a q i hi, hq i hi] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean new file mode 100644 index 0000000000..ba7f1a1e05 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean @@ -0,0 +1,35 @@ +/- +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.Count + +/-! +# Collision-counting register and memory frames + +Prime-search clients reuse the same fixed counting program on successive candidates. +Its input parameters and all memory below its scratch blocks are preserved. + +## Main results + +* `Count.memory_before`: cells below the two workspace blocks retain their contents. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count + +open Cslib.WordRAM + +/-- Neither the residue array nor the histogram modifies an earlier memory cell. -/ +theorem memory_before (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst a : ℕ) + (hs : a < scratch) (hd : a < dst) : memory mem values n M scratch dst a = mem a := by + simp [memory, Histogram.memory, Residues.memory, show ¬(dst ≤ a ∧ a < dst + M) by omega, + show ¬(scratch ≤ a ∧ a < scratch + n) by omega] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountTriple.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountTriple.lean new file mode 100644 index 0000000000..09ea12f05a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountTriple.lean @@ -0,0 +1,122 @@ +/- +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.CountCall +import Mathlib.Tactic.FinCases + +/-! +# Native collision counts for the three Chan–He sets + +All three calls are actual native counting programs, with charged argument and result copies. +The prime-search control registers and every input cell below the workspace are preserved. + +## Main definitions + +* `CountTriple.program`: three complete native collision-count calls. + +## Main results + +* `CountTriple.exec`: exact counts for all three sets and charged total time. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Run one full count call on each of the three arrays. -/ +def program : Code .standard 64 := + .seq (CountCall.program 0) (.seq (CountCall.program 1) (CountCall.program 2)) + +/-- Count collisions in all three injectively enumerated sets in at most +`34(n₀+n₁+n₂)+15M+72` instructions, excluding halt. All caller registers except the +three results are preserved, as is all memory before the counting workspace. -/ +theorem exec {w M : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs CountCall.modulus = encode (M : ℤ)) + (hfit : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hs : ∀ j, Fits (w + 2) (x.regs (CountCall.source j) + x.regs (CountCall.length j))) + (hk : ∀ j, Fits (w + 2) (x.regs CountCall.scratch + x.regs (CountCall.length j))) + (hd : Fits (w + 2) (x.regs CountCall.histogram + M)) + (ht : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) * x.regs (CountCall.length j) + + x.regs (CountCall.length j) + 1)) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y + (34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * M + 72) ∧ + (∀ j, y.regs (CountCall.result j) = Cslib.Algorithms.ThreeSum.ChanHe.coll + ((Finset.range (x.regs (CountCall.length j))).image (values j)) M) ∧ + (∀ i, (28 ≤ i.val ∧ i.val < 37) ∨ 40 ≤ i.val → y.regs i = x.regs i) ∧ + ∀ a < x.regs CountCall.scratch, y.mem a = x.mem a := by + let P (i : Fin 64) : Prop := (28 ≤ i.val ∧ i.val < 37) ∨ 40 ≤ i.val + let R (y : Data 64) : Prop := (∀ i, P i → y.regs i = x.regs i) ∧ + ∀ a < x.regs CountCall.scratch, y.mem a = x.mem a + have hcall (j : Fin 3) (y : Data 64) (hy : R y) : + ∃ z, ExecWithin (w + 2) (CountCall.program j) y z + (34 * x.regs (CountCall.length j) + 5 * M + 24) ∧ R z ∧ + z.regs (CountCall.result j) = Cslib.Algorithms.ThreeSum.ChanHe.coll + ((Finset.range (x.regs (CountCall.length j))).image (values j)) M ∧ + ∀ i, 28 ≤ i.val → i ≠ CountCall.result j → z.regs i = y.regs i := by + obtain ⟨hr, hm⟩ := hy + have hl : y.regs (CountCall.length j) = x.regs (CountCall.length j) := + hr _ (by dsimp [P, CountCall.length]; omega) + have hb : y.regs (CountCall.source j) = x.regs (CountCall.source j) := + hr _ (by dsimp [P, CountCall.source]; omega) + have hws : y.regs CountCall.scratch = x.regs CountCall.scratch := hr _ (by decide) + have hwh : y.regs CountCall.histogram = x.regs CountCall.histogram := hr _ (by decide) + have hwm : y.regs CountCall.modulus = encode (M : ℤ) := (hr _ (by decide)).trans hmodreg + obtain ⟨z, he, hzm, hzv, hzr⟩ := CountCall.exec j y (values j) hM hmod hwm + (by simpa only [hl] using hfit j) (by simpa only [hb, hl] using hs j) + (by simpa only [hws, hl] using hk j) (by simpa only [hwh] using hd) + (by simpa only [hl] using ht j) (by simpa only [hb, hl, hws] using hsk j) + (by simpa only [hws, hl, hwh] using hkd j) + (by + intro i hi + have hi' : i < x.regs (CountCall.length j) := by simpa only [hl] using hi + rw [hb, hm (x.regs (CountCall.source j) + i) (by have := hsk j; omega)] + exact hinput j i hi') + (by simpa only [hl] using hinj j) + refine ⟨z, by simpa only [hl] using he, ?_, by simpa only [hl] using hzv, hzr⟩ + refine ⟨?_, ?_⟩ + · intro i hi + have hlow : 28 ≤ i.val := by dsimp [P] at hi; omega + have hne : i ≠ CountCall.result j := by + intro heq + have := congrArg Fin.val heq + dsimp [P, CountCall.result] at * + omega + exact (hzr i hlow hne).trans (hr i hi) + · intro a ha + rw [hzm, Count.memory_before] + · exact hm a ha + · simpa only [hws] using ha + · have := hkd j + rw [hwh] + omega + have hx : R x := ⟨fun _ _ => rfl, fun _ _ => rfl⟩ + obtain ⟨a, ha, hRa, hva, hfa⟩ := hcall 0 x hx + obtain ⟨b, hb, hRb, hvb, hfb⟩ := hcall 1 a hRa + obtain ⟨c, hc, hRc, hvc, hfc⟩ := hcall 2 b hRb + refine ⟨c, (ha.seq (hb.seq hc)).mono (by omega), ?_, hRc⟩ + intro j + fin_cases j + · exact (hfc (CountCall.result 0) (by decide) (by decide)).trans + ((hfb (CountCall.result 0) (by decide) (by decide)).trans hva) + · exact (hfc (CountCall.result 1) (by decide) (by decide)).trans hvb + · exact hvc + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Query.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Query.lean new file mode 100644 index 0000000000..9e61f1a50a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Query.lean @@ -0,0 +1,99 @@ +/- +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.CountTriple +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CollisionTest + +/-! +# Complete native collision queries for Chan–He prime searches + +A query computes all three collision counts and tests their scaled thresholds. No count or +acceptance test is supplied as advice: all counting and comparison instructions are charged. + +## Main definitions + +* `Query.program`: the complete native collision-count and threshold query. + +## Main results + +* `Query.exec`: exact three-inequality acceptance, input frame and instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Query + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- Count collisions and test all three inequalities. -/ +def program : Code .standard 64 := .seq CountTriple.program CollisionTest.program + +/-- Execute a complete three-set collision query within +`34(n₀+n₁+n₂)+15M+85` instructions. Multiplication fits are bounded by `nⱼ²·card`, +independently of the actual input collision count. The final halt is excluded. -/ +theorem exec {w M : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) (hM : 0 < M) + (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmodreg : x.regs CountCall.modulus = encode (M : ℤ)) + (hfit : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hs : ∀ j, Fits (w + 2) (x.regs (CountCall.source j) + x.regs (CountCall.length j))) + (hk : ∀ j, Fits (w + 2) (x.regs CountCall.scratch + x.regs (CountCall.length j))) + (hd : Fits (w + 2) (x.regs CountCall.histogram + M)) + (ht : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) * x.regs (CountCall.length j) + + x.regs (CountCall.length j) + 1)) + (hproduct : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) * x.regs (CountCall.length j) * + x.regs CollisionTest.card)) + (hz : x.regs CollisionTest.zero = 0) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y + (34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * M + 85) ∧ + y.regs CollisionTest.good = (if + coll ((Finset.range (x.regs (CountCall.length 0))).image (values 0)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 0) ∧ + coll ((Finset.range (x.regs (CountCall.length 1))).image (values 1)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 1) ∧ + coll ((Finset.range (x.regs (CountCall.length 2))).image (values 2)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 2) then 1 else 0) ∧ + (∀ i, ((28 ≤ i.val ∧ i.val < 37) ∨ 40 ≤ i.val) → + i ≠ CollisionTest.scaled → i ≠ CollisionTest.good → y.regs i = x.regs i) ∧ + ∀ a < x.regs CountCall.scratch, y.mem a = x.mem a := by + obtain ⟨a, ha, hav, har, ham⟩ := CountTriple.exec x values hM hmod hmodreg + hfit hs hk hd ht hsk hkd hinput hinj + have hac : a.regs CollisionTest.card = x.regs CollisionTest.card := har _ (by decide) + have hat (j : Fin 3) : a.regs (CollisionTest.threshold j) = + x.regs (CollisionTest.threshold j) := har _ (by dsimp [CollisionTest.threshold]; omega) + have h1 : Fits (w + 2) 1 := (show Fits 2 1 from by decide).mono (by omega) + have hprod (j : Fin 3) : + Fits (w + 2) (a.regs (CountCall.result j) * a.regs CollisionTest.card) := by + rw [hav j, hac] + apply (hproduct j).of_le + apply Nat.mul_le_mul_right + have hcard : ((Finset.range (x.regs (CountCall.length j))).image (values j)).card ≤ + x.regs (CountCall.length j) := + Finset.card_image_le.trans_eq (Finset.card_range _) + exact (coll_le_sq _ _).trans (by simpa only [pow_two] using Nat.mul_self_le_mul_self hcard) + obtain ⟨b, hb, hbg, hbm, hbr⟩ := CollisionTest.exec a h1 + ((har _ (by decide)).trans hz) hprod + refine ⟨b, (ha.seq hb).mono (by omega), ?_, ?_, ?_⟩ + · simpa only [hav, hac, hat] using hbg + · intro i hi hs hg + exact (hbr i hs hg).trans (har i hi) + · intro i hi + rw [hbm] + exact ham i hi + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Query diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Search.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Search.lean new file mode 100644 index 0000000000..eef9c8d51e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Search.lean @@ -0,0 +1,507 @@ +/- +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.PrimeTable +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query +import Mathlib.Tactic.FinCases +import Mathlib.Tactic.NormNum + +/-! +# Native least-prime collision searches + +The search scans the prime bitmap in increasing order. A candidate runs the complete native +three-set collision query. Its first acceptance is retained while the remaining candidates +are visited without further bitmap reads. If no candidate is accepted, the answer is one, matching +`ChanHe.minOrOne`. + +## Main definitions + +* `Search.program`: a fixed native prime search using charged collision queries. + +## Main results + +* `Search.exec`: exact minimum/default selection with native fits, frames and charged cost. +* `Search.preserves_caller`: the upper five caller registers are preserved. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Search + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- Largest candidate prime. -/ +abbrev limit : Fin 64 := 40 +/-- Prime bitmap base. -/ +abbrev primes : Fin 64 := 41 +/-- Factor multiplied by the candidate prime. -/ +abbrev factor : Fin 64 := 43 +/-- Current candidate. -/ +abbrev cursor : Fin 64 := 47 +/-- One past the largest candidate. -/ +abbrev stop : Fin 64 := 48 +/-- Selected prime, or the default one. -/ +abbrev answer : Fin 64 := 49 +/-- Whether an acceptance has already occurred. -/ +abbrev found : Fin 64 := 50 +/-- Constant one. -/ +abbrev one : Fin 64 := 51 +/-- Constant zero. -/ +abbrev zero : Fin 64 := 52 +/-- Constant two, for positive signed encoding. -/ +abbrev two : Fin 64 := 53 +/-- Unencoded candidate modulus. -/ +abbrev rawModulus : Fin 64 := 54 +/-- Bitmap address scratch. -/ +abbrev addr : Fin 64 := 55 +/-- Loaded primality bit. -/ +abbrev bit : Fin 64 := 56 +/-- Encoded candidate modulus. -/ +abbrev modulus : Fin 64 := CountCall.modulus +/-- Acceptance flag returned by the collision query. -/ +abbrev good : Fin 64 := CollisionTest.good + +/-- Compute and encode the candidate modulus, then run the full collision query. -/ +def query : Code .standard 64 := + .seq (ram[ rawModulus ← factor * cursor; modulus ← rawModulus * two ]) Query.program + +/-- Record an accepted candidate, or retain the previous answer. -/ +def pick : Code .standard 64 := ram[ + if good = one then [ answer ← cursor; found ← one ] else [ answer ← answer ] ] + +/-- A charged instruction that retains the answer. -/ +def nop : Code .standard 64 := ram[ answer ← answer ] + +/-- Load a bitmap entry. -/ +def load : Code .standard 64 := ram[ addr ← primes + cursor; bit ← *addr ] + +/-- Before the first acceptance, run the query exactly on prime candidates. -/ +def inner : Code .standard 64 := + .branch (.eq found zero) + (.seq load (.branch (.eq bit one) (.seq query pick) nop)) nop + +/-- One candidate and its charged cursor increment. -/ +def body : Code .standard 64 := .seq inner (ram[ cursor ← cursor + one ]) + +/-- Initialize the selector and scan the entire bounded candidate range. -/ +def program : Code .standard 64 := + .seq (ram[ one ← #1; zero ← #0; two ← #2; cursor ← #0; + stop ← limit + one; answer ← one; found ← zero ]) (.loop (.lt cursor stop) body) + +/-- The five upper caller registers survive the complete native search. -/ +theorem preserves_caller (i : Fin 64) (hi : 59 ≤ i.val) : program.Preserves i := by + fin_cases i <;> norm_num at hi <;> decide + +private abbrev Fixed (i : Fin 64) : Prop := (28 ≤ i.val ∧ i.val ≤ 35) ∨ (40 ≤ i.val ∧ i.val ≤ 46) + +private abbrev Control (i : Fin 64) : Prop := + (28 ≤ i.val ∧ i.val ≤ 35) ∨ (40 ≤ i.val ∧ i.val ≤ 53) + +private def Stable (x y : Data 64) : Prop := + (∀ i, Fixed i → y.regs i = x.regs i) ∧ + ∀ a < x.regs CountCall.scratch, y.mem a = x.mem a + +private def Status (A : ℕ → Prop) (k : ℕ) (y : Data 64) : Prop := + (y.regs found = 0 ∧ y.regs answer = 1 ∧ ∀ q < k, ¬(q.Prime ∧ A q)) ∨ + (y.regs found = 1 ∧ y.regs answer < k ∧ (y.regs answer).Prime ∧ A (y.regs answer) ∧ + ∀ q < y.regs answer, ¬(q.Prime ∧ A q)) + +private theorem exec_nop {w : ℕ} (x : Data 64) : ExecWithin w nop x x 1 := by + have hx : x.set answer (x.regs answer) = x := Data.ext (Function.update_eq_self _ _) rfl + simpa only [nop, hx] using (ExecWithin.copy (B := .standard) (w := w) + (x := x) (d := answer) (s := answer)) + +private theorem exec_pick {w : ℕ} (x : Data 64) (A : Prop) [Decidable A] + (ho : x.regs one = 1) (hg : x.regs good = if A then 1 else 0) : + ∃ y, ExecWithin w pick x y 3 ∧ y.mem = x.mem ∧ + y.regs answer = (if A then x.regs cursor else x.regs answer) ∧ + y.regs found = (if A then 1 else x.regs found) ∧ + ∀ i, i ≠ answer → i ≠ found → y.regs i = x.regs i := by + by_cases hA : A + · let y := (x.set answer (x.regs cursor)).set found 1 + refine ⟨y, ?_, rfl, ?_, ?_, ?_⟩ + · ram_eval [pick, y, ho, hg, hA, good, one, cursor, answer, found, Fin.mk.injEq] + · simp +decide [y, hA, answer, found] + · simp [y, hA] + · intro i ha hf + simp only [y, Data.set_regs, Function.update_of_ne ha, Function.update_of_ne hf] + · refine ⟨x, ?_, rfl, by simp [hA], by simp [hA], fun _ _ _ => rfl⟩ + have hc : ¬(Cond.eq good one).Holds x := by simp [hg, hA, ho] + exact ExecWithin.branch_neg hc (exec_nop x) + +private theorem status_min {A : ℕ → Prop} [DecidablePred A] {m : ℕ} {y : Data 64} + (h : Status A (m + 1) y) : + y.regs answer = minOrOne {q ∈ Nat.primesLE m | A q} := by + let S := {q ∈ Nat.primesLE m | A q} + rcases h with ⟨hf, ha, hn⟩ | ⟨hf, hk, hp, hA, hn⟩ + · have hs : S = ∅ := by + apply Finset.eq_empty_iff_forall_notMem.mpr + intro q hq + have hq' := Finset.mem_filter.mp hq + have hmem := Finset.mem_filter.mp (by + simpa only [Nat.primesLE_eq_filter_range] using hq'.1) + exact hn q (Finset.mem_range.mp hmem.1) ⟨hmem.2, hq'.2⟩ + rw [ha] + change 1 = minOrOne S + simp [hs, minOrOne] + · have hmem : y.regs answer ∈ S := by + simp only [S, Finset.mem_filter, Nat.primesLE_eq_filter_range, Finset.mem_range] + exact ⟨⟨hk, hp⟩, hA⟩ + have hs : S.Nonempty := ⟨_, hmem⟩ + have hmin : S.min' hs = y.regs answer := by + apply le_antisymm (Finset.min'_le _ _ hmem) + apply Finset.le_min' + intro q hq + by_contra hnot + have hq' := Finset.mem_filter.mp hq + have hpq := (Finset.mem_filter.mp (by + simpa only [Nat.primesLE_eq_filter_range] using hq'.1)).2 + exact hn q (by omega) ⟨hpq, hq'.2⟩ + change y.regs answer = minOrOne S + simp only [minOrOne, dite_eq_left hs, hmin] + + +private theorem fixed_ne_high {i j : Fin 64} (hi : Fixed i) (hj : 47 ≤ j.val) : i ≠ j := by + intro h + have := congrArg Fin.val h + dsimp [Fixed] at hi + omega + +private theorem stable_set_high {x y : Data 64} (h : Stable x y) (i : Fin 64) + (hi : 47 ≤ i.val) (v : ℕ) : Stable x (y.set i v) := by + exact ⟨fun j hj => by + simpa only [Data.set_regs, Function.update_of_ne (fixed_ne_high hj hi)] using h.1 j hj, + h.2⟩ + +private theorem status_of_regs {A : ℕ → Prop} {k : ℕ} {x y : Data 64} + (hf : y.regs found = x.regs found) (ha : y.regs answer = x.regs answer) + (h : Status A k x) : Status A k y := by + simpa only [Status, hf, ha] using h + +private theorem status_extend {A : ℕ → Prop} {k : ℕ} {y : Data 64} + (h : Status A k y) (hnew : y.regs found = 0 → ¬(k.Prime ∧ A k)) : + Status A (k + 1) y := by + rcases h with ⟨hf, ha, hn⟩ | ⟨hf, hk, hp, hA, hn⟩ + · exact Or.inl ⟨hf, ha, fun q hq => by + by_cases hqk : q < k + · exact hn q hqk + · have : q = k := by omega + subst q + exact hnew hf⟩ + · exact Or.inr ⟨hf, by omega, hp, hA, hn⟩ + +private theorem exec_driver {w T : ℕ} (x : Data 64) (A : ℕ → Prop) [DecidablePred A] + (hfit : Fits w (x.regs limit + 3)) + (haddr : Fits w (x.regs primes + (x.regs limit + 1))) + (hbefore : x.regs primes + (x.regs limit + 1) ≤ x.regs CountCall.scratch) + (hbitmap : ∀ q ≤ x.regs limit, x.mem (x.regs primes + q) = if q.Prime then 1 else 0) + (hquery : ∀ y, Stable x y → y.regs cursor ≤ x.regs limit → + y.regs one = 1 → y.regs zero = 0 → y.regs two = 2 → (y.regs cursor).Prime → + ∃ z, ExecWithin w query y z T ∧ Stable x z ∧ + z.regs good = (if A (y.regs cursor) then 1 else 0) ∧ + ∀ i, Control i → z.regs i = y.regs i) : + ∃ y, ExecWithin w program x y ((x.regs limit + 1) * (T + 15) + 9) ∧ + Stable x y ∧ y.regs answer = minOrOne {q ∈ Nat.primesLE (x.regs limit) | A q} := by + let m := x.regs limit + let I (y : Data 64) : Prop := Stable x y ∧ y.regs stop = m + 1 ∧ + y.regs one = 1 ∧ y.regs zero = 0 ∧ y.regs two = 2 ∧ y.regs cursor ≤ m + 1 ∧ + Status A (y.regs cursor) y + have hbody (y : Data 64) (hy : I y) (hc : (Cond.lt cursor stop).Holds y) : + ∃ z, ExecWithin w body y z (T + 13) ∧ I z ∧ + m + 1 - z.regs cursor < m + 1 - y.regs cursor := by + obtain ⟨hstable, hstop, ho, hz, ht, hi, hstatus⟩ := hy + have hlt : y.regs cursor < m + 1 := by simpa only [Cond.holds_lt, hstop] using hc + have hinner : ∃ z, ExecWithin w inner y z (T + 12) ∧ Stable x z ∧ + z.regs stop = m + 1 ∧ z.regs one = 1 ∧ z.regs zero = 0 ∧ z.regs two = 2 ∧ + z.regs cursor = y.regs cursor ∧ Status A (y.regs cursor + 1) z := by + rcases hstatus with hnone | hsome + · obtain ⟨hf, ha, hn⟩ := hnone + have hfound : (Cond.eq found zero).Holds y := by simp [hf, hz] + have hpr : y.regs primes = x.regs primes := hstable.1 _ (by decide) + have hread : y.mem (y.regs primes + y.regs cursor) = + if (y.regs cursor).Prime then 1 else 0 := by + rw [hpr, hstable.2 (x.regs primes + y.regs cursor) (by omega)] + exact hbitmap _ (by change y.regs cursor ≤ m; omega) + have had : Fits w (y.regs primes + y.regs cursor) := haddr.of_le (by rw [hpr]; omega) + let u := (y.set addr (y.regs primes + y.regs cursor)).set bit + (if (y.regs cursor).Prime then 1 else 0) + have hload : ExecWithin w load y u 2 := by + ram_eval [load, u, had, hread, addr, bit, primes, cursor, Fin.mk.injEq] + have hu (i : Fin 64) (hC : Control i) : u.regs i = y.regs i := by + have hia : i ≠ addr := by intro h; subst i; norm_num [Control, addr] at hC + have hib : i ≠ bit := by intro h; subst i; norm_num [Control, bit] at hC + simp only [u, Data.set_regs, Function.update_of_ne hia, Function.update_of_ne hib] + have hus : Stable x u := stable_set_high + (stable_set_high hstable addr (by decide) _) bit (by decide) _ + have huo : u.regs one = 1 := (hu one (by decide)).trans ho + have huz : u.regs zero = 0 := (hu zero (by decide)).trans hz + have hut : u.regs two = 2 := (hu two (by decide)).trans ht + have hui : u.regs cursor = y.regs cursor := hu cursor (by decide) + have husp : u.regs stop = m + 1 := (hu stop (by decide)).trans hstop + have huf : u.regs found = 0 := (hu found (by decide)).trans hf + have hua : u.regs answer = 1 := (hu answer (by decide)).trans ha + by_cases hp : (y.regs cursor).Prime + · have hbit : (Cond.eq bit one).Holds u := by + simp only [Cond.holds_eq, huo] + simp +decide [u, bit, addr, hp] + obtain ⟨v, hv, hvs, hvg, hvr⟩ := hquery u hus (by rw [hui]; change _ ≤ m; omega) + huo huz hut (by simpa only [hui] using hp) + have hvo : v.regs one = 1 := (hvr one (by decide)).trans huo + obtain ⟨z, hpick, hzm, hza, hzf, hzr⟩ := exec_pick v (A (y.regs cursor)) hvo + (by simpa only [hui] using hvg) + have hzctrl (i : Fin 64) (hC : Control i) (hia : i ≠ answer) (hif : i ≠ found) : + z.regs i = y.regs i := + (hzr i hia hif).trans ((hvr i hC).trans (hu i hC)) + have hzs : Stable x z := ⟨fun i hF => + (hzr i (fixed_ne_high hF (by decide)) (fixed_ne_high hF (by decide))).trans + (hvs.1 i hF), fun a ha => by rw [hzm]; exact hvs.2 a ha⟩ + have hvi : v.regs cursor = y.regs cursor := (hvr cursor (by decide)).trans hui + have hvf : v.regs found = 0 := (hvr found (by decide)).trans huf + have hva : v.regs answer = 1 := (hvr answer (by decide)).trans hua + refine ⟨z, ?_, hzs, ?_, ?_, ?_, ?_, hzctrl cursor (by decide) (by decide) (by decide), ?_⟩ + · exact (ExecWithin.branch_pos hfound + (hload.seq (ExecWithin.branch_pos hbit (hv.seq hpick)))).mono (by omega) + · exact (hzctrl stop (by decide) (by decide) (by decide)).trans hstop + · exact (hzctrl one (by decide) (by decide) (by decide)).trans ho + · exact (hzctrl zero (by decide) (by decide) (by decide)).trans hz + · exact (hzctrl two (by decide) (by decide) (by decide)).trans ht + · by_cases hA : A (y.regs cursor) + · have hanswer : z.regs answer = y.regs cursor := by + simpa only [hA, ↓reduceIte, hvi] using hza + refine Or.inr ⟨by simpa only [hA, ↓reduceIte] using hzf, ?_, ?_, ?_, ?_⟩ + · rw [hanswer]; omega + · simpa only [hanswer] using hp + · simpa only [hanswer] using hA + · simpa only [hanswer] using hn + · have hanswer : z.regs answer = 1 := by simpa only [hA, ↓reduceIte, hva] using hza + refine Or.inl ⟨by simpa only [hA, ↓reduceIte, hvf] using hzf, hanswer, ?_⟩ + intro q hq hqA + by_cases hqk : q < y.regs cursor + · exact hn q hqk hqA + · have hqeq : q = y.regs cursor := by omega + subst q + exact hA hqA.2 + · have hbit : ¬(Cond.eq bit one).Holds u := by + simp only [Cond.holds_eq, huo] + simp +decide [u, bit, addr, hp] + have hnop := exec_nop (w := w) u + refine ⟨u, ?_, hus, husp, huo, huz, hut, hui, Or.inl ⟨huf, hua, ?_⟩⟩ + · exact (ExecWithin.branch_pos hfound + (hload.seq (ExecWithin.branch_neg hbit hnop))).mono (by omega) + · intro q hq hqA + by_cases hqk : q < y.regs cursor + · exact hn q hqk hqA + · have hqeq : q = y.regs cursor := by omega + subst q + exact hp hqA.1 + · have hfound : ¬(Cond.eq found zero).Holds y := by + simp only [Cond.holds_eq, hsome.1, hz]; omega + refine ⟨y, (ExecWithin.branch_neg hfound (exec_nop y)).mono (by omega), + hstable, hstop, ho, hz, ht, rfl, ?_⟩ + exact status_extend (Or.inr hsome) (fun h0 => by rw [hsome.1] at h0; omega) + obtain ⟨z, he, hs, hsp, hzo, hzz, hzt, hzi, hstat⟩ := hinner + have hinc : Fits w (z.regs cursor + z.regs one) := + hfit.of_le (by rw [hzi, hzo]; change _ ≤ m + 3; omega) + have hstep : ExecWithin w (Code.op (.bin .add cursor cursor one)) z + (z.set cursor (z.regs cursor + 1)) 1 := by + simpa only [BinOp.eval, hzo] using (ExecWithin.bin (f := .add) (d := cursor) (i := cursor) + (j := one) hinc) + refine ⟨z.set cursor (z.regs cursor + 1), he.seq hstep, ?_, ?_⟩ + · refine ⟨stable_set_high hs cursor (by decide) _, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [stop, cursor] using hsp + · simpa +decide [one, cursor] using hzo + · simpa +decide [zero, cursor] using hzz + · simpa +decide [two, cursor] using hzt + · simpa only [Data.set_regs, Function.update_self, hzi] using Nat.succ_le_of_lt hlt + · simpa only [Data.set_regs, Function.update_self] using + status_of_regs (x := z) (y := z.set cursor (z.regs cursor + 1)) + (by simp +decide [found, cursor]) + (by simp +decide [answer, cursor]) (by simpa only [hzi] using hstat) + · simp only [Data.set_regs, Function.update_self, hzi] + omega + let x₀ := ((((((x.set one 1).set zero 0).set two 2).set cursor 0).set stop (m + 1)).set + answer 1).set found 0 + have hinit : ExecWithin w (ram[ + one ← #1; zero ← #0; two ← #2; cursor ← #0; stop ← limit + one; + answer ← one; found ← zero]) x x₀ 7 := by + have h1 : Fits w 1 := hfit.of_le (by omega) + have h2 : Fits w 2 := hfit.of_le (by omega) + have h0 : Fits w 0 := hfit.of_le (by omega) + have hm : Fits w (m + 1) := hfit.of_le (by change m + 1 ≤ m + 3; omega) + ram_eval [x₀, m, one, zero, two, cursor, stop, limit, answer, found, + h1, h2, h0, hm, Fin.mk.injEq] + have hi : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, Or.inl ⟨?_, ?_, ?_⟩⟩ + · exact stable_set_high (stable_set_high (stable_set_high (stable_set_high + (stable_set_high (stable_set_high (stable_set_high ⟨fun _ _ => rfl, + fun _ _ => rfl⟩ one (by decide) _) zero (by decide) _) two (by decide) _) + cursor (by decide) _) stop (by decide) _) answer (by decide) _) found (by decide) _ + all_goals simp +decide [x₀, stop, one, zero, two, cursor, answer, found] + obtain ⟨y, he, hy, hexit⟩ := ExecWithin.loop_invariant I + (fun y => m + 1 - y.regs cursor) hbody hi + obtain ⟨hs, hsp, hyo, hyz, hyt, hyi, hstat⟩ := hy + have hcur : y.regs cursor = m + 1 := by + simp only [Cond.holds_lt, hsp] at hexit + omega + refine ⟨y, (hinit.seq he).mono ?_, hs, ?_⟩ + · have hcur₀ : x₀.regs cursor = 0 := by + simp +decide [x₀, cursor, found, answer, stop] + rw [hcur₀] + simp only [Nat.sub_zero] + change 7 + ((m + 1) * (T + 13 + 2) + 2) ≤ (m + 1) * (T + 15) + 9 + have hcost : T + 13 + 2 = T + 15 := by omega + rw [hcost] + omega + · exact status_min (by simpa only [hcur] using hstat) + +/-- The least prime whose three actual collision counts satisfy the supplied thresholds. +Every candidate query is executed by `Query.program`; the hypotheses only constrain native +input values, layout and word fits. All instructions, including the scan after acceptance, +are charged. The final halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) + (hfactor : 0 < x.regs factor) + (hmod : GenericRAM.Signed.Fits w ((x.regs factor * x.regs limit : ℕ) : ℤ)) + (hfit : Fits (w + 2) (x.regs limit + 3)) + (haddr : Fits (w + 2) (x.regs primes + (x.regs limit + 1))) + (hbefore : x.regs primes + (x.regs limit + 1) ≤ x.regs CountCall.scratch) + (hbitmap : ∀ q ≤ x.regs limit, x.mem (x.regs primes + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hs : ∀ j, Fits (w + 2) (x.regs (CountCall.source j) + x.regs (CountCall.length j))) + (hk : ∀ j, Fits (w + 2) (x.regs CountCall.scratch + x.regs (CountCall.length j))) + (hd : Fits (w + 2) (x.regs CountCall.histogram + x.regs factor * x.regs limit)) + (ht : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) * x.regs (CountCall.length j) + + x.regs (CountCall.length j) + 1)) + (hproduct : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) * x.regs (CountCall.length j) * + x.regs CollisionTest.card)) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y + ((x.regs limit + 1) * (34 * (x.regs (CountCall.length 0) + + x.regs (CountCall.length 1) + x.regs (CountCall.length 2)) + + 15 * (x.regs factor * x.regs limit) + 102) + 9) ∧ + (∀ i, ((28 ≤ i.val ∧ i.val ≤ 35) ∨ (40 ≤ i.val ∧ i.val ≤ 46)) → + y.regs i = x.regs i) ∧ + (∀ a < x.regs CountCall.scratch, y.mem a = x.mem a) ∧ + y.regs answer = minOrOne {q ∈ Nat.primesLE (x.regs limit) | ∀ j : Fin 3, + coll ((Finset.range (x.regs (CountCall.length j))).image (values j)) + (x.regs factor * q) * x.regs CollisionTest.card ≤ + x.regs (CollisionTest.threshold j)} := by + let A (q : ℕ) : Prop := ∀ j : Fin 3, + coll ((Finset.range (x.regs (CountCall.length j))).image (values j)) + (x.regs factor * q) * x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold j) + let T := 34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * (x.regs factor * x.regs limit) + 87 + have hquery (y : Data 64) (hy : Stable x y) (hiq : y.regs cursor ≤ x.regs limit) + (ho : y.regs one = 1) (hz : y.regs zero = 0) (htwo : y.regs two = 2) + (hp : (y.regs cursor).Prime) : + ∃ z, ExecWithin (w + 2) query y z T ∧ Stable x z ∧ + z.regs good = (if A (y.regs cursor) then 1 else 0) ∧ + ∀ i, Control i → z.regs i = y.regs i := by + let M := x.regs factor * y.regs cursor + have hM : 0 < M := Nat.mul_pos hfactor hp.pos + have hMle : M ≤ x.regs factor * x.regs limit := Nat.mul_le_mul_left _ hiq + have hMf : GenericRAM.Signed.Fits w (M : ℤ) := by + refine ⟨hmod.1, ?_, lt_of_le_of_lt (by exact_mod_cast hMle) hmod.2.2⟩ + have hnonneg : (0 : ℤ) ≤ M := Nat.cast_nonneg _ + have hpower : (0 : ℤ) ≤ 2 ^ (w - 1) := by positivity + omega + have henc : encode (M : ℤ) = 2 * M := by simp [encode] + have hencoded : Fits (w + 2) (2 * M) := by + rw [← henc] + exact (encode_fits hMf).mono (by omega) + have hraw : Fits (w + 2) M := hencoded.of_le (by omega) + have hyf : y.regs factor = x.regs factor := hy.1 _ (by decide) + let u := (y.set rawModulus M).set modulus (2 * M) + have hprep : ExecWithin (w + 2) + (ram[ rawModulus ← factor * cursor; modulus ← rawModulus * two ]) y u 2 := by + ram_eval [u, M, hyf, htwo, hraw, hencoded, rawModulus, modulus, factor, cursor, two, + CountCall.modulus, Fin.mk.injEq, Nat.mul_comm] + have huc (i : Fin 64) (hi : Control i) : u.regs i = y.regs i := by + have hr : i ≠ rawModulus := by intro h; subst i; norm_num [Control, rawModulus] at hi + have hm : i ≠ modulus := by + intro h; subst i; norm_num [Control, modulus, CountCall.modulus] at hi + simp only [u, Data.set_regs, Function.update_of_ne hr, Function.update_of_ne hm] + have huf (i : Fin 64) (hi : Fixed i) : u.regs i = x.regs i := + (huc i (by unfold Control Fixed at *; omega)).trans (hy.1 i hi) + have hlen (j : Fin 3) : u.regs (CountCall.length j) = x.regs (CountCall.length j) := + huf _ (by dsimp [CountCall.length, Fixed]; omega) + have hsrc (j : Fin 3) : u.regs (CountCall.source j) = x.regs (CountCall.source j) := + huf _ (by dsimp [CountCall.source, Fixed]; omega) + have hscratch : u.regs CountCall.scratch = x.regs CountCall.scratch := huf _ (by decide) + have hhist : u.regs CountCall.histogram = x.regs CountCall.histogram := huf _ (by decide) + have hcard : u.regs CollisionTest.card = x.regs CollisionTest.card := huf _ (by decide) + have hcut (j : Fin 3) : u.regs (CollisionTest.threshold j) = + x.regs (CollisionTest.threshold j) := + huf _ (by dsimp [Fixed, CollisionTest.threshold]; omega) + have hmodreg : u.regs CountCall.modulus = encode (M : ℤ) := by + simpa only [henc] using (show u.regs modulus = 2 * M from by simp [u]) + have huinput : ∀ j i, i < u.regs (CountCall.length j) → + u.mem (u.regs (CountCall.source j) + i) = encode (values j i) := by + intro j i hi + rw [hlen] at hi + change y.mem (u.regs (CountCall.source j) + i) = _ + rw [hsrc, hy.2 _ (by have := hsk j; omega)] + exact hinput j i hi + obtain ⟨z, he, hgood, hreg, hmem⟩ := Query.exec u values hM hMf hmodreg + (by simpa only [hlen] using hvalues) (by simpa only [hsrc, hlen] using hs) + (by simpa only [hscratch, hlen] using hk) + (by rw [hhist]; exact hd.of_le (Nat.add_le_add_left hMle _)) + (by simpa only [hlen] using ht) (by simpa only [hlen, hcard] using hproduct) + ((huc zero (by decide)).trans hz) + (by simpa only [hsrc, hlen, hscratch] using hsk) + (by simpa only [hscratch, hlen, hhist] using hkd) huinput + (by simpa only [hlen] using hinj) + have hcontrol (i : Fin 64) (hi : Control i) : z.regs i = y.regs i := by + have hscl : i ≠ CollisionTest.scaled := by + intro h; subst i; norm_num [Control, CollisionTest.scaled] at hi + have hg : i ≠ CollisionTest.good := by + intro h; subst i; norm_num [Control, CollisionTest.good] at hi + exact (hreg i (by unfold Control at hi; omega) hscl hg).trans (huc i hi) + refine ⟨z, (hprep.seq he).mono ?_, ?_, ?_, hcontrol⟩ + · dsimp only [T] + rw [hlen 0, hlen 1, hlen 2] + have := Nat.mul_le_mul_left 15 hMle + omega + · refine ⟨fun i hi => (hcontrol i (by unfold Fixed Control at *; omega)).trans + (hy.1 i hi), ?_⟩ + intro a ha + rw [hmem a (by simpa only [hscratch] using ha)] + exact hy.2 a ha + · have hA : A (y.regs cursor) ↔ + coll ((Finset.range (x.regs (CountCall.length 0))).image (values 0)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 0) ∧ + coll ((Finset.range (x.regs (CountCall.length 1))).image (values 1)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 1) ∧ + coll ((Finset.range (x.regs (CountCall.length 2))).image (values 2)) M * + x.regs CollisionTest.card ≤ x.regs (CollisionTest.threshold 2) := by + constructor + · intro h; exact ⟨h 0, h 1, h 2⟩ + · intro h j + fin_cases j + · exact h.1 + · exact h.2.1 + · exact h.2.2 + simpa only [hlen, hcard, hcut, ← hA] using hgood + obtain ⟨y, he, hs, ha⟩ := exec_driver x A hfit haddr hbefore hbitmap hquery + refine ⟨y, he.mono ?_, hs.1, hs.2, ha⟩ + dsimp only [T] + have hcost : 34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * (x.regs factor * x.regs limit) + 87 + 15 = + 34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * (x.regs factor * x.regs limit) + 102 := by omega + rw [hcost] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Search diff --git a/ComplexityTests/WordRAMChanHeQuery.lean b/ComplexityTests/WordRAMChanHeQuery.lean new file mode 100644 index 0000000000..efdddf0f61 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeQuery.lean @@ -0,0 +1,58 @@ +/- +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.Query + +/-! # Executed native three-set collision queries -/ + +namespace ComplexityTests.WordRAMChanHeQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +private def input : Data 64 := + ⟨fun i => if i.val = 28 then 1 else if i.val = 29 then 1 + else if i.val = 30 then 2 else if i.val = 31 then 5 + else if i.val = 32 then 0 else if i.val = 33 then 10 + else if i.val = 34 then 20 else if i.val = 35 then 30 + else if i.val = 36 then encode 3 else if i.val = 42 then 3 + else if i.val = 44 then 0 else if i.val = 45 then 6 + else if i.val = 46 then 0 else if i.val = 52 then 0 else 71, + fun a => if a = 1 then encode (-1) else if a = 2 then encode 0 + else if a = 3 then encode 1 else if a = 5 then encode (-5) + else if a = 6 then encode (-2) else if a = 7 then encode 1 + else if a = 8 then encode 4 else if a = 10 then encode 0 + else if a = 11 then encode 3 else 79⟩ + +-- The three complete counting calls unfold all renamed loops and their register maps. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +/-- Arrays of lengths one, two and zero test setup/result copies and empty-array counting. -/ +example : (Code.eval 8 3 CountTriple.program input).map + (fun yn => [yn.1.regs (CountCall.result 0), yn.1.regs (CountCall.result 1), + yn.1.regs (CountCall.result 2), yn.1.regs (40 : Fin 64), yn.1.mem 5]) = + some [0, 2, 0, 71, encode (-5)] := by decide + +-- The full query additionally reduces three scaled inequalities after the counting loops. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +/-- Equality at the scaled threshold is accepted, and caller controls and input are framed. -/ +example : (Code.eval 8 3 Query.program input).map + (fun yn => [yn.1.regs CollisionTest.good, yn.1.regs (40 : Fin 64), yn.1.mem 5]) = + some [1, 71, encode (-5)] := by decide + +-- This negative case executes the same complete native query with one smaller threshold. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +/-- A strict failure of one threshold rejects the three-set candidate. -/ +example : (Code.eval 8 3 Query.program + (input.set (CollisionTest.threshold 1) 5)).map (fun yn => yn.1.regs CollisionTest.good) = + some 0 := by decide + +end ComplexityTests.WordRAMChanHeQuery diff --git a/ComplexityTests/WordRAMChanHeSearch.lean b/ComplexityTests/WordRAMChanHeSearch.lean new file mode 100644 index 0000000000..11676596cb --- /dev/null +++ b/ComplexityTests/WordRAMChanHeSearch.lean @@ -0,0 +1,90 @@ +/- +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.Search + +/-! # Executed least-prime collision searches -/ + +namespace ComplexityTests.WordRAMChanHeSearch + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +private def emptyInput (m : ℕ) : Data 64 := + ⟨fun i => if i.val = 28 ∨ i.val = 30 ∨ i.val = 32 then 0 + else if i.val = 29 ∨ i.val = 31 ∨ i.val = 33 then 1 + else if i.val = 34 then 20 else if i.val = 35 then 30 + else if i.val = 40 then m else if i.val = 41 then 10 + else if i.val = 42 then 1 else if i.val = 43 then 1 + else if i.val = 44 ∨ i.val = 45 ∨ i.val = 46 then 0 else 71, + fun a => if a = 12 ∨ a = 13 then 1 else 0⟩ + +/-- Zero candidates return the specified default, without running a collision query. -/ +example : (Code.eval 8 2 Search.program (emptyInput 0)).map + (fun yn => yn.1.regs Search.answer) = some 1 := by decide + +/-- The nonprime boundary one still returns the default. -/ +example : (Code.eval 8 3 Search.program (emptyInput 1)).map + (fun yn => yn.1.regs Search.answer) = some 1 := by decide + +-- The positive case reduces the full renamed three-set query inside the search loop. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the complete native search exceeds the default heartbeat budget. +/-- The first prime is accepted at three zero thresholds for three empty sets. -/ +example : (Code.eval 8 4 Search.program (emptyInput 2)).map + (fun yn => [yn.1.regs Search.answer, yn.1.regs Search.found, yn.1.mem 12]) = + some [2, 1, 1] := by decide + +-- After accepting two, the search executes only the charged skip branch for three. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction includes the complete query and subsequent retained-answer iteration. +/-- Later accepted primes cannot overwrite the minimum or rerun the collision query. -/ +example : (Code.eval 8 5 Search.program (emptyInput 3)).map + (fun yn => [yn.1.regs Search.answer, yn.1.regs CountCall.modulus]) = + some [2, 4] := by decide + +-- The rejected prime still runs all three native collision-counting calls. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the complete rejected query exceeds the default heartbeat budget. +/-- The only prime fails because zero and two collide, so the answer is the default one. -/ +example : (Code.eval 8 4 Search.program + ⟨Function.update (emptyInput 2).regs (CountCall.length 0) 2, + Function.update (emptyInput 2).mem 2 4⟩).map + (fun yn => [yn.1.regs Search.answer, yn.1.regs Search.found]) = + some [1, 0] := by decide + +/-- The full execution contract has an admissible initialized instance, including every fit, +bitmap cell and input frame. -/ +example : ∃ y, ExecWithin 8 Search.program (emptyInput 2) y (3 * 132 + 9) ∧ + y.regs Search.answer = 2 := by + have hlen (j : Fin 3) : (emptyInput 2).regs (CountCall.length j) = 0 := by + fin_cases j <;> decide + have hcut (j : Fin 3) : (emptyInput 2).regs (CollisionTest.threshold j) = 0 := by + fin_cases j <;> decide + obtain ⟨y, he, hregs, hmem, ha⟩ := Search.exec (w := 6) (emptyInput 2) (fun _ _ => 0) + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro q hq; change q ≤ 2 at hq; interval_cases q <;> decide) + (by intros; decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by decide) (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by intro j i hi; rw [hlen] at hi; omega) + (by + intro j a ha + simp only [hlen, Finset.range_zero, Finset.coe_empty, Set.mem_empty_iff_false] at ha) + refine ⟨y, he, ?_⟩ + simp only [hlen, hcut, Finset.range_zero, Finset.image_empty, coll, Finset.offDiag_empty, + Finset.filter_empty, Finset.card_empty, Nat.zero_mul, Nat.le_refl, implies_true, + Finset.filter_true] at ha + have hselect : minOrOne (Nat.primesLE 2) = 2 := by decide + change y.regs Search.answer = minOrOne (Nat.primesLE 2) at ha + exact ha.trans hselect + +end ComplexityTests.WordRAMChanHeSearch diff --git a/ComplexityTests/WordRAMPrimeTable.lean b/ComplexityTests/WordRAMPrimeTable.lean new file mode 100644 index 0000000000..43c6cb6a40 --- /dev/null +++ b/ComplexityTests/WordRAMPrimeTable.lean @@ -0,0 +1,63 @@ +/- +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.PrimeTable + +/-! # Native trial division and prime bitmap regressions -/ + +namespace ComplexityTests.WordRAMPrimeTable + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM + +private def trialInput (n : ℕ) : Data 6 := (Data.init []).set PrimeTrial.arg n + +/-- The smallest candidate is prime; the first composite and a larger prime are distinguished. -/ +example : (Code.eval 8 8 PrimeTrial.program (trialInput 2)).map + (fun yn => yn.1.regs PrimeTrial.flag) = some 1 ∧ + (Code.eval 8 8 PrimeTrial.program (trialInput 4)).map + (fun yn => yn.1.regs PrimeTrial.flag) = some 0 ∧ + (Code.eval 8 8 PrimeTrial.program (trialInput 7)).map + (fun yn => yn.1.regs PrimeTrial.flag) = some 1 := by decide + +/-- The generic trial-division theorem is admissible on a composite candidate. -/ +example : ∃ y, ExecWithin 8 PrimeTrial.program (trialInput 4) y 20 ∧ + y.regs PrimeTrial.flag = 0 ∧ y.regs PrimeTrial.arg = 4 ∧ y.mem 0 = 0 := by + obtain ⟨y, hy, hf, ha, hm⟩ := PrimeTrial.exec (w := 8) (trialInput 4) + (by decide) (by decide) + exact ⟨y, hy, hf.trans (by decide), ha, by rw [hm]; rfl⟩ + +private def tableInput (n : ℕ) : Data 15 := + ⟨fun i => if i = PrimeTable.limit then n else if i = PrimeTable.base then 20 else 0, + fun _ => 79⟩ + +private def observe (n : ℕ) : Option (List ℕ × ℕ) := + (Code.eval 8 (n + 1) PrimeTable.program (tableInput n)).map fun yn => + ((List.range (n + 1)).map (fun i => yn.1.mem (20 + i)), yn.1.regs PrimeTable.count) + +/-- Zero and one are not prime; the length-two boundary adds the first prime bit. -/ +example : observe 0 = some ([0], 0) ∧ observe 1 = some ([0, 0], 0) ∧ + observe 2 = some ([0, 0, 1], 1) := by decide + +-- This concrete interpreter traverses all trial divisions up to ten and their register maps. +set_option maxRecDepth 4096 in +/-- The complete bitmap overwrites nonzero memory and counts all four primes up to ten. -/ +example : observe 10 = some ([0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0], 4) := by decide + +/-- The quantitative complete-table contract has a satisfiable frame and fit instance. -/ +example : ∃ y, ExecWithin 8 PrimeTable.program (tableInput 10) y 943 ∧ + y.regs PrimeTable.count = 4 ∧ y.mem 22 = 1 ∧ y.mem 24 = 0 ∧ + y.mem 19 = 79 ∧ y.mem 31 = 79 := by + obtain ⟨y, hy, hm, hc, _⟩ := PrimeTable.exec (w := 8) (tableInput 10) + (by decide) (by decide) + refine ⟨y, hy, hc.trans (by decide), ?_, ?_, ?_, ?_⟩ <;> rw [hm] <;> decide + +/-- A fitting base can still have a nonfitting bitmap endpoint, and then execution faults. -/ +example : (Code.eval 8 6 PrimeTable.program + ((tableInput 5).set PrimeTable.base 253)).map (fun yn => yn.1.regs PrimeTable.count) = + none := by decide + +end ComplexityTests.WordRAMPrimeTable diff --git a/ROADMAP.md b/ROADMAP.md index d31b9ede12..0af86de2b1 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -423,7 +423,10 @@ 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 + and the selected-prime false-positive bound ([#1442]) are proved mathematically. Native parameter, + collision-counting, prime-table and collision-tested minimum-search programs have explicit local + fit/frame/cost contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, + packed output and global preprocessing width/time composition remain. 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 @@ -552,4 +555,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 +[#1446]: https://github.com/cslib-community/complexitylib/issues/1446 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 diff --git a/UPSTREAM.md b/UPSTREAM.md index 8b49547dae..f7288d82ba 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -164,6 +164,11 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution are retained; the native proofs and representation choices are local. + Native numeric parameter computation, signed-residue/histogram counting, trial-division prime + tables and collision-tested least-prime scans also use the existing `WordRAM.Code`/`ExecWithin` + APIs. Their explicit local fit, frame and charged-cost contracts do not yet compose the complete + preprocessing from input through frontend, heavy sets, recursion and packed output; the global + logarithmic width and subquadratic preprocessing bound remain in #1446. `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 From a5a5391eeca0fb63d510bf41f8ea7a02480723e4 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 11:38:22 -0400 Subject: [PATCH 16/45] =?UTF-8?q?feat(wordram):=20compute=20the=20Chan?= =?UTF-8?q?=E2=80=93He=20two-prime=20modulus=20natively?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Complexity.lean | 1 + .../WordRAM/ThreeSum/ChanHe/Modulus.lean | 531 ++++++++++++++++++ ComplexityTests/WordRAMChanHeModulus.lean | 109 ++++ ROADMAP.md | 4 +- UPSTREAM.md | 5 +- 5 files changed, 646 insertions(+), 4 deletions(-) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean create mode 100644 ComplexityTests/WordRAMChanHeModulus.lean diff --git a/Complexity.lean b/Complexity.lean index 091db68880..b9265b0eda 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -112,6 +112,7 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean new file mode 100644 index 0000000000..d02fc2523e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean @@ -0,0 +1,531 @@ +/- +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.Search + +/-! +# Native setup of the two Chan–He prime searches + +The first search uses thresholds `3Λ|Sⱼ|²`. After saving its selected prime, native counting +at that prime provides the second thresholds `3Λ coll Sⱼ p`. Both searches use the complete +prime scan and collision queries, including explicit positive signed-modulus encoding. + +## Main definitions + +* `Modulus.firstSetup`: compute the first search's three thresholds. +* `Modulus.secondSetup`: compute the second thresholds from native counts. +* `Modulus.program`: compute the existing Chan–He modulus using two native minimum searches. + +## Main results + +* `Modulus.exec`: exact modulus, input frame and charged cost from explicit local fits. +* `Modulus.not_readsWidth`: the program does not inspect the physical word width. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- Collision divisor bound `Λ`. -/ +abbrev digits : Fin 64 := 59 +/-- First selected candidate, or one if none qualifies. -/ +abbrev first : Fin 64 := 60 +/-- Three times the collision divisor bound. -/ +abbrev multiplier : Fin 64 := 61 +/-- Unsigned product of the two selections, each defaulting to one. -/ +abbrev output : Fin 64 := 62 + +/-- Charge all six square/threshold multiplications and the three scalar setup instructions. -/ +def firstSetup : Code .standard 64 := ram[ + Search.rawModulus ← #3; multiplier ← digits * Search.rawModulus; Search.factor ← #1; + ⟪.op (.bin .mul (CollisionTest.threshold 0) (CountCall.length 0) (CountCall.length 0))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 0) multiplier (CollisionTest.threshold 0))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 1) (CountCall.length 1) (CountCall.length 1))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 1) multiplier (CollisionTest.threshold 1))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 2) (CountCall.length 2) (CountCall.length 2))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 2) multiplier (CollisionTest.threshold 2))⟫ ] + +/-- Charge all three second-threshold multiplications and their scalar setup. -/ +def secondSetup : Code .standard 64 := ram[ + Search.rawModulus ← #3; multiplier ← digits * Search.rawModulus; + ⟪.op (.bin .mul (CollisionTest.threshold 0) multiplier (CountCall.result 0))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 1) multiplier (CountCall.result 1))⟫; + ⟪.op (.bin .mul (CollisionTest.threshold 2) multiplier (CountCall.result 2))⟫; + Search.factor ← first ] + +/-- Run both concrete searches and compute their product. The prime table and its count are +shared by all nodes and are supplied in the search ABI. -/ +def program : Code .standard 64 := + .seq firstSetup (.seq Search.program + (.seq (ram[ first ← Search.answer; Search.two ← #2; CountCall.modulus ← first * Search.two ]) + (.seq CountTriple.program (.seq secondSetup + (.seq Search.program (ram[ output ← first * Search.answer ])))))) + +/-- The exact data after the first setup; memory is unchanged. -/ +def firstState (x : Data 64) : Data 64 := + ((((((((x.set Search.rawModulus 3).set multiplier (3 * x.regs digits)).set + Search.factor 1).set (CollisionTest.threshold 0) (x.regs (CountCall.length 0) ^ 2)).set + (CollisionTest.threshold 0) (3 * x.regs digits * x.regs (CountCall.length 0) ^ 2)).set + (CollisionTest.threshold 1) (x.regs (CountCall.length 1) ^ 2)).set + (CollisionTest.threshold 1) (3 * x.regs digits * x.regs (CountCall.length 1) ^ 2)).set + (CollisionTest.threshold 2) (x.regs (CountCall.length 2) ^ 2)).set + (CollisionTest.threshold 2) (3 * x.regs digits * x.regs (CountCall.length 2) ^ 2) + +/-- First-search thresholds are computed by nine actual native instructions. -/ +theorem exec_firstSetup {w : ℕ} (x : Data 64) (h3 : Fits w 3) + (htriple : Fits w (3 * x.regs digits)) + (hsq : ∀ j, Fits w (x.regs (CountCall.length j) ^ 2)) + (hcut : ∀ j, Fits w (3 * x.regs digits * x.regs (CountCall.length j) ^ 2)) : + ExecWithin w firstSetup x (firstState x) 9 := by + have h1 : Fits w 1 := h3.of_le (by omega) + simp only [digits] at htriple + simp only [CountCall.length, pow_two] at hsq hcut + ram_eval [firstSetup, firstState, h3, h1, htriple, hsq 0, hsq 1, hsq 2, + hcut 0, hcut 1, hcut 2, digits, multiplier, Search.rawModulus, Search.factor, + CountCall.length, CollisionTest.threshold, Fin.mk.injEq, pow_two, Nat.mul_comm] + +/-- The exact data after the second setup; memory is unchanged. -/ +def secondState (x : Data 64) : Data 64 := + (((((x.set Search.rawModulus 3).set multiplier (3 * x.regs digits)).set + (CollisionTest.threshold 0) (3 * x.regs digits * x.regs (CountCall.result 0))).set + (CollisionTest.threshold 1) (3 * x.regs digits * x.regs (CountCall.result 1))).set + (CollisionTest.threshold 2) (3 * x.regs digits * x.regs (CountCall.result 2))).set + Search.factor (x.regs first) + +/-- Second-search thresholds are computed by six actual native instructions. -/ +theorem exec_secondSetup {w : ℕ} (x : Data 64) (h3 : Fits w 3) + (htriple : Fits w (3 * x.regs digits)) + (hcut : ∀ j, Fits w (3 * x.regs digits * x.regs (CountCall.result j))) : + ExecWithin w secondSetup x (secondState x) 6 := by + simp only [digits] at htriple + simp only [CountCall.result] at hcut + ram_eval [secondSetup, secondState, h3, htriple, hcut, digits, multiplier, first, + Search.rawModulus, Search.factor, CountCall.result, CollisionTest.threshold, + Fin.mk.injEq, Nat.mul_comm] + +private theorem selector_bounds {m : ℕ} (hm : 1 ≤ m) (A : ℕ → Prop) [DecidablePred A] : + 1 ≤ minOrOne {q ∈ Nat.primesLE m | A q} ∧ + minOrOne {q ∈ Nat.primesLE m | A q} ≤ m := by + let S := {q ∈ Nat.primesLE m | A q} + by_cases h : S.Nonempty + · have hmem : S.min' h ∈ S := Finset.min'_mem _ _ + have hprime : (S.min' h).Prime ∧ S.min' h < m + 1 := by + have hh := (Finset.mem_filter.mp hmem).1 + rw [Nat.primesLE_eq_filter_range, Finset.mem_filter, Finset.mem_range] at hh + exact ⟨hh.2, hh.1⟩ + change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_left h] + exact ⟨by have := hprime.1.two_le; omega, by omega⟩ + · change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_right h] + exact ⟨le_refl _, hm⟩ + +private theorem forall_three {P : Fin 3 → Prop} : (∀ j, P j) ↔ P 0 ∧ P 1 ∧ P 2 := by + constructor + · intro h; exact ⟨h 0, h 1, h 2⟩ + · intro h j + fin_cases j + · exact h.1 + · exact h.2.1 + · exact h.2.2 + +private theorem first_selector (x : Data 64) (values : Fin 3 → ℕ → ℤ) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + minOrOne {q ∈ Nat.primesLE (x.regs Search.limit) | ∀ j : Fin 3, + coll ((Finset.range (x.regs (CountCall.length j))).image (values j)) q * + (Nat.primesLE (x.regs Search.limit)).card ≤ + 3 * x.regs digits * x.regs (CountCall.length j) ^ 2} = + firstP (Nat.primesLE (x.regs Search.limit)) (x.regs digits) + ((Finset.range (x.regs (CountCall.length 0))).image (values 0)) + ((Finset.range (x.regs (CountCall.length 1))).image (values 1)) + ((Finset.range (x.regs (CountCall.length 2))).image (values 2)) := by + have hcard (j : Fin 3) : ((Finset.range (x.regs (CountCall.length j))).image (values j)).card = + x.regs (CountCall.length j) := (Finset.card_image_of_injOn (hinj j)).trans + (Finset.card_range _) + simp only [firstP, hcard, forall_three] + +private abbrev Parameter (i : Fin 64) : Prop := + (28 ≤ i.val ∧ i.val ≤ 35) ∨ (40 ≤ i.val ∧ i.val ≤ 42) ∨ i = digits + +private def Same (x y : Data 64) : Prop := + (∀ i, Parameter i → y.regs i = x.regs i) ∧ + ∀ a < x.regs CountCall.scratch, y.mem a = x.mem a + +/-- One polynomial bound for selector counters, thresholds and memory addresses. -/ +def budget (x : Data 64) : ℕ := + let n := x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2) + x.regs CountCall.histogram + x.regs CountCall.scratch + x.regs Search.primes + + x.regs (CountCall.source 0) + x.regs (CountCall.source 1) + x.regs (CountCall.source 2) + + (n + 1) ^ 2 * (3 * x.regs digits + x.regs CollisionTest.card + 1) + + x.regs Search.limit ^ 2 + x.regs Search.limit + 100 + +private theorem length_le_sum (x : Data 64) (j : Fin 3) : x.regs (CountCall.length j) ≤ + x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2) := by + fin_cases j <;> dsimp [CountCall.length] <;> omega + +private theorem source_le_sum (x : Data 64) (j : Fin 3) : x.regs (CountCall.source j) ≤ + x.regs (CountCall.source 0) + x.regs (CountCall.source 1) + + x.regs (CountCall.source 2) := by + fin_cases j <;> dsimp [CountCall.source] <;> omega + +private theorem budget_bounds (x : Data 64) (j : Fin 3) : + x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ budget x ∧ + x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ budget x ∧ + x.regs CountCall.histogram + x.regs Search.limit ^ 2 ≤ budget x ∧ + x.regs Search.primes + (x.regs Search.limit + 1) ≤ budget x ∧ + x.regs Search.limit + 3 ≤ budget x ∧ + x.regs (CountCall.length j) ^ 2 + x.regs (CountCall.length j) + 1 ≤ budget x ∧ + x.regs (CountCall.length j) ^ 2 * x.regs CollisionTest.card ≤ budget x ∧ + 3 * x.regs digits * x.regs (CountCall.length j) ^ 2 ≤ budget x ∧ + 3 * x.regs digits ≤ budget x ∧ 3 ≤ budget x := by + let n := x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2) + let poly := (n + 1) ^ 2 * (3 * x.regs digits + x.regs CollisionTest.card + 1) + have hn : x.regs (CountCall.length j) ≤ n := length_le_sum x j + have hs := source_le_sum x j + have hpoly : (n + 1) ^ 2 ≤ poly := by + simpa only [Nat.mul_one] using Nat.mul_le_mul_left ((n + 1) ^ 2) + (show 1 ≤ 3 * x.regs digits + x.regs CollisionTest.card + 1 by omega) + have hsquare : x.regs (CountCall.length j) ^ 2 ≤ (n + 1) ^ 2 := by nlinarith + have hcard : x.regs (CountCall.length j) ^ 2 * x.regs CollisionTest.card ≤ poly := by + exact (Nat.mul_le_mul_right _ hsquare).trans + (Nat.mul_le_mul_left _ (by omega)) + have hcut : 3 * x.regs digits * x.regs (CountCall.length j) ^ 2 ≤ poly := by + rw [Nat.mul_comm (3 * x.regs digits)] + exact (Nat.mul_le_mul_right _ hsquare).trans + (Nat.mul_le_mul_left _ (by omega)) + have htriple : 3 * x.regs digits ≤ poly := by + have hbase : 1 ≤ (n + 1) ^ 2 := by nlinarith + exact (show 3 * x.regs digits ≤ 3 * x.regs digits * (n + 1) ^ 2 by + nlinarith).trans (by + rw [Nat.mul_comm] + exact Nat.mul_le_mul_left _ (by omega)) + have hb : budget x = x.regs CountCall.histogram + x.regs CountCall.scratch + + x.regs Search.primes + x.regs (CountCall.source 0) + x.regs (CountCall.source 1) + + x.regs (CountCall.source 2) + poly + x.regs Search.limit ^ 2 + + x.regs Search.limit + 100 := rfl + rw [hb] + have hsize : x.regs (CountCall.length j) ^ 2 + x.regs (CountCall.length j) + 1 ≤ poly := by + nlinarith + omega + +private theorem same_length {x y : Data 64} (h : Same x y) (j : Fin 3) : + y.regs (CountCall.length j) = x.regs (CountCall.length j) := + h.1 _ (by dsimp [Parameter, CountCall.length]; omega) + +private theorem same_source {x y : Data 64} (h : Same x y) (j : Fin 3) : + y.regs (CountCall.source j) = x.regs (CountCall.source j) := + h.1 _ (by dsimp [Parameter, CountCall.source]; omega) + +private theorem signed_nat_mono {w a b : ℕ} (h : GenericRAM.Signed.Fits w (b : ℤ)) + (hab : a ≤ b) : GenericRAM.Signed.Fits w (a : ℤ) := by + refine ⟨h.1, ?_, lt_of_le_of_lt (by exact_mod_cast hab) h.2.2⟩ + have hnonneg : (0 : ℤ) ≤ a := Nat.cast_nonneg _ + have hpower : (0 : ℤ) ≤ 2 ^ (w - 1) := by positivity + omega + +private structure LocalFits (w : ℕ) (x : Data 64) : Prop where + source : ∀ j, Fits (w + 2) (x.regs (CountCall.source j) + x.regs (CountCall.length j)) + scratch : ∀ j, Fits (w + 2) (x.regs CountCall.scratch + x.regs (CountCall.length j)) + histogram : Fits (w + 2) (x.regs CountCall.histogram + x.regs Search.limit ^ 2) + bitmap : Fits (w + 2) (x.regs Search.primes + (x.regs Search.limit + 1)) + limit : Fits (w + 2) (x.regs Search.limit + 3) + total : ∀ j, Fits (w + 2) + (x.regs (CountCall.length j) ^ 2 + x.regs (CountCall.length j) + 1) + product : ∀ j, Fits (w + 2) (x.regs (CountCall.length j) ^ 2 * x.regs CollisionTest.card) + cutoff : ∀ j, Fits (w + 2) (3 * x.regs digits * x.regs (CountCall.length j) ^ 2) + triple : Fits (w + 2) (3 * x.regs digits) + three : Fits (w + 2) 3 + +private theorem localFits_of_budget {w : ℕ} (x : Data 64) (h : Fits (w + 2) (budget x)) : + LocalFits w x := by + have hb := budget_bounds x 0 + refine ⟨fun j => h.of_le (budget_bounds x j).1, + fun j => h.of_le (budget_bounds x j).2.1, h.of_le hb.2.2.1, + h.of_le hb.2.2.2.1, h.of_le hb.2.2.2.2.1, ?_, ?_, ?_, + h.of_le hb.2.2.2.2.2.2.2.2.1, h.of_le hb.2.2.2.2.2.2.2.2.2⟩ + · exact fun j => h.of_le (budget_bounds x j).2.2.2.2.2.1 + · exact fun j => h.of_le (budget_bounds x j).2.2.2.2.2.2.1 + · exact fun j => h.of_le (budget_bounds x j).2.2.2.2.2.2.2.1 + +/-- One search with its factor bounded by the largest candidate. -/ +def searchTime (x : Data 64) : ℕ := + (x.regs Search.limit + 1) * (34 * (x.regs (CountCall.length 0) + + x.regs (CountCall.length 1) + x.regs (CountCall.length 2)) + + 15 * x.regs Search.limit ^ 2 + 102) + 9 + +private theorem exec_search {w : ℕ} (x y : Data 64) (values : Fin 3 → ℕ → ℤ) + (hmod : GenericRAM.Signed.Fits w ((x.regs Search.limit ^ 2 : ℕ) : ℤ)) + (hfits : LocalFits w x) (hy : Same x y) + (hfactor : 0 < y.regs Search.factor) (hfactorle : y.regs Search.factor ≤ x.regs Search.limit) + (hbefore : x.regs Search.primes + (x.regs Search.limit + 1) ≤ x.regs CountCall.scratch) + (hbitmap : ∀ q ≤ x.regs Search.limit, + x.mem (x.regs Search.primes + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ z, ExecWithin (w + 2) Search.program y z (searchTime x) ∧ Same x z ∧ + z.regs Search.answer = minOrOne {q ∈ Nat.primesLE (x.regs Search.limit) | ∀ j : Fin 3, + coll ((Finset.range (x.regs (CountCall.length j))).image (values j)) + (y.regs Search.factor * q) * x.regs CollisionTest.card ≤ + y.regs (CollisionTest.threshold j)} := by + have hl := same_length hy + have hs := same_source hy + have hk : y.regs CountCall.scratch = x.regs CountCall.scratch := hy.1 _ (by decide) + have hd : y.regs CountCall.histogram = x.regs CountCall.histogram := hy.1 _ (by decide) + have hm : y.regs Search.limit = x.regs Search.limit := hy.1 _ (by decide) + have hp : y.regs Search.primes = x.regs Search.primes := hy.1 _ (by decide) + have hc : y.regs CollisionTest.card = x.regs CollisionTest.card := hy.1 _ (by decide) + have hM : y.regs Search.factor * x.regs Search.limit ≤ x.regs Search.limit ^ 2 := by + simpa only [pow_two] using Nat.mul_le_mul_right (x.regs Search.limit) hfactorle + have hyinput : ∀ j i, i < y.regs (CountCall.length j) → + y.mem (y.regs (CountCall.source j) + i) = encode (values j i) := by + intro j i hi + rw [hl] at hi + rw [hs, hy.2 _ (by have := hsk j; omega)] + exact hinput j i hi + have hybitmap : ∀ q ≤ y.regs Search.limit, + y.mem (y.regs Search.primes + q) = if q.Prime then 1 else 0 := by + intro q hq + have hq' : q ≤ x.regs Search.limit := by simpa only [hm] using hq + rw [hp, hy.2 _ (by omega)] + exact hbitmap q hq' + obtain ⟨z, he, hr, hmem, ha⟩ := Search.exec y values hfactor + (by rw [hm]; exact signed_nat_mono hmod hM) + (by simpa only [hm] using hfits.limit) (by simpa only [hp, hm] using hfits.bitmap) + (by simpa only [hp, hm, hk] using hbefore) + hybitmap + (by simpa only [hl] using hvalues) (by simpa only [hs, hl] using hfits.source) + (by simpa only [hk, hl] using hfits.scratch) + (by rw [hd, hm]; exact hfits.histogram.of_le (Nat.add_le_add_left hM _)) + (by simpa only [hl, pow_two] using hfits.total) + (by simpa only [hl, hc, pow_two] using hfits.product) + (by simpa only [hs, hl, hk] using hsk) (by simpa only [hk, hl, hd] using hkd) + hyinput (by simpa only [hl] using hinj) + refine ⟨z, he.mono ?_, ?_, ?_⟩ + · simp only [hm, hl, searchTime] + exact Nat.add_le_add_right (Nat.mul_le_mul_left _ + (Nat.add_le_add_right (Nat.add_le_add_left (Nat.mul_le_mul_left 15 hM) _) _)) _ + · refine ⟨?_, fun a ha => (hmem a (by simpa only [hk] using ha)).trans (hy.2 a ha)⟩ + intro i hi + by_cases hid : i = digits + · subst i + exact (he.regs_eq (Search.preserves_caller _ (by decide))).trans (hy.1 _ hi) + · exact (hr i (by dsimp only [Parameter] at hi; omega)).trans (hy.1 i hi) + · simpa only [hm, hl, hc] using ha + +private theorem same_set {x y : Data 64} (h : Same x y) (i : Fin 64) + (hi : ¬Parameter i) (v : ℕ) : Same x (y.set i v) := by + refine ⟨fun j hj => ?_, h.2⟩ + have hji : j ≠ i := fun e => hi (e ▸ hj) + simpa only [Data.set_regs, Function.update_of_ne hji] using h.1 j hj + +private theorem firstSetup_preserves (i : Fin 64) (hi : Parameter i) : + firstSetup.Preserves i := by + fin_cases i <;> norm_num [Parameter, digits] at hi <;> decide + +private theorem secondSetup_preserves (i : Fin 64) (hi : Parameter i) : + secondSetup.Preserves i := by + fin_cases i <;> norm_num [Parameter, digits] at hi <;> decide + +private theorem exec_counts {w M : ℕ} (x y : Data 64) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < M) (hMle : M ≤ x.regs Search.limit ^ 2) + (hmod : GenericRAM.Signed.Fits w ((x.regs Search.limit ^ 2 : ℕ) : ℤ)) + (hfits : LocalFits w x) (hy : Same x y) + (hmodreg : y.regs CountCall.modulus = encode (M : ℤ)) + (hvalues : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ z, ExecWithin (w + 2) CountTriple.program y z + (34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * M + 72) ∧ Same x z ∧ + (∀ j, z.regs (CountCall.result j) = coll + ((Finset.range (x.regs (CountCall.length j))).image (values j)) M) ∧ + ∀ i, ((28 ≤ i.val ∧ i.val < 37) ∨ 40 ≤ i.val) → z.regs i = y.regs i := by + have hl := same_length hy + have hs := same_source hy + have hk : y.regs CountCall.scratch = x.regs CountCall.scratch := hy.1 _ (by decide) + have hd : y.regs CountCall.histogram = x.regs CountCall.histogram := hy.1 _ (by decide) + have hyinput : ∀ j i, i < y.regs (CountCall.length j) → + y.mem (y.regs (CountCall.source j) + i) = encode (values j i) := by + intro j i hi + rw [hl] at hi + rw [hs, hy.2 _ (by have := hsk j; omega)] + exact hinput j i hi + obtain ⟨z, he, hv, hr, hmem⟩ := CountTriple.exec y values hM + (signed_nat_mono hmod hMle) hmodreg (by simpa only [hl] using hvalues) + (by simpa only [hs, hl] using hfits.source) + (by simpa only [hk, hl] using hfits.scratch) + (by rw [hd]; exact hfits.histogram.of_le (Nat.add_le_add_left hMle _)) + (by simpa only [hl, pow_two] using hfits.total) + (by simpa only [hs, hl, hk] using hsk) (by simpa only [hk, hl, hd] using hkd) + hyinput (by simpa only [hl] using hinj) + refine ⟨z, ?_, ?_, ?_, hr⟩ + · simpa only [hl] using he + · refine ⟨fun i hi => (hr i ?_).trans (hy.1 i hi), ?_⟩ + · dsimp only [Parameter] at hi + rcases hi with hi | hi | rfl + · omega + · omega + · decide + · intro a ha + exact (hmem a (by simpa only [hk] using ha)).trans (hy.2 a ha) + · simpa only [hl] using hv + +/-- Both searches, a baseline collision count, scalar setup and the final product. -/ +def cost (x : Data 64) : ℕ := + 2 * searchTime x + 34 * (x.regs (CountCall.length 0) + x.regs (CountCall.length 1) + + x.regs (CountCall.length 2)) + 15 * x.regs Search.limit + 91 + +/-- Compute exactly the existing Chan–He modulus with two complete native prime searches. +The prime bitmap/count is a shared input built by `PrimeTable.program`. A single polynomial +budget bounds selector counters, thresholds and addresses; input values and the maximum modulus +fit the source width. The final halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs Search.limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs Search.limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (budget x)) + (hcard : x.regs CollisionTest.card = (Nat.primesLE (x.regs Search.limit)).card) + (hbefore : x.regs Search.primes + (x.regs Search.limit + 1) ≤ x.regs CountCall.scratch) + (hbitmap : ∀ q ≤ x.regs Search.limit, + x.mem (x.regs Search.primes + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (CountCall.length j) → GenericRAM.Signed.Fits w (values j i)) + (hsk : ∀ j, x.regs (CountCall.source j) + x.regs (CountCall.length j) ≤ + x.regs CountCall.scratch) + (hkd : ∀ j, x.regs CountCall.scratch + x.regs (CountCall.length j) ≤ + x.regs CountCall.histogram) + (hinput : ∀ j i, i < x.regs (CountCall.length j) → + x.mem (x.regs (CountCall.source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (CountCall.length j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y (cost x) ∧ + (∀ i, ((28 ≤ i.val ∧ i.val ≤ 35) ∨ (40 ≤ i.val ∧ i.val ≤ 42) ∨ i = digits) → + y.regs i = x.regs i) ∧ + (∀ a < x.regs CountCall.scratch, y.mem a = x.mem a) ∧ + y.regs first = firstP (Nat.primesLE (x.regs Search.limit)) (x.regs digits) + ((Finset.range (x.regs (CountCall.length 0))).image (values 0)) + ((Finset.range (x.regs (CountCall.length 1))).image (values 1)) + ((Finset.range (x.regs (CountCall.length 2))).image (values 2)) ∧ + y.regs output = modulus (Nat.primesLE (x.regs Search.limit)) (x.regs digits) + ((Finset.range (x.regs (CountCall.length 0))).image (values 0)) + ((Finset.range (x.regs (CountCall.length 1))).image (values 1)) + ((Finset.range (x.regs (CountCall.length 2))).image (values 2)) := by + let S (j : Fin 3) := (Finset.range (x.regs (CountCall.length j))).image (values j) + let Q := Nat.primesLE (x.regs Search.limit) + let p := firstP Q (x.regs digits) (S 0) (S 1) (S 2) + let q := secondP Q (x.regs digits) (S 0) (S 1) (S 2) p + have hp : 1 ≤ p ∧ p ≤ x.regs Search.limit := by + dsimp only [p, firstP, Q] + exact selector_bounds (by omega) _ + have hq : 1 ≤ q ∧ q ≤ x.regs Search.limit := by + dsimp only [q, secondP, Q] + exact selector_bounds (by omega) _ + have hfits := localFits_of_budget x hbudget + let a := firstState x + have ha : ExecWithin (w + 2) firstSetup x a 9 := exec_firstSetup x hfits.three hfits.triple + (fun j => (hfits.total j).of_le (by omega)) hfits.cutoff + have has : Same x a := ⟨fun i hi => ha.regs_eq (firstSetup_preserves i hi), fun _ _ => rfl⟩ + have haf : a.regs Search.factor = 1 := by rfl + have hacut (j : Fin 3) : a.regs (CollisionTest.threshold j) = + 3 * x.regs digits * x.regs (CountCall.length j) ^ 2 := by fin_cases j <;> rfl + obtain ⟨b, hb, hbs, hbp⟩ := exec_search x a values hmod hfits has + (by rw [haf]; omega) (by rw [haf]; omega) hbefore hbitmap hvalues hsk hkd hinput hinj + have hbanswer : b.regs Search.answer = p := by + rw [haf] at hbp + simp only [Nat.one_mul, hacut, hcard] at hbp + exact hbp.trans (first_selector x values hinj) + let c := ((b.set first p).set Search.two 2).set CountCall.modulus (2 * p) + have hpf : GenericRAM.Signed.Fits w (p : ℤ) := signed_nat_mono hmod + (hp.2.trans (Nat.le_self_pow two_ne_zero _)) + have henc : encode (p : ℤ) = 2 * p := by simp [encode] + have hencoded : Fits (w + 2) (2 * p) := by + rw [← henc] + exact (encode_fits hpf).mono (by omega) + have htwo : Fits (w + 2) 2 := hfits.three.of_le (by omega) + have hc : ExecWithin (w + 2) + (ram[ first ← Search.answer; Search.two ← #2; CountCall.modulus ← first * Search.two ]) + b c 3 := by + ram_eval [c, hbanswer, htwo, hencoded, first, Search.answer, Search.two, + CountCall.modulus, Fin.mk.injEq, Nat.mul_comm] + have hcs : Same x c := same_set (same_set (same_set hbs first (by decide) _) + Search.two (by decide) _) CountCall.modulus (by decide) _ + have hcm : c.regs CountCall.modulus = encode (p : ℤ) := by simpa only [henc] using + (show c.regs CountCall.modulus = 2 * p from rfl) + obtain ⟨d, hd, hds, hdcounts, hdr⟩ := exec_counts x c values (by omega) + (hp.2.trans (Nat.le_self_pow two_ne_zero _)) hmod hfits hcs hcm hvalues hsk hkd hinput hinj + have hdvalue : d.regs first = p := (hdr first (by decide)).trans (by rfl) + have hddigits : d.regs digits = x.regs digits := hds.1 _ (by decide) + have hcoll (j : Fin 3) : coll (S j) p ≤ x.regs (CountCall.length j) ^ 2 := by + have hsize : (S j).card ≤ x.regs (CountCall.length j) := + Finset.card_image_le.trans_eq (Finset.card_range _) + exact (coll_le_sq _ _).trans (Nat.pow_le_pow_left hsize 2) + let e := secondState d + have he : ExecWithin (w + 2) secondSetup d e 6 := exec_secondSetup d hfits.three + (by simpa only [hddigits] using hfits.triple) + (fun j => by + rw [hddigits, hdcounts] + exact (hfits.cutoff j).of_le (Nat.mul_le_mul_left _ (hcoll j))) + have hes : Same x e := ⟨fun i hi => (he.regs_eq (secondSetup_preserves i hi)).trans + (hds.1 i hi), hds.2⟩ + have hef : e.regs Search.factor = p := by + change d.regs first = p + exact hdvalue + have hecut (j : Fin 3) : e.regs (CollisionTest.threshold j) = + 3 * x.regs digits * coll (S j) p := by + have hr : e.regs (CollisionTest.threshold j) = + 3 * d.regs digits * d.regs (CountCall.result j) := by fin_cases j <;> rfl + rw [hr, hddigits, hdcounts] + obtain ⟨f, hf, hfs, hfq⟩ := exec_search x e values hmod hfits hes + (by rw [hef]; omega) (by rw [hef]; exact hp.2) + hbefore hbitmap hvalues hsk hkd hinput hinj + have hfanswer : f.regs Search.answer = q := by + simpa only [hef, hecut, hcard, forall_three, q, secondP] using hfq + have hfvalue : f.regs first = p := by + rw [hf.regs_eq (Search.preserves_caller first (by decide))] + change d.regs first = p + exact hdvalue + have hprod : Fits (w + 2) (f.regs first * f.regs Search.answer) := by + rw [hfvalue, hfanswer] + apply hfits.histogram.of_le + have hle := Nat.mul_le_mul hp.2 hq.2 + simpa only [pow_two] using hle.trans (Nat.le_add_left _ _) + have hfinish : ExecWithin (w + 2) (ram[ output ← first * Search.answer ]) f + (f.set output (p * q)) 1 := by + simpa only [BinOp.eval, hfvalue, hfanswer] using + (ExecWithin.bin (f := .mul) (d := output) (i := first) (j := Search.answer) hprod) + have hsame : Same x (f.set output (p * q)) := same_set hfs output (by decide) _ + refine ⟨f.set output (p * q), ?_, hsame.1, hsame.2, ?_, ?_⟩ + · exact (ha.seq (hb.seq (hc.seq (hd.seq (he.seq (hf.seq hfinish)))))).mono + (by dsimp only [cost]; omega) + · simpa +decide [output, first] using hfvalue + · change p * q = modulus Q (x.regs digits) (S 0) (S 1) (S 2) + rfl + +/-- The finite selector program never reads the word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus diff --git a/ComplexityTests/WordRAMChanHeModulus.lean b/ComplexityTests/WordRAMChanHeModulus.lean new file mode 100644 index 0000000000..9e5f46d517 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeModulus.lean @@ -0,0 +1,109 @@ +/- +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.Modulus + +/-! # Complete native two-prime modulus computations -/ + +namespace ComplexityTests.WordRAMChanHeModulus + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +private def input (n digits : ℕ) (values : ℕ → ℤ) : Data 64 := + ⟨fun i => if i.val = 28 ∨ i.val = 30 ∨ i.val = 32 then n + else if i.val = 29 ∨ i.val = 31 ∨ i.val = 33 then 1 + else if i.val = 34 then 20 else if i.val = 35 then 30 + else if i.val = 40 then 2 else if i.val = 41 then 10 + else if i.val = 42 ∨ i.val = 59 then (if i.val = 42 then 1 else digits) else 71, + fun a => if 1 ≤ a ∧ a < 1 + n then encode (values (a - 1)) + else if 10 ≤ a ∧ a ≤ 12 then (if a = 12 then 1 else 0) else 79⟩ + +-- Each execution includes two complete prime scans and three baseline counting calls. +set_option maxRecDepth 8192 in +set_option maxHeartbeats 2000000 in +-- Kernel reduction of the full two-search program exceeds the default heartbeat budget. +/-- Empty sets still compute the exact minimum/default specification, returning `2*2`. -/ +example : (Code.eval 8 6 Modulus.program (input 0 1 (fun i => i))).map + (fun yn => [yn.1.regs Modulus.first, yn.1.regs Modulus.output, + yn.1.regs Modulus.digits, yn.1.mem 12]) = some [2, 4, 1, 1] := by decide + +/-- A target word too small for the very first scalar setup is rejected. -/ +example : (Code.eval 1 1 Modulus.program (input 0 1 (fun i => i))).map + (fun yn => yn.1.regs Modulus.output) = none := by decide + +/-- The native execution contract is admissible at every small array length, including all +fits, the complete bitmap, preserved input and injective enumeration. -/ +example (n : Fin 3) : ∃ y, + ExecWithin 10 Modulus.program (input n 1 (fun i => i)) y (714 * (n : ℕ) + 1111) ∧ + y.regs Modulus.output = 4 := by + let x := input n 1 (fun i => i) + have hn (j : Fin 3) : x.regs (CountCall.length j) = n := by fin_cases j <;> rfl + have hs (j : Fin 3) : x.regs (CountCall.source j) = 1 := by fin_cases j <;> rfl + have hN : (n : ℕ) ≤ 2 := by omega + have hlimit : x.regs Search.limit = 2 := rfl + obtain ⟨y, he, hr, hm, hp, hv⟩ := Modulus.exec (w := 8) x (fun _ i => (i : ℤ)) + (by change 2 ≤ 2; decide) + (by change Cslib.GenericRAM.Signed.Fits 8 (4 : ℤ); decide) + (by fin_cases n <;> decide) (by change 1 = (Nat.primesLE 2).card; decide) + (by change 10 + (2 + 1) ≤ 20; decide) + (by intro q hq; change q ≤ 2 at hq; fin_cases n <;> interval_cases q <;> decide) + (by intro j i hi; rw [hn] at hi; have hi1 : i ≤ 1 := by omega + interval_cases i <;> decide) + (by intro j; rw [hs, hn]; change 1 + (n : ℕ) ≤ 20; omega) + (by intro j; rw [hn]; change 20 + (n : ℕ) ≤ 30; omega) + (by + intro j i hi + rw [hn] at hi + rw [hs] + change (if 1 ≤ 1 + i ∧ 1 + i < 1 + (n : ℕ) then encode ((1 + i - 1 : ℕ) : ℤ) + else if 10 ≤ 1 + i ∧ 1 + i ≤ 12 then (if 1 + i = 12 then 1 else 0) else 79) = _ + simp only [show 1 ≤ 1 + i ∧ 1 + i < 1 + (n : ℕ) by omega, + and_self, ↓reduceIte, show 1 + i - 1 = i by omega]) + (by intro j a _ b _ hab; change (a : ℤ) = (b : ℤ) at hab; exact_mod_cast hab) + have hcost : Modulus.cost x = 714 * (n : ℕ) + 1111 := by + simp only [Modulus.cost, Modulus.searchTime, hn, hlimit] + omega + refine ⟨y, by simpa only [hcost] using he, ?_⟩ + have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.modulus (Nat.primesLE 2) 1 + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) = 4 := by fin_cases n <;> decide + simp only [hn] at hv + exact hv.trans hvalue + +/-- A zero divisor bound and negative colliding values reject the only prime twice. The full +native theorem returns the exact default product one, with admissible input and resource bounds. -/ +example : ∃ y, ExecWithin 10 Modulus.program + (input 2 0 (fun i => if i = 0 then -2 else 0)) y + (Modulus.cost (input 2 0 (fun i => if i = 0 then -2 else 0))) ∧ + y.regs Modulus.first = 1 ∧ y.regs Modulus.output = 1 := by + let values (i : ℕ) : ℤ := if i = 0 then -2 else 0 + let x := input 2 0 values + have hn (j : Fin 3) : x.regs (CountCall.length j) = 2 := by fin_cases j <;> rfl + obtain ⟨y, he, hr, hm, hp, hv⟩ := Modulus.exec (w := 8) x (fun _ => values) + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro q hq; change q ≤ 2 at hq; interval_cases q <;> decide) + (by intro j i hi; rw [hn] at hi; interval_cases i <;> decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by intro j i hi; rw [hn] at hi; fin_cases j <;> interval_cases i <;> decide) + (by + intro j a ha b hb hab + simp only [Finset.mem_coe, Finset.mem_range, hn] at ha hb + interval_cases a <;> interval_cases b <;> norm_num [values] at *) + refine ⟨y, he, ?_, ?_⟩ + · have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.firstP (Nat.primesLE 2) 0 + ((Finset.range 2).image values) ((Finset.range 2).image values) + ((Finset.range 2).image values) = 1 := by decide + simp only [hn] at hp + exact hp.trans hvalue + · have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.modulus (Nat.primesLE 2) 0 + ((Finset.range 2).image values) ((Finset.range 2).image values) + ((Finset.range 2).image values) = 1 := by decide + simp only [hn] at hv + exact hv.trans hvalue + +end ComplexityTests.WordRAMChanHeModulus diff --git a/ROADMAP.md b/ROADMAP.md index 0af86de2b1..dc261bf18d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -424,8 +424,8 @@ each statement against the numbered result. Organize modules by subject, not cha ordinary distinct-position inputs and explicit size bounds. The finite lopsided matrix/counting interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting and the selected-prime false-positive bound ([#1442]) are proved mathematically. Native parameter, - collision-counting, prime-table and collision-tested minimum-search programs have explicit local - fit/frame/cost contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, + collision-counting, prime-table, minimum-search and complete two-prime modulus programs have + explicit local fit/frame/cost contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, packed output and global preprocessing width/time composition remain. 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/ diff --git a/UPSTREAM.md b/UPSTREAM.md index f7288d82ba..3070a33aa2 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -165,8 +165,9 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he 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. Native numeric parameter computation, signed-residue/histogram counting, trial-division prime - tables and collision-tested least-prime scans also use the existing `WordRAM.Code`/`ExecWithin` - APIs. Their explicit local fit, frame and charged-cost contracts do not yet compose the complete + tables, collision-tested least-prime scans and complete two-prime modulus selection also use + the existing `WordRAM.Code`/`ExecWithin` APIs. Their explicit local fit, frame and charged-cost + contracts do not yet compose the complete preprocessing from input through frontend, heavy sets, recursion and packed output; the global logarithmic width and subquadratic preprocessing bound remain in #1446. From ba4cab2a6096c4d692e2d7a75b59d7591ea5162e Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 11:47:22 -0400 Subject: [PATCH 17/45] =?UTF-8?q?feat(wordram):=20construct=20native=20Cha?= =?UTF-8?q?n=E2=80=93He=20node=20outputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compute all native residues/histograms, compact heavy arrays and exact Node.oneArray cells with a concrete disjoint frame and linear charged cost. Reuse Count.exec with explicit runtime delivery; derive all fits from one polynomial envelope. Add executed boundary regressions and admissibility clients. Validation: all six scripts/check.sh gates passed together; changed-module lints, hygiene --check-simp and diff --check pass at base 0e05605fb. Curated abstracts: no impact. Parent frontend/recursion/global composition remain open under #1446/#1394. Implements #1481. AI-assisted implementation and self-review: OpenAI Codex (gpt-6.1-sol). --- Complexity.lean | 15 + .../WordRAM/ThreeSum/ChanHe/BucketCounts.lean | 171 ++++++++++ .../WordRAM/ThreeSum/ChanHe/CountPlace.lean | 277 +++++++++++++++ .../ThreeSum/ChanHe/CountPlaceMemory.lean | 110 ++++++ .../WordRAM/ThreeSum/ChanHe/NodeExec.lean | 107 ++++++ .../ThreeSum/ChanHe/NodeInitialize.lean | 82 +++++ .../WordRAM/ThreeSum/ChanHe/NodeLayout.lean | 176 ++++++++++ .../WordRAM/ThreeSum/ChanHe/NodeMemory.lean | 314 ++++++++++++++++++ .../WordRAM/ThreeSum/ChanHe/NodeProgram.lean | 168 ++++++++++ .../WordRAM/ThreeSum/ChanHe/NodeRound.lean | 178 ++++++++++ .../WordRAM/ThreeSum/ChanHe/NodeSetup.lean | 197 +++++++++++ .../WordRAM/ThreeSum/ChanHe/NodeSpec.lean | 119 +++++++ .../WordRAM/ThreeSum/ChanHe/Padding.lean | 178 ++++++++++ .../WordRAM/ThreeSum/ChanHe/Place.lean | 221 ++++++++++++ .../WordRAM/ThreeSum/ChanHe/PlaceExec.lean | 277 +++++++++++++++ .../WordRAM/ThreeSum/ChanHe/PlaceMemory.lean | 308 +++++++++++++++++ ComplexityTests/NativeChanHeNode.lean | 176 ++++++++++ ROADMAP.md | 8 +- 18 files changed, 3079 insertions(+), 3 deletions(-) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean create mode 100644 ComplexityTests/NativeChanHeNode.lean diff --git a/Complexity.lean b/Complexity.lean index 091db68880..f805d87d9a 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -106,13 +106,28 @@ 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.BucketCounts public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CollisionTest public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlaceMemory public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeInitialize +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeLayout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeMemory +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeRound +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Padding public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Place +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean new file mode 100644 index 0000000000..aedff6aaea --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean @@ -0,0 +1,171 @@ +/- +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 + +/-! +# Histogram buckets for the native Chan–He node construction + +These bridges identify position counts with the existing set-based buckets. Injectivity is +essential: two positions containing the same integer do not make that integer heavy. +The proofs are original; no source machine semantics is used. + +## Main definitions + +* `residueKey`: the unsigned Euclidean key already computed by `Count.program`. + +## Main results + +* `count_eq_card_bucket`: histogram counts equal bucket cardinalities. +* `mem_heavy_iff_count`: the native count test recognizes the existing heavy subset. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +open Finset Cslib.Algorithms.ThreeSum.ChanHe + +/-- The ordinary unsigned remainder of an integer modulo a positive natural modulus. -/ +def residueKey (M : ℕ) (z : ℤ) : ℕ := (z % (M : ℤ)).toNat + +/-- Interpreting a key as an integer recovers its Euclidean remainder. -/ +theorem residueKey_cast {M : ℕ} (hM : 0 < M) (z : ℤ) : + (residueKey M z : ℤ) = z % (M : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +/-- Every key is a valid histogram address offset. -/ +theorem residueKey_lt {M : ℕ} (hM : 0 < M) (z : ℤ) : residueKey M z < M := by + have := residueKey_cast hM z + have := Int.emod_lt_of_pos z (show (0 : ℤ) < M by omega) + omega + +/-- Filtering positions before taking their image gives exactly the existing bucket. -/ +theorem bucket_image (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) (b : ℕ) : + bucket ((range n).image values) M b = + ({i ∈ range n | residueKey M (values i) = b}).image values := by + ext z + simp only [bucket, mem_filter, mem_image] + constructor + · rintro ⟨⟨i, hi, rfl⟩, hb⟩ + exact ⟨i, ⟨hi, by rw [residueKey, hb]; simp⟩, rfl⟩ + · rintro ⟨i, ⟨hi, hb⟩, rfl⟩ + exact ⟨⟨i, hi, rfl⟩, by rw [← residueKey_cast hM, hb]⟩ + +/-- Injective input enumeration makes the native histogram a set-cardinality computation. -/ +theorem count_eq_card_bucket (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) (b : ℕ) : + Histogram.count (fun i => residueKey M (values i)) n b = + (bucket ((range n).image values) M b).card := by + rw [bucket_image values n hM b, card_image_of_injOn (hinj.mono (by + intro i hi + exact (mem_filter.mp hi).1))] + rfl + +/-- A member is heavy precisely when its residue bucket has at least two elements. -/ +theorem mem_heavy_iff_bucket {S : Finset ℤ} {M : ℕ} {z : ℤ} (hz : z ∈ S) : + z ∈ heavy S M ↔ 1 < (bucket S M (z % (M : ℤ))).card := by + have hzB : z ∈ bucket S M (z % (M : ℤ)) := mem_filter.mpr ⟨hz, rfl⟩ + rw [heavy, mem_filter, and_iff_right hz] + constructor + · rintro ⟨y, hy, hne, hd⟩ + have hyB : y ∈ bucket S M (z % (M : ℤ)) := + mem_filter.mpr ⟨hy, ((natCast_dvd_sub_iff_emod_eq M z y).mp hd).symm⟩ + have hsub : {z, y} ⊆ bucket S M (z % (M : ℤ)) := by + intro a ha + rcases mem_insert.mp ha with rfl | ha + · exact hzB + · exact (mem_singleton.mp ha) ▸ hyB + have := card_le_card hsub + have hc : 2 ≤ (bucket S M (z % (M : ℤ))).card := by simpa [hne.symm] using this + omega + · intro hc + have hnot : ¬bucket S M (z % (M : ℤ)) ⊆ {z} := by + intro h + have := card_le_card h + simp only [card_singleton] at this + omega + obtain ⟨y, hy, hne⟩ := not_subset.mp hnot + obtain ⟨hyS, hyR⟩ := mem_filter.mp hy + exact ⟨y, hyS, by simpa only [mem_singleton] using hne, + (natCast_dvd_sub_iff_emod_eq M z y).mpr hyR.symm⟩ + +/-- The count test in the native scan selects exactly `ChanHe.heavy`. -/ +theorem mem_heavy_iff_count (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) {i : ℕ} (hi : i < n) : + values i ∈ heavy ((range n).image values) M ↔ + 1 < Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + rw [count_eq_card_bucket values n hM hinj, residueKey_cast hM] + exact mem_heavy_iff_bucket (mem_image.mpr ⟨i, mem_range.mpr hi, rfl⟩) + +/-- An input position contributes at least one to its own bucket. -/ +theorem count_residue_pos (values : ℕ → ℤ) (n M : ℕ) {i : ℕ} (hi : i < n) : + 0 < Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + exact card_pos.mpr ⟨i, mem_filter.mpr ⟨mem_range.mpr hi, rfl⟩⟩ + +/-- A singleton histogram bucket gives the exact set and hence its unique light value. -/ +theorem bucket_eq_singleton_of_count (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) {i : ℕ} (hi : i < n) + (hc : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1) : + bucket ((range n).image values) M (residueKey M (values i)) = {values i} := by + have hm : values i ∈ bucket ((range n).image values) M (residueKey M (values i)) := + mem_filter.mpr ⟨mem_image.mpr ⟨i, mem_range.mpr hi, rfl⟩, + (residueKey_cast hM _).symm⟩ + have hcard := (count_eq_card_bucket values n hM hinj (residueKey M (values i))).symm.trans hc + obtain ⟨z, hz⟩ := card_eq_one.mp hcard + rw [hz, mem_singleton] at hm + exact hz.trans (congrArg singleton hm.symm) + +/-- No two distinct positions can be light in the same bucket, even before injectivity is used. -/ +theorem eq_of_count_one (values : ℕ → ℤ) (n M : ℕ) {i j : ℕ} + (hi : i < n) (hj : j < n) + (hc : Histogram.count (fun k => residueKey M (values k)) n (residueKey M (values i)) = 1) + (he : residueKey M (values i) = residueKey M (values j)) : i = j := by + have hi' : i ∈ {k ∈ range n | residueKey M (values k) = residueKey M (values i)} := + mem_filter.mpr ⟨mem_range.mpr hi, rfl⟩ + have hj' : j ∈ {k ∈ range n | residueKey M (values k) = residueKey M (values i)} := + mem_filter.mpr ⟨mem_range.mpr hj, he.symm⟩ + obtain ⟨k, hk⟩ := card_eq_one.mp hc + rw [hk, mem_singleton] at hi' hj' + exact hi'.trans hj'.symm + +/-- Residue equality is unchanged by simultaneous negation. -/ +theorem residueKey_neg_eq_iff {M : ℕ} (hM : 0 < M) (a b : ℤ) : + residueKey M (-a) = residueKey M (-b) ↔ residueKey M a = residueKey M b := by + rw [← Int.natCast_inj, residueKey_cast hM, residueKey_cast hM, + ← Int.natCast_inj, residueKey_cast hM, residueKey_cast hM] + change -a ≡ -b [ZMOD (M : ℤ)] ↔ a ≡ b [ZMOD (M : ℤ)] + exact Int.neg_modEq_neg + +/-- A histogram built on original residues also classifies the negated third set exactly. -/ +theorem count_neg_residue (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) (i : ℕ) : + Histogram.count (fun j => residueKey M (-values j)) n (residueKey M (-values i)) = + Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + simp only [Histogram.count, residueKey_neg_eq_iff hM] + +/-- Every bucket at an index beyond the modulus is empty. -/ +theorem bucket_eq_empty_of_le (S : Finset ℤ) {M i : ℕ} (hM : 0 < M) (hi : M ≤ i) : + bucket S M i = ∅ := by + apply eq_empty_iff_forall_notMem.mpr + intro z hz + have he := (mem_filter.mp hz).2 + have := Int.emod_lt_of_pos z (show (0 : ℤ) < M by omega) + omega + +/-- The first two light arrays equal their prescribed padding beyond the modulus. -/ +theorem arrXY_eq_pad_of_le (S : Finset ℤ) {M i : ℕ} (hM : 0 < M) (hi : M ≤ i) (U : ℕ) : + arrXY S M U i = pad U := by + simp [arrXY, arr, bucket_eq_empty_of_le S hM hi] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean new file mode 100644 index 0000000000..2fe25904c2 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean @@ -0,0 +1,277 @@ +/- +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.CountCall +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory + +/-! +# A complete native counting and placement call + +This routine actually computes the residues and histogram before extracting a heavy array and +placing the corresponding packed column. The histogram is not a premise of the execution +contract. It also writes the compact output count to its caller-supplied descriptor cell. + +## Main definitions + +* `CountPlace.program`: modulus encoding, complete `CountCall.program 2`, runtime argument + delivery, complete `Place.program`, and the count store. + +## References + +This native composition is original and reuses the existing Chan–He mathematical definitions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Input length (the selected third argument of the existing counting call). -/ +abbrev len : Fin 64 := 32 +/-- Signed input base. -/ +abbrev src : Fin 64 := 33 +/-- Unsigned residue workspace base. -/ +abbrev scratch : Fin 64 := 34 +/-- Histogram workspace base. -/ +abbrev hist : Fin 64 := 35 +/-- Compact heavy output base. -/ +abbrev heavy : Fin 64 := 37 +/-- Packed node array base. -/ +abbrev packed : Fin 64 := 38 +/-- Positive modulus, decoded unsigned. -/ +abbrev modulus : Fin 64 := 40 +/-- Encoded positive tag. -/ +abbrev tag : Fin 64 := 41 +/-- Packed column. -/ +abbrev column : Fin 64 := 42 +/-- Distance between the two stores. -/ +abbrev distance : Fin 64 := 43 +/-- Zero for X/Y, one for Z. -/ +abbrev negative : Fin 64 := 44 +/-- Cell in which the exact heavy count is stored. -/ +abbrev descriptor : Fin 64 := 45 + +/-- Fixed embedding of the placement workspace below all caller parameters. -/ +def registers (i : Fin 32) : Fin 64 := ⟨i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j he + apply Fin.ext + exact congrArg (fun k : Fin 64 => k.val) he + +@[inherit_doc Place.src] +abbrev psrc : Fin 64 := registers Place.src +@[inherit_doc Place.keys] +abbrev pkeys : Fin 64 := registers Place.keys +@[inherit_doc Place.len] +abbrev plen : Fin 64 := registers Place.len +@[inherit_doc Place.hist] +abbrev phist : Fin 64 := registers Place.hist +@[inherit_doc Place.heavy] +abbrev pheavy : Fin 64 := registers Place.heavy +@[inherit_doc Place.packed] +abbrev ppacked : Fin 64 := registers Place.packed +@[inherit_doc Place.modulus] +abbrev pmodulus : Fin 64 := registers Place.modulus +@[inherit_doc Place.tag] +abbrev ptag : Fin 64 := registers Place.tag +@[inherit_doc Place.column] +abbrev pcolumn : Fin 64 := registers Place.column +@[inherit_doc Place.distance] +abbrev pdistance : Fin 64 := registers Place.distance +@[inherit_doc Place.negative] +abbrev pnegative : Fin 64 := registers Place.negative +@[inherit_doc Place.count] +abbrev pcount : Fin 64 := registers Place.count + +/-- All eleven runtime argument copies are real instructions. -/ +def prepare : Code .standard 64 := ram[ + psrc ← src; pkeys ← scratch; plen ← len; phist ← hist; pheavy ← heavy; + ppacked ← packed; pmodulus ← modulus; ptag ← tag; pcolumn ← column; + pdistance ← distance; pnegative ← negative ] + +/-- Complete fixed native routine: no host histogram, heavy filter or output constructor. -/ +def program : Code .standard 64 := + .seq (.op (.bin .add CountCall.modulus modulus modulus)) + (.seq (CountCall.program 2) (.seq prepare + (.seq (Place.program.rename registers) (.op (.store descriptor pcount))))) + +/-- Only the counting/placement workspace, the encoded modulus, and the discarded collision +counter can change. All caller registers at least 46 are included in this frame. -/ +theorem preserves (i : Fin 64) (hi : 32 ≤ i.val) (hm : i ≠ CountCall.modulus) + (hc : i ≠ CountCall.result 2) : program.Preserves i := by + have h : 32 ≤ i.val ∧ i ≠ CountCall.modulus ∧ i ≠ CountCall.result 2 := ⟨hi, hm, hc⟩ + fin_cases i <;> norm_num [CountCall.modulus, CountCall.result] at h <;> decide + +/-- Polynomial envelope for addresses, counters, encoded magnitudes and every scan intermediate. -/ +def budget (x : Data 64) (M N U t : ℕ) : ℕ := + x.regs src + x.regs scratch + x.regs hist + x.regs heavy + x.regs packed + + x.regs len * x.regs len + 2 * x.regs len + M + 4 * N + 2 * t + 2 * U + 64 + +/-- Exact memory, including actual residue/histogram construction and the descriptor write. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) : ℕ → ℕ := + Function.update + (Place.memory (Count.memory mem values n M scratch hist) values n M out dst col dist t neg n) + descriptor (Place.heavyList values n M n).length + +/-- Complete native count/extract/place execution, charging modulus encoding, all subroutine +argument/result copies and the final count write. The caller supplies actual signed input words; +no histogram or light/heavy-output contract is assumed. -/ +theorem exec {w M N U t : ℕ} (x : Data 64) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmreg : x.regs modulus = M) (hN : 2 * M ≤ N) (ht : U ≤ t) + (htag : x.regs tag = 2 * t) (hneg : x.regs negative = if neg then 1 else 0) + (hc : x.regs column < 4) (hd : x.regs distance ≤ 4 * M) + (hbdd : ∀ i < x.regs len, (values i).natAbs ≤ U) + (hsigned : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hfit : Fits (w + 2) (budget x M N U t)) + (hsh : x.regs src + x.regs len ≤ x.regs heavy) + (hhp : x.regs heavy + x.regs len ≤ x.regs packed) + (hpk : x.regs packed + 4 * N ≤ x.regs scratch) + (hkh : x.regs scratch + x.regs len ≤ x.regs hist) + (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 (66 * x.regs len + 5 * M + 45) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t (x.regs scratch) (x.regs hist) + (x.regs descriptor) neg ∧ + ∀ i, 32 ≤ i.val → i ≠ CountCall.modulus → i ≠ CountCall.result 2 → + y.regs i = x.regs i := by + let z := x.set CountCall.modulus (encode (M : ℤ)) + have hemod : encode (M : ℤ) = M + M := by simp [encode, two_mul] + have hz : ExecWithin (w + 2) (.op (.bin .add CountCall.modulus modulus modulus)) x z 1 := by + have hf : Fits (w + 2) (x.regs modulus + x.regs modulus) := by + rw [hmreg, ← hemod] + exact (encode_fits hmod).mono (by omega) + simpa only [BinOp.eval, hmreg, ← hemod] using + (ExecWithin.bin (d := CountCall.modulus) (f := .add) (i := modulus) (j := modulus) hf) + have zmem : z.mem = x.mem := rfl + have zreg (i : Fin 64) (hi : i ≠ CountCall.modulus) : z.regs i = x.regs i := by + simp only [z, Data.set_regs, Function.update_of_ne hi] + have fs : Fits (w + 2) (x.regs src + x.regs len) := hfit.of_le (by + dsimp only [budget]; omega) + have fk : Fits (w + 2) (x.regs scratch + x.regs len) := hfit.of_le (by + dsimp only [budget]; omega) + have fh : Fits (w + 2) (x.regs hist + M) := hfit.of_le (by dsimp only [budget]; omega) + have fn : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1) := + hfit.of_le (by dsimp only [budget]; omega) + have zlen : z.regs (CountCall.length 2) = x.regs len := zreg _ (by decide) + have zsrc : z.regs (CountCall.source 2) = x.regs src := zreg _ (by decide) + have zscratch : z.regs CountCall.scratch = x.regs scratch := zreg _ (by decide) + have zhist : z.regs CountCall.histogram = x.regs hist := zreg _ (by decide) + obtain ⟨a, ha, ham, _, har⟩ := CountCall.exec 2 z values hM hmod + (by simp [z]) (by simpa only [zlen] using hsigned) + (by simpa only [zsrc, zlen] using fs) (by simpa only [zscratch, zlen] using fk) + (by simpa only [zhist] using fh) (by simpa only [zlen] using fn) + (by rw [zsrc, zlen, zscratch]; omega) + (by simpa only [zscratch, zlen, zhist] using hkh) + (by simpa only [zsrc, zlen, zmem] using hinput) + (by simpa only [zlen] using hinj) + have ar (i : Fin 64) (hi : 32 ≤ i.val) (hm : i ≠ CountCall.modulus) + (hc : i ≠ CountCall.result 2) : a.regs i = x.regs i := + (har i (by omega) hc).trans (zreg i hm) + have am : a.mem = Count.memory x.mem values (x.regs len) M + (x.regs scratch) (x.regs hist) := by + simpa only [zlen, zscratch, zhist, zmem] using ham + let q := ((((((((((a.set psrc (x.regs src)).set pkeys (x.regs scratch)).set plen + (x.regs len)).set phist (x.regs hist)).set pheavy (x.regs heavy)).set ppacked + (x.regs packed)).set pmodulus (x.regs modulus)).set ptag (x.regs tag)).set pcolumn + (x.regs column)).set pdistance (x.regs distance)).set pnegative (x.regs negative) + have hp : ExecWithin (w + 2) prepare a q 11 := by + have rsrc := ar src (by decide) (by decide) (by decide) + have rlen := ar len (by decide) (by decide) (by decide) + have rk := ar scratch (by decide) (by decide) (by decide) + have rh := ar hist (by decide) (by decide) (by decide) + have ro := ar heavy (by decide) (by decide) (by decide) + have rp := ar packed (by decide) (by decide) (by decide) + have rm := ar modulus (by decide) (by decide) (by decide) + have rt := ar tag (by decide) (by decide) (by decide) + have rc := ar column (by decide) (by decide) (by decide) + have rd := ar distance (by decide) (by decide) (by decide) + have rn := ar negative (by decide) (by decide) (by decide) + ram_eval [prepare, q, rsrc, rlen, rk, rh, ro, rp, rm, rt, rc, rd, rn] + have qs : (q.comap registers).regs Place.src = x.regs src := by + simp +decide [q, registers, Place.src] + have qk : (q.comap registers).regs Place.keys = x.regs scratch := by + simp +decide [q, registers, Place.keys] + have qn : (q.comap registers).regs Place.len = x.regs len := by + simp +decide [q, registers, Place.len] + have qh : (q.comap registers).regs Place.hist = x.regs hist := by + simp +decide [q, registers, Place.hist] + have qo : (q.comap registers).regs Place.heavy = x.regs heavy := by + simp +decide [q, registers, Place.heavy] + have qp : (q.comap registers).regs Place.packed = x.regs packed := by + simp +decide [q, registers, Place.packed] + have qm : (q.comap registers).regs Place.modulus = x.regs modulus := by + simp +decide [q, registers, Place.modulus] + have qt : (q.comap registers).regs Place.tag = x.regs tag := by + simp +decide [q, registers, Place.tag] + have qc : (q.comap registers).regs Place.column = x.regs column := by + simp +decide [q, registers, Place.column] + have qd : (q.comap registers).regs Place.distance = x.regs distance := by + simp +decide [q, registers, Place.distance] + have qg : (q.comap registers).regs Place.negative = x.regs negative := by + simp +decide [q, registers, Place.negative] + have qmem : q.mem = a.mem := rfl + have qfit : Fits (w + 2) (Place.budget (q.comap registers) M N U t) := by + rw [Place.budget, qs, qk, qh, qo, qp, qn] + exact hfit.of_le (by dsimp only [budget]; omega) + obtain ⟨b, hb, hbm, hbc, _⟩ := Place.exec (q.comap registers) values neg hM hN + (by simpa only [qc] using hc) (by simpa only [qd] using hd) ht + (qm.trans hmreg) (qt.trans htag) (qg.trans hneg) + (by simpa only [qn] using hbdd) qfit + (by simpa only [qs, qn, qo] using hsh) (by simpa only [qo, qn, qp] using hhp) + (by simpa only [qp, qk] using hpk) (by simpa only [qk, qn, qh] using hkh) + (by + intro i hi + rw [Data.comap_mem, qmem, am, qs] + have hi' : i < x.regs len := by simpa only [qn] using hi + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hinput i hi') + (by + intro i hi + have hi' : i < x.regs len := by simpa only [qn] using hi + rw [Data.comap_mem, qmem, am, qk, Count.memory, Histogram.memory, + ite_eq_right (by omega)] + simp [Residues.memory, show i < x.regs len by simpa only [qn] using hi, residueKey]) + (by + intro r hr + rw [Data.comap_mem, qmem, am, qh, Count.memory, Histogram.memory_read _ _ _ _ _ _ hr] + rfl) + let y := b.extend registers q + have hy : ExecWithin (w + 2) (Place.program.rename registers) q y + (32 * x.regs len + 8) := by simpa only [qn] using hb.rename registers_injective + have yc : y.regs pcount = (Place.heavyList values (x.regs len) M (x.regs len)).length := by + change Function.extend registers b.regs q.regs (registers Place.count) = _ + rw [registers_injective.extend_apply, hbc, qn] + have yd : y.regs descriptor = x.regs descriptor := by + change Function.extend registers b.regs q.regs descriptor = _ + rw [Function.extend_apply' _ _ _ (by + rintro ⟨i, hi⟩ + have := congrArg Fin.val hi + dsimp only [registers, descriptor] at this + omega)] + have hq : q.regs descriptor = a.regs descriptor := by simp +decide [q, descriptor] + exact hq.trans (ar descriptor (by decide) (by decide) (by decide)) + have ha' : ExecWithin (w + 2) (CountCall.program 2) z a + (34 * x.regs len + 5 * M + 24) := by simpa only [zlen] using ha + have he := hz.seq (ha'.seq + (hp.seq (hy.seq (ExecWithin.store (a := descriptor) (s := pcount))))) + refine ⟨y.write (y.regs descriptor) (y.regs pcount), he.mono (by omega), ?_, ?_⟩ + · simp only [Data.write_mem, yd, yc] + change Function.update b.mem (x.regs descriptor) _ = _ + rw [hbm, Data.comap_mem, qmem, am, qn, qo, qp, qc, qd] + rfl + · intro i hi hm hc + exact ExecWithin.regs_eq he (preserves i hi hm hc) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean new file mode 100644 index 0000000000..24f7f33dca --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean @@ -0,0 +1,110 @@ +/- +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.CountPlace + +/-! +# Memory frames and output readback for complete counting/placement calls + +## Main results + +* `CountPlace.memory_outside`: only the specified data, descriptor and workspace cells change. +* `CountPlace.memory_heavy`: exact compact heavy data. +* `CountPlace.memory_arrXY`, `CountPlace.memory_arrZ`: exact packed light columns. + +## References + +These composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace + +open Cslib.WordRAM.Signed + +/-- No write escapes the four explicit blocks and descriptor cell. -/ +theorem memory_outside (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor N : ℕ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : col < 4) (hd : dist ≤ 4 * M) + (a : ℕ) (ho : ¬(out ≤ a ∧ a < out + n)) (hp : ¬(dst ≤ a ∧ a < dst + 4 * N)) + (hk : ¬(scratch ≤ a ∧ a < scratch + n)) (hh : ¬(hist ≤ a ∧ a < hist + M)) + (he : a ≠ descriptor) : + memory mem values n M out dst col dist t scratch hist descriptor neg a = mem a := by + rw [memory, Function.update_of_ne he, + Place.memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd le_rfl ho hp, + Count.memory, Histogram.memory_outside _ _ _ _ _ _ hh] + simp only [Residues.memory, hk, ↓reduceIte] + +/-- The count store always writes the compact output length. -/ +theorem memory_descriptor (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) : + memory mem values n M out dst col dist t scratch hist descriptor neg descriptor = + (Place.heavyList values n M n).length := by + exact Function.update_self .. + +/-- The compact output contains the original signed encodings in input order. -/ +theorem memory_heavy (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) + (hdisj : out + n ≤ dst) (he : descriptor < out) + {j : ℕ} (hj : j < (Place.heavyList values n M n).length) : + memory mem values n M out dst col dist t scratch hist descriptor neg (out + j) = + encode ((Place.heavyList values n M n)[j]) := by + rw [memory, Function.update_of_ne (by omega)] + exact Place.memory_heavy _ _ _ _ _ _ _ _ _ _ hdisj le_rfl hj + +/-- The counting workspace and descriptor do not affect other packed columns. -/ +theorem memory_other_column (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor N : ℕ) (neg : Bool) + (hdisj : out + n ≤ dst) (hc : col < 4) (hd : dist = 0 ∨ dist = 4 * M) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + {u : ℕ} (hu : u < 4 * N) (hcol : u % 4 ≠ col) : + memory mem values n M out dst col dist t scratch hist descriptor neg (dst + u) = + mem (dst + u) := by + rw [memory, Function.update_of_ne (by omega), + Place.memory_other_column _ _ _ _ _ _ _ _ _ _ hdisj hc hd u hcol, + Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + +/-- A complete counting/placement call writes the exact first or second light column. -/ +theorem memory_arrXY (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col t U scratch hist descriptor N : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) + {i : ℕ} (hi : 4 * i + col < 4 * N) + (hpad : mem (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col 0 t scratch hist descriptor false + (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range n).image values) M U i + (t : ℤ)) := by + rw [memory, Function.update_of_ne (by omega)] + apply Place.memory_arrXY _ _ _ _ _ _ _ _ _ hM hdisj hinj i + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hpad + +/-- A complete counting/placement call writes the exact duplicated and negated third column. -/ +theorem memory_arrZ (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col t U scratch hist descriptor N : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) + {i : ℕ} (hi : 4 * i + col < 4 * N) + (hpad : mem (dst + 4 * i + col) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col (4 * M) t scratch hist descriptor true + (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range n).image values) M U i + (t : ℤ)) := by + rw [memory, Function.update_of_ne (by omega)] + apply Place.memory_arrZ _ _ _ _ _ _ _ _ _ hM hdisj hinj i + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hpad + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean new file mode 100644 index 0000000000..1927476414 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean @@ -0,0 +1,107 @@ +/- +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.NodeRound + +/-! +# Complete native Chan–He node construction + +The parent supplies three injective signed-encoded input arrays ending before a fresh allocation +base, a positive modulus `M`, `N ≥ 2M`, and magnitude bound `U`. The fixed native program computes +all residues/histograms, the three exact compact heavy arrays and the existing `Node.oneArray` +in `4N` cells. Initialization, tags, address calculations, all called code and count stores are +charged. A single polynomial envelope derives every arithmetic/address fit. + +## Main results + +* `NodePacking.exec`: complete native execution in `66(n₀+n₁+n₂)+15M+12N+228` instructions. +* `NodePacking.time_le_linear`: a uniform linear instruction bound. + +Prime selection, the frontend, recursion, global `State.init` composition and the global width +and preprocessing asymptotics remain the responsibility of the parent construction. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Complete node work, with only actual supplied array contents and one explicit polynomial +width envelope as premises. The instruction bound excludes the final halt. -/ +theorem exec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < (layout x).M) (hN : 2 * (layout x).M ≤ (layout x).N) + (hwidth : GenericRAM.Signed.Fits w ((layout x).budget : ℤ)) + (hsrc : ∀ j, x.regs (source j) + (layout x).n j ≤ (layout x).base) + (hbdd : ∀ j i, i < (layout x).n j → (values j i).natAbs ≤ (layout x).U) + (hinput : ∀ j i, i < (layout x).n j → + x.mem (x.regs (source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range ((layout x).n j)) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y + (66 * (layout x).total + 15 * (layout x).M + 12 * (layout x).N + 228) ∧ + y.mem = memory (layout x) values x.mem ∧ Parameters x y := by + let L := layout x + have hfit : Fits (w + 2) L.budget := unsigned_fits_of_signed hwidth + have hwidth' : GenericRAM.Signed.Fits w (L.budget : ℤ) := hwidth + have hM' : 0 < L.M := hM + have hN' : 2 * L.M ≤ L.N := hN + have hmod : GenericRAM.Signed.Fits w (L.M : ℤ) := signed_fits_of_natAbs_le hwidth' + (by simpa only [Int.natAbs_natCast] using + (show L.M ≤ L.budget by have := L.bounds.2.2; omega)) + have hu : L.U ≤ L.budget := by have := L.bounds.2.2; omega + have hsigned (j : Fin 3) (i : ℕ) (hi : i < L.n j) : + GenericRAM.Signed.Fits w (values j i) := + signed_fits_of_natAbs_le hwidth' ((hbdd j i hi).trans hu) + let R (y : Data 64) : Prop := Parameters x y ∧ Prepared L y ∧ + ∀ a < L.base, y.mem a = x.mem a + obtain ⟨a, ha, hap, har, ham⟩ := exec_initialize x hfit + have hRa : R a := by + refine ⟨hap, har, ?_⟩ + intro b hb + rw [ham] + have hp : L.base ≤ L.packed := by dsimp only [Layout.packed]; omega + have hnot : ¬(L.packed ≤ b ∧ b < L.packed + 4 * L.N) := by omega + change paddingMemory L x.mem b = x.mem b + simp only [paddingMemory, Padding.memory, hnot, ↓reduceIte] + have hcall (j : Fin 3) (y : Data 64) (hy : R y) : + ∃ z, ExecWithin (w + 2) (round j) y z (66 * L.n j + 5 * L.M + 65) ∧ + R z ∧ z.mem = roundMemory L values j y.mem := by + obtain ⟨hp, hr, hm⟩ := hy + obtain ⟨z, hz, hzm, _, hzp, hzr⟩ := exec_round x y values j hp hr hfit hM' hN' hmod + (hsrc j) (hbdd j) (hsigned j) + (by + intro i hi + have hi' : i < L.n j := hi + have hsi := hsrc j + have hsi' : x.regs (source j) + L.n j ≤ L.base := hsi + rw [hm _ (by omega)] + exact hinput j i hi) + (hinj j) + refine ⟨z, hz, ⟨hzp, hzr, ?_⟩, hzm⟩ + intro b hb + rw [hzm, roundMemory_before L values j y.mem hM' hN' + (lt_of_lt_of_le hb (L.base_le_descriptor j)), hm b hb] + obtain ⟨b, hb, hRb, hbm⟩ := hcall 0 a hRa + obtain ⟨c, hc, hRc, hcm⟩ := hcall 1 b hRb + obtain ⟨d, hd, hRd, hdm⟩ := hcall 2 c hRc + refine ⟨d, (ha.seq (hb.seq (hc.seq hd))).mono (by dsimp only [L, Layout.total]; omega), + ?_, hRd.1⟩ + rw [hdm, hcm, hbm, ham] + rfl + +/-- The complete charged bound is uniformly linear in input lengths, modulus and output length. -/ +theorem time_le_linear (L : Layout) : + 66 * L.total + 15 * L.M + 12 * L.N + 228 ≤ 256 * (L.total + L.M + L.N + 1) := by + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean new file mode 100644 index 0000000000..ddce7d18d0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean @@ -0,0 +1,82 @@ +/- +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.NodeSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeMemory + +/-! +# Complete native initialization of a Chan–He node + +## Main results + +* `NodePacking.exec_initialize`: exact runtime layout, encoded constants and all `4N` padding + writes, charged within `12N+33` native instructions. + +## References + +This native composition proof is original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +private theorem paddingRegisters_injective : Function.Injective paddingRegisters := by + intro i j he + apply Fin.ext + exact congrArg (fun k : Fin 64 => k.val) he + +private theorem padding_extend_frame (a : Data 10) (x : Data 64) : + Frame x (a.extend paddingRegisters x) := by + intro i hi hne + change Function.extend paddingRegisters a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + have := congrArg Fin.val hj + dsimp only [paddingRegisters] at this + omega + +/-- All setup and padding are real instructions, starting from arbitrary old output memory. -/ +theorem exec_initialize {w : ℕ} (x : Data 64) (hfit : Fits w (layout x).budget) : + ∃ y, ExecWithin w initializeCode x y (12 * (layout x).N + 33) ∧ Parameters x y ∧ + Prepared (layout x) y ∧ y.mem = paddingMemory (layout x) x.mem := by + let L := layout x + have hfit' : Fits w L.budget := hfit + obtain ⟨a, ha, har, ham, hap, hak, hah, ha1, ha4, ha3⟩ := exec_layout x hfit + have haU : a.regs magnitude = L.U := har magnitude (by decide) + have haN : a.regs groups = L.N := har groups (by decide) + obtain ⟨b, hb, hbr, hbm, hready, hpad⟩ := exec_constants L a hfit' haU haN hak hah hap ha1 ha4 ha3 + have hargs : Parameters x b := fun i hi => (hbr i hi).trans (har i hi) + obtain ⟨hbd, hbn, hbp₀, hbp₁, hbp₂, hbp₃⟩ := hpad + have hpd : (b.comap paddingRegisters).regs Padding.dst = L.packed := hbd + have hpn : (b.comap paddingRegisters).regs Padding.len = L.N := hbn + have hf4 : Fits w 4 := hfit'.of_le (by have := L.bounds.2.2; omega) + have hpend : Fits w ((b.comap paddingRegisters).regs Padding.dst + + 4 * (b.comap paddingRegisters).regs Padding.len) := by + rw [hpd, hpn] + exact (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + obtain ⟨c, hc, hcm, _⟩ := Padding.exec (b.comap paddingRegisters) hf4 hpend + let y := c.extend paddingRegisters b + have hy : ExecWithin w (Padding.program.rename paddingRegisters) b y (12 * L.N + 5) := + by simpa only [hpn] using hc.rename paddingRegisters_injective + have hframe : Frame b y := padding_extend_frame c b + refine ⟨y, (ha.seq (hb.seq hy)).mono (by dsimp only [L] at *; omega), hargs.of_frame hframe, + hready.of_frame hframe, ?_⟩ + change c.mem = _ + rw [hcm, Data.comap_mem, hbm, ham, hpd, hpn] + change Padding.memory x.mem L.packed (b.regs pad0) (b.regs pad1) + (b.regs pad2) (b.regs pad3) L.N = _ + rw [hbp₀, hbp₁, hbp₂, hbp₃] + rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean new file mode 100644 index 0000000000..74f0ccbde6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean @@ -0,0 +1,176 @@ +/- +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.CountPlace +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Padding +public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers + +/-! +# Disjoint memory layout for complete native Chan–He node construction + +Three supplied signed input arrays precede `base`. The allocated block consists of three +count-prefixed heavy arrays (capacities `n₀,n₁,n₂`), the `4N` packed cells, a reusable residue +array of capacity `n₀+n₁+n₂`, and `M` histogram cells. Empty arrays still have a count cell. + +## Main definitions + +* `NodePacking.Layout`: concrete bases and a polynomial resource envelope. + +## References + +This memory layout is an original implementation choice. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Runtime sizes and the fresh allocation base for one complete node. -/ +structure Layout where + /-- The input arrays end no later than this address. -/ + base : ℕ + /-- The three supplied input lengths. -/ + n : Fin 3 → ℕ + /-- Common number of groups; the packed output has `4N` cells. -/ + N : ℕ + /-- Positive modulus, supplied by the parent prime search. -/ + M : ℕ + /-- Bound on the magnitudes of this node's inputs. -/ + U : ℕ + +/-- Total input capacity, shared by the reusable residue workspace. -/ +def Layout.total (L : Layout) : ℕ := L.n 0 + L.n 1 + L.n 2 + +/-- Count cell immediately preceding a compact heavy array. -/ +def Layout.descriptor (L : Layout) (j : Fin 3) : ℕ := + L.base + j.val + if j = 0 then 0 else if j = 1 then L.n 0 else L.n 0 + L.n 1 + +/-- Compact heavy output data starts one cell after its count. -/ +def Layout.heavy (L : Layout) (j : Fin 3) : ℕ := L.descriptor j + 1 + +/-- All three compact output capacities precede the packed array. -/ +def Layout.packed (L : Layout) : ℕ := L.base + 3 + L.total + +/-- A reusable residue array, after every permanent output. -/ +def Layout.scratch (L : Layout) : ℕ := L.packed + 4 * L.N + +/-- A reusable histogram, after the full residue capacity. -/ +def Layout.hist (L : Layout) : ℕ := L.scratch + L.total + +/-- One-past-end address of the complete allocated block. -/ +def Layout.endpoint (L : Layout) : ℕ := L.hist + L.M + +/-- The ordinary one-array tag, `3(2U+1)+1`. -/ +def Layout.gap (L : Layout) : ℕ := 6 * L.U + 4 + +/-- Positive tag for the selected packed column. -/ +def Layout.tag (L : Layout) (j : Fin 3) : ℕ := + if j = 0 then L.gap else if j = 1 then 3 * L.gap else 4 * L.gap + +/-- Polynomial envelope for all addresses, signed values, encodings and histogram counters. +Its fixed constant also covers code labels and all setup constants. -/ +def Layout.budget (L : Layout) : ℕ := + 4096 * (L.base + L.total + L.M + L.N + L.U + 1) ^ 2 + +/-- Each input fits the reusable residue capacity. -/ +theorem Layout.n_le_total (L : Layout) (j : Fin 3) : L.n j ≤ L.total := by + fin_cases j <;> simp +decide [Layout.total] <;> omega + +/-- All three compact arrays and their count cells are disjoint, with the packed array next. -/ +theorem Layout.heavy_end_le_packed (L : Layout) (j : Fin 3) : + L.heavy j + L.n j ≤ L.packed := by + fin_cases j <;> simp +decide [Layout.heavy, Layout.descriptor, Layout.packed, Layout.total] <;> + omega + +/-- Every descriptor is inside its allocated output prefix. -/ +theorem Layout.base_le_descriptor (L : Layout) (j : Fin 3) : L.base ≤ L.descriptor j := by + dsimp only [Layout.descriptor] + omega + +/-- Every descriptor precedes the packed array, including for empty inputs. -/ +theorem Layout.descriptor_lt_packed (L : Layout) (j : Fin 3) : L.descriptor j < L.packed := by + have := L.heavy_end_le_packed j + dsimp only [Layout.heavy] at this + omega + +/-- The allocated endpoint has a linear size formula. -/ +theorem Layout.endpoint_eq (L : Layout) : + L.endpoint = L.base + 3 + 2 * L.total + 4 * L.N + L.M := by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch, Layout.packed] + omega + +/-- Descriptor/capacity blocks end where the next one begins. -/ +theorem Layout.next_descriptor (L : Layout) : + L.heavy 0 + L.n 0 = L.descriptor 1 ∧ + L.heavy 1 + L.n 1 = L.descriptor 2 ∧ L.heavy 2 + L.n 2 = L.packed := by + norm_num [Layout.heavy, Layout.descriptor, Layout.packed, Layout.total] + omega + +/-- Every address and padding/tag value is covered by one polynomial envelope. -/ +theorem Layout.bounds (L : Layout) : + L.endpoint ≤ L.budget ∧ 120 * L.U + 80 ≤ L.budget ∧ + L.base + L.total + L.M + L.N + L.U + 16 ≤ L.budget := by + let S := L.base + L.total + L.M + L.N + L.U + 1 + have hs : 1 ≤ S := by dsimp only [S]; omega + have hsq : S ≤ S ^ 2 := by nlinarith + have hb : L.base ≤ S := by dsimp only [S]; omega + have hn : L.total ≤ S := by dsimp only [S]; omega + have hm : L.M ≤ S := by dsimp only [S]; omega + have hN : L.N ≤ S := by dsimp only [S]; omega + have hU : L.U ≤ S := by dsimp only [S]; omega + rw [Layout.endpoint_eq] + change _ ≤ 4096 * S ^ 2 ∧ _ ≤ 4096 * S ^ 2 ∧ _ ≤ 4096 * S ^ 2 + constructor + · nlinarith + constructor <;> nlinarith + +/-- The complete counting/placement call's budget is derived from the concrete node layout. -/ +theorem Layout.count_budget_le (L : Layout) (j : Fin 3) (src t : ℕ) + (hs : src ≤ L.base) (ht : t ≤ 4 * L.gap) : + src + L.scratch + L.hist + L.heavy j + L.packed + L.n j * L.n j + + 2 * L.n j + L.M + 4 * L.N + 2 * t + 2 * L.U + 64 ≤ L.budget := by + let S := L.base + L.total + L.M + L.N + L.U + 1 + have hS : 1 ≤ S := by dsimp only [S]; omega + have hsq : S ≤ S ^ 2 := by nlinarith + have hb : L.base ≤ S := by dsimp only [S]; omega + have hn : L.total ≤ S := by dsimp only [S]; omega + have hm : L.M ≤ S := by dsimp only [S]; omega + have hN : L.N ≤ S := by dsimp only [S]; omega + have hU : L.U ≤ S := by dsimp only [S]; omega + have hj := L.n_le_total j + have hjsq : L.n j * L.n j ≤ S ^ 2 := by nlinarith + have ho := L.heavy_end_le_packed j + dsimp only [Layout.gap] at ht + dsimp only [Layout.hist, Layout.scratch, Layout.packed] + change _ ≤ 4096 * S ^ 2 + dsimp only [Layout.packed] at ho + nlinarith + +/-- All three tags are positive, dominate the input magnitude, and fit the common tag bound. -/ +theorem Layout.tag_bounds (L : Layout) (j : Fin 3) : + L.U ≤ L.tag j ∧ L.tag j ≤ 4 * L.gap := by + fin_cases j <;> simp +decide [Layout.tag, Layout.gap] <;> omega + +/-- A signed fit for a nonnegative envelope also covers every integer bounded by it. -/ +theorem signed_fits_of_natAbs_le {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) + {z : ℤ} (hz : z.natAbs ≤ B) : GenericRAM.Signed.Fits w z := by + obtain ⟨hw, hlo, hhi⟩ := hfit + have habs := abs_le.mp (show |z| ≤ (B : ℤ) by + rw [← Int.natCast_natAbs] + exact_mod_cast hz) + exact ⟨hw, by omega, by omega⟩ + +/-- One signed envelope suffices for every unsigned intermediate at the execution width. -/ +theorem unsigned_fits_of_signed {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) : + Fits (w + 2) B := + ((encode_fits hfit).mono (by omega)).of_le (by simp [Signed.encode]; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean new file mode 100644 index 0000000000..4e6e4be17e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean @@ -0,0 +1,314 @@ +/- +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.NodeLayout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlaceMemory + +/-! +# Exact output memory of complete native Chan–He node construction + +## Main definitions + +* `NodePacking.paddingMemory`, `roundMemory`, `memory`: exact instruction-level write specs. +* `NodePacking.node`: the existing mathematical node for the three supplied enumerations. + +## References + +These memory composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM.Signed + +/-- Every output cell is initialized, even when the three input arrays are empty. -/ +def paddingMemory (L : Layout) (mem : ℕ → ℕ) : ℕ → ℕ := + Padding.memory mem L.packed (120 * L.U + 80) (16 * L.U + 10) + (40 * L.U + 26) (44 * L.U + 30) L.N + +/-- Exact complete count/place call for the selected supplied input array. -/ +def roundMemory (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) (mem : ℕ → ℕ) : ℕ → ℕ := + CountPlace.memory mem (values j) (L.n j) L.M (L.heavy j) L.packed (j.val + 1) + (if j = 2 then 4 * L.M else 0) (L.tag j) L.scratch L.hist (L.descriptor j) + (decide (j = 2)) + +/-- Exact memory after padding and all three complete native calls. -/ +def memory (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) : ℕ → ℕ := + roundMemory L values 2 (roundMemory L values 1 (roundMemory L values 0 (paddingMemory L mem))) + +/-- The existing mathematical Chan–He node, with no alternative 3SUM semantics. -/ +def node (L : Layout) (values : Fin 3 → ℕ → ℤ) : + Cslib.Algorithms.ThreeSum.ChanHe.Node := + ⟨(Finset.range (L.n 0)).image (values 0), (Finset.range (L.n 1)).image (values 1), + (Finset.range (L.n 2)).image (values 2), L.M⟩ + +/-- The native unsigned pattern encodes exactly the signed padding and tags of `oneArray`. -/ +theorem padding_values (L : Layout) : + 120 * L.U + 80 = encode (10 * (L.gap : ℤ)) ∧ + 16 * L.U + 10 = encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.gap) ∧ + 40 * L.U + 26 = encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * L.gap) ∧ + 44 * L.U + 30 = encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap) := by + have h₀ : 10 * (L.gap : ℤ) = ((60 * L.U + 40 : ℕ) : ℤ) := by + dsimp only [Layout.gap] + push_cast + ring + have h₁ : Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + (L.gap : ℤ) = + ((8 * L.U + 5 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + have h₂ : Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * (L.gap : ℤ) = + ((20 * L.U + 13 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + have h₃ : -Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * (L.gap : ℤ) = + ((22 * L.U + 15 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + rw [h₀, h₁, h₂, h₃] + simp only [encode, Int.natAbs_natCast, Int.natCast_nonneg, not_lt_of_ge, + ↓reduceIte, Nat.add_zero] + omega + +/-- Reading initialized packed memory at any fitting index. -/ +theorem paddingMemory_read (L : Layout) (mem : ℕ → ℕ) {u : ℕ} (hu : u < 4 * L.N) : + paddingMemory L mem (L.packed + u) = + Padding.pattern (120 * L.U + 80) (16 * L.U + 10) (40 * L.U + 26) (44 * L.U + 30) u := by + simp [paddingMemory, Padding.memory, hu] + +/-- Later rounds preserve every earlier count/capacity block. -/ +theorem roundMemory_before (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) + {a : ℕ} (ha : a < L.descriptor j) : roundMemory L values j mem a = mem a := by + have ho := L.descriptor_lt_packed j + apply CountPlace.memory_outside _ _ _ _ _ _ _ _ _ _ _ _ L.N _ hM hN + (by omega) (by split_ifs <;> omega) + · dsimp only [Layout.heavy]; omega + · omega + · dsimp only [Layout.scratch]; omega + · dsimp only [Layout.hist, Layout.scratch]; omega + · omega + +/-- Every complete call preserves every other packed column, including column zero. -/ +theorem roundMemory_other (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) {u : ℕ} (hu : u < 4 * L.N) (hc : u % 4 ≠ j.val + 1) : + roundMemory L values j mem (L.packed + u) = mem (L.packed + u) := by + have hn := L.n_le_total j + apply CountPlace.memory_other_column _ _ _ _ _ _ _ _ _ _ _ _ L.N _ + (L.heavy_end_le_packed j) (by omega) + (by by_cases hj : j = 2 <;> simp [hj]) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed j) hu hc + +/-- The first two complete rounds produce the existing mathematical light array. -/ +theorem roundMemory_arrXY (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hj : j ≠ 2) (hM : 0 < L.M) + (hinj : Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) + (hpad : mem (L.packed + 4 * i + (j.val + 1)) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.tag j)) : + roundMemory L values j mem (L.packed + 4 * i + (j.val + 1)) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range (L.n j)).image (values j)) L.M L.U i + L.tag j) := by + have hn := L.n_le_total j + simpa only [roundMemory, hj, ↓reduceIte, decide_false] using + CountPlace.memory_arrXY mem (values j) (L.n j) L.M (L.heavy j) L.packed (j.val + 1) + (L.tag j) L.U L.scratch L.hist (L.descriptor j) L.N hM (L.heavy_end_le_packed j) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed j) hinj + (by omega) hpad + +/-- The third complete round produces the existing duplicated, negated mathematical array. -/ +theorem roundMemory_arrZ (L : Layout) (values : Fin 3 → ℕ → ℤ) + (mem : ℕ → ℕ) (hM : 0 < L.M) + (hinj : Set.InjOn (values 2) (↑(Finset.range (L.n 2)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) + (hpad : mem (L.packed + 4 * i + 3) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap)) : + roundMemory L values 2 mem (L.packed + 4 * i + 3) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range (L.n 2)).image (values 2)) L.M L.U i + 4 * L.gap) := by + have hn := L.n_le_total 2 + simpa +decide only [roundMemory, Layout.tag, Nat.reduceAdd, ↓reduceIte, + decide_true, Fin.val_two, Nat.cast_mul, Nat.cast_ofNat] using + CountPlace.memory_arrZ mem (values 2) (L.n 2) L.M (L.heavy 2) L.packed 3 + (4 * L.gap) L.U L.scratch L.hist (L.descriptor 2) L.N hM (L.heavy_end_le_packed 2) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed 2) hinj + (by omega) hpad + +/-- All four packed columns have exactly the existing node's required values. -/ +theorem memory_cells (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) : + memory L values mem (L.packed + 4 * i) = encode (10 * (L.gap : ℤ)) ∧ + memory L values mem (L.packed + 4 * i + 1) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrXY (node L values).S₁ L.M L.U i + L.gap) ∧ + memory L values mem (L.packed + 4 * i + 2) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrXY (node L values).S₂ L.M L.U i + 3 * L.gap) ∧ + memory L values mem (L.packed + 4 * i + 3) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrZ (node L values).S₃ L.M L.U i + 4 * L.gap) := by + have hp (c : ℕ) (hc : c < 4) : + paddingMemory L mem (L.packed + (4 * i + c)) = + Padding.pattern (120 * L.U + 80) (16 * L.U + 10) + (40 * L.U + 26) (44 * L.U + 30) (4 * i + c) := + paddingMemory_read L mem (by omega) + have hp₀ : paddingMemory L mem (L.packed + 4 * i) = encode (10 * (L.gap : ℤ)) := by + have he := hp 0 (by decide) + simpa [Padding.pattern, (padding_values L).1] using he + have hp₁ : paddingMemory L mem (L.packed + 4 * i + 1) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.gap) := by + have he := hp 1 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.1] using he + have hp₂ : paddingMemory L mem (L.packed + 4 * i + 2) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * L.gap) := by + have he := hp 2 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.2.1] using he + have hp₃ : paddingMemory L mem (L.packed + 4 * i + 3) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap) := by + have he := hp 3 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.2.2] using he + have other (j : Fin 3) (μ : ℕ → ℕ) (c : ℕ) (hc : c < 4) (hne : c ≠ j.val + 1) : + roundMemory L values j μ (L.packed + 4 * i + c) = μ (L.packed + 4 * i + c) := by + simpa only [Nat.add_assoc] using roundMemory_other L values j μ + (u := 4 * i + c) (by omega) (by omega) + refine ⟨?_, ?_, ?_, ?_⟩ + · change roundMemory L values 2 _ _ = _ + rw [show L.packed + 4 * i = L.packed + 4 * i + 0 by omega, + other 2 _ 0 (by decide) (by decide), other 1 _ 0 (by decide) (by decide), + other 0 _ 0 (by decide) (by decide)] + exact hp₀ + · change roundMemory L values 2 _ _ = _ + rw [other 2 _ 1 (by decide) (by decide), other 1 _ 1 (by decide) (by decide)] + simpa +decide [Layout.tag, node] using roundMemory_arrXY L values 0 (paddingMemory L mem) + (by decide) hM (hinj 0) hi (by simpa [Layout.tag] using hp₁) + · change roundMemory L values 2 _ _ = _ + rw [other 2 _ 2 (by decide) (by decide)] + simpa +decide [Layout.tag, node, Nat.cast_mul] using roundMemory_arrXY L values 1 + (roundMemory L values 0 (paddingMemory L mem)) (by decide) hM (hinj 1) hi (by + change roundMemory L values 0 (paddingMemory L mem) (L.packed + 4 * i + 2) = _ + rw [other 0 _ 2 (by decide) (by decide)] + simpa [Layout.tag, Nat.cast_mul] using hp₂) + · change roundMemory L values 2 _ _ = _ + apply roundMemory_arrZ L values _ hM (hinj 2) hi + rw [other 1 _ 3 (by decide) (by decide), other 0 _ 3 (by decide) (by decide)] + exact hp₃ + +/-- Every one of the `4N` native output cells equals the existing mathematical `Node.oneArray`. +This includes column-zero sentinels, empty and heavy buckets, and the entire padded tail. -/ +theorem memory_oneArray (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {u : ℕ} (hu : u < 4 * L.N) : + memory L values mem (L.packed + u) = encode ((node L values).oneArray L.U u) := by + obtain ⟨h₀, h₁, h₂, h₃⟩ := memory_cells L values mem hM hinj (i := u / 4) (by omega) + have hgap : 3 * (((2 * L.U + 1 : ℕ) : ℤ)) + 1 = (L.gap : ℤ) := by + dsimp only [Layout.gap] + push_cast + ring + by_cases h1 : u % 4 = 1 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 1 by omega, h₁] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h1, ↓reduceIte, hgap, node] + by_cases h2 : u % 4 = 2 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 2 by omega, h₂] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h2, ↓reduceIte, hgap, node, Nat.reduceEqDiff] + by_cases h3 : u % 4 = 3 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 3 by omega, h₃] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h3, ↓reduceIte, hgap, node, Nat.reduceEqDiff] + · rw [show L.packed + u = L.packed + 4 * (u / 4) by omega, h₀] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h1, h2, h3, ↓reduceIte, hgap] + +/-- Every count cell and every used heavy cell survives subsequent rounds. -/ +theorem memory_heavy (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) (j : Fin 3) : + memory L values mem (L.descriptor j) = (Place.heavyList (values j) (L.n j) L.M (L.n j)).length ∧ + ∀ k (hk : k < (Place.heavyList (values j) (L.n j) L.M (L.n j)).length), + memory L values mem (L.heavy j + k) = + encode ((Place.heavyList (values j) (L.n j) L.M (L.n j))[k]) := by + have hround (j : Fin 3) (μ : ℕ → ℕ) : + roundMemory L values j μ (L.descriptor j) = + (Place.heavyList (values j) (L.n j) L.M (L.n j)).length ∧ + ∀ k (hk : k < (Place.heavyList (values j) (L.n j) L.M (L.n j)).length), + roundMemory L values j μ (L.heavy j + k) = + encode ((Place.heavyList (values j) (L.n j) L.M (L.n j))[k]) := by + constructor + · exact CountPlace.memory_descriptor .. + · intro k hk + exact CountPlace.memory_heavy _ _ _ _ _ _ _ _ _ _ _ _ _ + (L.heavy_end_le_packed j) (by dsimp only [Layout.heavy]; omega) hk + have hnext := L.next_descriptor + have hdesc01 : L.descriptor 0 < L.descriptor 1 := by + dsimp only [Layout.heavy] at hnext + omega + have hdesc12 : L.descriptor 1 < L.descriptor 2 := by + dsimp only [Layout.heavy] at hnext + omega + fin_cases j <;> simp only [Fin.mk_zero, Fin.mk_one] + · obtain ⟨hc, hd⟩ := hround 0 (paddingMemory L mem) + constructor + · change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega), + roundMemory_before L values 1 _ hM hN (by omega)] + exact hc + · intro k hk + have hlen := Place.length_heavyList_le (values 0) (L.n 0) L.M (L.n 0) + change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega), + roundMemory_before L values 1 _ hM hN (by omega)] + exact hd k hk + · obtain ⟨hc, hd⟩ := hround 1 (roundMemory L values 0 (paddingMemory L mem)) + constructor + · change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega)] + exact hc + · intro k hk + have hlen := Place.length_heavyList_le (values 1) (L.n 1) L.M (L.n 1) + change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega)] + exact hd k hk + · exact hround 2 (roundMemory L values 1 (roundMemory L values 0 (paddingMemory L mem))) + +/-- Each complete round preserves all memory outside the concrete allocation. -/ +theorem roundMemory_outside (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) + {a : ℕ} (ha : a < L.base ∨ L.endpoint ≤ a) : roundMemory L values j mem a = mem a := by + have ho := L.heavy_end_le_packed j + have hb := L.base_le_descriptor j + have hd := L.descriptor_lt_packed j + have hn := L.n_le_total j + have hp : L.packed + 4 * L.N ≤ L.endpoint := by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega + have hk : L.scratch + L.n j ≤ L.hist := Nat.add_le_add_left hn _ + have hh : L.hist + L.M = L.endpoint := rfl + have hs : L.scratch = L.packed + 4 * L.N := rfl + have hout : L.heavy j = L.descriptor j + 1 := rfl + apply CountPlace.memory_outside _ _ _ _ _ _ _ _ _ _ _ _ L.N _ hM hN + (by omega) (by split_ifs <;> omega) + all_goals rcases ha with ha | ha <;> omega + +/-- Inputs, caller memory before the allocation, and memory beyond its endpoint are preserved. -/ +theorem memory_outside (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) {a : ℕ} (ha : a < L.base ∨ L.endpoint ≤ a) : + memory L values mem a = mem a := by + rw [memory, roundMemory_outside L values 2 _ hM hN ha, + roundMemory_outside L values 1 _ hM hN ha, roundMemory_outside L values 0 _ hM hN ha] + have hp : L.packed + 4 * L.N ≤ L.endpoint := by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega + have hb : L.base ≤ L.packed := by dsimp only [Layout.packed]; omega + have hnot : ¬(L.packed ≤ a ∧ a < L.packed + 4 * L.N) := by + rcases ha with ha | ha <;> omega + simp only [paddingMemory, Padding.memory, hnot, ↓reduceIte] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean new file mode 100644 index 0000000000..65255a32cd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean @@ -0,0 +1,168 @@ +/- +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.NodeLayout + +/-! +# Complete fixed native program for one Chan–He node + +The source comparator is [NodeArray.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/ChanHe/NodeArray.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The layout, native code and proofs are original. The source's `Light` machine is not imported. + +## Main definitions + +* `NodePacking.program`: computes the layout and tags, pads every output cell, then performs + three actual residue/histogram/extraction/placement calls, including all three count stores. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- The three supplied signed input bases. -/ +def source (j : Fin 3) : Fin 64 := ⟨46 + 2 * j.val, by omega⟩ +/-- The three supplied input lengths. -/ +def length (j : Fin 3) : Fin 64 := ⟨47 + 2 * j.val, by omega⟩ +/-- Fresh allocation base after all input arrays. -/ +abbrev base : Fin 64 := 52 +/-- Common number of four-cell groups. -/ +abbrev groups : Fin 64 := 53 +/-- Node input magnitude bound. -/ +abbrev magnitude : Fin 64 := 54 +/-- Positive modulus. -/ +abbrev modulus : Fin 64 := 55 +/-- Computed residue workspace base. -/ +abbrev scratch : Fin 64 := 56 +/-- Computed histogram workspace base. -/ +abbrev histogram : Fin 64 := 57 +/-- Computed packed output base. -/ +abbrev packed : Fin 64 := 58 +/-- Twice the gap `G=6U+4`, ready for sign-and-magnitude tagging. -/ +abbrev gap : Fin 64 := 59 +/-- Twice the padding magnitude `W=2U+1`. -/ +abbrev padding : Fin 64 := 60 +/-- Setup scratch. -/ +abbrev temporary : Fin 64 := 61 +/-- Constant one, retained across the complete calls. -/ +abbrev one : Fin 64 := 62 +/-- Constant four, retained across the complete calls. -/ +abbrev four : Fin 64 := 63 + +/-- The mathematical layout determined by the runtime parameters. -/ +def layout (x : Data 64) : Layout := + ⟨x.regs base, fun j => x.regs (length j), x.regs groups, x.regs modulus, x.regs magnitude⟩ + +/-- Padding workspace embedded below all persistent node parameters. -/ +def paddingRegisters (i : Fin 10) : Fin 64 := ⟨i.val, by omega⟩ + +@[inherit_doc Padding.dst] +abbrev pdst : Fin 64 := paddingRegisters Padding.dst +@[inherit_doc Padding.len] +abbrev plen : Fin 64 := paddingRegisters Padding.len +@[inherit_doc Padding.pad0] +abbrev pad0 : Fin 64 := paddingRegisters Padding.pad0 +@[inherit_doc Padding.pad1] +abbrev pad1 : Fin 64 := paddingRegisters Padding.pad1 +@[inherit_doc Padding.pad2] +abbrev pad2 : Fin 64 := paddingRegisters Padding.pad2 +@[inherit_doc Padding.pad3] +abbrev pad3 : Fin 64 := paddingRegisters Padding.pad3 + +/-- Compute every permanent and scratch block address in twelve native instructions. -/ +def layoutCode : Code .standard 64 := + let n₀ := length 0 + let n₁ := length 1 + let n₂ := length 2 + ram[ one ← #1; four ← #4; temporary ← #3; + packed ← base + temporary; packed ← packed + n₀; packed ← packed + n₁; + packed ← packed + n₂; scratch ← four * groups; scratch ← packed + scratch; + histogram ← scratch + n₀; histogram ← histogram + n₁; histogram ← histogram + n₂ ] + +/-- Compute the exact positive encoded padding pattern, including all signed-negation/tag work. +The third padding word uses subtraction because its integer contribution is `-W`. -/ +def constantsCode : Code .standard 64 := ram[ + padding ← magnitude + magnitude; padding ← padding + one; padding ← padding + padding; + gap ← temporary * padding; temporary ← #2; gap ← gap + temporary; + pad0 ← #10; pad0 ← pad0 * gap; pad1 ← padding + gap; + temporary ← #3; pad2 ← temporary * gap; pad2 ← pad2 + padding; + pad3 ← four * gap; pad3 ← pad3 - padding; pdst ← packed; plen ← groups ] + +/-- Compute the selected heavy-array descriptor address. The choice is one of three fixed +pieces of code, never a program whose size depends on an input length. -/ +def descriptorCode (j : Fin 3) : Code .standard 64 := + let d := CountPlace.descriptor + let n₀ := length 0 + let n₁ := length 1 + if j = 0 then ram[ d ← base ] else if j = 1 then ram[ d ← base + one; d ← d + n₀ ] + else ram[ d ← base + one; d ← d + n₀; d ← d + one; d ← d + n₁ ] + +/-- Deliver the exact encoded tag for one fixed column. -/ +def tagCode (j : Fin 3) : Code .standard 64 := + let t := CountPlace.tag + if j = 0 then ram[ t ← gap ] else if j = 1 then ram[ temporary ← #3; t ← temporary * gap ] + else ram[ t ← four * gap ] + +/-- Deliver the duplicated-Z distance; the other columns perform two coincident stores. -/ +def distanceCode (j : Fin 3) : Code .standard 64 := + let d := CountPlace.distance + if j = 2 then ram[ d ← four * modulus ] else ram[ d ← #0 ] + +/-- Every runtime input and output parameter is explicitly delivered to the complete call. -/ +def prepare (j : Fin 3) : Code .standard 64 := + let n := length j + let s := source j + let l := CountPlace.len + let a := CountPlace.src + let k := CountPlace.scratch + let h := CountPlace.hist + let d := CountPlace.descriptor + let o := CountPlace.heavy + let p := CountPlace.packed + let m := CountPlace.modulus + let c := CountPlace.column + let neg := CountPlace.negative + .seq (ram[ l ← n; a ← s; k ← scratch; h ← histogram ]) + (.seq (descriptorCode j) (.seq (ram[ o ← d + one; p ← packed; m ← modulus ]) + (.seq (tagCode j) (.seq (.op (.const c (j.val + 1))) + (.seq (distanceCode j) (.op (.const neg (if j = 2 then 1 else 0)))))))) + +/-- Complete one-set native round with charged setup and a written heavy count. -/ +def round (j : Fin 3) : Code .standard 64 := .seq (prepare j) CountPlace.program + +/-- Complete charged address/tag/padding initialization. -/ +def initializeCode : Code .standard 64 := + .seq layoutCode (.seq constantsCode (Padding.program.rename paddingRegisters)) + +/-- Complete one-node preprocessing. This code is independent of `n,w,M,N,U`. -/ +def program : Code .standard 64 := + .seq initializeCode (.seq (round 0) (.seq (round 1) (round 2))) + +/-- The original three input bases/lengths and base, length, magnitude and modulus parameters +are preserved by the complete program. -/ +theorem preserves_parameters (i : Fin 64) (hi : 46 ≤ i.val ∧ i.val ≤ 55) : + program.Preserves i := by + fin_cases i <;> norm_num at hi <;> decide + +/-- The assembled code length has a fixed bound, independent of every runtime parameter. -/ +theorem size_program_le : program.size ≤ 4096 := by decide + +/-- The complete program is deterministic and never reads the word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- No instruction samples randomness or invokes an oracle. -/ +theorem isRandFree : program.IsRandFree := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean new file mode 100644 index 0000000000..15ec928703 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean @@ -0,0 +1,178 @@ +/- +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.NodeInitialize + +/-! +# Complete charged native rounds of a Chan–He node + +## Main definitions + +* `NodePacking.CallArgs`: exact runtime delivery to the existing complete counting/placement code. + +## References + +These native setup and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- Exact runtime parameters delivered to one complete counting/placement call. -/ +structure CallArgs (L : Layout) (s : Fin 3 → ℕ) (j : Fin 3) (x : Data 64) : Prop where + /-- Actual input length. -/ + len : x.regs CountPlace.len = L.n j + /-- Actual signed input base. -/ + src : x.regs CountPlace.src = s j + /-- Computed residue workspace. -/ + scratch : x.regs CountPlace.scratch = L.scratch + /-- Computed histogram workspace. -/ + hist : x.regs CountPlace.hist = L.hist + /-- Computed compact heavy data base. -/ + heavy : x.regs CountPlace.heavy = L.heavy j + /-- Computed packed array base. -/ + packed : x.regs CountPlace.packed = L.packed + /-- Actual positive modulus. -/ + modulus : x.regs CountPlace.modulus = L.M + /-- Exact encoded positive tag. -/ + tag : x.regs CountPlace.tag = 2 * L.tag j + /-- Packed column. -/ + column : x.regs CountPlace.column = j.val + 1 + /-- Duplicate-store offset. -/ + distance : x.regs CountPlace.distance = if j = 2 then 4 * L.M else 0 + /-- Sign multiplier flag. -/ + negative : x.regs CountPlace.negative = if j = 2 then 1 else 0 + /-- Computed descriptor/count cell. -/ + descriptor : x.regs CountPlace.descriptor = L.descriptor j + +/-- Runtime argument delivery keeps all persistent caller registers. -/ +theorem prepare_preserves (j : Fin 3) (i : Fin 64) (hi : 46 ≤ i.val) (hne : i ≠ temporary) : + (prepare j).Preserves i := by + have h : 46 ≤ i.val ∧ i ≠ temporary := ⟨hi, hne⟩ + fin_cases j <;> fin_cases i <;> norm_num [temporary] at h <;> decide + +/-- Every complete native round retains the caller arguments and prepared layout/constants. -/ +theorem round_preserves (j : Fin 3) (i : Fin 64) (hi : 46 ≤ i.val) (hne : i ≠ temporary) : + (round j).Preserves i := by + change (prepare j).Preserves i ∧ CountPlace.program.Preserves i + exact ⟨prepare_preserves j i hi hne, CountPlace.preserves i (by omega) + (by intro he; subst i; norm_num [CountCall.modulus] at hi) + (by intro he; subst i; norm_num [CountCall.result] at hi)⟩ + +/-- All count/place arguments are computed from actual retained caller values in at most +twenty instructions. In particular no output pointer or encoded tag is supplied for free. -/ +theorem exec_prepare {w : ℕ} (x y : Data 64) (j : Fin 3) + (hparams : Parameters x y) (hready : Prepared (layout x) y) + (hfit : Fits w (layout x).budget) (hN : 2 * (layout x).M ≤ (layout x).N) : + ∃ q, ExecWithin w (prepare j) y q 20 ∧ + CallArgs (layout x) (fun j => x.regs (source j)) j q ∧ q.mem = y.mem := by + let L := layout x + have hfit' : Fits w L.budget := hfit + have hN' : 2 * L.M ≤ L.N := hN + obtain ⟨hk, hh, hp, hg, hpad, h1, h4⟩ := hready + have hl (k : Fin 3) : y.regs (length k) = L.n k := + hparams _ (by dsimp only [length]; omega) + have hs (k : Fin 3) : y.regs (source k) = x.regs (source k) := + hparams _ (by dsimp only [source]; omega) + have hb : y.regs base = L.base := hparams _ (by decide) + have hm : y.regs modulus = L.M := hparams _ (by decide) + have f (v : ℕ) (hv : v ≤ L.packed) : Fits w v := + (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + have fc (v : ℕ) (hv : v ≤ 16) : Fits w v := hfit'.of_le (by have := L.bounds.2.2; omega) + have fm (v : ℕ) (hv : v ≤ 120 * L.U + 80) : Fits w v := + (hfit'.of_le L.bounds.2.1).of_le hv + have f0 : Fits w 0 := fits_zero _ + have f1 : Fits w 1 := fc _ (by decide) + have f2 : Fits w 2 := fc _ (by decide) + have f3 : Fits w 3 := fc _ (by decide) + have fb₀ : Fits w (L.base + 1) := f _ (by dsimp only [Layout.packed]; omega) + have fb₁ : Fits w (L.base + 1 + L.n 0) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fb₂ : Fits w (L.base + 1 + L.n 0 + 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fb₃ : Fits w (L.base + 1 + L.n 0 + 1 + L.n 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fo₁ : Fits w (L.base + 1 + L.n 0 + 1) := fb₂ + have fo₂ : Fits w (L.base + 1 + L.n 0 + 1 + L.n 1 + 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have ft₁ : Fits w (3 * (2 * L.gap)) := fm _ (by dsimp only [Layout.gap]; omega) + have ft₂ : Fits w (4 * (2 * L.gap)) := fm _ (by dsimp only [Layout.gap]; omega) + have fd : Fits w (4 * L.M) := (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + dsimp only [L] at * + fin_cases j <;> simp only [Fin.mk_zero, Fin.mk_one] + all_goals + refine ⟨?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [prepare, descriptorCode, tagCode, distanceCode, Fin.val_zero, Fin.val_one, + hk, hh, hp, hg, h1, h4, hl, hs, hb, hm, f0, f1, f2, f3, + fb₀, fb₁, fb₂, fb₃, fo₁, fo₂, ft₁, ft₂, fd] + · constructor <;> + simp +decide [CountPlace.len, CountPlace.src, CountPlace.scratch, + CountPlace.hist, CountPlace.heavy, CountPlace.packed, CountPlace.modulus, + CountPlace.tag, CountPlace.column, CountPlace.distance, CountPlace.negative, + CountPlace.descriptor, Layout.heavy, Layout.descriptor, Layout.tag, layout, + source, length] <;> omega + · rfl + +/-- A complete native round, with all actual counting calls discharged and all pointer, +tag, duplicate-store and count-descriptor work charged. -/ +theorem exec_round {w : ℕ} (x y : Data 64) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (hparams : Parameters x y) (hready : Prepared (layout x) y) + (hfit : Fits (w + 2) (layout x).budget) + (hM : 0 < (layout x).M) (hN : 2 * (layout x).M ≤ (layout x).N) + (hmod : GenericRAM.Signed.Fits w ((layout x).M : ℤ)) + (hsrc : x.regs (source j) + (layout x).n j ≤ (layout x).base) + (hbdd : ∀ i < (layout x).n j, (values j i).natAbs ≤ (layout x).U) + (hsigned : ∀ i < (layout x).n j, GenericRAM.Signed.Fits w (values j i)) + (hinput : ∀ i < (layout x).n j, y.mem (x.regs (source j) + i) = + Signed.encode (values j i)) + (hinj : Set.InjOn (values j) (↑(Finset.range ((layout x).n j)) : Set ℕ)) : + ∃ z, ExecWithin (w + 2) (round j) y z (66 * (layout x).n j + 5 * (layout x).M + 65) ∧ + z.mem = roundMemory (layout x) values j y.mem ∧ Frame y z ∧ + Parameters x z ∧ Prepared (layout x) z := by + let L := layout x + have hsrc' : x.regs (source j) + L.n j ≤ L.base := hsrc + obtain ⟨q, hp, hq, hqm⟩ := exec_prepare x y j hparams hready hfit hN + have hf : Fits (w + 2) (CountPlace.budget q L.M L.N L.U (L.tag j)) := by + rw [CountPlace.budget, hq.src, hq.scratch, hq.hist, hq.heavy, hq.packed, hq.len] + exact hfit.of_le (L.count_budget_le j _ _ (by omega) (L.tag_bounds j).2) + have hn := L.n_le_total j + obtain ⟨z, hz, hzm, _⟩ := CountPlace.exec q (values j) (decide (j = 2)) hM hmod + hq.modulus hN (L.tag_bounds j).1 hq.tag + (by by_cases hj : j = 2 <;> simpa [hj] using hq.negative) + (by rw [hq.column]; omega) (by rw [hq.distance]; split_ifs <;> omega) + (by simpa only [hq.len] using hbdd) (by simpa only [hq.len] using hsigned) hf + (by + rw [hq.src, hq.len, hq.heavy] + have hd : (layout x).base ≤ (layout x).descriptor j := L.base_le_descriptor j + dsimp only [Layout.heavy] + omega) + (by rw [hq.heavy, hq.len, hq.packed]; exact L.heavy_end_le_packed j) + (by rw [hq.packed, hq.scratch]; rfl) + (by rw [hq.scratch, hq.len, hq.hist]; exact Nat.add_le_add_left hn _) + (by simpa only [hq.len, hq.src, hqm] using hinput) + (by simpa only [hq.len] using hinj) + have he : ExecWithin (w + 2) (round j) y z (66 * L.n j + 5 * L.M + 65) := by + have hz' : ExecWithin (w + 2) CountPlace.program q z (66 * L.n j + 5 * L.M + 45) := + by simpa only [hq.len] using hz + exact (hp.seq hz').mono (by omega) + have hframe : Frame y z := fun i hi hne => ExecWithin.regs_eq he (round_preserves j i hi hne) + refine ⟨z, he, ?_, hframe, hparams.of_frame hframe, hready.of_frame hframe⟩ + rw [hzm, hqm, hq.len, hq.heavy, hq.packed, hq.column, hq.distance, + hq.scratch, hq.hist, hq.descriptor] + rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean new file mode 100644 index 0000000000..444119ae48 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean @@ -0,0 +1,197 @@ +/- +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.NodeProgram + +/-! +# Charged runtime layout and tag setup for one Chan–He node + +## Main definitions + +* `NodePacking.Prepared`: exact computed layout, encoded tags and fixed constants. + +## Main results + +* `NodePacking.exec_layout`: all address calculations are native instructions. + +## References + +These native setup proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- The immutable caller arguments survive every phase of the node program. -/ +def Parameters (x y : Data 64) : Prop := + ∀ i, 46 ≤ i.val ∧ i.val ≤ 55 → y.regs i = x.regs i + +/-- Runtime arguments, computed bases, encoded tags and constants retained by every round. +Register 61 is the sole setup scratch register in this range. -/ +def Frame (x y : Data 64) : Prop := + ∀ i, 46 ≤ i.val → i ≠ temporary → y.regs i = x.regs i + +/-- The computed runtime layout and encoded tag constants. -/ +def Prepared (L : Layout) (y : Data 64) : Prop := + y.regs scratch = L.scratch ∧ y.regs histogram = L.hist ∧ y.regs packed = L.packed ∧ + y.regs gap = 2 * L.gap ∧ y.regs padding = 4 * L.U + 2 ∧ + y.regs one = 1 ∧ y.regs four = 4 + +/-- A caller frame transports the prepared address/tag interface. -/ +theorem Prepared.of_frame {L : Layout} {x y : Data 64} (hx : Prepared L x) (hr : Frame x y) : + Prepared L y := by + obtain ⟨hk, hh, hp, hg, hpad, h1, h4⟩ := hx + exact ⟨(hr scratch (by decide) (by decide)).trans hk, + (hr histogram (by decide) (by decide)).trans hh, + (hr packed (by decide) (by decide)).trans hp, + (hr gap (by decide) (by decide)).trans hg, + (hr padding (by decide) (by decide)).trans hpad, + (hr one (by decide) (by decide)).trans h1, (hr four (by decide) (by decide)).trans h4⟩ + +/-- A caller frame transports the immutable input/size interface. -/ +theorem Parameters.of_frame {x y z : Data 64} (hy : Parameters x y) (hr : Frame y z) : + Parameters x z := by + intro i hi + have hne : i ≠ temporary := by intro h; subst i; norm_num [temporary] at hi + exact (hr i hi.1 hne).trans (hy i hi) + +/-- Exact native arguments for writing the four-column padding pattern. -/ +def PaddingReady (L : Layout) (y : Data 64) : Prop := + y.regs pdst = L.packed ∧ y.regs plen = L.N ∧ + y.regs pad0 = 120 * L.U + 80 ∧ y.regs pad1 = 16 * L.U + 10 ∧ + y.regs pad2 = 40 * L.U + 26 ∧ y.regs pad3 = 44 * L.U + 30 + +/-- Compute the disjoint addresses from the supplied runtime lengths and fresh base. -/ +theorem exec_layout {w : ℕ} (x : Data 64) (hfit : Fits w (layout x).budget) : + ∃ y, ExecWithin w layoutCode x y 12 ∧ Parameters x y ∧ y.mem = x.mem ∧ + y.regs packed = (layout x).packed ∧ y.regs scratch = (layout x).scratch ∧ + y.regs histogram = (layout x).hist ∧ y.regs one = 1 ∧ y.regs four = 4 ∧ + y.regs temporary = 3 := by + let L := layout x + have hfit' : Fits w L.budget := hfit + have fb := hfit'.of_le L.bounds.1 + have f (v : ℕ) (hv : v ≤ L.endpoint) : Fits w v := fb.of_le hv + have fc (v : ℕ) (hv : v ≤ 16) : Fits w v := + hfit'.of_le (by have := L.bounds.2.2; omega) + have f1 : Fits w 1 := fc _ (by decide) + have f3 : Fits w 3 := fc _ (by decide) + have f4 : Fits w 4 := fc _ (by decide) + have fp₀ : Fits w (x.regs base + 3) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp₁ : Fits w (x.regs base + 3 + x.regs (length 0)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp₂ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fn : Fits w (4 * x.regs groups) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fk : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh₀ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh₁ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0) + x.regs (length 1)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [layoutCode, f1, f3, f4, fp₀, fp₁, fp₂, fp, fn, fk, fh₀, fh₁, fh] + · intro i hi + fin_cases i <;> norm_num at hi <;> rfl + · rfl + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.packed, Layout.total] + omega + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.scratch, Layout.packed, Layout.total] + omega + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.hist, Layout.scratch, Layout.packed, Layout.total] + omega + · rfl + · rfl + · rfl + +/-- Compute encoded tags and signed padding through native arithmetic in sixteen instructions. +The single envelope supplies every intermediate fit, including the third-column subtraction. -/ +theorem exec_constants {w : ℕ} (L : Layout) (x : Data 64) + (hfit : Fits w L.budget) (hU : x.regs magnitude = L.U) (hN : x.regs groups = L.N) + (hk : x.regs scratch = L.scratch) (hh : x.regs histogram = L.hist) + (hp : x.regs packed = L.packed) (h1 : x.regs one = 1) (h4 : x.regs four = 4) + (h3 : x.regs temporary = 3) : + ∃ y, ExecWithin w constantsCode x y 16 ∧ Parameters x y ∧ y.mem = x.mem ∧ + Prepared L y ∧ PaddingReady L y := by + have f (v : ℕ) (hv : v ≤ 120 * L.U + 80) : Fits w v := + (hfit.of_le L.bounds.2.1).of_le hv + have f₂ : Fits w 2 := f _ (by omega) + have f₃ : Fits w 3 := f _ (by omega) + have f₁₀ : Fits w 10 := f _ (by omega) + have fw₀ : Fits w (L.U + L.U) := f _ (by omega) + have fw₁ : Fits w (L.U + L.U + 1) := f _ (by omega) + have fw : Fits w (L.U + L.U + 1 + (L.U + L.U + 1)) := f _ (by omega) + have fg₀ : Fits w (3 * (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + have fg : Fits w (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) := f _ (by omega) + have fp₀ : Fits w (10 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₁ : Fits w (L.U + L.U + 1 + (L.U + L.U + 1) + + (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₂₀ : Fits w (3 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₂ : Fits w (3 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) + + (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + have fp₃₀ : Fits w (4 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₃ : Fits w (4 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) - + (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [constantsCode, hU, hN, hp, h1, h4, h3, f₂, f₃, f₁₀, + fw₀, fw₁, fw, fg₀, fg, fp₀, fp₁, fp₂₀, fp₂, fp₃₀, fp₃] + · intro i hi + fin_cases i <;> norm_num at hi <;> rfl + · rfl + · refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact hk + · exact hh + · exact hp + · simp +decide [gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3, Layout.gap] + omega + · simp +decide [gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3] + omega + · exact h1 + · exact h4 + · simp +decide [PaddingReady, gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean new file mode 100644 index 0000000000..9cc16a520b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean @@ -0,0 +1,119 @@ +/- +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.NodeExec + +/-! +# Exact output contract for complete native Chan–He node construction + +The output uses the existing mathematical heavy subsets and `Node.oneArray`. A heavy array +contains each selected integer once, in input order, preceded by its exact cardinality. The +packed array has `4N` cells, including every sentinel and padded cell. Memory outside the +explicit allocation and caller registers 46–55 are preserved. + +## Main results + +* `NodePacking.exec_spec`: complete native execution and exact semantic readback. +* `NodePacking.packed_length_square`: the preprocessing specialization has `8m²` cells. +* `NodePacking.abs_oneArray_le`: the node magnitude bound is `60U+40`. + +These programs and composition proofs are original. The mathematical comparator is the +existing Chan–He development; no host-level output is substituted for native execution. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Exact semantic readback from the concrete native allocation. -/ +structure Output (L : Layout) (values : Fin 3 → ℕ → ℤ) (before after : ℕ → ℕ) : Prop where + /-- Each compact list enumerates precisely the existing heavy set, without duplicates. -/ + heavy : ∀ j, ∃ zs : List ℤ, zs.Nodup ∧ + zs.toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy + ((Finset.range (L.n j)).image (values j)) L.M ∧ + after (L.descriptor j) = zs.length ∧ + ∀ k (hk : k < zs.length), after (L.heavy j + k) = encode zs[k] + /-- Every packed cell equals the existing mathematical node array. -/ + packed : ∀ u < 4 * L.N, after (L.packed + u) = encode ((node L values).oneArray L.U u) + /-- All memory outside the fresh allocation retains its old contents. -/ + frame : ∀ a, a < L.base ∨ L.endpoint ≤ a → after a = before a + +/-- Exact readback of the fully composed memory theorem. -/ +theorem memory_output (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) + (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) : + Output L values mem (memory L values mem) := by + refine ⟨?_, fun _ hu => memory_oneArray L values mem hM hinj hu, + fun _ ha => memory_outside L values mem hM hN ha⟩ + intro j + obtain ⟨hc, hd⟩ := memory_heavy L values mem hM hN j + exact ⟨Place.heavyList (values j) (L.n j) L.M (L.n j), + Place.heavyList_nodup _ _ _ (hinj j), Place.heavyList_toFinset _ _ hM (hinj j), hc, hd⟩ + +/-- The native descriptor is the cardinality of the existing heavy set. -/ +theorem Output.heavy_card {L : Layout} {values : Fin 3 → ℕ → ℤ} + {before after : ℕ → ℕ} (h : Output L values before after) (j : Fin 3) : + after (L.descriptor j) = (Cslib.Algorithms.ThreeSum.ChanHe.heavy + ((Finset.range (L.n j)).image (values j)) L.M).card := by + obtain ⟨zs, hnd, hs, hc, _⟩ := h.heavy j + rw [hc, ← hs] + exact (List.toFinset_card_of_nodup hnd).symm + +/-- The complete fixed native program constructs all three exact heavy outputs and the exact +packed node array, charging setup, clearing, padding, signed tags, every address and count store, +and every actual residue/histogram call. Initial output and scratch contents are arbitrary. -/ +theorem exec_spec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < (layout x).M) (hN : 2 * (layout x).M ≤ (layout x).N) + (hwidth : GenericRAM.Signed.Fits w ((layout x).budget : ℤ)) + (hsrc : ∀ j, x.regs (source j) + (layout x).n j ≤ (layout x).base) + (hbdd : ∀ j i, i < (layout x).n j → (values j i).natAbs ≤ (layout x).U) + (hinput : ∀ j i, i < (layout x).n j → + x.mem (x.regs (source j) + i) = encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range ((layout x).n j)) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y + (256 * ((layout x).total + (layout x).M + (layout x).N + 1)) ∧ + Output (layout x) values x.mem y.mem ∧ Parameters x y := by + obtain ⟨y, hy, hm, hp⟩ := exec x values hM hN hwidth hsrc hbdd hinput hinj + refine ⟨y, hy.mono (time_le_linear _), ?_, hp⟩ + rw [hm] + exact memory_output _ _ _ hM hN hinj + +/-- The supplied magnitude bound transfers to the existing finite-set notion. -/ +theorem bdd_image (L : Layout) (values : Fin 3 → ℕ → ℤ) + (hbdd : ∀ j i, i < L.n j → (values j i).natAbs ≤ L.U) (j : Fin 3) : + Cslib.Algorithms.ThreeSum.Bdd L.U ((Finset.range (L.n j)).image (values j)) := by + intro z hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp hz + have h := hbdd j i (Finset.mem_range.mp hi) + rw [← Int.natCast_natAbs] + exact_mod_cast h + +/-- The existing mathematical output bound applies to every native packed value. -/ +theorem abs_oneArray_le (L : Layout) (values : Fin 3 → ℕ → ℤ) + (hbdd : ∀ j i, i < L.n j → (values j i).natAbs ≤ L.U) (u : ℕ) : + |(node L values).oneArray L.U u| ≤ 60 * (L.U : ℤ) + 40 := + Cslib.Algorithms.ThreeSum.ChanHe.Node.abs_oneArray_le + (bdd_image L values hbdd 0) (bdd_image L values hbdd 1) (bdd_image L values hbdd 2) u + +/-- The full preprocessing's common group count gives exactly its existing packed length. -/ +theorem packed_length_square (m : ℕ) : 4 * (2 * m ^ 2) = 8 * m ^ 2 := by omega + +/-- The square specialization is admissible whenever the parent supplies `M ≤ m²`. -/ +theorem square_admissible {M m : ℕ} (hM : M ≤ m ^ 2) : 2 * M ≤ 2 * m ^ 2 := by omega + +/-- Exact readback at the full preprocessing's common length, with no extra padding assumption. -/ +theorem Output.packed_square {L : Layout} {values : Fin 3 → ℕ → ℤ} + {before after : ℕ → ℕ} (h : Output L values before after) + {m : ℕ} (hN : L.N = 2 * m ^ 2) {u : ℕ} (hu : u < 8 * m ^ 2) : + after (L.packed + u) = encode ((node L values).oneArray L.U u) := + h.packed u (by rw [hN, packed_length_square]; exact hu) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean new file mode 100644 index 0000000000..ad4c2c0e4c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean @@ -0,0 +1,178 @@ +/- +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 +import Mathlib.Tactic.NormNum + +/-! +# Initializing the four columns of a Chan–He node + +## Main definitions + +* `Padding.program`: a fixed loop writing a four-word pattern to every group. + +## Main results + +* `Padding.exec`: exact memory and a charged `12N+5` instruction bound. + +## References + +This native program and its execution proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Padding + +open Cslib.WordRAM + +/-- Packed output base. -/ +abbrev dst : Fin 10 := 0 +/-- Number of four-cell groups. -/ +abbrev len : Fin 10 := 1 +/-- Padding word for column zero. -/ +abbrev pad0 : Fin 10 := 2 +/-- Padding word for column one. -/ +abbrev pad1 : Fin 10 := 3 +/-- Padding word for column two. -/ +abbrev pad2 : Fin 10 := 4 +/-- Padding word for column three. -/ +abbrev pad3 : Fin 10 := 5 +/-- Group cursor. -/ +abbrev idx : Fin 10 := 6 +/-- Temporary address. -/ +abbrev addr : Fin 10 := 7 +/-- Constant one. -/ +abbrev one : Fin 10 := 8 +/-- Constant four. -/ +abbrev four : Fin 10 := 9 + +/-- A concrete four-word pattern, addressed by its residue modulo four. -/ +def pattern (p₀ p₁ p₂ p₃ u : ℕ) : ℕ := + if u % 4 = 0 then p₀ else if u % 4 = 1 then p₁ else if u % 4 = 2 then p₂ else p₃ + +/-- Initial memory with the first `k` groups overwritten, including all padding. -/ +def memory (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ k : ℕ) : ℕ → ℕ := fun a => + if dst ≤ a ∧ a < dst + 4 * k then pattern p₀ p₁ p₂ p₃ (a - dst) else mem a + +@[simp] theorem memory_zero (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ : ℕ) : + memory mem dst p₀ p₁ p₂ p₃ 0 = mem := by + funext a + simp [memory] + +/-- Four distinct stores make one complete group. -/ +theorem memory_succ (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ k : ℕ) : + memory mem dst p₀ p₁ p₂ p₃ (k + 1) = + Function.update (Function.update (Function.update (Function.update + (memory mem dst p₀ p₁ p₂ p₃ k) (dst + 4 * k) p₀) + (dst + 4 * k + 1) p₁) (dst + 4 * k + 2) p₂) (dst + 4 * k + 3) p₃ := by + funext a + by_cases h₀ : a = dst + 4 * k + · subst a; simp [memory, pattern] + by_cases h₁ : a = dst + 4 * k + 1 + · subst a + simp [memory, pattern, show dst + 4 * k + 1 - dst = 4 * k + 1 by omega, + show dst ≤ dst + 4 * k + 1 by omega, + show dst + 4 * k + 1 < dst + 4 * (k + 1) by omega] + by_cases h₂ : a = dst + 4 * k + 2 + · subst a + simp [memory, pattern, show dst + 4 * k + 2 - dst = 4 * k + 2 by omega, + show dst ≤ dst + 4 * k + 2 by omega, + show dst + 4 * k + 2 < dst + 4 * (k + 1) by omega] + by_cases h₃ : a = dst + 4 * k + 3 + · subst a + simp [memory, pattern, show dst + 4 * k + 3 - dst = 4 * k + 3 by omega, + show dst ≤ dst + 4 * k + 3 by omega, + show dst + 4 * k + 3 < dst + 4 * (k + 1) by omega] + simp only [Function.update_of_ne h₃, Function.update_of_ne h₂, + Function.update_of_ne h₁, Function.update_of_ne h₀, memory] + have he : (dst ≤ a ∧ a < dst + 4 * (k + 1)) ↔ (dst ≤ a ∧ a < dst + 4 * k) := by omega + simp only [he] + +/-- Every address, store and increment of a four-word group is charged. -/ +def body : Code .standard 10 := ram[ + addr ← four * idx; addr ← dst + addr; *addr ← pad0; + addr ← addr + one; *addr ← pad1; addr ← addr + one; *addr ← pad2; + addr ← addr + one; *addr ← pad3; idx ← idx + one ] + +/-- Runtime pattern values and length, with fixed code and registers. -/ +def program : Code .standard 10 := + .seq (ram[ idx ← #0; one ← #1; four ← #4 ]) (.loop (.lt idx len) body) + +/-- Initialize exactly `4N` cells from arbitrary old memory. -/ +theorem exec {w : ℕ} (x : Data 10) (h4 : Fits w 4) + (hfit : Fits w (x.regs dst + 4 * x.regs len)) : + ∃ y, ExecWithin w program x y (12 * x.regs len + 5) ∧ + y.mem = memory x.mem (x.regs dst) (x.regs pad0) (x.regs pad1) + (x.regs pad2) (x.regs pad3) (x.regs len) ∧ + ∀ i, i.val < 6 → y.regs i = x.regs i := by + let I (y : Data 10) : Prop := + (∀ i, i.val < 6 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs four = 4 ∧ + y.regs idx ≤ x.regs len ∧ y.mem = memory x.mem (x.regs dst) + (x.regs pad0) (x.regs pad1) (x.regs pad2) (x.regs pad3) (y.regs idx) + have hbody (y : Data 10) (hy : I y) (hcond : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 10 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, h1, hfour, hi, hm⟩ := hy + have hd := hr dst (by decide) + have hl := hr len (by decide) + have hp₀ := hr pad0 (by decide) + have hp₁ := hr pad1 (by decide) + have hp₂ := hr pad2 (by decide) + have hp₃ := hr pad3 (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hcond + have fa : Fits w (4 * y.regs idx) := hfit.of_le (by omega) + have fb : Fits w (x.regs dst + 4 * y.regs idx) := hfit.of_le (by omega) + have fc : Fits w (x.regs dst + 4 * y.regs idx + 1) := hfit.of_le (by omega) + have fd : Fits w (x.regs dst + 4 * y.regs idx + 1 + 1) := hfit.of_le (by omega) + have fe : Fits w (x.regs dst + 4 * y.regs idx + 1 + 1 + 1) := hfit.of_le (by omega) + have fi : Fits w (y.regs idx + 1) := hfit.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, h1, hfour, hd, hp₀, hp₁, hp₂, hp₃, fa, fb, fc, fd, fe, fi] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have ha : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hn : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne ha, + Function.update_of_ne hn] using hr i hi + · simpa +decide [one, addr, idx] using h1 + · simpa +decide [four, addr, idx] using hfour + · simpa only [Data.set_regs, Function.update_self, h1] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem] + rw [hm, ← memory_succ] + rfl + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := ((x.set idx 0).set one 1).set four 4 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hi₁ : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hi₄ : i ≠ four := by intro h; subst i; norm_num [four] at hi + have hin : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hi₁, + Function.update_of_ne hi₄, Function.update_of_ne hin] + all_goals simp +decide [x₀, one, four, idx] + obtain ⟨y, he, hy, hstop⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, h1, hfour, 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 hstop + omega + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; one ← #1; four ← #4 ]) x x₀ 3 := by + ram_eval [x₀, h4, h4.of_le (by decide : 1 ≤ 4)] + refine ⟨y, ?_, by simpa only [hend] using hm, hr⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, idx, one, four, Data.set_regs, Function.update_apply, + ↓reduceIte, Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Padding diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean new file mode 100644 index 0000000000..5fc95cc148 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean @@ -0,0 +1,221 @@ +/- +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.BucketCounts +public import Complexity.Computability.WordRAM.Signed.Arithmetic +import Mathlib.Tactic.FinCases + +/-! +# Native heavy extraction and light placement + +The input, unsigned residue array and histogram are supplied by the actual `Count.program`. +One scan copies heavy integers, in input order, to a compact output. Each light integer is +tagged and placed directly in the packed node array. The third pass negates its integer and +its residue, and writes both copies required by `arrZ`. + +The mathematical comparator is Anthropic's [Buckets.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/ChanHe/Buckets.lean) +and `NodeArray.lean` at `formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The native code, encoding arithmetic and execution proofs here are original. No `Light` +execution theorem or source-machine simulation is assumed. + +## Main definitions + +* `Place.program`: a fixed scan with runtime bases, length, modulus, tag and column. +* `Place.heavyList`: compact heavy elements in input order. +* `Place.memory`: exact writes of a prefix of the scan, including both third-column copies. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Input array base. -/ +abbrev src : Fin 32 := 9 +/-- Unsigned residue array base. -/ +abbrev keys : Fin 32 := 10 +/-- Input length. -/ +abbrev len : Fin 32 := 11 +/-- Histogram base. -/ +abbrev hist : Fin 32 := 12 +/-- Compact heavy output base. -/ +abbrev heavy : Fin 32 := 13 +/-- Packed node output base. -/ +abbrev packed : Fin 32 := 14 +/-- Positive modulus, decoded unsigned. -/ +abbrev modulus : Fin 32 := 15 +/-- Twice the positive tag added to a light integer. -/ +abbrev tag : Fin 32 := 16 +/-- Column in a four-cell group. -/ +abbrev column : Fin 32 := 17 +/-- Distance to the second write: zero for X/Y, `4M` for Z. -/ +abbrev distance : Fin 32 := 18 +/-- Zero for X/Y, one for Z. -/ +abbrev negative : Fin 32 := 19 +/-- Input cursor. -/ +abbrev idx : Fin 32 := 20 +/-- Number of heavy integers already written. -/ +abbrev count : Fin 32 := 21 +/-- Temporary address. -/ +abbrev addr : Fin 32 := 22 +/-- Current remainder. -/ +abbrev key : Fin 32 := 23 +/-- Current value and its tagged encoding. -/ +abbrev value : Fin 32 := 24 +/-- Twice the current magnitude. -/ +abbrev magnitude : Fin 32 := 25 +/-- Current input sign. -/ +abbrev sign : Fin 32 := 26 +/-- Constant one. -/ +abbrev one : Fin 32 := 27 +/-- Constant two. -/ +abbrev two : Fin 32 := 28 +/-- Constant four. -/ +abbrev four : Fin 32 := 29 +/-- Constant zero. -/ +abbrev zero : Fin 32 := 30 +/-- Current histogram count. -/ +abbrev bucketSize : Fin 32 := 31 + +/-- Negating a Euclidean residue needs only a subtraction and a remainder. -/ +def targetKey (M : ℕ) (neg : Bool) (z : ℤ) : ℕ := + if neg then (M - residueKey M z) % M else residueKey M z + +/-- Integer contribution before the positive tag. -/ +def signedValue (neg : Bool) (z : ℤ) : ℤ := if neg then -z else z + +/-- The target index is exactly the residue of the signed contribution. -/ +theorem targetKey_eq {M : ℕ} (hM : 0 < M) (neg : Bool) (z : ℤ) : + targetKey M neg z = residueKey M (signedValue neg z) := by + cases neg + · rfl + · have hr := residueKey_lt hM z + apply Int.ofNat_inj.mp + change (((M - residueKey M z) % M : ℕ) : ℤ) = (residueKey M (-z) : ℤ) + rw [Int.natCast_emod, Nat.cast_sub (Nat.le_of_lt hr), residueKey_cast hM, + residueKey_cast hM] + rw [Int.sub_emod_emod, Int.sub_emod_left] + +/-- A prefix of the compact output; membership is tested against the full histogram. -/ +def heavyList (values : ℕ → ℤ) (n M : ℕ) : ℕ → List ℤ + | 0 => [] + | k + 1 => heavyList values n M k ++ + if 1 < Histogram.count (fun i => residueKey M (values i)) n (residueKey M (values k)) + then [values k] else [] + +/-- A compact prefix has at most as many cells as the corresponding input prefix. -/ +theorem length_heavyList_le (values : ℕ → ℤ) (n M k : ℕ) : + (heavyList values n M k).length ≤ k := by + induction k with + | zero => rfl + | succ k ih => + simp only [heavyList, List.length_append] + split_ifs <;> simp only [List.length_singleton, List.length_nil] <;> omega + +/-- The compact array is the input subsequence selected by the histogram test. -/ +theorem heavyList_eq_filter (values : ℕ → ℤ) (n M k : ℕ) : + heavyList values n M k = ((List.range k).filter fun i => + decide (1 < Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)))).map values := by + induction k with + | zero => rfl + | succ k ih => + rw [heavyList, List.range_succ, List.filter_append, List.map_append, ih] + split_ifs <;> simp_all + +/-- The native compact output has exactly the existing heavy subset as its underlying set. -/ +theorem heavyList_toFinset (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).toFinset = + Cslib.Algorithms.ThreeSum.ChanHe.heavy ((Finset.range n).image values) M := by + ext z + rw [heavyList_eq_filter] + simp only [List.mem_toFinset, List.mem_map, List.mem_filter, List.mem_range, decide_eq_true_eq] + constructor + · rintro ⟨i, ⟨hi, hc⟩, rfl⟩ + exact (mem_heavy_iff_count values n hM hinj hi).mpr hc + · intro hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp + ((Finset.mem_filter.mp hz).1) + exact ⟨i, ⟨Finset.mem_range.mp hi, + (mem_heavy_iff_count values n hM hinj (Finset.mem_range.mp hi)).mp hz⟩, rfl⟩ + +/-- The exact per-input write, either to the next compact heavy cell or to packed light cells. -/ +def write (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (k : ℕ) : ℕ → ℕ := + if 1 < Histogram.count (fun i => residueKey M (values i)) n (residueKey M (values k)) then + Function.update mem (out + (heavyList values n M k).length) (encode (values k)) + else + let a := dst + 4 * targetKey M neg (values k) + col + let v := encode (signedValue neg (values k) + (t : ℤ)) + Function.update (Function.update mem a v) (a + dist) v + +/-- All writes of the first `k` iterations, framed by arbitrary initial memory. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) : ℕ → ℕ → ℕ + | 0 => mem + | k + 1 => write (memory mem values n M out dst col dist t neg k) + values n M out dst col dist t neg k + +/-- Copy a heavy integer without changing its signed encoding. -/ +def heavyCode : Code .standard 32 := ram[ + addr ← heavy + count; *addr ← value; count ← count + one ] + +/-- Tag a light integer using its magnitude and sign, then write its required cells. -/ +def lightCode : Code .standard 32 := ram[ + if negative = zero then [ key ← key ] + else [ key ← modulus - key; key ← key % modulus ]; + magnitude ← value / two; magnitude ← magnitude + magnitude; sign ← value % two; + if sign = negative then [ value ← tag + magnitude ] else [ value ← tag - magnitude ]; + addr ← four * key; addr ← packed + addr; addr ← addr + column; *addr ← value; + addr ← addr + distance; *addr ← value ] + +/-- Every iteration reads the original integer, its unsigned key and its exact bucket size. -/ +def body : Code .standard 32 := ram[ + addr ← keys + idx; key ← *addr; addr ← hist + key; bucketSize ← *addr; + addr ← src + idx; value ← *addr; + if one < bucketSize then [ ⟪heavyCode⟫ ] else [ ⟪lightCode⟫ ]; + idx ← idx + one ] + +/-- The same finite code handles every input length, width, modulus and output length. -/ +def program : Code .standard 32 := + .seq (ram[ idx ← #0; count ← #0; one ← #1; two ← #2; four ← #4; zero ← #0 ]) + (.loop (.lt idx len) body) + +/-- All runtime parameters survive the scan. -/ +theorem preserves_parameters (i : Fin 32) (hi : 9 ≤ i.val ∧ i.val ≤ 19) : + program.Preserves i := by + fin_cases i <;> norm_num at hi <;> decide + +/-- A scan can only change its heavy capacity block and its packed output block. -/ +theorem memory_outside (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t N : ℕ) + (neg : Bool) (hM : 0 < M) (hN : 2 * M ≤ N) (hc : col < 4) (hd : dist ≤ 4 * M) + {k : ℕ} (hk : k ≤ n) {a : ℕ} + (ha : ¬(out ≤ a ∧ a < out + n)) (hb : ¬(dst ≤ a ∧ a < dst + 4 * N)) : + memory mem values n M out dst col dist t neg k a = mem a := by + induction k with + | zero => rfl + | succ k ih => + have hh := length_heavyList_le values n M k + have hr : targetKey M neg (values k) < M := by + rw [targetKey_eq hM] + exact residueKey_lt hM _ + have hn₁ : a ≠ out + (heavyList values n M k).length := by omega + have hn₂ : a ≠ dst + 4 * targetKey M neg (values k) + col := by omega + have hn₃ : a ≠ dst + 4 * targetKey M neg (values k) + col + dist := by omega + simp only [memory, write] + split_ifs <;> simp only [Function.update_of_ne hn₁, Function.update_of_ne hn₂, + Function.update_of_ne hn₃] <;> exact ih (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean new file mode 100644 index 0000000000..d985795a0b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean @@ -0,0 +1,277 @@ +/- +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.Place + +/-! +# Charged execution of the native Chan–He placement scan + +## Main results + +* `Place.tagged_encode`: the unsigned sign/magnitude calculation is the exact tagged integer. + +## References + +The encoding calculation and native proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The encoded tagged value can be computed without a signed primitive. The positive tag +dominates the integer magnitude, so unsigned subtraction cannot truncate a negative result. -/ +theorem tagged_encode (neg : Bool) (z : ℤ) (t : ℕ) (ht : z.natAbs ≤ t) : + encode (signedValue neg z + (t : ℤ)) = + if encode z % 2 = (if neg then 1 else 0) then + 2 * t + (encode z / 2 + encode z / 2) + else 2 * t - (encode z / 2 + encode z / 2) := by + cases neg <;> by_cases hz : z < 0 + · have he : (z.natAbs : ℤ) = -z := Int.ofNat_natAbs_of_nonpos (by omega) + have hnon : 0 ≤ z + (t : ℤ) := by omega + simp only [signedValue, Bool.false_eq_true, ↓reduceIte, encode_mod_two, hz, + encode_div_two, Nat.reduceEqDiff] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = z := (Int.eq_natAbs_of_nonneg (by omega)).symm + have hnon : 0 ≤ z + (t : ℤ) := by omega + simp only [signedValue, Bool.false_eq_true, ↓reduceIte, encode_mod_two, hz, + encode_div_two] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = -z := Int.ofNat_natAbs_of_nonpos (by omega) + have hnon : 0 ≤ -z + (t : ℤ) := by omega + simp only [signedValue, ↓reduceIte, encode_mod_two, hz, encode_div_two] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = z := (Int.eq_natAbs_of_nonneg (by omega)).symm + have hnon : 0 ≤ -z + (t : ℤ) := by omega + simp only [signedValue, ↓reduceIte, encode_mod_two, hz, encode_div_two, Nat.reduceEqDiff] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + +/-- A polynomial envelope for every value and address produced by the placement scan. +The caller supplies a fit for this sum; no fit of an uncomputed output is assumed. -/ +def budget (x : Data 32) (M N U t : ℕ) : ℕ := + x.regs src + x.regs keys + x.regs hist + x.regs heavy + x.regs packed + x.regs len + + M + 4 * N + 2 * t + 2 * U + 16 + + +/-- One complete scan round, including classification, sign/tag work, both light stores, +heavy count maintenance and the input cursor increment. -/ +theorem exec_body {w M N U t : ℕ} (x : Data 32) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : x.regs column < 4) + (hd : x.regs distance ≤ 4 * M) (ht : U ≤ t) + (hmod : x.regs modulus = M) (htag : x.regs tag = 2 * t) + (hneg : x.regs negative = if neg then 1 else 0) + (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h4 : x.regs four = 4) + (h0 : x.regs zero = 0) (hi : x.regs idx < x.regs len) + (hcount : x.regs count = (heavyList values (x.regs len) M (x.regs idx)).length) + (hbdd : (values (x.regs idx)).natAbs ≤ U) + (hfit : Fits w (budget x M N U t)) + (hinput : x.mem (x.regs src + x.regs idx) = encode (values (x.regs idx))) + (hkey : x.mem (x.regs keys + x.regs idx) = residueKey M (values (x.regs idx))) + (hhist : x.mem (x.regs hist + residueKey M (values (x.regs idx))) = + Histogram.count (fun j => residueKey M (values j)) (x.regs len) + (residueKey M (values (x.regs idx)))) : + ∃ y, ExecWithin w body x y 30 ∧ + y.mem = write x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (x.regs idx) ∧ + y.regs count = (heavyList values (x.regs len) M (x.regs idx + 1)).length ∧ + y.regs idx = x.regs idx + 1 ∧ + (∀ i, (9 ≤ i.val ∧ i.val ≤ 19) ∨ i = one ∨ i = two ∨ i = four ∨ i = zero → + y.regs i = x.regs i) := by + let k := x.regs idx + let n := x.regs len + let r := residueKey M (values k) + let c := Histogram.count (fun j => residueKey M (values j)) n r + have hr : r < M := residueKey_lt hM _ + have hr' : targetKey M neg (values k) < M := by + rw [targetKey_eq hM] + exact residueKey_lt hM _ + have f (v : ℕ) (hv : v ≤ budget x M N U t) : Fits w v := hfit.of_le hv + have fone : Fits w 1 := f _ (by dsimp only [budget]; omega) + have fzero : Fits w 0 := fits_zero _ + have fs : Fits w (x.regs src + k) := f _ (by dsimp only [budget, k]; omega) + have fk : Fits w (x.regs keys + k) := f _ (by dsimp only [budget, k]; omega) + have fh : Fits w (x.regs hist + r) := f _ (by dsimp only [budget]; omega) + have fo : Fits w (x.regs heavy + x.regs count) := f _ (by + have hl := length_heavyList_le values n M k + dsimp only [n, k] at hl + dsimp only [budget] + omega) + have fcount : Fits w (x.regs count + 1) := f _ (by + have hl := length_heavyList_le values n M k + dsimp only [n, k] at hl + dsimp only [budget] + omega) + have fi : Fits w (k + 1) := f _ (by dsimp only [budget, k]; omega) + have fm : Fits w (M - r) := f _ (by dsimp only [budget]; omega) + have fr : Fits w ((M - r) % M) := f _ (by + have := Nat.mod_lt (M - r) hM + dsimp only [budget] + omega) + have fmag : Fits w (values k).natAbs := f _ (by dsimp only [budget, k]; omega) + have fmag2 : Fits w ((values k).natAbs + (values k).natAbs) := + f _ (by dsimp only [budget, k]; omega) + have fsign : Fits w (encode (values k) % 2) := f _ (by + have := Nat.mod_lt (encode (values k)) (by decide : 0 < 2) + dsimp only [budget] + omega) + have fadd : Fits w (2 * t + ((values k).natAbs + (values k).natAbs)) := + f _ (by dsimp only [budget, k]; omega) + have fsub : Fits w (2 * t - ((values k).natAbs + (values k).natAbs)) := + f _ (by dsimp only [budget]; omega) + have fa : Fits w (4 * targetKey M neg (values k)) := + f _ (by dsimp only [budget]; omega) + have fb : Fits w (x.regs packed + 4 * targetKey M neg (values k)) := + f _ (by dsimp only [budget]; omega) + have fc : Fits w (x.regs packed + 4 * targetKey M neg (values k) + x.regs column) := + f _ (by dsimp only [budget]; omega) + have fd : Fits w (x.regs packed + 4 * targetKey M neg (values k) + x.regs column + + x.regs distance) := f _ (by dsimp only [budget]; omega) + have hv := tagged_encode neg (values k) t (hbdd.trans ht) + have hcp : 0 < c := count_residue_pos values n M (show k < n from hi) + by_cases hh : 1 < c + · refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [body, heavyCode, src, keys, idx, hist, addr, key, bucketSize, value, one, + heavy, count, Fin.mk.injEq, h1, hinput, hkey, hhist, hh, + fs, fk, fh, fo, fcount, fi, k, n, r, c] + · simp [write, hh, k, n, r, c, hcount] + · simp +decide [heavyList, hh, k, n, r, c, hcount, count, idx] + · simp +decide [idx] + · intro i hi + fin_cases i <;> norm_num [one, two, four, zero] at hi <;> rfl + · have hc1 : c = 1 := by omega + cases neg <;> by_cases hs : values k < 0 + all_goals + simp only [targetKey, Bool.false_eq_true, ↓reduceIte] at fa fb fc fd + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [body, lightCode, src, keys, idx, hist, addr, key, bucketSize, value, + one, two, four, zero, modulus, negative, tag, magnitude, sign, packed, column, + distance, Fin.mk.injEq, h1, h2, h4, h0, hmod, htag, hneg, hinput, hkey, hhist, + hh, hc1, hs, encode_div_two, encode_mod_two, fs, fk, fh, fi, fm, fr, fmag, + fmag2, fsign, fadd, fsub, fone, fzero, fa, fb, fc, fd, targetKey, k, n, r, c] + · simp only [write, show ¬1 < Histogram.count (fun i => residueKey M (values i)) + (x.regs len) (residueKey M (values (x.regs idx))) from hh, ↓reduceIte] + simp only [Data.set_mem, Data.write_mem] + simp +decide [targetKey, k, hs, encode_div_two, encode_mod_two, hv] + · simp +decide [heavyList, hh, k, n, r, c, hcount, count, idx] + · simp +decide [idx] + · intro i hi + fin_cases i <;> norm_num [one, two, four, zero] at hi <;> rfl + + +/-- Complete native placement in at most `32n+8` instructions, excluding halt. All input, +residue and histogram reads are proved from the memory frame, rather than assumed anew at +iterations. The heavy block precedes the packed block, which precedes the counting workspace. -/ +theorem exec {w M N U t : ℕ} (x : Data 32) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : x.regs column < 4) + (hd : x.regs distance ≤ 4 * M) (ht : U ≤ t) + (hmod : x.regs modulus = M) (htag : x.regs tag = 2 * t) + (hneg : x.regs negative = if neg then 1 else 0) + (hbdd : ∀ i < x.regs len, (values i).natAbs ≤ U) + (hfit : Fits w (budget x M N U t)) + (hsh : x.regs src + x.regs len ≤ x.regs heavy) + (hhp : x.regs heavy + x.regs len ≤ x.regs packed) + (hpk : x.regs packed + 4 * N ≤ x.regs keys) + (hkh : x.regs keys + x.regs len ≤ x.regs hist) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) + (hkey : ∀ i < x.regs len, x.mem (x.regs keys + i) = residueKey M (values i)) + (hhist : ∀ r < M, x.mem (x.regs hist + r) = + Histogram.count (fun j => residueKey M (values j)) (x.regs len) r) : + ∃ y, ExecWithin w program x y (32 * x.regs len + 8) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (x.regs len) ∧ + y.regs count = (heavyList values (x.regs len) M (x.regs len)).length ∧ + ∀ i, 9 ≤ i.val ∧ i.val ≤ 19 → y.regs i = x.regs i := by + let P (i : Fin 32) : Prop := 9 ≤ i.val ∧ i.val ≤ 19 + let I (y : Data 32) : Prop := + (∀ i, P i → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs four = 4 ∧ y.regs zero = 0 ∧ y.regs idx ≤ x.regs len ∧ + y.regs count = (heavyList values (x.regs len) M (y.regs idx)).length ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (y.regs idx) + have hbody (y : Data 32) (hy : I y) (hcond : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 30 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, h1, h2, h4, h0, hi, hcount, hm⟩ := hy + have hs := hr src (by decide) + have hk := hr keys (by decide) + have hl := hr len (by decide) + have hh := hr hist (by decide) + have ho := hr heavy (by decide) + have hp := hr packed (by decide) + have hcol := hr column (by decide) + have hdist := hr distance (by decide) + have hmod' := (hr modulus (by decide)).trans hmod + have htag' := (hr tag (by decide)).trans htag + have hneg' := (hr negative (by decide)).trans hneg + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hcond + have hbefore (a : ℕ) (ha : a < x.regs heavy) : y.mem a = x.mem a := by + rw [hm] + apply memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd hi <;> omega + have hafter (a : ℕ) (ha : x.regs keys ≤ a) : y.mem a = x.mem a := by + rw [hm] + apply memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd hi <;> omega + obtain ⟨z, he, hzm, hzc, hzi, hzr⟩ := exec_body y values neg hM hN + (by simpa only [hcol] using hc) (by simpa only [hdist] using hd) ht hmod' htag' hneg' + h1 h2 h4 h0 (by simpa only [hl] using hlt) (by simpa only [hl] using hcount) + (hbdd _ hlt) + (by simpa only [budget, hs, hk, hh, ho, hp, hl] using hfit) + (by rw [hs, hbefore _ (by omega)]; exact hinput _ hlt) + (by rw [hk, hafter _ (by omega)]; exact hkey _ hlt) + (by rw [hh, hl, hafter _ (by omega)]; exact hhist _ (residueKey_lt hM _)) + refine ⟨z, he, ?_, by omega⟩ + refine ⟨fun i hi => (hzr i (Or.inl hi)).trans (hr i hi), + (hzr one (by simp)).trans h1, + (hzr two (by simp)).trans h2, (hzr four (by simp)).trans h4, + (hzr zero (by simp)).trans h0, by omega, + by simpa only [hl, hzi] using hzc, ?_⟩ + rw [hzm, hl, ho, hp, hcol, hdist, hm, hzi] + rfl + let x₀ := (((((x.set idx 0).set count 0).set one 1).set two 2).set four 4).set zero 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + fin_cases i <;> norm_num [P] at hi <;> rfl + all_goals simp +decide [x₀, idx, count, one, two, four, zero, heavyList, memory] + obtain ⟨y, he, hy, hstop⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, h1, h2, h4, h0, hi, hcount, hm⟩ := hy + have hend : y.regs idx = x.regs len := by + have hl := hr len (by decide) + simp only [Cond.holds_lt, hl] at hstop + omega + have f4 : Fits w 4 := hfit.of_le (by dsimp only [budget]; omega) + have f2 : Fits w 2 := f4.of_le (by decide) + have f1 : Fits w 1 := f4.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ + idx ← #0; count ← #0; one ← #1; two ← #2; four ← #4; zero ← #0 ]) x x₀ 6 := by + ram_eval [x₀, f1, f2, f4] + refine ⟨y, ?_, by simpa only [hend] using hm, by simpa only [hend] using hcount, hr⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, idx, count, one, two, four, zero, Data.set_regs, + Function.update_apply, ↓reduceIte, Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean new file mode 100644 index 0000000000..9753467df9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean @@ -0,0 +1,308 @@ +/- +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.Place + +/-! +# Meaning of the native Chan–He placement writes + +## Main results + +* `Place.heavyList_nodup`: compact output retains injective enumeration. +* `Place.memory_heavy`: each compact cell contains its original signed integer. + +## References + +These memory proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM.Signed + +/-- Selecting a subsequence preserves injective input enumeration. -/ +theorem heavyList_nodup (values : ℕ → ℤ) (n M : ℕ) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).Nodup := by + rw [heavyList_eq_filter] + apply List.Nodup.map_on + · intro i hi j hj he + exact hinj (Finset.mem_range.mpr (List.mem_range.mp (List.mem_filter.mp hi).1)) + (Finset.mem_range.mpr (List.mem_range.mp (List.mem_filter.mp hj).1)) he + · exact List.nodup_range.filter _ + +/-- The number returned by the scan is the exact cardinality of the existing heavy subset. -/ +theorem length_heavyList (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).length = + (Cslib.Algorithms.ThreeSum.ChanHe.heavy ((Finset.range n).image values) M).card := by + rw [← heavyList_toFinset values n hM hinj] + exact (List.toFinset_card_of_nodup (heavyList_nodup values n M hinj)).symm + +/-- Every compact cell is written once, without padding or a tag. Unused capacity retains +its arbitrary old contents. -/ +theorem memory_heavy (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) {k : ℕ} (hk : k ≤ n) + {j : ℕ} (hj : j < (heavyList values n M k).length) : + memory mem values n M out dst col dist t neg k (out + j) = + encode ((heavyList values n M k)[j]) := by + induction k generalizing j with + | zero => simp only [heavyList, List.length_nil] at hj; omega + | succ k ih => + let L := heavyList values n M k + have hl : L.length ≤ k := length_heavyList_le values n M k + have hlen : (heavyList values n M k).length = L.length := rfl + by_cases hh : 1 < Histogram.count (fun i => residueKey M (values i)) n + (residueKey M (values k)) + · have hlist : heavyList values n M (k + 1) = L ++ [values k] := by + simp only [heavyList, hh, ↓reduceIte, L] + simp only [hlist] at hj ⊢ + simp only [memory, write, hh, ↓reduceIte] + by_cases hj' : j < L.length + · rw [Function.update_of_ne (by omega), ih (by omega) hj'] + rw [List.getElem_append_left hj'] + · have he : j = L.length := by + simp only [List.length_append, List.length_singleton] at hj + omega + subst j + rw [Function.update_self] + simp + · have hlist : heavyList values n M (k + 1) = L := by + simp only [heavyList, hh, ↓reduceIte, List.append_nil, L] + simp only [hlist] at hj ⊢ + simp only [memory, write, hh, ↓reduceIte] + rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact ih (by omega) hj + +/-- The signed target keys identify exactly the same buckets as the original keys. -/ +theorem targetKey_eq_iff {M : ℕ} (hM : 0 < M) (neg : Bool) (a b : ℤ) : + targetKey M neg a = targetKey M neg b ↔ residueKey M a = residueKey M b := by + rw [targetKey_eq hM, targetKey_eq hM] + cases neg + · rfl + · exact residueKey_neg_eq_iff hM a b + +/-- The two copies cannot alias another bucket's cells. -/ +theorem targetKey_eq_of_address_eq {M dst col dist r s : ℕ} (hr : r < M) (hs : s < M) + (hd : dist = 0 ∨ dist = 4 * M) {a : ℕ} + (ha : a = dst + 4 * r + col ∨ a = dst + 4 * r + col + dist) + (hb : a = dst + 4 * s + col ∨ a = dst + 4 * s + col + dist) : r = s := by + rcases hd with hd | hd <;> rcases ha with ha | ha <;> rcases hb with hb | hb <;> omega + +/-- A light entry is written to all of its required cells, and no later input can overwrite it. +The count-one hypothesis itself proves uniqueness of the position. -/ +theorem memory_light (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hM : 0 < M) (hd : dist = 0 ∨ dist = 4 * M) (hdisj : out + n ≤ dst) + {i k a : ℕ} (hk : k ≤ n) (hi : i < k) + (hlight : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1) + (ha : a = dst + 4 * targetKey M neg (values i) + col ∨ + a = dst + 4 * targetKey M neg (values i) + col + dist) : + memory mem values n M out dst col dist t neg k a = + encode (signedValue neg (values i) + (t : ℤ)) := by + have ha' : dst ≤ a := by rcases ha with ha | ha <;> omega + induction k with + | zero => omega + | succ k ih => + have hl := length_heavyList_le values n M k + simp only [memory, write] + by_cases hh : 1 < Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values k)) + · rw [ite_eq_left hh, Function.update_of_ne (by omega)] + apply ih (by omega) (by + by_contra h + have : i = k := by omega + subst i + omega) + · rw [ite_eq_right hh] + by_cases he : i = k + · subst i + rcases ha with rfl | rfl <;> simp [Function.update_apply] + · have hne (b : ℕ) + (hb : b = dst + 4 * targetKey M neg (values k) + col ∨ + b = dst + 4 * targetKey M neg (values k) + col + dist) : a ≠ b := by + intro hab + have heq := targetKey_eq_of_address_eq + (by rw [targetKey_eq hM]; exact residueKey_lt hM _) + (by rw [targetKey_eq hM]; exact residueKey_lt hM _) hd ha (hab ▸ hb) + have hr := (targetKey_eq_iff hM neg (values i) (values k)).mp heq + exact he (eq_of_count_one values n M (by omega) (by omega) hlight hr) + rw [Function.update_of_ne (hne _ (Or.inr rfl)), + Function.update_of_ne (hne _ (Or.inl rfl))] + exact ih (by omega) (by omega) + +/-- A packed cell to which no light input is assigned retains its initialized padding. -/ +theorem memory_unwritten (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) {k a : ℕ} (hk : k ≤ n) (ha : dst ≤ a) + (hnot : ∀ i < k, Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1 → + a ≠ dst + 4 * targetKey M neg (values i) + col ∧ + a ≠ dst + 4 * targetKey M neg (values i) + col + dist) : + memory mem values n M out dst col dist t neg k a = mem a := by + induction k with + | zero => rfl + | succ k ih => + have hl := length_heavyList_le values n M k + simp only [memory, write] + split_ifs with hh + · rw [Function.update_of_ne (by omega)] + exact ih (by omega) (fun i hi => hnot i (by omega)) + · have hc : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values k)) = 1 := by + have := count_residue_pos values n M (show k < n by omega) + omega + obtain ⟨hne₁, hne₂⟩ := hnot k (by omega) hc + rw [Function.update_of_ne hne₂, Function.update_of_ne hne₁] + exact ih (by omega) (fun i hi => hnot i (by omega)) + +/-- The first or second packed column is exactly the existing `arrXY`, including all padding +beyond the modulus. No initial contents of the output block are required except its pattern. -/ +theorem memory_arrXY (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col t U : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) (i : ℕ) + (hpad : mem (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col 0 t false n (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range n).image values) M U i + (t : ℤ)) := by + let S := (Finset.range n).image values + by_cases hc : (Cslib.Algorithms.ThreeSum.ChanHe.bucket S M i).card = 1 + · obtain ⟨z, hz⟩ := Finset.card_eq_one.mp hc + have hmem : z ∈ Cslib.Algorithms.ThreeSum.ChanHe.bucket S M i := by + rw [hz] + exact Finset.mem_singleton_self _ + obtain ⟨hS, hres⟩ := Finset.mem_filter.mp hmem + obtain ⟨j, hj, rfl⟩ := Finset.mem_image.mp hS + have hkey : residueKey M (values j) = i := by + simp only [residueKey, hres, Int.toNat_natCast] + have hj' := Finset.mem_range.mp hj + have hlight : Histogram.count (fun k => residueKey M (values k)) n + (residueKey M (values j)) = 1 := by + rw [hkey, count_eq_card_bucket values n hM hinj] + exact hc + have he := memory_light mem values n M out dst col 0 t false hM (Or.inl rfl) + hdisj le_rfl hj' hlight (a := dst + 4 * i + col) + (Or.inl (by simp only [targetKey, Bool.false_eq_true, ↓reduceIte, hkey])) + rw [he] + dsimp only [S] at hc hz + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrXY, + Cslib.Algorithms.ThreeSum.ChanHe.arr, ↓reduceIte, hz, Finset.sum_singleton, + Finset.card_singleton, + signedValue, Bool.false_eq_true] + · have he := memory_unwritten mem values n M out dst col 0 t false hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hne : dst + 4 * i + col ≠ dst + 4 * targetKey M false (values j) + col := by + intro heq + have hkey : residueKey M (values j) = i := by + change dst + 4 * i + col = dst + 4 * residueKey M (values j) + col at heq + omega + apply hc + rw [← count_eq_card_bucket values n hM hinj, ← hkey] + exact hlight + exact ⟨hne, by simpa only [Nat.add_zero] using hne⟩) + rw [he, hpad] + dsimp only [S] at hc + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrXY, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hc, ↓reduceIte] + +/-- Below `2M`, a residue occurs at exactly its first and second copies. -/ +theorem mod_eq_iff_copies {M i r : ℕ} (hi : i < 2 * M) (hr : r < M) : + i % M = r ↔ i = r ∨ i = r + M := by + by_cases h : i < M + · rw [Nat.mod_eq_of_lt h] + omega + · rw [Nat.mod_eq_sub_mod (by omega : M ≤ i), Nat.mod_eq_of_lt (by omega : i - M < M)] + omega + +/-- The duplicated, negated third column is exactly the existing `arrZ`. -/ +theorem memory_arrZ (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col t U : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) (i : ℕ) + (hpad : mem (dst + 4 * i + col) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col (4 * M) t true n (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range n).image values) M U i + (t : ℤ)) := by + let vals := fun j => -values j + let T := (Finset.range n).image vals + have hT : ((Finset.range n).image values).image (fun z : ℤ => -z) = T := by + rw [Finset.image_image] + rfl + have hinj' : Set.InjOn vals (↑(Finset.range n) : Set ℕ) := by + intro j hj k hk he + exact hinj hj hk (neg_injective he) + have hkey (j : ℕ) : targetKey M true (values j) = residueKey M (vals j) := + targetKey_eq hM true _ + have hcount (j : ℕ) : + Histogram.count (fun k => residueKey M (vals k)) n (residueKey M (vals j)) = + Histogram.count (fun k => residueKey M (values k)) n (residueKey M (values j)) := + count_neg_residue values n hM j + by_cases hi : i < 2 * M + · by_cases hc : (Cslib.Algorithms.ThreeSum.ChanHe.bucket T M ((i % M : ℕ) : ℤ)).card = 1 + · obtain ⟨z, hz⟩ := Finset.card_eq_one.mp hc + have hmem : z ∈ Cslib.Algorithms.ThreeSum.ChanHe.bucket T M ((i % M : ℕ) : ℤ) := by + rw [hz] + exact Finset.mem_singleton_self _ + obtain ⟨hS, hres⟩ := Finset.mem_filter.mp hmem + obtain ⟨j, hj, rfl⟩ := Finset.mem_image.mp hS + have hr : residueKey M (vals j) = i % M := by + simp only [residueKey, hres, Int.toNat_natCast] + have hj' := Finset.mem_range.mp hj + have hlight : Histogram.count (fun k => residueKey M (values k)) n + (residueKey M (values j)) = 1 := by + rw [← hcount, hr, count_eq_card_bucket vals n hM hinj'] + exact hc + have hcopies := (mod_eq_iff_copies hi (residueKey_lt hM (vals j))).mp hr.symm + have he := memory_light mem values n M out dst col (4 * M) t true hM (Or.inr rfl) + hdisj le_rfl hj' hlight (a := dst + 4 * i + col) (by + rw [hkey] + rcases hcopies with hc | hc + · exact Or.inl (by omega) + · exact Or.inr (by omega)) + rw [he] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte, hT, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hz, Finset.sum_singleton, + Finset.card_singleton, signedValue] + rfl + · have he := memory_unwritten mem values n M out dst col (4 * M) t true hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hn : i % M ≠ targetKey M true (values j) := by + intro heq + apply hc + rw [← count_eq_card_bucket vals n hM hinj', heq, hkey, hcount] + exact hlight + have hr := residueKey_lt hM (vals j) + have hcopies := (mod_eq_iff_copies hi hr).not.mp (by simpa only [hkey] using hn) + rw [hkey] + constructor <;> intro heq <;> apply hcopies <;> omega) + rw [he, hpad] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte, hT, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hc] + · have he := memory_unwritten mem values n M out dst col (4 * M) t true hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hr : targetKey M true (values j) < M := by rw [hkey]; exact residueKey_lt hM _ + constructor <;> omega) + rw [he, hpad] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte] + +/-- Placement changes only its own packed column. -/ +theorem memory_other_column (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) (hc : col < 4) + (hd : dist = 0 ∨ dist = 4 * M) (u : ℕ) (hu : u % 4 ≠ col) : + memory mem values n M out dst col dist t neg n (dst + u) = mem (dst + u) := by + apply memory_unwritten _ _ _ _ _ _ _ _ _ _ hdisj le_rfl (by omega) + intro j hj hlight + rcases hd with hd | hd <;> constructor <;> intro he <;> apply hu <;> omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/ComplexityTests/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean new file mode 100644 index 0000000000..b5c3648e8f --- /dev/null +++ b/ComplexityTests/NativeChanHeNode.lean @@ -0,0 +1,176 @@ +/- +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.NodeSpec +meta import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec + +/-! +# Complete native Chan–He node regression clients + +The native interpreter starts with nonzero arbitrary output memory and nonzero unused +registers. Numeric expectations below are computed from the existing mathematical tags, +including the two negated-Z copies. The tests do not provide a histogram or a heavy/light oracle. +-/ + +namespace ComplexityTests.NativeChanHeNode + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +private def input (xs : Fin 3 → List ℤ) (M N U : ℕ) : Data 64 := + let n₀ := (xs 0).length + let n₁ := (xs 1).length + let n₂ := (xs 2).length + let all := xs 0 ++ xs 1 ++ xs 2 + ⟨fun i => + if i = source 0 then 1 else if i = length 0 then n₀ + else if i = source 1 then 1 + n₀ else if i = length 1 then n₁ + else if i = source 2 then 1 + n₀ + n₁ else if i = length 2 then n₂ + else if i = base then 1 + n₀ + n₁ + n₂ else if i = groups then N + else if i = magnitude then U else if i = modulus then M else 777, + fun a => if a = 0 then 999 else if a ≤ all.length then encode (all[a - 1]?.getD 0) else 99⟩ + +private def run (w : ℕ) (xs : Fin 3 → List ℤ) (M N U : ℕ) : Option (Data 64 × ℕ) := + program.eval w 64 (input xs M N U) + +private def observe (w : ℕ) (xs : Fin 3 → List ℤ) (M N U : ℕ) : + Option (List (List ℕ)) := do + let (y, _) ← run w xs M N U + let L := layout (input xs M N U) + pure ([List.ofFn (fun j : Fin 3 => y.mem (L.descriptor j))] ++ + List.ofFn (fun j : Fin 3 => (List.range (y.mem (L.descriptor j))).map fun k => + y.mem (L.heavy j + k)) ++ + [(List.range (4 * N)).map (fun k => y.mem (L.packed + k)), + [y.mem 0, y.mem L.endpoint], + List.ofFn (fun i : Fin 10 => y.regs ⟨46 + i.val, by omega⟩)]) + +private def empty : Fin 3 → List ℤ := ![[], [], []] +private def singleton : Fin 3 → List ℤ := ![[-1], [0], [1]] +private def mixed : Fin 3 → List ℤ := ![[-2, 1], [2], [-2, 2]] +private def endpoints : Fin 3 → List ℤ := ![[-4], [4], [0]] +private def allHeavy : Fin 3 → List ℤ := ![[-2, 1], [-1, 2], [-3, 0]] +private def twoUnique : Fin 3 → List ℤ := ![[-1, 0], [0, 1], [-1, 0]] + +-- Empty inputs still clear and pad all cells, and write all three zero descriptors. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 empty 1 2 0 = some + [[0, 0, 0], [], [], [], [80, 10, 26, 30, 80, 10, 26, 30], + [999, 99], [1, 0, 1, 0, 1, 0, 1, 2, 0, 1]]) + +-- Unique negative and zero buckets are placed with their exact signed tags. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 singleton 3 6 1 = some + [[0, 0, 0], [], [], [], + [200, 26, 60, 74, 200, 26, 66, 74, 200, 18, 66, 78, + 200, 26, 66, 74, 200, 26, 66, 74, 200, 26, 66, 78], + [999, 99], [1, 1, 2, 1, 3, 1, 4, 6, 1, 3]]) + +-- Two colliding X values are compacted while unique Y/Z buckets are placed. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 mixed 3 6 2 = some + [[2, 0, 0], [5, 2], [], [], + [320, 42, 106, 118, 320, 42, 106, 124, 320, 42, 100, 132, + 320, 42, 106, 118, 320, 42, 106, 124, 320, 42, 106, 132], + [999, 99], [1, 2, 3, 1, 4, 2, 6, 6, 2, 3]]) + +-- Both admissible magnitude endpoints work at `M=1`, `N=2M`. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 endpoints 1 2 4 = some + [[0, 0, 0], [], [], [], [560, 48, 176, 224, 560, 74, 186, 224], + [999, 99], [1, 1, 2, 1, 3, 1, 4, 2, 4, 1]]) + +-- All three compact outputs contain the original signed integers, including the Z output. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 allHeavy 3 6 3 = some + [[2, 2, 2], [5, 2], [3, 4], [7, 0], + [440, 58, 146, 162, 440, 58, 146, 162, 440, 58, 146, 162, + 440, 58, 146, 162, 440, 58, 146, 162, 440, 58, 146, 162], + [999, 99], [1, 2, 3, 2, 5, 2, 7, 6, 3, 3]]) + +-- Two distinct singleton buckets exercise the negative-residue permutation at modulus two. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 16 twoUnique 2 4 1 = some + [[0, 0, 0], [], [], [], + [200, 20, 60, 80, 200, 18, 62, 82, 200, 26, 66, 80, 200, 26, 66, 82], + [999, 99], [1, 2, 3, 2, 5, 2, 7, 4, 1, 2]]) + +-- An extra group beyond `2M` is fully padded, including the duplicated-Z column. +/-- info: true -/ +#guard_msgs in +#eval decide ((run 16 singleton 3 7 1).map (fun q => + (List.range 4).map (fun k => q.1.mem ((layout (input singleton 3 7 1)).packed + 24 + k))) = + some [200, 26, 66, 74]) + +-- The full charged execution, including Count, is observed rather than assumed. +/-- info: some true -/ +#guard_msgs in +#eval (run 16 mixed 3 6 2).map (fun q => decide (q.2 ≤ 675)) + +-- The small width fails during native address computation. +/-- info: true -/ +#guard_msgs in +#eval (run 4 singleton 3 6 1).isNone + +-- Layout and fit hypotheses are promises of the component, and these invalid cases violate them. +public theorem invalid_group_count : ¬(2 * 3 ≤ 5) := by decide +example : ¬Cslib.GenericRAM.Signed.Fits 4 ((layout (input singleton 3 6 1)).budget : ℤ) := by + decide +example : ¬((input singleton 3 6 1).regs (source 0) + + (layout (input singleton 3 6 1)).n 0 ≤ 0) := by decide + +example : ¬Set.InjOn (fun _ : ℕ => (1 : ℤ)) (↑(Finset.range 2) : Set ℕ) := by + intro h + have he := h (by decide : 0 ∈ (↑(Finset.range 2) : Set ℕ)) + (by decide : 1 ∈ (↑(Finset.range 2) : Set ℕ)) rfl + omega + +-- The theorem's polynomial fit/layout assumptions admit actual supplied native inputs. +example : Cslib.GenericRAM.Signed.Fits 32 ((layout (input empty 1 2 0)).budget : ℤ) := by decide + +example : ∃ y, ExecWithin 34 program (input empty 1 2 0) y + (256 * ((layout (input empty 1 2 0)).total + 1 + 2 + 1)) ∧ + Output (layout (input empty 1 2 0)) (fun _ _ => 0) (input empty 1 2 0).mem y.mem ∧ + Parameters (input empty 1 2 0) y := by + apply exec_spec _ _ (by decide) (by decide) (by decide) + · intro j + fin_cases j <;> decide + · intro j i hi + fin_cases j <;> norm_num [layout, input, empty, length, source] at hi + · intro j i hi + fin_cases j <;> norm_num [layout, input, empty, length, source] at hi + · intro j i hi + fin_cases j <;> norm_num [layout, input, empty, length, source] at hi + +private def singletonValues (j : Fin 3) (_ : ℕ) : ℤ := ![-1, 0, 1] j + +-- A nonempty signed instance also satisfies every premise of the complete semantic theorem. +example : ∃ y, ExecWithin 34 program (input singleton 3 6 1) y 3328 ∧ + Output (layout (input singleton 3 6 1)) singletonValues + (input singleton 3 6 1).mem y.mem ∧ Parameters (input singleton 3 6 1) y := by + apply exec_spec _ _ (by decide) (by decide) (by decide) + · intro j + fin_cases j <;> decide + · intro j i hi + rw [show (layout (input singleton 3 6 1)).U = 1 by decide] + fin_cases j <;> norm_num [singletonValues] + · intro j i hi + have hi0 : i = 0 := by + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi <;> omega + subst i + fin_cases j <;> decide + · intro j i hi k hk _ + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi hk <;> omega + +end ComplexityTests.NativeChanHeNode diff --git a/ROADMAP.md b/ROADMAP.md index 0af86de2b1..e5cc965d77 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -425,9 +425,10 @@ each statement against the numbered result. Organize modules by subject, not cha interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting and the selected-prime false-positive bound ([#1442]) are proved mathematically. Native parameter, collision-counting, prime-table and collision-tested minimum-search programs have explicit local - fit/frame/cost contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, - packed output and global preprocessing width/time composition remain. It still needs - residue/chunk/piece splitting and witness recovery, tensor/sparse-query programs, native + fit/frame/cost contracts ([#1446]). The complete native one-node component ([#1481]) computes + all residues/histograms, three compact heavy arrays and exact packed output in linear time; + frontend enumeration, recursive traversal and global preprocessing width/time composition remain. + 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 @@ -557,3 +558,4 @@ not planned, not as proved. [#1442]: https://github.com/cslib-community/complexitylib/issues/1442 [#1446]: https://github.com/cslib-community/complexitylib/issues/1446 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 +[#1481]: https://github.com/cslib-community/complexitylib/issues/1481 From b2554d58b9eb90291e7d9314128433b62016e203 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 12:03:10 -0400 Subject: [PATCH 18/45] fix(wordram): export sorted radix keys from composed contracts Return strict unsigned key order already proved by RunEncoding from both composed APIs. Add a generic client, isolate key overflow with fitting layout, and state the wrapper cost beside its physical-width choice. Predicate reuse migration requested by review is tracked in #1509. Validation: all six scripts/check.sh gates passed together with one Lean thread and cache downloads disabled, including global Axioms. Changed-file lints against fd69be7bc, hygiene --check-simp, PR names and diff checks pass. No curated abstract impact. Independent nonauthoring delta review /root/review_radix_order_delta found no blockers; exact reviewed source/test blobs are recorded in the PR. Outside coordinator review remains required. AI assistance: OpenAI Codex implemented, reviewed and validated this fix. --- Complexity/Algorithms/WordRAM/RadixSort.lean | 6 ++++-- .../Algorithms/WordRAM/RadixSort/Deduplicate.lean | 3 ++- ComplexityTests/WordRAMRadixSort.lean | 12 ++++++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/RadixSort.lean b/Complexity/Algorithms/WordRAM/RadixSort.lean index d3a1a6584c..a7409b44cd 100644 --- a/Complexity/Algorithms/WordRAM/RadixSort.lean +++ b/Complexity/Algorithms/WordRAM/RadixSort.lean @@ -16,6 +16,7 @@ public import Complexity.Computability.WordRAM.Time The input is the ordinary native list of unsigned words. The code reads its length and the physical word width, and uses that width as an explicit key-bit bound. The lower-level `deduplicateCode` accepts a smaller caller-supplied bit bound when known. +The standard-input wrapper's charged bound is `O(n * w)` for positive `n` and `w`. The key array starts at `1+2n`, the count array at `1+3n`; the return value is their common logical length. The two sorting buffers occupy `[1,1+2n)`. The implementation accepts repeated @@ -78,6 +79,7 @@ theorem input_exec {w : ℕ} (xs : List ℕ) (hinput : InputFits w xs) Block.HoldsList (1 + 2 * xs.length) (rs.map Prod.fst) y.mem ∧ Block.HoldsList (1 + 3 * xs.length) (rs.map Prod.snd) y.mem ∧ (rs.map Prod.fst).Nodup ∧ + (rs.map Prod.fst).Pairwise (· < ·) ∧ (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ xs) ∧ (∀ p ∈ rs, 0 < p.2 ∧ xs.count p.1 = p.2) ∧ Modifies (Block.mk 1 (4 * xs.length)).footprint (Data.init xs : Data 16).mem y.mem := by @@ -97,10 +99,10 @@ theorem input_exec {w : ℕ} (xs : List ℕ) (hinput : InputFits w xs) change (Data.init xs : Data 16).mem (1 + i.val) = xs[i.val] rw [Nat.add_comm, Data.init_mem_succ] simp only [List.getD, List.getElem?_eq_getElem i.isLt, Option.getD_some] - obtain ⟨y, rs, he, hyout, hyrk, hyrc, hnodup, hset, hcount, hframe⟩ := + obtain ⟨y, rs, he, hyout, hyrk, hyrc, hnodup, hordered, hset, hcount, hframe⟩ := deduplicate_exec xs u hn hs hd hb hm hinput.2 hinput.2 hfit (fits_self w) h2 obtain ⟨steps, hsteps, hexec⟩ := hi.seq he - refine ⟨y, rs, steps, hexec, ?_, by omega, hyrk, hyrc, hnodup, hset, hcount, hframe⟩ + refine ⟨y, rs, steps, hexec, ?_, by omega, hyrk, hyrc, hnodup, hordered, hset, hcount, hframe⟩ simpa only [program, inputCode, hyout, State.init, x] using hexec.executes Deduplicate.out end Cslib.Algorithms.WordRAM.RadixSort diff --git a/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean b/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean index eed159b431..49d3ce5bb2 100644 --- a/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean +++ b/Complexity/Algorithms/WordRAM/RadixSort/Deduplicate.lean @@ -66,6 +66,7 @@ theorem deduplicate_exec {w base keyBits : ℕ} (xs : List ℕ) (x : Data 16) Block.HoldsList (base + 2 * xs.length) (rs.map Prod.fst) y.mem ∧ Block.HoldsList (base + 3 * xs.length) (rs.map Prod.snd) y.mem ∧ (rs.map Prod.fst).Nodup ∧ + (rs.map Prod.fst).Pairwise (· < ·) ∧ (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ xs) ∧ (∀ p ∈ rs, 0 < p.2 ∧ xs.count p.1 = p.2) ∧ Modifies (Block.mk base (4 * xs.length)).footprint x.mem y.mem := by @@ -106,7 +107,7 @@ theorem deduplicate_exec {w base keyBits : ℕ} (xs : List ℕ) (x : Data 16) (by rw [hlen]; exact hfit.of_le (by omega)) (h2.of_le (by omega)) have heq : base + 2 * xs.length + xs.length = base + 3 * xs.length := by omega refine ⟨y, rs, ?_, hyout, hyrk, by simpa only [hlen, heq] using hyrc, - hrs.keys_nodup, ?_, ?_, ?_⟩ + hrs.keys_nodup, List.pairwise_map.mpr hrs.ordered, ?_, ?_, ?_⟩ · have hall := hinit.seq (hu.seq (hl.seq hy)) apply hall.mono simp only [deduplicateTime, timeBound, hlen] diff --git a/ComplexityTests/WordRAMRadixSort.lean b/ComplexityTests/WordRAMRadixSort.lean index 55ddb07ef7..06b443a9ad 100644 --- a/ComplexityTests/WordRAMRadixSort.lean +++ b/ComplexityTests/WordRAMRadixSort.lean @@ -46,10 +46,10 @@ private def observe (w fuel : ℕ) (xs : List ℕ) : Option (List ℕ × List #guard_msgs in #eval decide (observe 2 4 [2] = none) --- A raw unsigned input beyond the declared physical word bound faults as well. +-- Key overflow faults even when the singleton's four-buffer layout fits. /-- info: true -/ #guard_msgs in -#eval decide (observe 2 4 [4] = none) +#eval decide (observe 4 5 [16] = none) -- The assembled program is executed using the ordinary native initial state. /-- info: true -/ @@ -65,3 +65,11 @@ example : RadixSort.passes 0 [0, 0] = [0, 0] ∧ -- One finite instruction list is used at every input length and word width. example : RadixSort.program.length = 81 := by decide +kernel + +-- Clients recover strict unsigned key order directly from the complete input contract. +example (w : ℕ) (xs : List ℕ) (hinput : InputFits w xs) + (hfit : Fits w (1 + 4 * xs.length)) (h2 : Fits w 2) : + ∃ keys : List ℕ, keys.Pairwise (· < ·) ∧ ∀ k, k ∈ keys ↔ k ∈ xs := by + obtain ⟨_, rs, _, _, _, _, _, _, _, hordered, hset, _, _⟩ := + RadixSort.input_exec xs hinput hfit h2 + exact ⟨rs.map Prod.fst, hordered, hset⟩ From 712b07d9f74af9f57bc9bdb6a4337aa9fd9b992b Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 13:36:49 -0400 Subject: [PATCH 19/45] feat(wordram): implement complete native distinct-position frontend Compute exact ThreeSum.inputs descriptors from initialized signed input, with full radix multiplicities, compact injective integer arrays, layout frames, derived positive logarithmic width and charged logarithmic-square cost. Preserve the two multiplicity roots and all small input cases. Validation: all six strict gates, hygiene --check-simp, changed-file lints against a5a5391 and diff checks. Curated abstracts are unaffected. Part of #1482 under #1446/#1394. AI assistance: OpenAI Codex. --- Complexity.lean | 11 + .../Algorithms/WordRAM/ThreeSum/Frontend.lean | 129 +++++++ .../WordRAM/ThreeSum/Frontend/Arrays.lean | 343 +++++++++++++++++ .../WordRAM/ThreeSum/Frontend/Bounds.lean | 184 +++++++++ .../WordRAM/ThreeSum/Frontend/Execute.lean | 315 +++++++++++++++ .../WordRAM/ThreeSum/Frontend/Input.lean | 240 ++++++++++++ .../WordRAM/ThreeSum/Frontend/Layout.lean | 358 ++++++++++++++++++ .../WordRAM/ThreeSum/Frontend/Model.lean | 282 ++++++++++++++ .../WordRAM/ThreeSum/Frontend/Program.lean | 167 ++++++++ .../WordRAM/ThreeSum/Frontend/Scan.lean | 277 ++++++++++++++ .../WordRAM/ThreeSum/Frontend/Select.lean | 250 ++++++++++++ .../WordRAM/ThreeSum/Frontend/Time.lean | 64 ++++ ComplexityTests/WordRAMThreeSumFrontend.lean | 89 +++++ .../WordRAMThreeSumFrontendContracts.lean | 104 +++++ ROADMAP.md | 8 +- 15 files changed, 2819 insertions(+), 2 deletions(-) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean create mode 100644 ComplexityTests/WordRAMThreeSumFrontend.lean create mode 100644 ComplexityTests/WordRAMThreeSumFrontendContracts.lean diff --git a/Complexity.lean b/Complexity.lean index 50e3f76e28..1955ea8c5c 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -126,6 +126,17 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search public import Complexity.Algorithms.WordRAM.ThreeSum.Fill +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Execute +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Scan +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Select +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Time public import Complexity.Algorithms.WordRAM.ThreeSum.Memory public import Complexity.Algorithms.WordRAM.ThreeSum.Weight public import Complexity.Algorithms.WordRAM.VanEmdeBoas.Call diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean new file mode 100644 index 0000000000..cb6c81922f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.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.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Time + +/-! +# Complete native distinct-position 3SUM frontend + +One finite 128-register program per fixed exponent starts from ordinary native initialized +signed input. It computes `(n+2)^κ`, sorts with an explicit magnitude-derived bit count, +retains full multiplicities, and writes six-word descriptors of compact injective arrays. +Reading the descriptors gives exactly the existing `ThreeSum.inputs`, in its existing order, +including empty arrays and the two multiplicity triples at lengths zero, one and two. + +The mathematical definitions are reused from the frontend adaptation of +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +`PaperStatements.lean` lines 1358–1376. In particular `twiceSet` contains positive `2*a`. +Native programs, layout and charged proofs here are original; no source machine is imported. +This is the frontend component, not complete Chan–He preprocessing or a subquadratic solver. + +## Main definitions + +* `program`: native code depending only on the fixed polynomial exponent. +* `layout`: six-word descriptors followed by disjoint capacity-`n+1` payload blocks. +* `width`, `cost`: derived positive logarithmic width and all charged instructions. + +## Main results + +* `exec_input`: initialized-input execution, exact slots/frame and full multiplicities. +* `runs_input`: exact existing-root readback and complete native halting execution. +* `cost_bound`: explicit uniform logarithmic-square instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +private theorem readsWidth_rename {r r' : ℕ} (f : Fin r → Fin r') (c : Code .standard r) : + (c.rename f).ReadsWidth ↔ c.ReadsWidth := by + induction c with + | op o => cases o <;> rfl + | seq c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | branch b c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | loop b c hc => exact hc + | rand => rfl + +/-- Physical word width is never read, so it never determines the radix iteration count. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by + have hp := (readsWidth_rename parameterRegisters (ChanHe.Parameters.program κ)).not.mpr + (ChanHe.Parameters.not_readsWidth κ) + exact not_or.mpr ⟨hp, by decide⟩ + +/-- Code size depends only on the fixed exponent; the assembled final halt adds one. -/ +theorem size_program (κ : ℕ) : (program κ).size = κ + 71 + construct.size := by + simp only [program, Code.size, Code.size_rename, ChanHe.Parameters.size_program] + +/-- The complete finite program halts with its descriptor count, and following its descriptors +gives exactly the existing frontend list. No slot, bit budget, sorting result or fit is advice. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (y : Data 128) (steps : ℕ), + Executes w ((program κ).program descriptorCount) (State.init (input.map Signed.encode)) + (layout κ input.length).count steps ∧ steps ≤ cost κ input.length ∧ + readRoots (layout κ input.length) y.mem = + Cslib.Algorithms.ThreeSum.inputs ((input.length + 2) ^ κ) + (fun i : Fin input.length => input[i]) ∧ + y.regs descriptorBase = (layout κ input.length).descriptors ∧ + y.regs descriptorCount = (layout κ input.length).count ∧ + y.regs heapEnd = (layout κ input.length).endpoint ∧ y.Bounded w := by + obtain ⟨y, rs, he, ho, hbase, hcount, hend, _, _, hkeys, hcounts, _, _, _, _⟩ := + exec_input input hw hvalues + obtain ⟨steps, hsteps, hexec⟩ := he + have hpos : 0 < cost κ input.length := by dsimp only [cost]; omega + refine ⟨y, steps + 1, ?_, by omega, ?_, hbase, hcount, hend, ?_⟩ + · simpa only [hcount, State.init] using hexec.executes descriptorCount + · exact ho.readback hkeys (fun p hp => (hcounts p hp).2) hvalues + · exact hexec.bounded (Data.Bounded.init (input_fits input hw hvalues)) + +private theorem prefix_at_smaller_width {w w' r : ℕ} {p : Program .standard r} + (hp : ¬p.ReadsWidth) (hw : w ≤ w') {s t : State r} {k v total : ℕ} + (hwide : Runs w' p s t k) (hlow : Executes w p s v total) : Runs w p s t k := by + induction hwide using Relation.RelatesInSteps.head_induction_on generalizing total with + | hrefl => exact .refl _ + | @hhead a c k hstep hrest ih => + obtain ⟨z, m, hm, hz, _⟩ := hlow + cases m with + | zero => + have he := hm.zero + subst z + have hh : step w' p a = .halt v := step_halt_mono_width hz + rw [show step w' p a = .next c from hstep] at hh + cases hh + | succ m => + obtain ⟨u, hu, htail⟩ := hm.succ' + have he : u = c := by + have hmono := hu.mono_width hp hw + change step w' p a = .next u at hmono + rw [show step w' p a = .next c from hstep] at hmono + exact Outcome.next.inj hmono.symm + subst u + exact (ih (total := m + 1) ⟨z, m, htail, hz, rfl⟩).head a c t k hu + +/-- Every run prefix at every admitted width uses polynomially bounded words. This includes +all memory words and registers, rather than merely the final output or a pair-count metric. -/ +theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps : ℕ} + (hr : Runs w ((program κ).program descriptorCount) + (State.init (input.map Signed.encode)) s steps) : + (∀ i, s.data.regs i < (input.length + 2) ^ (160 * κ + 968)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (160 * κ + 968) := by + obtain ⟨_, _, he, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) descriptorCount) hw hr he + have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) + have hpoly := two_pow_width_le κ input.length + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean new file mode 100644 index 0000000000..368f2ec4cc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean @@ -0,0 +1,343 @@ +/- +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.Frontend.Program + +/-! +# Native frontend array and descriptor execution + +## Main results + +The compact scan contract is composed with every array setup and descriptor store. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Local arithmetic budget covers array addresses, native labels and loop setup. -/ +def budget (l : Layout) (U : ℕ) : ℕ := l.endpoint + 8 * U + 8 * l.digits + 64 + +/-- The hypotheses of a concrete compact scan, before choosing its bit coordinates and mode. -/ +structure ScanReady (w : ℕ) (rs : List (ℕ × ℕ)) (x : Data 48) : Prop where + /-- The source length is the distinct-key count. -/ + length : x.regs len = rs.length + /-- Native small constants. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + /-- Both radix arrays have exact contents. -/ + keys : Block.HoldsList (x.regs Scan.keys) (rs.map Prod.fst) x.mem + counts : Block.HoldsList (x.regs Scan.counts) (rs.map Prod.snd) x.mem + /-- Every key and the native label arithmetic fit. -/ + words : ∀ p ∈ rs, Fits w p.1 + absolute : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude + double : ∀ p ∈ rs, Fits w (2 * p.1) + labels : Fits w (4 * x.regs magnitude + 2) + small : Fits w 5 + /-- Source and output endpoints fit. -/ + keyAddress : Fits w (x.regs Scan.keys + rs.length) + countAddress : Fits w (x.regs Scan.counts + rs.length) + outputAddress : Fits w (x.regs dst + rs.length + 1) + /-- Both source blocks precede the output. -/ + keysBefore : x.regs Scan.keys + rs.length ≤ x.regs dst + countsBefore : x.regs Scan.counts + rs.length ≤ x.regs dst + +/-- Parameters changed by split/mode setup. -/ +def ScanParametersPreserved (x y : Data 48) : Prop := + y.mem = x.mem ∧ ∀ j, j.val < 27 → j ≠ beta → j ≠ beta' → j ≠ digit → j ≠ mode → + y.regs j = x.regs j + +/-- A mode/bit setup preserves every actual scan precondition. -/ +theorem ScanReady.transfer {w : ℕ} {rs : List (ℕ × ℕ)} {x y : Data 48} + (h : ScanReady w rs x) (he : ScanParametersPreserved x y) : ScanReady w rs y := by + obtain ⟨hm, hr⟩ := he + have eq (j : Fin 48) (hlt : j.val < 27) (hj : j ≠ beta ∧ j ≠ beta' ∧ j ≠ digit ∧ j ≠ mode) : + y.regs j = x.regs j := hr j hlt hj.1 hj.2.1 hj.2.2.1 hj.2.2.2 + constructor + · simpa only [eq len (by decide) (by decide)] using h.length + · simpa only [eq zero (by decide) (by decide), eq one (by decide) (by decide), + eq two (by decide) (by decide), + eq three (by decide) (by decide), eq four (by decide) (by decide), + eq five (by decide) (by decide)] using h.constants + · simpa only [eq Scan.keys (by decide) (by decide), hm] using h.keys + · simpa only [eq Scan.counts (by decide) (by decide), hm] using h.counts + · exact h.words + · simpa only [eq magnitude (by decide) (by decide)] using h.absolute + · exact h.double + · simpa only [eq magnitude (by decide) (by decide)] using h.labels + · exact h.small + · simpa only [eq Scan.keys (by decide) (by decide)] using h.keyAddress + · simpa only [eq Scan.counts (by decide) (by decide)] using h.countAddress + · simpa only [eq dst (by decide) (by decide)] using h.outputAddress + · simpa only [eq Scan.keys (by decide) (by decide), + eq dst (by decide) (by decide)] using h.keysBefore + · simpa only [eq Scan.counts (by decide) (by decide), + eq dst (by decide) (by decide)] using h.countsBefore + +/-- Invoke the proved native scan from its actual arithmetic/layout context. -/ +theorem ScanReady.exec {w : ℕ} {rs : List (ℕ × ℕ)} {x : Data 48} + (h : ScanReady w rs x) : + ∃ y, ExecWithin w Scan.program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, Scan.body.Preserves j → y.regs j = x.regs j) := + Scan.exec rs x h.length h.constants.1 h.constants.2.1 h.constants.2.2.1 + h.constants.2.2.2.1 h.constants.2.2.2.2.1 h.constants.2.2.2.2.2 + h.keys h.counts h.words h.absolute h.double h.labels h.small h.keyAddress + h.countAddress h.outputAddress h.keysBefore h.countsBefore + +private theorem exec_mode {w m : ℕ} (r : Fin 48) (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hr : x.regs r = m) : + ∃ y, ExecWithin w (.seq (ram[ mode ← r ]) Scan.program) x y (43 * rs.length + 5) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem := by + let a := x.set mode m + have ha : ExecWithin (B := .standard) w (ram[ mode ← r ]) x a 1 := by + ram_eval [a, hr] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro j _ _ _ _ hj + exact Function.update_of_ne hj _ _ + obtain ⟨y, hy, hmem, hout, hframe, _⟩ := (h.transfer har).exec + refine ⟨y, (ha.seq hy).mono (by omega), ?_, ?_, hframe⟩ + · simpa +decide [a] using hmem + · simpa +decide [a] using hout + +private theorem exec_singleton {w : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) : + ∃ y, ExecWithin w singleton x y 2 ∧ + Block.HoldsList (x.regs dst) [0] y.mem ∧ y.regs out = 1 ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have f1 : Fits w 1 := h.small.of_le (by decide) + have fd : Fits w (x.regs dst) := h.outputAddress.of_le (by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [singleton, h.constants.1, h.constants.2.1, f1, fd] + · intro i + simp [Block.read, Data.set_mem, Data.write_mem] + · simp + · intro a ha + have hne : a ≠ x.regs dst := by + intro he + apply ha + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + (rs.length + 1) + omega + simp only [Data.set_mem, Data.write_mem, Function.update_of_ne hne] + +/-- Select exactly the array named by one physical slot. Every split/multiplicity setup is +executed; the width and label budget are scalar runtime values, independent of code size. -/ +theorem pick_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hU : x.regs magnitude = U) + (hΛ : x.regs digits = Λ) (hs : x.regs splits = 2 * Λ ^ 2) + (ht : x.regs triple = j / 3) (hslot : x.regs slot = j % 3) + (hjfit : Fits w j) : + ∃ y, ExecWithin w pick x y (43 * rs.length + 20) ∧ + Block.HoldsList (x.regs dst) (Math.array U Λ rs j) y.mem ∧ + y.regs out = (Math.array U Λ rs j).length ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have hz := h.constants.1 + have h1 := h.constants.2.1 + have h2 := h.constants.2.2.1 + have h3 := h.constants.2.2.2.1 + have h4 := h.constants.2.2.2.2.1 + have h5 := h.constants.2.2.2.2.2 + have frame {mem : ℕ → ℕ} + (hf : Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem mem) : + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem mem := by + intro a ha + apply hf a + intro hmem + apply ha + rw [Block.mem_footprint] at hmem ⊢ + dsimp only [Block.Owns] at hmem ⊢ + omega + by_cases hsplit : j / 3 < 2 * Λ ^ 2 + · let a := ((((x.set beta (j / 3 / 2)).set beta' (j / 3 / 2 % Λ)).set + beta (j / 3 / 2 / Λ)).set digit (j / 3 % 2)).set mode (j % 3) + have f0 := hjfit.of_le (show j / 3 / 2 ≤ j by omega) + have f1' := hjfit.of_le (show j / 3 / 2 % Λ ≤ j from + (Nat.mod_le _ _).trans (by omega)) + have f2' := hjfit.of_le (show j / 3 / 2 / Λ ≤ j from + (Nat.div_le_self _ _).trans (by omega)) + have f3' := h.small.of_le (show j / 3 % 2 ≤ 5 by omega) + have f4' := h.small.of_le (show j % 3 ≤ 5 by omega) + have ha : ExecWithin w splitSetup x a 5 := by + ram_eval [splitSetup, a, h2, ht, hslot, hΛ, f0, f1', f2', f3', f4'] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro i _ hb hb' hv hm + simp only [a, Data.set_regs, Function.update_of_ne hb, Function.update_of_ne hb', + Function.update_of_ne hv, Function.update_of_ne hm] + obtain ⟨y, hy, hm, ho, hf, _⟩ := (h.transfer har).exec + have he : Math.array U Λ rs j = Math.select U (j / 3 / 2 / Λ) + (j / 3 / 2 % Λ) (j / 3 % 2) (j % 3) rs := by + have hn : j % 3 ≠ 6 := by omega + simp [Math.array, Math.mode, Math.beta, Math.beta', Math.digit, hsplit, hn] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((ha.seq hy).branch_pos (by simpa only [Cond.holds_lt, ht, hs] using hsplit)).mono + (by omega) + · simpa +decide [a, hU, he] using hm + · simpa +decide [a, hU, he] using ho + · have hcond : ¬(Cond.lt triple splits).Holds x := by + simpa only [Cond.holds_lt, ht, hs] using hsplit + by_cases htwice : j / 3 = 2 * Λ ^ 2 + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode three x rs h h3 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 3 rs := by + simp [Math.array, Math.mode, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hone : j % 3 = 1 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, htwice, hone] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact ((((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode five x rs h h5 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 5 rs := by + simp [Math.array, Math.mode, htwice, hzero, hone, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, + h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, + hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode four x rs h h4 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 4 rs := by + simp [Math.array, Math.mode, hsplit, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, hsplit, htwice, hzero] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact (((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + +private theorem write_exec {w : ℕ} (x : Data 48) (h1 : x.regs one = 1) + (ha : Fits w (x.regs descriptors + 2 * x.regs current + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) + (hi : Fits w (x.regs current + 1)) : + ∃ y, ExecWithin w writeDescriptor x y 7 ∧ + y.regs current = x.regs current + 1 ∧ + y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem = Function.update (Function.update x.mem + (x.regs descriptors + 2 * x.regs current) (x.regs out)) + (x.regs descriptors + 2 * x.regs current + 1) (x.regs dst) := by + have f0 := ha.of_le (show x.regs current + x.regs current ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f1 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f2 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors + 1 ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [writeDescriptor, h1, f0, f1, f2, hh, hi] + all_goals simp +decide [Data.set_regs, Data.set_mem, Data.write_mem, + show x.regs current + x.regs current + x.regs descriptors = + x.regs descriptors + 2 * x.regs current by omega] + +/-- One complete array, its exact descriptor words, allocation advance and local frame. -/ +theorem body_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (hready : ScanReady w rs (x.set dst (x.regs heap))) + (hU : x.regs magnitude = U) (hΛ : x.regs digits = Λ) + (hs : x.regs splits = 2 * Λ ^ 2) (hj : x.regs current = j) + (hcap : rs.length + 1 ≤ x.regs capacity) + (hbefore : x.regs descriptors + 2 * j + 2 ≤ x.regs heap) + (ha : Fits w (x.regs descriptors + 2 * j + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) (hi : Fits w (j + 1)) : + ∃ y, ExecWithin w body x y (43 * rs.length + 30) ∧ + y.regs current = j + 1 ∧ y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem (x.regs descriptors + 2 * j) = (Math.array U Λ rs j).length ∧ + y.mem (x.regs descriptors + 2 * j + 1) = x.regs heap ∧ + Block.HoldsList (x.regs heap) (Math.array U Λ rs j) y.mem ∧ + (∀ a, (a < x.regs heap ∨ x.regs heap + x.regs capacity ≤ a) → + a ≠ x.regs descriptors + 2 * j → a ≠ x.regs descriptors + 2 * j + 1 → + y.mem a = x.mem a) := by + have h3 : x.regs three = 3 := by + simpa +decide using hready.constants.2.2.2.1 + have h1 : x.regs one = 1 := by simpa +decide using hready.constants.2.1 + let a := ((x.set triple (j / 3)).set slot (j % 3)).set dst (x.regs heap) + have f0 := hi.of_le (show j / 3 ≤ j + 1 by omega) + have f1 := hi.of_le (show j % 3 ≤ j + 1 from (Nat.mod_le _ _).trans (by omega)) + have he : ExecWithin w + (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) x a 3 := by + ram_eval [a, h3, hj, f0, f1] + have hr : ScanReady w rs a := by + apply hready.transfer + refine ⟨rfl, ?_⟩ + intro i hil _ _ _ _ + have hit : i ≠ triple := by intro h; subst i; norm_num [triple] at hil + have his : i ≠ slot := by intro h; subst i; norm_num [slot] at hil + simp only [a, Data.set_regs, Function.update_apply] + by_cases hid : i = dst + · subst i; simp +decide + · simp only [hid, hit, his, ↓reduceIte] + obtain ⟨u, hu, hmem, hout, hf⟩ := pick_exec (j := j) a rs hr + (by simpa +decide [a] using hU) (by simpa +decide [a] using hΛ) + (by simpa +decide [a] using hs) (by simp +decide [a]) (by simp +decide [a]) + (hi.of_le (by omega)) + have hreg (i : Fin 48) (hp : pick.Preserves i) + (ht : i ≠ triple) (hs : i ≠ slot) (hd : i ≠ dst) : u.regs i = x.regs i := by + simpa only [a, Data.set_regs, Function.update_of_ne ht, Function.update_of_ne hs, + Function.update_of_ne hd] using hu.regs_eq hp + have huc : u.regs current = j := (hreg current (by decide) (by decide) (by decide) + (by decide)).trans hj + have huh : u.regs heap = x.regs heap := hreg heap (by decide) (by decide) (by decide) (by decide) + have hucap := hreg capacity (by decide) (by decide) (by decide) (by decide) + have hudesc := hreg descriptors (by decide) (by decide) (by decide) (by decide) + have hu1 : u.regs one = 1 := (hreg one (by decide) (by decide) (by decide) (by decide)).trans h1 + have hud : u.regs dst = x.regs heap := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hum : Block.HoldsList (x.regs heap) (Math.array U Λ rs j) u.mem := by + simpa +decide [a] using hmem + obtain ⟨y, hy, hyc, hyh, hym⟩ := write_exec u hu1 + (by simpa only [huc, hudesc] using ha) (by simpa only [huh, hucap] using hh) + (by simpa only [huc] using hi) + rw [huc, hudesc, hout, hud] at hym + refine ⟨y, (he.seq (hu.seq hy)).mono (by omega), by simpa only [huc] using hyc, + by simpa only [huh, hucap] using hyh, ?_, ?_, ?_, ?_⟩ + · rw [hym, Function.update_of_ne (by omega), Function.update_self] + · rw [hym, Function.update_self] + · apply hum.frame + intro b hlo hhi + rw [hym, Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + · intro b hb hb0 hb1 + rw [hym, Function.update_of_ne hb1, Function.update_of_ne hb0] + apply hf b + rw [Block.mem_footprint] + change ¬(a.regs dst ≤ b ∧ b < a.regs dst + (rs.length + 1)) + have had : a.regs dst = x.regs heap := by simp [a] + rw [had] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean new file mode 100644 index 0000000000..7a778c6c4a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean @@ -0,0 +1,184 @@ +/- +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.Frontend.Execute + +/-! +# Derived native frontend word width and charged cost + +The physical unsigned width is four times the existing complete parameter width. This coarse +positive logarithmic threshold covers all descriptor/payload addresses and native intermediates. +The bit budget passed to radix is the explicit magnitude-derived label count, never physical width. + +## Main definitions + +* `layout`: concrete runtime layout at fixed exponent and input length. +* `width`: positive logarithmic physical word-width threshold. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Layout computed from the actual unsigned input-length header and polynomial promise. -/ +def layout (κ n : ℕ) : Layout := + ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * (n + 2) ^ κ)⟩ + +/-- Physical unsigned width, including the two bits reserved when composing signed kernels. -/ +def width (κ n : ℕ) : ℕ := 4 * ChanHe.Parameters.width κ n + +/-- Concrete charged instruction bound, including the final halt. -/ +def cost (κ n : ℕ) : ℕ := + let U := (n + 2) ^ κ + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ChanHe.Parameters.time κ n U + RadixSort.deduplicateTime n Λ + + (layout κ n).arrays * (43 * n + 32) + 38 + +/-- The width coefficient is positive, including exponent zero and empty input. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := ChanHe.Parameters.width_pos κ n + dsimp only [width] + omega + +/-- The physical width is explicitly `Cκ * clog₂(n+2)` with a positive coefficient. -/ +theorem width_eq (κ n : ℕ) : + width κ n = (80 * κ + 484) * Nat.clog 2 (n + 2) := by + dsimp only [width, ChanHe.Parameters.width] + ring + +/-- All frontend arithmetic is bounded by the fourth power of the existing parameter budget. -/ +theorem array_budget_le (n U : ℕ) : + Arrays.budget ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U ≤ + ChanHe.Parameters.Math.budget n U ^ 4 := by + let P := ChanHe.Parameters.Math.budget n U + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + let A := 3 * (2 * Λ ^ 2 + 2) + have hP : 8 * U + 4 * n + 64 ≤ P := by dsimp [P, ChanHe.Parameters.Math.budget]; omega + have h64 : 64 ≤ P := by omega + have hn : n + 3 ≤ P := by omega + have hΛ : Λ ≤ P := by + have hlog := Nat.log_le_self 2 (4 * U) + dsimp only [Λ, Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + omega + have hsq : Λ ^ 2 ≤ P ^ 2 := Nat.pow_le_pow_left hΛ 2 + have hA : A ≤ 12 * P ^ 2 := by dsimp only [A]; nlinarith + have hm := Nat.mul_le_mul hn hA + have hbudget : Arrays.budget ⟨n, Λ⟩ U = 1 + 4 * n + (n + 3) * A + 8 * U + 8 * Λ + 64 := by + dsimp only [Arrays.budget, Layout.endpoint, Layout.pointer, Layout.payloads, + Layout.descriptors, Layout.capacity, Layout.arrays, Layout.count, Layout.splits, A] + ring + have hP3 : P ≤ P ^ 3 := by nlinarith + have hP4 : 64 * P ^ 3 ≤ P ^ 4 := by + have := Nat.mul_le_mul_right (P ^ 3) h64 + nlinarith + change Arrays.budget ⟨n, Λ⟩ U ≤ P ^ 4 + rw [hbudget] + nlinarith + +/-- The derived physical width discharges the complete native frontend budget. -/ +theorem array_budget_fits {κ n w : ℕ} (hw : width κ n ≤ w) : + Fits w (Arrays.budget (layout κ n) ((n + 2) ^ κ)) := by + have hp : Fits (ChanHe.Parameters.width κ n) + (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ)) := ChanHe.Parameters.budget_fits le_rfl + have hpow := Nat.pow_lt_pow_left hp (by decide : (4 : ℕ) ≠ 0) + have hf : Fits (width κ n) (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4) := by + simpa only [Fits, width, ← pow_mul, Nat.mul_comm] using hpow + exact (hf.of_le (array_budget_le n ((n + 2) ^ κ))).mono hw + +/-- Input/header fitting follows from the same global magnitude and width promises. -/ +theorem input_fits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := by + apply ChanHe.Parameters.inputFits input (le_trans ?_ hw) hvalues + dsimp only [width] + omega + +/-- The lower-level radix bit bound includes the largest signed key, even when `κ=0`. -/ +theorem key_lt_two_pow {U : ℕ} (hU : 1 ≤ U) {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z < 2 ^ Cslib.Algorithms.ThreeSum.labelBits (2 * U) := by + have hk := (Math.key_bounds hz).1 + have hp := Nat.lt_pow_succ_log_self (by decide : 1 < 2) (4 * U) + change Signed.encode z < 2 ^ (Nat.log 2 (2 * (2 * U)) + 1) + rw [show 2 * (2 * U) = 4 * U by omega] + exact lt_of_le_of_lt (show Signed.encode z ≤ 4 * U by omega) hp + +/-- The explicit radix bit count has a logarithmic bound independent of admitted physical width. -/ +theorem digits_bound (κ n : ℕ) : + (layout κ n).digits ≤ (20 * κ + 121) * Nat.clog 2 (n + 2) := by + let E := 20 * κ + 120 + let L := Nat.clog 2 (n + 2) + let U := (n + 2) ^ κ + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hp : ChanHe.Parameters.Math.budget n U ≤ 2 ^ (E * L) := by + calc + _ ≤ (n + 2) ^ E := ChanHe.Parameters.Math.budget_polynomial κ n + _ ≤ (2 ^ L) ^ E := Nat.pow_le_pow_left (Nat.le_pow_clog (by decide) _) _ + _ = _ := by rw [← pow_mul]; congr 1; ring + have h4 : 4 * U ≤ ChanHe.Parameters.Math.budget n U := by + dsimp only [ChanHe.Parameters.Math.budget] + omega + have hlog : Nat.log 2 (4 * U) ≤ E * L := by + calc + _ ≤ Nat.log 2 (2 ^ (E * L)) := Nat.log_mono_right (h4.trans hp) + _ = _ := Nat.log_pow (by decide) _ + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ _ + dsimp only [Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + dsimp only [E, L] at * + nlinarith + +/-- The entire allocated region and arithmetic budget are polynomial in actual input length. -/ +theorem array_budget_polynomial (κ n : ℕ) : + Arrays.budget (layout κ n) ((n + 2) ^ κ) ≤ (n + 2) ^ (80 * κ + 480) := by + calc + _ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4 := array_budget_le _ _ + _ ≤ ((n + 2) ^ (20 * κ + 120)) ^ 4 := + Nat.pow_le_pow_left (ChanHe.Parameters.Math.budget_polynomial κ n) 4 + _ = _ := by rw [← pow_mul]; congr 1; omega + +/-- Every compact output integer fits the parent's signed kernel width `physicalWidth-2`. +Thus the parent's native signed arithmetic compiler runs at exactly the physical width here. -/ +theorem signed_output_fits {κ n w : ℕ} (hw : width κ n ≤ w) {z : ℤ} + (hz : |z| ≤ (2 * (n + 2) ^ κ : ℕ)) : GenericRAM.Signed.Fits (w - 2) z := by + have hP := ChanHe.Parameters.budget_fits (κ := κ) (n := n) (w := ChanHe.Parameters.width κ n) + le_rfl + have hpos := ChanHe.Parameters.width_pos κ n + have hW : ChanHe.Parameters.width κ n + 3 ≤ w := by dsimp only [width] at hw; omega + have hfit : 2 * (n + 2) ^ κ < 2 ^ (w - 3) := by + have hmag : 2 * (n + 2) ^ κ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) := by + dsimp only [ChanHe.Parameters.Math.budget]; omega + exact (hmag.trans_lt hP).trans_le (Nat.pow_le_pow_right (by decide) (by omega)) + have habs := abs_le.mp hz + have hfi : ((2 * (n + 2) ^ κ : ℕ) : ℤ) < (2 : ℤ) ^ (w - 3) := by exact_mod_cast hfit + refine ⟨by omega, ?_, ?_⟩ + all_goals rw [show w - 2 - 1 = w - 3 by omega] + all_goals omega + +/-- A word at the canonical physical width has polynomial magnitude. -/ +theorem two_pow_width_le (κ n : ℕ) : 2 ^ width κ n ≤ (n + 2) ^ (160 * κ + 968) := by + let L := Nat.clog 2 (n + 2) + have hL : 0 < L := Nat.clog_pos (by decide) (by omega) + have hp : 2 ^ (L - 1) < n + 2 := + Nat.pow_pred_clog_lt_self (b := 2) (x := n + 2) (by decide) (by omega) + have heq : 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 [heq]; nlinarith + rw [width_eq] + calc + _ = (2 ^ L) ^ (80 * κ + 484) := by rw [← pow_mul]; congr 1; ring + _ ≤ ((n + 2) ^ 2) ^ (80 * κ + 484) := Nat.pow_le_pow_left hceil _ + _ = _ := by + rw [← pow_mul] + exact congrArg (fun e : ℕ => (n + 2) ^ e) (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean new file mode 100644 index 0000000000..578321d60c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean @@ -0,0 +1,315 @@ +/- +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.Frontend.Arrays + +/-! +# Complete descriptor-table execution + +The invariant retains every completed descriptor and compact payload, both full radix arrays, +and all memory outside the allocated frontend region. + +## Main results + +The enumeration loop charges each native array scan, descriptor store and allocation advance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Immutable registers of the complete enumeration loop. -/ +structure Registers (l : Layout) (U k : ℕ) (x : Data 48) : Prop where + /-- Original magnitude promise. -/ + magnitude : x.regs Select.magnitude = U + /-- Original input length and explicit bit budget. -/ + inputLen : x.regs Arrays.inputLen = l.n + digits : x.regs Arrays.digits = l.digits + /-- Exact radix key/count pointers and distinct-key length. -/ + keys : x.regs Scan.keys = 1 + 2 * l.n + counts : x.regs Scan.counts = 1 + 3 * l.n + len : x.regs Scan.len = k + /-- Computed layout values. -/ + splits : x.regs Arrays.splits = l.splits + triples : x.regs Arrays.triples = l.count + arrays : x.regs Arrays.arrays = l.arrays + capacity : x.regs Arrays.capacity = l.capacity + descriptors : x.regs Arrays.descriptors = l.descriptors + heapEnd : x.regs Arrays.heapEnd = l.endpoint + /-- All small constants are real native registers. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + +/-- The body preserves every immutable layout and source register. -/ +theorem Registers.transfer {l : Layout} {U k : ℕ} {x y : Data 48} + (h : Registers l U k x) (hr : ∀ i, body.Preserves i → y.regs i = x.regs i) : + Registers l U k y := by + constructor + · exact (hr _ (by decide)).trans h.magnitude + · exact (hr _ (by decide)).trans h.inputLen + · exact (hr _ (by decide)).trans h.digits + · exact (hr _ (by decide)).trans h.keys + · exact (hr _ (by decide)).trans h.counts + · exact (hr _ (by decide)).trans h.len + · exact (hr _ (by decide)).trans h.splits + · exact (hr _ (by decide)).trans h.triples + · exact (hr _ (by decide)).trans h.arrays + · exact (hr _ (by decide)).trans h.capacity + · exact (hr _ (by decide)).trans h.descriptors + · exact (hr _ (by decide)).trans h.heapEnd + · obtain ⟨h0, h1, h2, h3, h4, h5⟩ := h.constants + exact ⟨(hr _ (by decide)).trans h0, (hr _ (by decide)).trans h1, + (hr _ (by decide)).trans h2, (hr _ (by decide)).trans h3, + (hr _ (by decide)).trans h4, (hr _ (by decide)).trans h5⟩ + +/-- Every layout scalar and partial allocation is below the explicit arithmetic budget. -/ +theorem layout_bounds (l : Layout) (U : ℕ) : + l.n + 1 ≤ budget l U ∧ l.digits * l.digits ≤ budget l U ∧ + l.splits ≤ budget l U ∧ l.count ≤ budget l U ∧ l.arrays ≤ budget l U ∧ + 2 * l.arrays ≤ budget l U ∧ l.payloads ≤ budget l U ∧ + l.arrays * l.capacity ≤ budget l U ∧ l.endpoint ≤ budget l U ∧ + 4 * U + 2 ≤ budget l U ∧ 5 ≤ budget l U := by + have hb : budget l U = l.endpoint + 8 * U + 8 * l.digits + 64 := rfl + have he : l.endpoint = l.payloads + l.capacity * l.arrays := rfl + have hp : l.payloads = l.descriptors + 2 * l.arrays := rfl + have hd : l.descriptors = 1 + 4 * l.n := rfl + have hs : l.splits = 2 * l.digits ^ 2 := rfl + have hc : l.count = l.splits + 2 := rfl + have ha : l.arrays = 3 * l.count := rfl + have hmul : l.arrays * l.capacity = l.capacity * l.arrays := Nat.mul_comm _ _ + have hsq : l.digits * l.digits = l.digits ^ 2 := (pow_two _).symm + omega + +/-- Setup computes all constants and addresses with actual instructions, preserving all memory. -/ +theorem setup_exec {w U : ℕ} (l : Layout) (k : ℕ) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hk : x.regs Scan.keys = 1 + 2 * l.n) + (hc : x.regs Scan.counts = 1 + 3 * l.n) (hlen : x.regs len = k) + (hd : x.regs descriptors = l.descriptors) (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w setup x y 19 ∧ Registers l U k y ∧ + y.regs current = 0 ∧ y.regs heap = l.pointer 0 ∧ y.mem = x.mem := by + obtain ⟨fN, fSq, fS, fD, fA, f2A, fP, fAC, fE, _, f5⟩ := layout_bounds l U + have f0 : Fits w 0 := fits_zero w + have f1 : Fits w 1 := hfit.of_le (by omega) + have f2 : Fits w 2 := hfit.of_le (by omega) + have f3 : Fits w 3 := hfit.of_le (by omega) + have f4 : Fits w 4 := hfit.of_le (by omega) + have f5' : Fits w 5 := hfit.of_le f5 + have fn := hfit.of_le fN + have fsq := hfit.of_le fSq + have fs : Fits w (l.digits * l.digits * 2) := by + simpa only [Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fS + have fd : Fits w (l.digits * l.digits * 2 + 2) := by + simpa only [Layout.count, Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fD + have fa : Fits w ((l.digits * l.digits * 2 + 2) * 3) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm] + using hfit.of_le fA + have f2a : Fits w ((l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le f2A + have fp : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.payloads, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fP + have fac : Fits w ((l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.capacity, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm] using hfit.of_le fAC + have fe : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2 + + (l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.capacity, + Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fE + refine ⟨?y, ?run, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [setup, hn, hΛ, hd, f0, f1, f2, f3, f4, f5', fn, fsq, + fs, fd, fa, f2a, fp, fac, fe] + · constructor + all_goals simp +decide [Data.set_regs, hU, hn, hΛ, hk, hc, hlen, hd, + Layout.splits, Layout.count, Layout.arrays, Layout.capacity, Layout.endpoint, + Layout.pointer, Layout.payloads, pow_two, Nat.mul_comm, Nat.mul_assoc] + · simp + · simp +decide [Data.set_regs, Layout.pointer, Layout.payloads, Layout.arrays, + Layout.count, Layout.splits, pow_two, Nat.mul_comm, Nat.mul_assoc] + · rfl + +/-- All compact arrays and six-word descriptors are produced by the complete native loop. -/ +theorem loop_exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hr : Registers l U rs.length x) (hidx : x.regs current = 0) + (hheap : x.regs heap = l.pointer 0) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w (.loop (.lt current arrays) body) x y + (l.arrays * (43 * rs.length + 32) + 2) ∧ + Output l U rs y.mem ∧ y.regs current = l.arrays ∧ y.regs heap = l.endpoint ∧ + Registers l U rs.length y ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + have hb := layout_bounds l U + have he := hb.2.2.2.2.2.2.2.2.1 + have hlabel := hb.2.2.2.2.2.2.2.2.2.1 + have hsmall := hb.2.2.2.2.2.2.2.2.2.2 + have hbase : l.descriptors ≤ l.pointer 0 := by dsimp [Layout.pointer, Layout.payloads]; omega + have hN : 1 + 4 * l.n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors] + omega + let I (y : Data 48) : Prop := + Registers l U rs.length y ∧ y.regs current ≤ l.arrays ∧ + y.regs heap = l.pointer (y.regs current) ∧ + (∀ i < y.regs current, + y.mem (l.descriptors + 2 * i) = (Math.array U l.digits rs i).length ∧ + y.mem (l.descriptors + 2 * i + 1) = l.pointer i ∧ + Block.HoldsList (l.pointer i) (Math.array U l.digits rs i) y.mem) ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt current arrays).Holds y) : + ∃ z, ExecWithin w body y z (43 * rs.length + 30) ∧ I z ∧ + l.arrays - z.regs current < l.arrays - y.regs current := by + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hy + have hj : y.regs current < l.arrays := by + simpa only [Cond.holds_lt, hyr.arrays] using ht + have hnext : l.pointer (y.regs current + 1) ≤ l.endpoint := + l.pointer_mono (by omega) + have hcur : l.pointer (y.regs current) ≤ l.endpoint := l.pointer_mono (by omega) + have hcappos : 0 < l.capacity := by dsimp [Layout.capacity]; omega + have hsource : 1 + 4 * l.n ≤ l.pointer (y.regs current) := by + have := l.pointer_mono (i := 0) (j := y.regs current) (by omega) + change l.descriptors ≤ _ + omega + have hready : ScanReady w rs (y.set dst (y.regs heap)) := by + constructor + · simpa +decide using hyr.len + · simpa +decide using hyr.constants + · simpa +decide [hyr.keys] using hykeys + · simpa +decide [hyr.counts] using hycounts + · intro p hp + exact hfit.of_le ((hkeys p hp).1.trans (by omega)) + · intro p hp + simpa +decide [hyr.magnitude] using (hkeys p hp).2.trans (show U ≤ 2 * U by omega) + · intro p hp + exact hfit.of_le (show 2 * p.1 ≤ budget l U by have := (hkeys p hp).1; omega) + · simpa +decide [hyr.magnitude] using hfit.of_le hlabel + · exact hfit.of_le hsmall + · simpa +decide [hyr.keys] using hfit.of_le + (show 1 + 2 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyr.counts] using hfit.of_le + (show 1 + 3 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyheap] using hfit.of_le + (show l.pointer (y.regs current) + rs.length + 1 ≤ budget l U by + rw [Layout.pointer_succ] at hnext + dsimp only [Layout.capacity] at hnext + omega) + · simpa +decide [hyr.keys, hyheap] using + (show 1 + 2 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + · simpa +decide [hyr.counts, hyheap] using + (show 1 + 3 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + obtain ⟨z, hz, hzi, hzh, hzd0, hzd1, hzp, hzf⟩ := body_exec + (j := y.regs current) y rs hready hyr.magnitude hyr.digits + (by simpa only [Layout.splits] using hyr.splits) rfl + (by rw [hyr.capacity]; dsimp [Layout.capacity]; omega) + (by rw [hyr.descriptors, hyheap]; exact l.descriptor_before hj _) + (hfit.of_le (by + rw [hyr.descriptors] + have := l.descriptor_before hj 0 + have hp0 : l.pointer 0 ≤ l.endpoint := l.pointer_mono (i := 0) (j := l.arrays) (by omega) + omega)) + (by rw [hyheap, hyr.capacity, ← Layout.pointer_succ]; exact hfit.of_le (hnext.trans he)) + (hfit.of_le (by have := hb.2.2.2.2.1; omega)) + simp only [hyr.descriptors, hyheap, hyr.capacity] at hzd0 hzd1 hzp hzf hzh + have hf (b : ℕ) (houtside : b < l.pointer (y.regs current) ∨ + l.pointer (y.regs current + 1) ≤ b) + (hb0 : b ≠ l.descriptors + 2 * y.regs current) + (hb1 : b ≠ l.descriptors + 2 * y.regs current + 1) : z.mem b = y.mem b := by + apply hzf b _ hb0 hb1 + simpa only [Layout.pointer_succ] using houtside + refine ⟨z, hz, ?_, by omega⟩ + refine ⟨hyr.transfer (fun i hi => hz.regs_eq hi), by omega, ?_, ?_, ?_, ?_, ?_⟩ + · rw [hzi, Layout.pointer_succ] + exact hzh + · intro i hi + by_cases hij : i = y.regs current + · subst i + exact ⟨hzd0, hzd1, hzp⟩ + · have hi' : i < y.regs current := by omega + obtain ⟨h0, h1, hp⟩ := hyout i hi' + have hbefore := l.descriptor_before (hi'.trans hj) (y.regs current) + refine ⟨(hf _ (.inl (by omega)) (by omega) (by omega)).trans h0, + (hf _ (.inl (by omega)) (by omega) (by omega)).trans h1, ?_⟩ + apply hp.frame + intro b hlo hhi + have hlen' := (Math.length_array_le U l.digits rs i).trans + (show rs.length + 1 ≤ l.capacity by dsimp [Layout.capacity]; omega) + have hptr := l.pointer_mono (show i + 1 ≤ y.regs current by omega) + rw [Layout.pointer_succ] at hptr + have htable := l.descriptor_before hj i + exact hf b (.inl (by omega)) (by omega) (by omega) + · apply hykeys.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · apply hycounts.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · intro b hnot + have hb : b < l.descriptors ∨ l.endpoint ≤ b := by + rw [Block.mem_footprint] at hnot + dsimp only [Block.Owns] at hnot + have := hbase.trans (l.pointer_mono (show 0 ≤ l.arrays by omega)) + omega + have htable := l.descriptor_before hj (y.regs current) + have hb0 : b ≠ l.descriptors + 2 * y.regs current := by omega + have hb1 : b ≠ l.descriptors + 2 * y.regs current + 1 := by omega + exact (hf b (by rcases hb with hb | hb <;> omega) hb0 hb1).trans (hyframe b hnot) + have hx : I x := by + refine ⟨hr, by rw [hidx]; omega, by simpa only [hidx] using hheap, ?_, hk, hc, + Modifies.refl _ _⟩ + intro i hi + rw [hidx] at hi + omega + obtain ⟨y, hy, hI, ht⟩ := ExecWithin.loop_invariant I + (fun y => l.arrays - y.regs current) hbody hx + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hI + have hend : y.regs current = l.arrays := by + simp only [Cond.holds_lt, hyr.arrays] at ht + omega + refine ⟨y, ?_, ?_, hend, ?_, hyr, hykeys, hycounts, hyframe⟩ + · simpa only [hidx, Nat.sub_zero, Nat.mul_comm] using hy + · intro j hj + exact hyout j (by omega) + · simpa only [hend, Layout.endpoint] using hyheap + +/-- Complete array/descriptor construction, including all native constant and layout setup. -/ +theorem exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hkreg : x.regs Scan.keys = 1 + 2 * l.n) + (hcreg : x.regs Scan.counts = 1 + 3 * l.n) (hlenreg : x.regs len = rs.length) + (hd : x.regs descriptors = l.descriptors) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w program x y (l.arrays * (43 * rs.length + 32) + 21) ∧ + Output l U rs y.mem ∧ Registers l U rs.length y ∧ y.regs heap = l.endpoint ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + obtain ⟨a, ha, har, hai, hah, ham⟩ := setup_exec l rs.length x hU hn hΛ hkreg hcreg + hlenreg hd hfit + obtain ⟨y, hy, hout, _, hyh, hyr, hykeys, hycounts, hyframe⟩ := loop_exec l rs a har hai hah + hlen (by simpa only [ham] using hk) (by simpa only [ham] using hc) hkeys hfit + exact ⟨y, (ha.seq hy).mono (by omega), hout, hyr, hyh, hykeys, hycounts, + by simpa only [ham] using hyframe⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean new file mode 100644 index 0000000000..0d6dc16694 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean @@ -0,0 +1,240 @@ +/- +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.Frontend.Bounds + +/-! +# Exact native frontend from initialized signed input + +The header is the unsigned length; payload cell one starts the canonical signed encoding. +The parameter program computes the magnitude and explicit radix bit count. The radix program +preserves full multiplicities; the complete descriptor program enumerates the existing inputs. + +## Main results + +Initialized-input execution uses a positive logarithmic physical width and fixed code per exponent. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +theorem parameterRegisters_injective : Function.Injective parameterRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [parameterRegisters] at this + omega + +theorem radixRegisters_injective : Function.Injective radixRegisters := by + intro i j h + exact Fin.ext (congrArg (fun z : Fin 128 => z.val) h) + +theorem arrayRegisters_injective : Function.Injective arrayRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [arrayRegisters] at this + omega + +/-- Parameter outputs remain available to the parent traversal without recomputation. -/ +theorem construct_preserves_parameters (i : Fin 24) : + construct.Preserves (parameterRegisters i) := by + fin_cases i <;> decide + +private theorem cost_sum_bound (p r A n k : ℕ) (hk : k ≤ n) : + p + (4 + (r + (9 + (A * (43 * k + 32) + 21 + 3)))) ≤ + p + r + A * (43 * n + 32) + 38 - 1 := by + have := Nat.mul_le_mul_left A (show 43 * k + 32 ≤ 43 * n + 32 by omega) + omega + +/-- Complete initialized-input native construction, with no supplied algorithm or fit advice. -/ +theorem exec_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (y : Data 128) (rs : List (ℕ × ℕ)), + ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y (cost κ input.length - 1) ∧ + Output (layout κ input.length) ((input.length + 2) ^ κ) rs y.mem ∧ + y.regs descriptorBase = (layout κ input.length).descriptors ∧ + y.regs descriptorCount = (layout κ input.length).count ∧ + y.regs heapEnd = (layout κ input.length).endpoint ∧ + (rs.map Prod.fst).Nodup ∧ (rs.map Prod.fst).Pairwise (· < ·) ∧ + (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) ∧ + (∀ p ∈ rs, 0 < p.2 ∧ (input.map Signed.encode).count p.1 = p.2) ∧ + Block.HoldsList (1 + 2 * input.length) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * input.length) (rs.map Prod.snd) y.mem ∧ + ChanHe.Parameters.Output input.length ((input.length + 2) ^ κ) + (y.comap parameterRegisters) ∧ + Modifies (Block.mk 1 ((layout κ input.length).endpoint - 1)).footprint + (Data.init (input.map Signed.encode) : Data 128).mem y.mem := by + let n := input.length + let U := (n + 2) ^ κ + let l := layout κ n + let x : Data 128 := Data.init (input.map Signed.encode) + have hparam : ChanHe.Parameters.width κ n ≤ w := by + change ChanHe.Parameters.width κ input.length ≤ w + dsimp only [width] at hw + omega + have hp := ChanHe.Parameters.budget_fits hparam + have hfront := array_budget_fits hw + have hN : Fits w (1 + 4 * n) := hp.of_le (by + dsimp only [ChanHe.Parameters.Math.budget]; omega) + have h1 : Fits w 1 := hN.of_le (by omega) + have h2 : Fits w 2 := hp.of_le (by dsimp only [ChanHe.Parameters.Math.budget]; omega) + have hB : Fits w l.digits := hp.of_le (by + have hl := Nat.log_le_self 2 (4 * U) + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ ChanHe.Parameters.Math.budget n U + dsimp only [Cslib.Algorithms.ThreeSum.labelBits, ChanHe.Parameters.Math.budget] + rw [show 2 * (2 * U) = 4 * U by omega] + omega) + have hU : 1 ≤ U := Nat.one_le_pow _ _ (by omega) + obtain ⟨p, hep, hop, hmp⟩ := ChanHe.Parameters.exec κ (x.comap parameterRegisters) + (by simpa only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] using hp) + simp only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] at hep hop + let a := p.extend parameterRegisters x + have ha := hep.rename parameterRegisters_injective + have hpr (i : Fin 24) : a.regs (parameterRegisters i) = p.regs i := by + exact congrArg (fun z : Data 24 => z.regs i) + (Data.comap_extend parameterRegisters_injective p x) + have han : a.regs (parameterRegisters ChanHe.Parameters.len) = n := + (hpr _).trans hop.len + have haU : a.regs (parameterRegisters ChanHe.Parameters.magnitude) = U := + (hpr _).trans hop.magnitude + have haB : a.regs (parameterRegisters ChanHe.Parameters.digits) = l.digits := + (hpr _).trans hop.digits + have ham : a.mem = x.mem := hmp + let b := (((a.set (radixRegisters RadixSort.len) n).set (radixRegisters RadixSort.src) 1).set + (radixRegisters RadixSort.dst) (n + 1)).set (radixRegisters RadixSort.bits) l.digits + have hb : ExecWithin w radixSetup a b 4 := by + have hn1 := hN.of_le (show n + 1 ≤ 1 + 4 * n by omega) + ram_eval [radixSetup, b, han, haB, h1, hB, hn1] + have hmem : Block.HoldsList 1 (input.map Signed.encode) (b.comap radixRegisters).mem := by + intro i + change a.mem (1 + i.val) = (input.map Signed.encode)[i.val] + rw [ham] + dsimp only [x] + rw [Nat.add_comm, Data.init_mem_succ] + simp only [List.getD, List.getElem?_eq_getElem i.isLt, Option.getD_some] + obtain ⟨q, rs, heq, hqout, hqkeys, hqcounts, hnodup, horder, hset, hcounts, hframe⟩ := + RadixSort.deduplicate_exec (input.map Signed.encode) (b.comap radixRegisters) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]) + (by simp +decide [b, Data.comap_regs, radixRegisters]) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]; omega) + (by simp +decide [b, Data.comap_regs, radixRegisters]) hmem + (by + intro u hu + obtain ⟨z, hz, rfl⟩ := List.mem_map.mp hu + exact key_lt_two_pow hU (hvalues z hz)) + (input_fits input hw hvalues).2 (by simpa only [List.length_map] using hN) hB h2 + have hrlen : rs.length ≤ n := by + have ht := hnodup.length_le_of_subset (fun k hk => (hset k).mp hk) + simpa only [List.length_map] using ht + let c := q.extend radixRegisters b + have hradix := heq.rename radixRegisters_injective + have hcout : c.regs (radixRegisters Deduplicate.out) = rs.length := by + have he := congrArg (fun z : Data 16 => z.regs Deduplicate.out) + (Data.comap_extend radixRegisters_injective q b) + exact he.trans hqout + have hhigh (i : Fin 24) : c.regs (parameterRegisters i) = a.regs (parameterRegisters i) := by + have hnot : ¬∃ j, radixRegisters j = parameterRegisters i := by + rintro ⟨j, he⟩ + have := congrArg Fin.val he + dsimp only [radixRegisters, parameterRegisters] at this + omega + simp only [c, Data.extend, Function.extend_apply' _ _ _ hnot] + have hne (j : Fin 16) : parameterRegisters i ≠ radixRegisters j := by + intro he; exact hnot ⟨j, he.symm⟩ + simp only [b, Data.set_regs, Function.update_of_ne (hne _)] + have hcn := (hhigh _).trans han + have hcU := (hhigh _).trans haU + have hcB := (hhigh _).trans haB + let d := ((((((((c.set (arrayRegisters Scan.len) rs.length).set + (arrayRegisters Select.magnitude) U).set (arrayRegisters Arrays.inputLen) n).set + (arrayRegisters Arrays.digits) l.digits).set (arrayRegisters Select.one) 1).set + (arrayRegisters Scan.keys) (n + n)).set (arrayRegisters Scan.keys) (n + n + 1)).set + (arrayRegisters Scan.counts) (n + n + 1 + n)).set + (arrayRegisters Arrays.descriptors) (n + n + 1 + n + n) + have hd : ExecWithin w arraySetup c d 9 := by + have f0 := hN.of_le (show n + n ≤ 1 + 4 * n by omega) + have f1 := hN.of_le (show n + n + 1 ≤ 1 + 4 * n by omega) + have f2 := hN.of_le (show n + n + 1 + n ≤ 1 + 4 * n by omega) + have f3 := hN.of_le (show n + n + 1 + n + n ≤ 1 + 4 * n by omega) + ram_eval [arraySetup, d, hcn, hcU, hcB, hcout, h1, f0, f1, f2, f3] + have hrawkeys : ∀ v ∈ rs, v.1 ≤ 2 * U + 1 ∧ v.1 / 2 ≤ U := by + intro v hv + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hset v.1).mp (List.mem_map.mpr ⟨v, hv, rfl⟩)) + rw [← he] + exact ⟨(Math.key_bounds (hvalues z hz)).1, + by + have hz' := hvalues z hz + rw [Signed.encode_div_two] + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by + simpa only [Int.natCast_natAbs] using hz')⟩ + obtain ⟨r, her, hor, hregs, hrheap, hrkeys, hrcounts, hrframe⟩ := Arrays.exec l rs + (d.comap arrayRegisters) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, Layout.descriptors, l, layout]; omega) + hrlen (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqkeys) + (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqcounts) hrawkeys hfront + let e := r.extend arrayRegisters d + have harray := her.rename arrayRegisters_injective + have herg (i : Fin 48) : e.regs (arrayRegisters i) = r.regs i := + congrArg (fun z : Data 48 => z.regs i) (Data.comap_extend arrayRegisters_injective r d) + let y := ((e.set descriptorBase l.descriptors).set descriptorCount l.count).set heapEnd l.endpoint + have hy : ExecWithin w finish e y 3 := by + ram_eval [finish, y, herg Arrays.descriptors, herg Arrays.triples, herg Arrays.heapEnd, + hregs.descriptors, hregs.triples, hregs.heapEnd] + have hall := ha.seq (hb.seq (hradix.seq (hd.seq (harray.seq hy)))) + refine ⟨y, rs, ?_, hor, ?_, ?_, ?_, hnodup, horder, hset, hcounts, hrkeys, hrcounts, ?_, ?_⟩ + · apply hall.mono + simpa only [cost, List.length_map, n, U, l, layout] using cost_sum_bound + (ChanHe.Parameters.time κ n U) (RadixSort.deduplicateTime n l.digits) + l.arrays n rs.length hrlen + · rfl + · rfl + · rfl + · have hconstruct := hb.seq (hradix.seq (hd.seq (harray.seq hy))) + have hprf (i : Fin 24) : y.regs (parameterRegisters i) = p.regs i := + (hconstruct.regs_eq (construct_preserves_parameters i)).trans (hpr i) + -- Only register equalities are needed: the parameter output does not constrain memory. + exact ⟨(hprf _).trans hop.len, (hprf _).trans hop.magnitude, + (hprf _).trans hop.digits, (hprf _).trans hop.height, (hprf _).trans hop.fuel, + (hprf _).trans hop.want, (hprf _).trans hop.primeBound, + (hprf _).trans hop.packedLen, (hprf _).trans hop.padding⟩ + · intro baddr hbaddr + have hbfront : baddr ∉ (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hdesc : 1 ≤ l.descriptors := by dsimp [Layout.descriptors]; omega + omega + have hbradix : baddr ∉ (Block.mk 1 (4 * n)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hbe : 1 + 4 * n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors, l, layout] + omega + omega + exact (hrframe baddr hbfront).trans ((hframe baddr + (by simpa only [List.length_map, n] using hbradix)).trans (congrFun ham baddr)) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean new file mode 100644 index 0000000000..349ba2ea31 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean @@ -0,0 +1,358 @@ +/- +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.Frontend.Model +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Native frontend descriptor layout + +Descriptors have six words, three consecutive `(length,pointer)` pairs. Arrays occupy +disjoint blocks of capacity `n+1`; unused capacity is outside the compact enumeration. +The first four `n`-word blocks remain reserved for radix sorting and full multiplicities. + +## Main definitions + +* `Layout`: concrete descriptor and payload addresses. +* `array`: the compact list represented by one array slot. +* `Output`: exact descriptor words and payload readback, including empty arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Concrete layout at input length `n` and runtime label-bit budget `Λ`. -/ +structure Layout where + /-- Input length. -/ + n : ℕ + /-- Number of label bits. -/ + digits : ℕ + +namespace Layout + +/-- Number of bit-split triples. -/ +def splits (l : Layout) : ℕ := 2 * l.digits ^ 2 +/-- Total descriptor count, including the two multiplicity triples. -/ +def count (l : Layout) : ℕ := l.splits + 2 +/-- Total compact array count. -/ +def arrays (l : Layout) : ℕ := 3 * l.count +/-- Capacity allows singleton zero even when the original input is empty. -/ +def capacity (l : Layout) : ℕ := l.n + 1 +/-- Descriptor table follows all four radix buffers. -/ +def descriptors (l : Layout) : ℕ := 1 + 4 * l.n +/-- Payload blocks follow the complete descriptor table. -/ +def payloads (l : Layout) : ℕ := l.descriptors + 2 * l.arrays +/-- Pointer for array slot `j`. -/ +def pointer (l : Layout) (j : ℕ) : ℕ := l.payloads + l.capacity * j +/-- One-past-end address of all allocated blocks. -/ +def endpoint (l : Layout) : ℕ := l.pointer l.arrays + +/-- Payload pointers advance by the full reserved capacity. -/ +theorem pointer_succ (l : Layout) (j : ℕ) : l.pointer (j + 1) = l.pointer j + l.capacity := by + simp only [pointer, Nat.mul_add, Nat.mul_one, Nat.add_assoc] + +/-- Payload blocks occur in allocation order. -/ +theorem pointer_mono (l : Layout) {i j : ℕ} (h : i ≤ j) : l.pointer i ≤ l.pointer j := + Nat.add_le_add_left (Nat.mul_le_mul_left _ h) _ + +/-- Every descriptor word precedes every payload block. -/ +theorem descriptor_before (l : Layout) {j : ℕ} (hj : j < l.arrays) (i : ℕ) : + l.descriptors + 2 * j + 2 ≤ l.pointer i := by + dsimp only [pointer, payloads] + omega + +end Layout + +namespace Math + +/-- Selection mode for a physical array slot. -/ +def mode (Λ j : ℕ) : ℕ := + let t := j / 3 + let s := j % 3 + if t < 2 * Λ ^ 2 then s + else if t = 2 * Λ ^ 2 then if s = 0 then 3 else if s = 1 then 6 else 5 + else if s = 0 then 4 else 6 + +/-- Bit coordinates of a physical slot in the split part of the table. -/ +def beta (Λ j : ℕ) : ℕ := (j / 3 / 2) / Λ +@[inherit_doc beta] def beta' (Λ j : ℕ) : ℕ := (j / 3 / 2) % Λ +@[inherit_doc beta] def digit (j : ℕ) : ℕ := j / 3 % 2 + +/-- Compact encoded list for one physical array slot. -/ +def array (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : List ℕ := + if mode Λ j = 6 then [0] + else select U (beta Λ j) (beta' Λ j) (digit j) (mode Λ j) rs + +/-- The two bit coordinates are irrelevant in the three nonsplitting scan modes. -/ +theorem select_extra (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) (hm : 3 ≤ mode) : + select U β β' v mode rs = select U 0 0 0 mode rs := by + unfold select + congr 2 + funext p + unfold keep flag + split <;> first | rfl | omega + +/-- Physical array slots within a split triple follow A, B₀, B₁ order. -/ +theorem array_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t s : ℕ) + (ht : t < 2 * Λ ^ 2) (hs : s < 3) : + array U Λ rs (3 * t + s) = select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) s rs := by + have hd : (3 * t + s) / 3 = t := by omega + have hm : (3 * t + s) % 3 = s := by omega + have hn : s ≠ 6 := by omega + simp [array, mode, hd, hm, ht, hn, beta, beta', digit] + +/-- Read the three array specifications belonging to a descriptor. -/ +def root (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) : List ℕ × List ℕ × List ℕ := + (array U Λ rs (3 * t), array U Λ rs (3 * t + 1), array U Λ rs (3 * t + 2)) + +/-- A descriptor in the split prefix has exactly the requested three compact selections. -/ +theorem root_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) (ht : t < 2 * Λ ^ 2) : + root U Λ rs t = + (select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 0 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 1 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 2 rs) := by + have h0 := array_split U Λ rs t 0 ht (by decide) + simp only [Nat.add_zero] at h0 + simp only [root, h0, array_split U Λ rs t 1 ht (by decide), + array_split U Λ rs t 2 ht (by decide)] + +/-- The two final descriptors retain their singleton zeros and exact multiplicity scans. -/ +theorem root_extras (U Λ : ℕ) (rs : List (ℕ × ℕ)) : + root U Λ rs (2 * Λ ^ 2) = + (select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs) ∧ + root U Λ rs (2 * Λ ^ 2 + 1) = (select U 0 0 0 4 rs, [0], [0]) := by + constructor + all_goals simp [root, array, mode, select_extra, Nat.add_div, Nat.add_mod, + show ∀ n, ¬n + 1 < n from fun _ => by omega] + +/-- Every array fits in its `n+1` capacity, including empty-input singleton zeros. -/ +theorem length_array_le (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : + (array U Λ rs j).length ≤ rs.length + 1 := by + dsimp only [array] + split_ifs + · simp + · exact (length_select_le _ _ _ _ _ _).trans (by omega) + +/-- Every descriptor represents an injective enumeration. -/ +theorem array_nodup (U Λ : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) (j : ℕ) : (array U Λ rs j).Nodup := by + dsimp only [array] + split_ifs + · simp + · exact select_nodup _ _ _ _ _ h + +/-- Each output word is canonical zero, an original input key, or the key of twice an input. -/ +theorem array_values {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + u = 0 ∨ ∃ z ∈ input, u = Signed.encode z ∨ u = Signed.encode (2 * z) := by + unfold array at hu + split_ifs at hu + · exact .inl (by simpa only [List.mem_singleton] using hu) + · unfold select at hu + obtain ⟨p, hp, rfl⟩ := List.mem_map.mp hu + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr + ⟨p, (List.mem_filter.mp hp).1, rfl⟩)) + refine .inr ⟨z, hz, ?_⟩ + rw [← he] + unfold value + split_ifs + · exact .inr (twice_encode z) + · exact .inl rfl + +/-- Every compact output word is a canonical signed encoding. -/ +theorem array_canonical {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : Signed.encode (Signed.decode u) = u := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, _, rfl | rfl⟩ + · rfl + all_goals rw [Signed.decode_encode] + +/-- Injectivity holds for the represented integer array, as well as its native keys. -/ +theorem array_decoded_nodup {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (h : (rs.map Prod.fst).Nodup) (U Λ j : ℕ) : + ((array U Λ rs j).map Signed.decode).Nodup := by + apply List.Nodup.map_on _ (array_nodup U Λ h j) + intro u hu v hv he + rw [← array_canonical hkeys U Λ j u hu, ← array_canonical hkeys U Λ j v hv, he] + +/-- Every decoded payload integer has the exact reduction's common magnitude bound `2U`. -/ +theorem array_magnitude {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + |Signed.decode u| ≤ (2 * U : ℕ) := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, hz, rfl | rfl⟩ + · simp [Signed.decode] + · rw [Signed.decode_encode] + have := hvalues z hz + omega + · rw [Signed.decode_encode] + have := abs_le.mp (hvalues z hz) + apply abs_le.mpr + constructor <;> omega + +/-- The compact root lists before their native layout is assigned. -/ +def roots (U : ℕ) (rs : List (ℕ × ℕ)) : List (List ℕ × List ℕ × List ℕ) := + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ((List.range Λ).flatMap fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) ++ + [(select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs), + (select U 0 0 0 4 rs, [0], [0])] + +/-- Decode a compact triple to the finite sets used by the existing reduction. -/ +def decodeRoot (T : List ℕ × List ℕ × List ℕ) : Finset ℤ × Finset ℤ × Finset ℤ := + ((T.1.map Signed.decode).toFinset, (T.2.1.map Signed.decode).toFinset, + (T.2.2.map Signed.decode).toFinset) + +/-- Exact list equality with `ThreeSum.inputs`, preserving root order and every empty array. -/ +theorem roots_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + (roots U rs).map decodeRoot = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + simp only [roots, Cslib.Algorithms.ThreeSum.inputs, List.map_append, List.map_flatMap, + decodeRoot, select_A hkeys hcounts hvalues, + show ∀ β β' v, ((select U β β' v.toNat 1 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v false + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v false, + show ∀ β β' v, ((select U β β' v.toNat 2 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v true + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v true, + List.map_cons, List.map_nil, List.toFinset_cons, List.toFinset_nil, + Signed.decode, Nat.zero_mod, ↓reduceIte, Nat.zero_div, Int.natCast_zero, + Finset.insert_empty, select_twice hkeys hcounts, select_original hkeys hcounts, + select_zero hkeys hcounts] + +private theorem map_range_mul {α : Type*} (a b : ℕ) (f : ℕ → α) : + (List.range (a * b)).map f = + (List.range a).flatMap (fun i => (List.range b).map fun j => f (i * b + j)) := by + induction a with + | zero => simp + | succ a ih => + rw [Nat.succ_mul, List.range_add, List.map_append, ih, List.range_succ, + List.flatMap_append, List.flatMap_singleton] + simp [List.map_map, Function.comp_def, Nat.add_comm] + +/-- Physical descriptor indices have exactly the nested order of the finite root list. -/ +theorem roots_indexed (U : ℕ) (rs : List (ℕ × ℕ)) : + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = roots U rs := by + dsimp only + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + have hΛ : 0 < Λ := by dsimp [Λ, Cslib.Algorithms.ThreeSum.labelBits]; omega + have hlen : 2 * Λ ^ 2 = Λ * (Λ * 2) := by ring + change (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = _ + rw [List.range_add, List.map_append] + have hprefix : (List.range (2 * Λ ^ 2)).map (root U Λ rs) = + (List.range Λ).flatMap (fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) := by + rw [hlen, map_range_mul] + apply List.flatMap_congr + intro β hβ + rw [map_range_mul] + apply List.flatMap_congr + intro β' hβ' + have hb : β < Λ := List.mem_range.mp hβ + have hb' : β' < Λ := List.mem_range.mp hβ' + have hdiv : (β * Λ + β') / Λ = β := by + rw [Nat.add_comm, Nat.add_mul_div_right _ _ hΛ, Nat.div_eq_of_lt hb', Nat.zero_add] + have hmod : (β * Λ + β') % Λ = β' := Nat.mul_add_mod_of_lt hb' + have hlt0 : β * (Λ * 2) + (β' * 2 + 0) < 2 * Λ ^ 2 := by nlinarith + have hlt1 : β * (Λ * 2) + (β' * 2 + 1) < 2 * Λ ^ 2 := by nlinarith + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + root_split _ _ _ _ hlt0, root_split _ _ _ _ hlt1] + have hmul : β * (Λ * 2) = 2 * (β * Λ) := by ring + have hd0 : (β * (Λ * 2) + (β' * 2 + 0)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hd1 : (β * (Λ * 2) + (β' * 2 + 1)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hm0 : (β * (Λ * 2) + (β' * 2 + 0)) % 2 = 0 := by rw [hmul]; omega + have hm1 : (β * (Λ * 2) + (β' * 2 + 1)) % 2 = 1 := by rw [hmul]; omega + simp only [hd0, hd1, hm0, hm1, hdiv, hmod, Bool.toNat_false, Bool.toNat_true] + rw [hprefix] + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + Nat.add_zero, (root_extras U Λ rs).1, + (root_extras U Λ rs).2] + rfl + +end Math + +/-- Exact descriptor and compact payload readback. Unused capacity is unrestricted. -/ +def Output (l : Layout) (U : ℕ) (rs : List (ℕ × ℕ)) (mem : ℕ → ℕ) : Prop := + ∀ j < l.arrays, + mem (l.descriptors + 2 * j) = (Math.array U l.digits rs j).length ∧ + mem (l.descriptors + 2 * j + 1) = l.pointer j ∧ + Block.HoldsList (l.pointer j) (Math.array U l.digits rs j) mem + +/-- Follow one `(length,pointer)` pair in native memory. This is output interpretation, +not an instruction or an assumed computation by the frontend. -/ +def readArray (mem : ℕ → ℕ) (a : ℕ) : List ℕ := + (List.range (mem a)).map fun i => mem (mem (a + 1) + i) + +/-- Follow all six-word descriptors in order and decode their represented sets. -/ +def readRoots (l : Layout) (mem : ℕ → ℕ) : List (Finset ℤ × Finset ℤ × Finset ℤ) := + (List.range l.count).map fun t => + let a := l.descriptors + 6 * t + Math.decodeRoot (readArray mem a, readArray mem (a + 2), readArray mem (a + 4)) + +/-- Exact slot readback includes zero-length arrays and ignores unused capacity. -/ +theorem Output.read_array {l : Layout} {U : ℕ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output l U rs mem) {j : ℕ} (hj : j < l.arrays) : + readArray mem (l.descriptors + 2 * j) = Math.array U l.digits rs j := by + obtain ⟨hl, hp, hm⟩ := h j hj + unfold readArray + rw [hl, hp] + apply List.ext_getElem + · simp + · intro i hi hi' + simpa only [List.getElem_map, List.getElem_range] using hm.get i hi' + +/-- The descriptor table reads back as exactly the existing finite frontend list, in order. -/ +theorem Output.readback {n U : ℕ} {input : List ℤ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U rs mem) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + readRoots ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ mem = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + let l : Layout := ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + have he : readRoots l mem = + ((List.range l.count).map (Math.root U l.digits rs)).map Math.decodeRoot := by + simp only [readRoots, List.map_map, Function.comp_def] + apply List.map_congr_left + intro t ht + have hti : t < l.count := List.mem_range.mp ht + have hs (s : ℕ) (hs : s < 3) : + readArray mem (l.descriptors + 6 * t + 2 * s) = Math.array U l.digits rs (3 * t + s) := by + have hj : 3 * t + s < l.arrays := by dsimp only [Layout.arrays]; omega + have ha : l.descriptors + 6 * t + 2 * s = l.descriptors + 2 * (3 * t + s) := by omega + rw [ha] + exact h.read_array hj + have h0 := hs 0 (by decide) + have h1 := hs 1 (by decide) + have h2 := hs 2 (by decide) + simpa only [Math.root, Nat.mul_zero, Nat.add_zero, Nat.mul_one] using + congrArg Math.decodeRoot (congrArg₂ Prod.mk h0 (congrArg₂ Prod.mk h1 h2)) + rw [he] + have hi := Math.roots_indexed U rs + change (List.range l.count).map (Math.root U l.digits rs) = Math.roots U rs at hi + rw [hi] + exact Math.roots_decoded hkeys hcounts hvalues + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean new file mode 100644 index 0000000000..9e704e6bdb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean @@ -0,0 +1,282 @@ +/- +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.ThreeSets +public import Complexity.Computability.WordRAM.Signed.Encoding +public import Complexity.Computability.WordRAM.ListArray +public import Mathlib.Data.List.Nodup + +/-! +# Finite specifications for the native distinct-position frontend + +The mathematical sets are the existing `ThreeSum.splitA`, `ThreeSum.splitB`, +`ThreeSum.twiceSet` and `ThreeSum.zeroSet`. Native keys remain in unsigned encoding order. +The specifications here describe compact subsequences of the radix key/count arrays. + +## Main definitions + +* `label`: compute the shifted label from a canonical signed key. +* `twice`: the canonical key of twice the represented integer. +* `select`: compact selections from full key/count pairs. + +## Main results + +* `label_encode`: native label arithmetic agrees with the existing mathematical label. +* `twice_encode`: doubling has the required positive sign. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM +namespace Math + +/-- Shift a sign-and-magnitude key into the nonnegative label interval. -/ +def label (U u : ℕ) : ℕ := if u % 2 = 0 then 2 * U + u / 2 else 2 * U - u / 2 + +/-- The canonical key for twice the integer represented by a canonical key. -/ +def twice (u : ℕ) : ℕ := 2 * u - u % 2 + +/-- A mode selects A, B₀, B₁, repeated nonzero values, repeated zero, or all values. +Mode six is reserved for the separately written constant singleton zero. -/ +def flag (a b v mode : ℕ) (p : ℕ × ℕ) : Bool := + match mode with + | 0 => decide (a = v) + | 1 => decide (a ≠ v ∧ b = 0) + | 2 => decide (a ≠ v ∧ b = 1) + | 3 => decide (p.1 ≠ 0 ∧ 2 ≤ p.2) + | 4 => decide (p.1 = 0 ∧ 3 ≤ p.2) + | 5 => true + | _ => false + +/-- Selection from the two native label digits. -/ +def keep (U β β' v mode : ℕ) (p : ℕ × ℕ) : Bool := + flag ((label U p.1 >>> β) % 2) ((label U p.1 >>> β') % 2) v mode p + +/-- Key transformation after selection; only the repeated-nonzero mode doubles values. -/ +def value (mode u : ℕ) : ℕ := if mode = 3 then twice u else u + +/-- Compact output of one native scan. Multiplicities are never truncated. -/ +def select (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : List ℕ := + ((rs.filter (keep U β β' v mode)).map fun p => value mode p.1) + +/-- A single scan writes at most one cell per key/count pair. -/ +theorem length_select_le (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : + (select U β β' v mode rs).length ≤ rs.length := by + simpa only [select, List.length_map] using List.length_filter_le (keep U β β' v mode) rs + +/-- Native label arithmetic, including the absence of subtraction underflow. -/ +theorem label_encode {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + label U (Signed.encode z) = Cslib.Algorithms.ThreeSum.lab (2 * U) z := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + by_cases hneg : z < 0 + · have habs : z = -(z.natAbs : ℤ) := by + rw [Int.ofNat_natAbs_of_nonpos (by omega : z ≤ 0)] + omega + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, one_ne_zero, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + · have habs := Int.eq_natAbs_of_nonneg (by omega : 0 ≤ z) + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + +/-- The transient unsigned double is corrected by its low sign bit. -/ +theorem twice_encode (z : ℤ) : twice (Signed.encode z) = Signed.encode (2 * z) := by + have hs : (2 * z < 0) ↔ z < 0 := by omega + rw [twice, Signed.encode_mod_two] + simp only [Signed.encode, Int.natAbs_mul, show (2 : ℤ).natAbs = 2 from rfl, hs] + split_ifs <;> omega + +/-- Label arithmetic and the transient double stay within a common explicit bound. -/ +theorem key_bounds {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z ≤ 2 * U + 1 ∧ label U (Signed.encode z) ≤ 3 * U ∧ + Signed.encode z / 2 ≤ 2 * U ∧ 2 * Signed.encode z ≤ 4 * U + 2 := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + simp only [label, Signed.encode] + split_ifs <;> omega + +/-- The unsigned transformation is injective, even before restricting to canonical keys. -/ +theorem twice_injective : Function.Injective twice := by + intro a b h + have ha := Nat.mod_lt a (by decide : 0 < 2) + have hb := Nat.mod_lt b (by decide : 0 < 2) + dsimp only [twice] at h + omega + +/-- Every output value transformation preserves injectivity. -/ +theorem value_injective (mode : ℕ) : Function.Injective (value mode) := by + intro a b h + by_cases hm : mode = 3 + · exact twice_injective (by simpa only [value, hm, ↓reduceIte] using h) + · simpa only [value, hm, ↓reduceIte] using h + +/-- Every compact scan enumerates its output without repetitions. -/ +theorem select_nodup (U β β' v mode : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) : (select U β β' v mode rs).Nodup := by + have hk : ((rs.filter (keep U β β' v mode)).map Prod.fst).Nodup := + h.sublist ((List.filter_sublist).map _) + simpa only [select, List.map_map, Function.comp_def] using + hk.map (value_injective mode) + +/-- The supplied radix contracts identify every run with its exact input multiplicity. -/ +theorem mem_runs {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (p : ℕ × ℕ) : + p ∈ rs ↔ ∃ z ∈ input, p = (Signed.encode z, input.count z) := by + constructor + · intro hp + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr ⟨p, hp, rfl⟩)) + have hc := hcounts p hp + rw [← he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + exact ⟨z, hz, Prod.ext he.symm hc.symm⟩ + · rintro ⟨z, hz, rfl⟩ + obtain ⟨p, hp, he⟩ := List.mem_map.mp ((hkeys (Signed.encode z)).mpr + (List.mem_map.mpr ⟨z, hz, rfl⟩)) + have hc := hcounts p hp + rw [he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + have hp' : p = (Signed.encode z, input.count z) := Prod.ext he hc.symm + exact hp' ▸ hp + +/-- A compact native selection represents precisely its finite filtered image. -/ +theorem select_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v mode : ℕ) : + ((select U β β' v mode rs).map Signed.decode).toFinset = + (input.toFinset.filter fun z => keep U β β' v mode + (Signed.encode z, input.count z) = true).image + (fun z => Signed.decode (value mode (Signed.encode z))) := by + ext z + simp only [List.mem_toFinset, List.mem_map, select, List.mem_filter, Finset.mem_image, + Finset.mem_filter, mem_runs hkeys hcounts] + constructor + · rintro ⟨u, ⟨p, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + exact ⟨a, ⟨ha, hkeep⟩, rfl⟩ + · rintro ⟨a, ⟨ha, hkeep⟩, rfl⟩ + exact ⟨_, ⟨_, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + +/-- List multiplicity is exactly the number of original input positions. -/ +theorem count_positions (input : List ℤ) (z : ℤ) : + (Finset.univ.filter fun i : Fin input.length => input[i] = z).card = input.count z := + Fin.card_filter_univ_eq_vector_get_eq_count z ⟨input, rfl⟩ + +/-- Original input values are precisely the image of its positions. -/ +theorem input_values (input : List ℤ) : + Finset.univ.image (fun i : Fin input.length => input[i]) = input.toFinset := by + ext z + simp only [Finset.mem_image, Finset.mem_univ, true_and, List.mem_toFinset] + constructor + · rintro ⟨i, rfl⟩ + exact List.getElem_mem i.isLt + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp hz + exact ⟨⟨i, hi⟩, he⟩ + +/-- Native division/shift digits agree with the existing Boolean bit operation. -/ +theorem digit_eq (u β : ℕ) (v : Bool) : (u >>> β) % 2 = v.toNat ↔ u.testBit β = v := by + rw [Nat.shiftRight_eq_div_pow, ← Nat.toNat_testBit] + cases u.testBit β <;> cases v <;> decide + +/-- Zero has exactly one canonical key. -/ +theorem encode_eq_zero (z : ℤ) : Signed.encode z = 0 ↔ z = 0 := by + change Signed.encode z = Signed.encode 0 ↔ z = 0 + exact Signed.encode_injective.eq_iff + +/-- Exact bridge from the repeated-nonzero native scan to the existing `twiceSet`. -/ +theorem select_twice {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 3 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.twiceSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, ne_eq, encode_eq_zero, value, ↓reduceIte, + twice_encode, Signed.decode_encode, Cslib.Algorithms.ThreeSum.twiceSet, + input_values, count_positions] + +/-- The original-set scan preserves every distinct input value. -/ +theorem select_original {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 5 rs).map Signed.decode).toFinset = + Finset.univ.image (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts, input_values] + simp [keep, flag, value] + +/-- The zero scan uses the full original multiplicity and threshold three. -/ +theorem select_zero {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 4 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.zeroSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, encode_eq_zero, value, Nat.reduceEqDiff, + ↓reduceIte, Signed.decode_encode, Finset.image_id', Cslib.Algorithms.ThreeSum.zeroSet, + count_positions] + by_cases hc : 3 ≤ input.count 0 + · have hm : (0 : ℤ) ∈ input := List.count_pos_iff.mp (by omega) + ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.mem_singleton] + constructor + · exact fun h => h.2.1 + · rintro rfl + exact ⟨hm, rfl, hc⟩ + · ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.notMem_empty, + iff_false, not_and] + intro _ hz + subst z + exact hc + +/-- The A scan represents the exact existing bit split. -/ +theorem select_A {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v : Bool) : + ((select U β β' v.toNat 0 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitA (2 * U) β v + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + simp only [value, Nat.reduceEqDiff, ↓reduceIte, Signed.decode_encode, Finset.image_id'] + apply Finset.filter_congr + intro z hz + simp only [List.mem_toFinset] at hz + simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz), digit_eq] + +/-- Both B scans represent the exact existing two-bit split, including `β=β'`. -/ +theorem select_B {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v w : Bool) : + ((select U β β' v.toNat (if w then 2 else 1) rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v w + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + cases w + all_goals simp only [Bool.false_eq_true, ↓reduceIte, value, Nat.reduceEqDiff, + Signed.decode_encode, Finset.image_id'] + all_goals apply Finset.filter_congr + all_goals intro z hz + all_goals simp only [List.mem_toFinset] at hz + all_goals simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz)] + all_goals simp only [ne_eq, digit_eq _ _ v] + all_goals cases v <;> cases (Cslib.Algorithms.ThreeSum.lab (2 * U) z).testBit β <;> + simp only [Bool.false_eq_true, Bool.true_eq_false, not_false_eq_true, not_true_eq_false, + true_and, false_and, Bool.not_true, Bool.not_false] + all_goals first | exact digit_eq _ _ false | exact digit_eq _ _ true + +end Math +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean new file mode 100644 index 0000000000..8463c29760 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean @@ -0,0 +1,167 @@ +/- +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.Frontend.Scan +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.RadixSort.Deduplicate + +/-! +# Initialized-input native distinct-position frontend + +The ABI has 128 registers. Radix uses 0–15; frontend locals use 16–63; the existing parameter +program uses 64–87. Registers 88–90 return descriptor base, descriptor count and heap end. +Each descriptor has six words `(length, pointer)` for its three compact injective arrays. +Each array has capacity `n+1`, including the singleton zeros required when `n=0`. + +## Main definitions + +* `Arrays.program`: native enumeration and descriptor construction. +* `program`: parameters, explicit-bit radix deduplication, and all frontend arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Arrays + +open Select Scan + +/-- Original input length and label-bit budget. -/ +abbrev inputLen : Fin 48 := 27 +@[inherit_doc inputLen] abbrev digits : Fin 48 := 28 +/-- Number of split triples, total triple count and total array count. -/ +abbrev splits : Fin 48 := 29 +@[inherit_doc splits] abbrev triples : Fin 48 := 30 +@[inherit_doc splits] abbrev arrays : Fin 48 := 31 +/-- Capacity per compact array. -/ +abbrev capacity : Fin 48 := 32 +/-- Next payload address. -/ +abbrev heap : Fin 48 := 33 +/-- Descriptor table base. -/ +abbrev descriptors : Fin 48 := 34 +/-- Current array index, current triple and slot. -/ +abbrev current : Fin 48 := 35 +@[inherit_doc current] abbrev triple : Fin 48 := 36 +@[inherit_doc current] abbrev slot : Fin 48 := 37 +/-- Arithmetic temporary and final heap endpoint. -/ +abbrev temp : Fin 48 := 38 +@[inherit_doc temp] abbrev heapEnd : Fin 48 := 39 + +/-- Write the singleton-zero array, including on empty input. -/ +def singleton : Code .standard 48 := ram[ *dst ← zero; out ← one ] + +/-- Compute the exact array selection mode for the two multiplicity triples. -/ +def extra : Code .standard 48 := ram[ + if triple = splits then [ + if slot = zero then [ mode ← three; ⟪Scan.program⟫ ] else [ + if slot = one then [ ⟪singleton⟫ ] else [ mode ← five; ⟪Scan.program⟫ ] ] + ] else [ + if slot = zero then [ mode ← four; ⟪Scan.program⟫ ] else [ ⟪singleton⟫ ] ] ] + +/-- Decode `β`, then `β'`, then `[false,true]`, and scan the indicated one of three arrays. -/ +def splitSetup : Code .standard 48 := ram[ + beta ← triple / two; beta' ← beta % digits; beta ← beta / digits; + digit ← triple % two; mode ← slot ] + +/-- Split setup followed by the complete compacting scan. -/ +def split : Code .standard 48 := .seq splitSetup Scan.program + +/-- Native choice between the split prefix and the two multiplicity triples. -/ +def pick : Code .standard 48 := .branch (.lt triple splits) split extra + +/-- Write a compact array's two descriptor words and advance the allocation cursor. -/ +def writeDescriptor : Code .standard 48 := ram[ + addr ← current + current; addr ← addr + descriptors; *addr ← out; + addr ← addr + one; *addr ← dst; + heap ← heap + capacity; current ← current + one ] + +/-- One complete array and its two descriptor words. -/ +def body : Code .standard 48 := + .seq (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) + (.seq pick writeDescriptor) + +/-- Initialize every constant, layout and cursor, then enumerate every array, including empties. -/ +def setup : Code .standard 48 := ram[ + zero ← #0; one ← #1; two ← #2; three ← #3; four ← #4; five ← #5; + beta ← #0; beta' ← #0; digit ← #0; + capacity ← inputLen + one; + splits ← digits * digits; splits ← splits * two; + triples ← splits + two; arrays ← triples * three; + temp ← arrays * two; heap ← descriptors + temp; + temp ← arrays * capacity; heapEnd ← heap + temp; + current ← #0 ] + +/-- Setup and all array iterations. -/ +def program : Code .standard 48 := .seq setup (.loop (.lt current arrays) body) + +end Arrays + +/-- Embed the 24-register parameter program above both native kernels. -/ +def parameterRegisters (i : Fin 24) : Fin 128 := ⟨64 + i.val, by omega⟩ +/-- Embed the 16-register radix program in the low registers. -/ +def radixRegisters (i : Fin 16) : Fin 128 := ⟨i.val, by omega⟩ +/-- Embed the 48-register frontend program between radix and parameters. -/ +def arrayRegisters (i : Fin 48) : Fin 128 := ⟨16 + i.val, by omega⟩ + +/-- Final descriptor base, descriptor count and one-past-end payload address. -/ +abbrev descriptorBase : Fin 128 := 88 +@[inherit_doc descriptorBase] abbrev descriptorCount : Fin 128 := 89 +@[inherit_doc descriptorBase] abbrev heapEnd : Fin 128 := 90 + +/-- Setup for the lower-level sorter with the magnitude-derived bit bound, never physical width. -/ +def radixSetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let B := parameterRegisters ChanHe.Parameters.digits + let radixLen := radixRegisters RadixSort.len + let radixSrc := radixRegisters RadixSort.src + let radixDst := radixRegisters RadixSort.dst + let radixBits := radixRegisters RadixSort.bits + ram[ + radixLen ← n; radixSrc ← #1; radixDst ← radixLen + radixSrc; radixBits ← B ] + +/-- Transfer actual parameters and radix outputs into the concrete frontend ABI. -/ +def arraySetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let U := parameterRegisters ChanHe.Parameters.magnitude + let B := parameterRegisters ChanHe.Parameters.digits + let distinct := radixRegisters Deduplicate.out + let len := arrayRegisters Scan.len + let mag := arrayRegisters Select.magnitude + let inputLen := arrayRegisters Arrays.inputLen + let digits := arrayRegisters Arrays.digits + let keys := arrayRegisters Scan.keys + let counts := arrayRegisters Scan.counts + let descriptors := arrayRegisters Arrays.descriptors + let one := arrayRegisters Select.one + ram[ + len ← distinct; mag ← U; inputLen ← n; digits ← B; one ← #1; + keys ← n + n; keys ← keys + one; + counts ← keys + n; descriptors ← counts + n ] + +/-- Return metadata while preserving parameter outputs and full radix key/count arrays. -/ +def finish : Code .standard 128 := + let base := arrayRegisters Arrays.descriptors + let count := arrayRegisters Arrays.triples + let endpoint := arrayRegisters Arrays.heapEnd + ram[ descriptorBase ← base; descriptorCount ← count; heapEnd ← endpoint ] + +/-- Native frontend after the existing complete parameter program. -/ +def construct : Code .standard 128 := + .seq radixSetup (.seq (RadixSort.deduplicateCode.rename radixRegisters) + (.seq arraySetup (.seq (Arrays.program.rename arrayRegisters) finish))) + +/-- One finite native program for each fixed polynomial magnitude exponent. -/ +def program (κ : ℕ) : Code .standard 128 := + .seq ((ChanHe.Parameters.program κ).rename parameterRegisters) construct + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean new file mode 100644 index 0000000000..086209b833 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean @@ -0,0 +1,277 @@ +/- +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.Frontend.Select + +/-! +# Compact native frontend scans + +Each pass reads both canonical keys and their full multiplicities. It appends only selected +keys to a disjoint destination. The singleton-zero arrays are written separately, so empty +input needs no exceptional semantics. + +## Main definitions + +* `Scan.program`: charged compact selection from the radix output. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan + +open Cslib.WordRAM Select + +/-- Key pointer, count pointer and number of distinct keys. -/ +abbrev keys : Fin 48 := 20 +@[inherit_doc keys] abbrev counts : Fin 48 := 21 +@[inherit_doc keys] abbrev len : Fin 48 := 22 +/-- Output pointer, input cursor and output length. -/ +abbrev dst : Fin 48 := 23 +@[inherit_doc dst] abbrev idx : Fin 48 := 24 +@[inherit_doc dst] abbrev out : Fin 48 := 25 +/-- Address scratch. -/ +abbrev addr : Fin 48 := 26 + +/-- Load one full key/count pair, compute its selection, optionally append, and advance. -/ +def body : Code .standard 48 := + .seq (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) + (.seq Select.program (ram[ + if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ])) + +/-- Reset both cursors and scan all distinct keys. -/ +def program : Code .standard 48 := + .seq (ram[ idx ← #0; out ← #0 ]) (.loop (.lt idx len) body) + +/-- One charged compacting iteration. -/ +theorem step {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (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) + (hsmall : Fits w 5) + (hkaddr : Fits w (x.regs keys + x.regs idx)) + (hcaddr : Fits w (x.regs counts + x.regs idx)) + (hdaddr : Fits w (x.regs dst + x.regs out)) + (hi : Fits w (x.regs idx + 1)) (ho : Fits w (x.regs out + 1)) + (hkey : Fits w (x.mem (x.regs keys + x.regs idx))) + (habs : x.mem (x.regs keys + x.regs idx) / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.mem (x.regs keys + x.regs idx))) : + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + ∃ y, ExecWithin w body x y 41 ∧ + y.regs idx = x.regs idx + 1 ∧ y.regs out = x.regs out + k.toNat ∧ + y.mem = (if k then Function.update x.mem (x.regs dst + x.regs out) + (Math.value (x.regs mode) p.1) else x.mem) := by + dsimp only + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let a := (((x.set addr (x.regs keys + x.regs idx)).set key p.1).set + addr (x.regs counts + x.regs idx)).set count p.2 + have ha : ExecWithin w + (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) x a 4 := by + ram_eval [a, p, hkaddr, hcaddr, hkey] + obtain ⟨u, hu, hut, huk, hum⟩ := Select.exec a + (by simpa +decide [a] using hz) (by simpa +decide [a] using h1) + (by simpa +decide [a] using h2) (by simpa +decide [a] using h3) + (by simpa +decide [a] using h4) (by simpa +decide [a] using h5) + hkey (by simpa +decide [a, p] using habs) (by simpa +decide [a] using hfit) + hdouble hsmall + have hui : u.regs idx = x.regs idx := by + simpa +decide [a] using hu.regs_eq (i := idx) (by decide) + have huo : u.regs out = x.regs out := by + simpa +decide [a] using hu.regs_eq (i := out) (by decide) + have hud : u.regs dst = x.regs dst := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hu1 : u.regs one = 1 := by + simpa +decide [a] using (hu.regs_eq (i := one) (by decide)).trans + (show a.regs one = 1 by simpa +decide [a] using h1) + simp +decide only [a, Data.set_regs, Function.update_apply, ↓reduceIte] at hut huk hum + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + have hv : Fits w (Math.value (x.regs mode) p.1) := by + dsimp only [Math.value, Math.twice] + split_ifs + · exact hdouble.of_le (Nat.sub_le _ _) + · exact hkey + have hdo : Fits w (u.regs dst + u.regs out) := by simpa only [hud, huo] using hdaddr + have hii : Fits w (u.regs idx + 1) := by simpa only [hui] using hi + have hoo : Fits w (u.regs out + 1) := by simpa only [huo] using ho + have hvu : Fits w (u.regs key) := by rw [huk]; exact hv + by_cases he : k = true + · have ht : u.regs take = 1 := by rw [hut]; change k.toNat = 1; simp [he] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?posY 6 := by + ram_eval [ht, hu1, hdo, hii, hoo, hvu] + refine ⟨?posY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he] + · simp +decide [Data.set_mem, Data.write_mem, hud, huo, huk, hum, k, p, he] + · have he' : k = false := Bool.eq_false_iff.mpr he + have ht : u.regs take = 0 := by rw [hut]; change k.toNat = 0; simp [he'] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?negY 6 := by + ram_eval [ht, hu1, hii] + refine ⟨?negY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he'] + · simpa only [k, p, he', Bool.false_eq_true, ↓reduceIte, Data.set_mem] using hum + +/-- Complete compact scan, with arbitrary old output/scratch memory and an exact memory frame. +The source key/count blocks precede the output; their contents and all caller registers that +the body does not write are retained. -/ +theorem exec {w : ℕ} (rs : List (ℕ × ℕ)) (x : Data 48) + (hn : x.regs len = rs.length) + (hz : x.regs zero = 0) (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) + (hk : Block.HoldsList (x.regs keys) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (x.regs counts) (rs.map Prod.snd) x.mem) + (hkey : ∀ p ∈ rs, Fits w p.1) + (habs : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude) + (hdouble : ∀ p ∈ rs, Fits w (2 * p.1)) + (hfit : Fits w (4 * x.regs magnitude + 2)) (hsmall : Fits w 5) + (hka : Fits w (x.regs keys + rs.length)) + (hca : Fits w (x.regs counts + rs.length)) + (hda : Fits w (x.regs dst + rs.length + 1)) + (hkd : x.regs keys + rs.length ≤ x.regs dst) + (hcd : x.regs counts + rs.length ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) + (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, body.Preserves j → y.regs j = x.regs j) := by + let selected (i : ℕ) := Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (rs.take i) + have hselected (i : ℕ) : (selected i).length ≤ min i rs.length := + (Math.length_select_le _ _ _ _ _ _).trans_eq List.length_take + let I (y : Data 48) : Prop := + (∀ j, body.Preserves j → y.regs j = x.regs j) ∧ + y.regs idx ≤ rs.length ∧ y.regs out = (selected (y.regs idx)).length ∧ + Block.HoldsList (x.regs keys) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (x.regs counts) (rs.map Prod.snd) y.mem ∧ + Block.HoldsList (x.regs dst) (selected (y.regs idx)) y.mem ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 41 ∧ I z ∧ + rs.length - z.regs idx < rs.length - y.regs idx := by + obtain ⟨hr, hle, hout, hkeys, hcounts, hacc, hframe⟩ := hy + have hyl := (hr len (by decide)).trans hn + have hys := hr keys (by decide) + have hyc := hr counts (by decide) + have hyd := hr dst (by decide) + have hyU := hr magnitude (by decide) + have hyb := hr beta (by decide) + have hyb' := hr beta' (by decide) + have hyv := hr digit (by decide) + have hym := hr mode (by decide) + have hi : y.regs idx < rs.length := by simpa only [Cond.holds_lt, hyl] using ht + have hgetk : y.mem (y.regs keys + y.regs idx) = rs[y.regs idx].1 := by + rw [hys] + exact (hkeys.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hgetc : y.mem (y.regs counts + y.regs idx) = rs[y.regs idx].2 := by + rw [hyc] + exact (hcounts.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hu : y.regs out ≤ y.regs idx := by + rw [hout]; exact (hselected _).trans (min_le_left _ _) + obtain ⟨z, he, hzi, hzo, hzm⟩ := step y + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) hsmall + (hka.of_le (by rw [hys]; omega)) (hca.of_le (by rw [hyc]; omega)) + (hda.of_le (by rw [hyd]; omega)) (hka.of_le (by omega)) (hda.of_le (by omega)) + (by rw [hgetk]; exact hkey _ (List.getElem_mem hi)) + (by rw [hgetk, hyU]; exact habs _ (List.getElem_mem hi)) + (by simpa only [hyU] using hfit) + (by rw [hgetk]; exact hdouble _ (List.getElem_mem hi)) + dsimp only at hzo hzm + rw [hyU, hyb, hyb', hyv, hym, hgetk, hgetc] at hzo hzm + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs[y.regs idx] + change z.regs out = y.regs out + k.toNat at hzo + change z.mem = (if k then Function.update y.mem (y.regs dst + y.regs out) + (Math.value (x.regs mode) rs[y.regs idx].1) else y.mem) at hzm + have hsuc : selected (y.regs idx + 1) = selected (y.regs idx) ++ + (if k then [Math.value (x.regs mode) rs[y.regs idx].1] else []) := by + dsimp only [selected] + rw [List.take_succ_eq_append_getElem hi] + simp only [Math.select, List.filter_append, List.map_append, List.filter_cons, + List.filter_nil] + split_ifs <;> rfl + have hf (a : ℕ) (hne : a ≠ x.regs dst + y.regs out) : z.mem a = y.mem a := by + rw [hzm, hyd] + split_ifs <;> simp only [Function.update_of_ne hne] + refine ⟨z, he, ?_, by omega⟩ + refine ⟨?_, by omega, ?_, ?_, ?_, ?_, ?_⟩ + · intro j hj + exact (he.regs_eq hj).trans (hr j hj) + · rw [hzi, hsuc, List.length_append, hzo, hout] + cases k <;> simp + · apply hkeys.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · apply hcounts.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · rw [hzi, hsuc] + by_cases htake : k = true + · rw [ite_eq_left htake] + have hm : z.mem = Function.update y.mem (x.regs dst + (selected (y.regs idx)).length) + (Math.value (x.regs mode) rs[y.regs idx].1) := by + simpa only [htake, ↓reduceIte, hyd, hout] using hzm + rw [hm] + exact hacc.append_one _ + · have hfalse : k = false := Bool.eq_false_iff.mpr htake + rw [ite_eq_right htake, List.append_nil] + have hm : z.mem = y.mem := by simpa only [hfalse, Bool.false_eq_true, ↓reduceIte] + using hzm + simpa only [hm] using hacc + · intro a hnot + have hne : a ≠ x.regs dst + y.regs out := by + intro ha + apply hnot + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + rs.length + omega + exact (hf a hne).trans (hframe a hnot) + let x₀ := (x.set idx 0).set out 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, hk, hc, ?_, Modifies.refl _ _⟩ + · intro j hj + have hji : j ≠ idx := by rintro rfl; exact (by decide : ¬body.Preserves idx) hj + have hjo : j ≠ out := by rintro rfl; exact (by decide : ¬body.Preserves out) hj + simp only [x₀, Data.set_regs, Function.update_of_ne hji, Function.update_of_ne hjo] + · simp +decide [x₀] + · simp +decide [x₀, selected, Math.select] + · simpa +decide [x₀, selected, Math.select] using Block.holdsList_nil (x.regs dst) x.mem + obtain ⟨y, he, hy, ht⟩ := ExecWithin.loop_invariant I + (fun y => rs.length - y.regs idx) hbody hx₀ + obtain ⟨hr, hle, hout, _, _, hacc, hframe⟩ := hy + have hend : y.regs idx = rs.length := by + have hyl := (hr len (by decide)).trans hn + simp only [Cond.holds_lt, hyl] at ht + omega + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; out ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + refine ⟨y, ?_, ?_, ?_, hframe, hr⟩ + · convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, ↓reduceIte, Nat.sub_zero] + omega + · simpa only [hend, selected, List.take_length] using hacc + · simpa only [hend, selected, List.take_length] using hout + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean new file mode 100644 index 0000000000..7eb8c2d172 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean @@ -0,0 +1,250 @@ +/- +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.Frontend.Model +public import Complexity.Computability.WordRAM.Notation + +/-! +# Native selection of canonical signed keys + +All arithmetic is unsigned. The sign bit and magnitude are decoded explicitly, the label +is formed by addition or subtraction, and its two digits are computed with native shifts and +remainders. The repeated-value case doubles the key and subtracts its original sign bit. + +## Main definitions + +* `Select.program`: concrete selection and transformation instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Select + +/-- Magnitude bound, followed by two bit positions, selected digit and scan mode. -/ +abbrev magnitude : Fin 48 := 0 +@[inherit_doc magnitude] abbrev beta : Fin 48 := 1 +@[inherit_doc magnitude] abbrev beta' : Fin 48 := 2 +@[inherit_doc magnitude] abbrev digit : Fin 48 := 3 +@[inherit_doc magnitude] abbrev mode : Fin 48 := 4 +/-- Input key; replaced by its selected output value. -/ +abbrev key : Fin 48 := 5 +/-- Full input multiplicity, read without truncation. -/ +abbrev count : Fin 48 := 6 +/-- Small constants installed by the scan. -/ +abbrev zero : Fin 48 := 7 +@[inherit_doc zero] abbrev one : Fin 48 := 8 +@[inherit_doc zero] abbrev two : Fin 48 := 9 +@[inherit_doc zero] abbrev three : Fin 48 := 10 +@[inherit_doc zero] abbrev four : Fin 48 := 11 +@[inherit_doc zero] abbrev five : Fin 48 := 12 +/-- Scratch registers for decoding and label digits. -/ +abbrev sign : Fin 48 := 13 +@[inherit_doc sign] abbrev label : Fin 48 := 14 +@[inherit_doc sign] abbrev bitA : Fin 48 := 15 +@[inherit_doc sign] abbrev bitB : Fin 48 := 16 +/-- Selection flag, either zero or one. -/ +abbrev take : Fin 48 := 17 +/-- Magnitude temporary. -/ +abbrev absolute : Fin 48 := 18 + +/-- Explicit native sign/magnitude decoding, shifted label and binary digit computation. -/ +def labels : Code .standard 48 := ram[ + sign ← key % two; absolute ← key / two; label ← magnitude + magnitude; + if sign = zero then [ label ← label + absolute ] else [ label ← label - absolute ]; + bitA ← label >>> beta; bitA ← bitA % two; + bitB ← label >>> beta'; bitB ← bitB % two ] + +/-- A no-op used by unsuccessful selections. -/ +def skip : Code .standard 48 := ram[ take ← take ] + +/-- Select one of the six finite scan modes and perform the repeated-value transformation. -/ +def choose : Code .standard 48 := ram[ + take ← #0; + if mode = zero then [ + if bitA = digit then [ take ← one ] else [ ⟪skip⟫ ] + ] else [ if mode = one then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = zero then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = two then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = one then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = three then [ + if key = zero then [ ⟪skip⟫ ] else [ + if count < two then [ ⟪skip⟫ ] else [ take ← one ] ]; + key ← key + key; key ← key - sign + ] else [ if mode = four then [ + if key = zero then [ + if count < three then [ ⟪skip⟫ ] else [ take ← one ] + ] else [ ⟪skip⟫ ] + ] else [ if mode = five then [ take ← one ] else [ ⟪skip⟫ ] ] ] ] ] ] ] + +/-- Every decoding, arithmetic, branch and selection instruction is part of this code. -/ +def program : Code .standard 48 := .seq labels choose + +/-- This scalar program never reads the physical word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- Registers below the decoding scratch are preserved by label calculation. -/ +theorem labels_preserves (j : Fin 48) (hj : j.val ≤ 12) : labels.Preserves j := by + have hne (k : Fin 48) (hk : 13 ≤ k.val) : (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [labels, Code.Preserves, Op.dest] + exact ⟨hne sign (by decide), hne absolute (by decide), hne label (by decide), + ⟨hne label (by decide), hne label (by decide)⟩, hne bitA (by decide), + hne bitA (by decide), hne bitB (by decide), hne bitB (by decide)⟩ + +/-- Selection preserves all caller parameters, constants and scan registers. -/ +theorem preserves (j : Fin 48) (hj : (j.val ≤ 12 ∧ j.val ≠ 5) ∨ 19 ≤ j.val) : + program.Preserves j := by + have hne (k : Fin 48) (hk : k.val = 5 ∨ 13 ≤ k.val ∧ k.val ≤ 18) : + (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [program, labels, choose, skip, Code.Preserves, Op.bin, Op.dest] + repeat' constructor + all_goals exact hne _ (by decide) + +/-- Decoding and both binary digits cost at most eleven native instructions. -/ +theorem labels_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h2 : x.regs two = 2) + (hkey : Fits w (x.regs key)) + (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) : + ∃ y, ExecWithin w labels x y 11 ∧ + y.regs sign = x.regs key % 2 ∧ + y.regs label = Math.label (x.regs magnitude) (x.regs key) ∧ + y.regs bitA = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta) % 2 ∧ + y.regs bitB = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta') % 2 ∧ + y.mem = x.mem := by + have fm := hkey.of_le (Nat.div_le_self _ 2) + have fs := hfit.of_le (show x.regs key % 2 ≤ 4 * x.regs magnitude + 2 by omega) + have fd := hfit.of_le (show x.regs magnitude + x.regs magnitude ≤ + 4 * x.regs magnitude + 2 by omega) + have fa := hfit.of_le (show x.regs magnitude + x.regs magnitude + x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + have fb := hfit.of_le (show x.regs magnitude + x.regs magnitude - x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + by_cases he : x.regs key % 2 = 0 + · have fsa := fa.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fa.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fa, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + · have fsa := fb.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fb.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fb, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + +/-- Selection from already computed digits, with the full repeated-value count. -/ +theorem choose_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (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) + (hs : x.regs sign = x.regs key % 2) + (hfit : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w choose x y 20 ∧ + y.regs take = (Math.flag (x.regs bitA) (x.regs bitB) (x.regs digit) + (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + have f1 := hsmall.of_le (show 1 ≤ 5 by decide) + have fd : Fits w (x.regs key + x.regs key) := by simpa only [two_mul] using hfit + have fs : Fits w (x.regs key + x.regs key - x.regs key % 2) := fd.of_le (Nat.sub_le _ _) + rcases (show x.regs mode = 0 ∨ x.regs mode = 1 ∨ x.regs mode = 2 ∨ + x.regs mode = 3 ∨ x.regs mode = 4 ∨ x.regs mode = 5 ∨ 6 ≤ x.regs mode by omega) with + hm | hm | hm | hm | hm | hm | hm + · by_cases ha : x.regs bitA = x.regs digit + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 0 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 1 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases hk : x.regs key = 0 + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hs, fd, fs] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice] + · by_cases hc : x.regs count < 2 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, hs, + fd, fs, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice, hc, + show (2 ≤ x.regs count) ↔ ¬x.regs count < 2 from by omega, two_mul] + · by_cases hk : x.regs key = 0 + · by_cases hc : x.regs count < 3 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, hc, + show (3 ≤ x.regs count) ↔ ¬x.regs count < 3 from by omega] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, f1] + all_goals simp +decide [Data.set_regs, hm, Math.value, Math.flag] + · have hn0 : x.regs mode ≠ 0 := by omega + have hn1 : x.regs mode ≠ 1 := by omega + have hn2 : x.regs mode ≠ 2 := by omega + have hn3 : x.regs mode ≠ 3 := by omega + have hn4 : x.regs mode ≠ 4 := by omega + have hn5 : x.regs mode ≠ 5 := by omega + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hn0, hn1, hn2, hn3, hn4, hn5] + all_goals simp +decide [Data.set_regs, Math.value, Math.flag, hn3] + +/-- Complete native scalar selection. Its cost includes decoding, both label bits and doubling. -/ +theorem exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (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) + (hkey : Fits w (x.regs key)) (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w program x y 31 ∧ + y.regs take = (Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + obtain ⟨u, hu, hus, _, hua, hub, hum⟩ := labels_exec x hz h2 hkey habs hfit + have hr (j : Fin 48) (hj : j.val ≤ 12) : u.regs j = x.regs j := + hu.regs_eq (labels_preserves j hj) + obtain ⟨y, hy, hyt, hyk, hym⟩ := choose_exec u + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) + (by simpa only [hr key (by decide)] using hus) + (by simpa only [hr key (by decide)] using hdouble) hsmall + refine ⟨y, hu.seq hy, ?_, ?_, hym.trans hum⟩ + · simpa only [Math.keep, hr mode (by decide), hr digit (by decide), hr key (by decide), + hr count (by decide), hua, hub] using hyt + · simpa only [hr mode (by decide), hr key (by decide)] using hyk + +end Select +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean new file mode 100644 index 0000000000..02d4d8a195 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.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 + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds + +/-! +# Uniform logarithmic-square native frontend cost + +The bound includes the complete parameter program, explicit-bit radix sorting, full +multiplicities, every compact scan, all descriptor/payload setup and writes, and the final halt. +It is uniform over all admitted sufficiently wide words, including empty input. + +## Main results + +* `cost_bound`: an explicit `Oκ((n+1) clog₂(n+2)²)` instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +/-- Explicit uniform coefficient; deliberately coarse and independent of physical word width. -/ +def costCoefficient (κ : ℕ) : ℕ := 2000 * (20 * κ + 121) ^ 2 + 400 * κ + 4000 + +/-- Concrete derivation from the actual charged instruction formula to logarithmic-square cost. -/ +theorem cost_bound (κ n : ℕ) : + cost κ n ≤ costCoefficient κ * (n + 1) * Nat.clog 2 (n + 2) ^ 2 := by + let L := Nat.clog 2 (n + 2) + let K := 20 * κ + 121 + let Λ := (layout κ n).digits + let A := (layout κ n).arrays + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hK : 1 ≤ K := by dsimp [K]; omega + have hΛ : Λ ≤ K * L := digits_bound κ n + have hKL : 1 ≤ K * L := by nlinarith + have hKLsq : K * L ≤ (K * L) ^ 2 := by nlinarith + have hAs : A ≤ 12 * (K * L) ^ 2 := by + have hs := Nat.pow_le_pow_left hΛ 2 + dsimp only [A, Layout.arrays, Layout.count, Layout.splits, Λ] + nlinarith + have hRad : RadixSort.deduplicateTime n Λ ≤ (37 * Λ + 43) * (n + 1) := by + dsimp only [RadixSort.deduplicateTime] + nlinarith + have hRcoef : 37 * Λ + 43 ≤ 80 * (K * L) ^ 2 := by nlinarith + have hR := hRad.trans (Nat.mul_le_mul_right (n + 1) hRcoef) + have hA := Nat.mul_le_mul hAs (show 43 * n + 32 ≤ 75 * (n + 1) by omega) + have hParams := ChanHe.Parameters.time_bound κ n + have hsqrt := Nat.sqrt_le_self n + have hLsq : L ≤ L ^ 2 := by nlinarith + have hLN : L ≤ (n + 1) * L ^ 2 := by nlinarith + have hN : n + 1 ≤ (n + 1) * L ^ 2 := by nlinarith + have hC := Nat.mul_le_mul_left (400 * κ + 3000) hLN + change ChanHe.Parameters.time κ n ((n + 2) ^ κ) + RadixSort.deduplicateTime n Λ + + A * (43 * n + 32) + 38 ≤ _ + dsimp only [costCoefficient, K, L] at * + nlinarith + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/ComplexityTests/WordRAMThreeSumFrontend.lean b/ComplexityTests/WordRAMThreeSumFrontend.lean new file mode 100644 index 0000000000..fc41e964a6 --- /dev/null +++ b/ComplexityTests/WordRAMThreeSumFrontend.lean @@ -0,0 +1,89 @@ +/- +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 + +meta import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program + +/-! +# Executed initialized-input frontend boundary cases + +These checks execute the complete structured native program, including parameters and radix. +The observation follows each concrete descriptor and decodes its compact payload to a set. +-/ + +set_option linter.privateModule false + +open Cslib.WordRAM Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +private meta def observe (κ w : ℕ) (input : List ℤ) : + Option (List (Finset ℤ × Finset ℤ × Finset ℤ) × ℕ) := + ((program κ).eval w 512 (Data.init (input.map Signed.encode))).map fun (x, time) => + let array (a : ℕ) := ((List.range (x.mem a)).map fun i => + Signed.decode (x.mem (x.mem (a + 1) + i))).toFinset + ((List.range (x.regs descriptorCount)).map fun i => + let a := x.regs descriptorBase + 6 * i + (array a, array (a + 2), array (a + 4)), time) + +private meta def exact (κ : ℕ) (input : List ℤ) : Bool := + (observe κ 64 input).map (fun p => p.1 == Cslib.Algorithms.ThreeSum.inputs + ((input.length + 2) ^ κ) (fun i : Fin input.length => input[i])) == some true + +-- Empty arrays coexist with all constant singleton-zero arrays; exponent zero is admitted. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [] + +-- One and two positions are kept exactly; signed endpoints remain canonical. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [-1] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [-4, 4] + +-- Nonzero multiplicity one versus two; twiceSet uses positive 2*a. +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, -2] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, 1, -2] + +-- Zero multiplicity two versus three, and full multiplicity above three. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0] + +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0, 0] + +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0, 0, 0, -1, -1, 1] + +-- Actual distinct-position yes/no inputs, including three distinct values and equal bit positions. +/-- info: true -/ +#guard_msgs in +#eval exact 1 [-3, 1, 2] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, 2, 3] + +-- A wider admitted physical word does not change output or charged execution time. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 1 64 [-3, 1, 2] = observe 1 512 [-3, 1, 2]) + +-- Invalid width faults during genuine setup/layout arithmetic. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 0 3 [] = none) diff --git a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean new file mode 100644 index 0000000000..055bfea26e --- /dev/null +++ b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean @@ -0,0 +1,104 @@ +/- +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.Frontend + +/-! +# Fully admissible clients of the complete native frontend + +The execution observations are in `WordRAMThreeSumFrontend`. These clients consume proved +native contracts, discharge all global promises, and check integer injectivity and signed-width +composition. They do not claim interpreter execution. +-/ + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +-- The empty case still has twenty triples and sixty array slots at κ=0. +example : width 0 0 = 484 ∧ (layout 0 0).count = 20 ∧ (layout 0 0).arrays = 60 := by + decide +kernel + +example : ∃ (y : Data 128) (steps : ℕ), + Executes 4096 ((program 0).program descriptorCount) (State.init []) 20 steps ∧ + steps ≤ cost 0 0 ∧ readRoots (layout 0 0) y.mem = + Cslib.Algorithms.ThreeSum.inputs 1 (fun i : Fin 0 => ([] : List ℤ)[i]) := by + obtain ⟨y, steps, he, ht, ho, _, _, _, _⟩ := runs_input (κ := 0) (w := 4096) [] (by decide) + (by simp) + exact ⟨y, steps, he, ht, ho⟩ + +-- The signed endpoints satisfy both the input promise and the physical/signed width offset. +example : ∃ steps, + Executes 4096 ((program 1).program descriptorCount) + (State.init ([(-4 : ℤ), 4].map Signed.encode)) (layout 1 2).count steps ∧ + steps ≤ cost 1 2 := by + have hv : ∀ z ∈ ([(-4 : ℤ), 4] : List ℤ), |z| ≤ ((2 + 2) ^ 1 : ℕ) := by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false] at hz + rcases hz with rfl | rfl <;> decide + obtain ⟨y, steps, he, ht, _, _, _, _, _⟩ := + runs_input (κ := 1) (w := 4096) [(-4 : ℤ), 4] (by decide) hv + exact ⟨steps, he, ht⟩ + +-- A generic caller obtains actual native slots, injective integer arrays and full multiplicities. +example {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hv : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ y rs, ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y + (cost κ input.length - 1) ∧ + Output (layout κ input.length) ((input.length + 2) ^ κ) rs y.mem ∧ + (∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) ∧ + (∀ j < (layout κ input.length).arrays, + ((readArray y.mem ((layout κ input.length).descriptors + 2 * j)).map Signed.decode).Nodup ∧ + ∀ u ∈ readArray y.mem ((layout κ input.length).descriptors + 2 * j), + Cslib.GenericRAM.Signed.Fits (w - 2) (Signed.decode u)) := by + obtain ⟨y, rs, he, ho, _, _, _, hn, _, hk, hc, _, _, _, _⟩ := exec_input input hw hv + refine ⟨y, rs, he, ho, fun p hp => (hc p hp).2, ?_⟩ + intro j hj + rw [ho.read_array hj] + refine ⟨Math.array_decoded_nodup hk hn _ _ j, ?_⟩ + intro u hu + exact signed_output_fits hw (Math.array_magnitude hk hv _ j u hu) + +-- A polynomial allocation bound includes descriptor addresses as well as all output payloads. +example (κ n : ℕ) : (layout κ n).endpoint ≤ (n + 2) ^ (80 * κ + 480) := by + exact (Arrays.layout_bounds (layout κ n) ((n + 2) ^ κ)).2.2.2.2.2.2.2.2.1 |>.trans + (array_budget_polynomial κ n) + +-- The original distinct-position problem admits equal values, but never reuses a position. +example : Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => + ([1, 1, -2] : List ℤ)[i]) := by + unfold Cslib.Algorithms.ThreeSum.HasSolution + decide +kernel + +example : ¬Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => + ([1, 2, 3] : List ℤ)[i]) := by + unfold Cslib.Algorithms.ThreeSum.HasSolution + decide +kernel + +-- The two width promises are explicit; a physical width of three is not globally admissible. +example : ¬width 0 0 ≤ 3 ∧ + Cslib.GenericRAM.Signed.Fits (4096 - 2) (-8) := by + constructor + · decide + · exact signed_output_fits (κ := 1) (n := 2) (by decide) (by decide) + +-- Complete array construction also accepts arbitrary old output and scratch memory. +example (mem : ℕ → ℕ) : + let x : Data 48 := ⟨(fun i => match i.val with + | 0 | 20 | 21 | 34 => 1 + | 28 => 3 + | _ => 0), mem⟩ + ∃ y, ExecWithin 16 Arrays.program x y (60 * 32 + 21) ∧ + Output ⟨0, 3⟩ 1 [] y.mem ∧ + Modifies (Block.mk 1 180).footprint mem y.mem := by + dsimp only + obtain ⟨y, he, ho, _, _, _, _, hf⟩ := Arrays.exec (w := 16) (U := 1) ⟨0, 3⟩ [] + ⟨(fun i => match i.val with | 0 | 20 | 21 | 34 => 1 | 28 => 3 | _ => 0), mem⟩ + rfl rfl rfl rfl rfl rfl rfl (by decide) (Block.holdsList_nil _ _) (Block.holdsList_nil _ _) + (by simp) (by decide) + exact ⟨y, he, ho, hf⟩ diff --git a/ROADMAP.md b/ROADMAP.md index dc261bf18d..b964d8bfd6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -425,8 +425,11 @@ each statement against the numbered result. Organize modules by subject, not cha interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting and the selected-prime false-positive bound ([#1442]) are proved mathematically. Native parameter, collision-counting, prime-table, minimum-search and complete two-prime modulus programs have - explicit local fit/frame/cost contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, - packed output and global preprocessing width/time composition remain. It still needs + explicit local fit/frame/cost contracts ([#1446]). The complete native distinct-position frontend + ([#1482]) starts from initialized signed input and produces exact compact root descriptors with + derived logarithmic width and uniform `Oκ((n+1) log²(n+2))` charged cost. Heavy extraction and + packed-node integration, recursive traversal, packed output and global preprocessing width/time + composition remain. 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 @@ -556,4 +559,5 @@ not planned, not as proved. [#1438]: https://github.com/cslib-community/complexitylib/issues/1438 [#1442]: https://github.com/cslib-community/complexitylib/issues/1442 [#1446]: https://github.com/cslib-community/complexitylib/issues/1446 +[#1482]: https://github.com/cslib-community/complexitylib/issues/1482 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 From 6372d314a08ff7215b78425b25315021c0c936a7 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 14:02:51 -0400 Subject: [PATCH 20/45] fix(wordram): link frontend readback to its native run Strengthen runs_input with the actual native pre-halt Runs prefix to the same memory witness, using the existing assembly theorem. Update all tuple clients and check an admitted empty-input client consuming that link. Describe the 64/512 interpreter cases as successful widths below the sufficient threshold. All six scripts/check.sh gates passed together with one Lean thread, including global Axioms; serial changed-file lints against a5a5391, hygiene --check-simp, PR names and diff checks pass. Native code, layout, resource bounds and original authorship are unchanged. Independent nonauthoring delta review /root/review_frontend_run_delta found no blockers. Curated abstracts: no impact. Outside coordinator review remains required. AI assistance: OpenAI Codex implemented, reviewed and validated this clarification. --- .../Algorithms/WordRAM/ThreeSum/Frontend.lean | 14 ++++++++++---- ComplexityTests/WordRAMThreeSumFrontend.lean | 3 ++- .../WordRAMThreeSumFrontendContracts.lean | 8 +++++--- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean index cb6c81922f..7c33fcf39b 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean @@ -64,12 +64,15 @@ theorem size_program (κ : ℕ) : (program κ).size = κ + 71 + construct.size : simp only [program, Code.size, Code.size_rename, ChanHe.Parameters.size_program] /-- The complete finite program halts with its descriptor count, and following its descriptors -gives exactly the existing frontend list. No slot, bit budget, sorting result or fit is advice. -/ +gives exactly the existing frontend list. The memory witness is the actual native state just +before the final halt. No slot, bit budget, sorting result or fit is advice. -/ theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : ∃ (y : Data 128) (steps : ℕ), Executes w ((program κ).program descriptorCount) (State.init (input.map Signed.encode)) - (layout κ input.length).count steps ∧ steps ≤ cost κ input.length ∧ + (layout κ input.length).count steps ∧ + Runs w ((program κ).program descriptorCount) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ (steps - 1) ∧ steps ≤ cost κ input.length ∧ readRoots (layout κ input.length) y.mem = Cslib.Algorithms.ThreeSum.inputs ((input.length + 2) ^ κ) (fun i : Fin input.length => input[i]) ∧ @@ -80,8 +83,11 @@ theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length exec_input input hw hvalues obtain ⟨steps, hsteps, hexec⟩ := he have hpos : 0 < cost κ input.length := by dsimp only [cost]; omega - refine ⟨y, steps + 1, ?_, by omega, ?_, hbase, hcount, hend, ?_⟩ + refine ⟨y, steps + 1, ?_, ?_, by omega, ?_, hbase, hcount, hend, ?_⟩ · simpa only [hcount, State.init] using hexec.executes descriptorCount + · simpa only [State.init, Nat.zero_add, Nat.add_sub_cancel] using + hexec.runs (pc := 0) (p := (program κ).program descriptorCount) + (Program.Contains.self _).append_left · exact ho.readback hkeys (fun p hp => (hcounts p hp).2) hvalues · exact hexec.bounded (Data.Bounded.init (input_fits input hw hvalues)) @@ -119,7 +125,7 @@ theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) (State.init (input.map Signed.encode)) s steps) : (∀ i, s.data.regs i < (input.length + 2) ^ (160 * κ + 968)) ∧ ∀ a, s.data.mem a < (input.length + 2) ^ (160 * κ + 968) := by - obtain ⟨_, _, he, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + obtain ⟨_, _, he, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues have hsmall := prefix_at_smaller_width (Code.not_readsWidth_program (not_readsWidth κ) descriptorCount) hw hr he have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) diff --git a/ComplexityTests/WordRAMThreeSumFrontend.lean b/ComplexityTests/WordRAMThreeSumFrontend.lean index fc41e964a6..a11266ed81 100644 --- a/ComplexityTests/WordRAMThreeSumFrontend.lean +++ b/ComplexityTests/WordRAMThreeSumFrontend.lean @@ -78,7 +78,8 @@ private meta def exact (κ : ℕ) (input : List ℤ) : Bool := #guard_msgs in #eval exact 1 [1, 2, 3] --- A wider admitted physical word does not change output or charged execution time. +-- Two successful physical widths give identical output and charged time. +-- Both are below the exported sufficient threshold of 1692 for this input. /-- info: true -/ #guard_msgs in #eval decide (observe 1 64 [-3, 1, 2] = observe 1 512 [-3, 1, 2]) diff --git a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean index 055bfea26e..08cf0b89d7 100644 --- a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean +++ b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean @@ -26,11 +26,13 @@ example : width 0 0 = 484 ∧ (layout 0 0).count = 20 ∧ (layout 0 0).arrays = example : ∃ (y : Data 128) (steps : ℕ), Executes 4096 ((program 0).program descriptorCount) (State.init []) 20 steps ∧ + Runs 4096 ((program 0).program descriptorCount) (State.init []) + ⟨(program 0).size, y⟩ (steps - 1) ∧ steps ≤ cost 0 0 ∧ readRoots (layout 0 0) y.mem = Cslib.Algorithms.ThreeSum.inputs 1 (fun i : Fin 0 => ([] : List ℤ)[i]) := by - obtain ⟨y, steps, he, ht, ho, _, _, _, _⟩ := runs_input (κ := 0) (w := 4096) [] (by decide) + obtain ⟨y, steps, he, hr, ht, ho, _, _, _, _⟩ := runs_input (κ := 0) (w := 4096) [] (by decide) (by simp) - exact ⟨y, steps, he, ht, ho⟩ + exact ⟨y, steps, he, hr, ht, ho⟩ -- The signed endpoints satisfy both the input promise and the physical/signed width offset. example : ∃ steps, @@ -41,7 +43,7 @@ example : ∃ steps, intro z hz simp only [List.mem_cons, List.not_mem_nil, or_false] at hz rcases hz with rfl | rfl <;> decide - obtain ⟨y, steps, he, ht, _, _, _, _, _⟩ := + obtain ⟨y, steps, he, _, ht, _, _, _, _, _⟩ := runs_input (κ := 1) (w := 4096) [(-4 : ℤ), 4] (by decide) hv exact ⟨steps, he, ht⟩ From ceb537f13272ba9ed9d06ef2466015b0b39aa5b9 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 14:53:46 -0400 Subject: [PATCH 21/45] feat(three-sum): prove complete native Chan-He root traversal Checkpoint for #1512 under #1446. Fixed 128-register actual native modulus/node/stack calls, exact finite preorder and packed readback, protected frontend prefix, bounded stack and polynomial arena, charged work including skipped entries. Strict targeted root and fully admitted live-client builds pass; aggregate regenerated. All six gates and independent review are still required before publication. Full forest/State.init, logarithmic width and global subquadratic-time composition remain open under #1446; #1394 remains open. Original code and proofs assisted by OpenAI Codex. --- Complexity.lean | 24 ++ .../ThreeSum/ChanHe/SelectorBounds.lean | 64 ++++ .../WordRAM/ThreeSum/ChanHe/Modulus.lean | 17 +- .../ThreeSum/ChanHe/TraversalAdmitted.lean | 88 +++++ .../ThreeSum/ChanHe/TraversalCall.lean | 271 ++++++++++++++ .../ThreeSum/ChanHe/TraversalChildren.lean | 166 +++++++++ .../ThreeSum/ChanHe/TraversalDeliver.lean | 245 +++++++++++++ .../ThreeSum/ChanHe/TraversalEmission.lean | 167 +++++++++ .../ChanHe/TraversalEmissionFrame.lean | 272 ++++++++++++++ .../ThreeSum/ChanHe/TraversalExec.lean | 93 +++++ .../ThreeSum/ChanHe/TraversalFrames.lean | 277 ++++++++++++++ .../ThreeSum/ChanHe/TraversalInvariant.lean | 181 +++++++++ .../ThreeSum/ChanHe/TraversalLive.lean | 259 +++++++++++++ .../ThreeSum/ChanHe/TraversalLoop.lean | 84 +++++ .../ThreeSum/ChanHe/TraversalModel.lean | 169 +++++++++ .../ThreeSum/ChanHe/TraversalOutput.lean | 94 +++++ .../ThreeSum/ChanHe/TraversalPending.lean | 98 +++++ .../ThreeSum/ChanHe/TraversalProgram.lean | 234 ++++++++++++ .../ThreeSum/ChanHe/TraversalResources.lean | 134 +++++++ .../ThreeSum/ChanHe/TraversalRoot.lean | 215 +++++++++++ .../ThreeSum/ChanHe/TraversalSkip.lean | 128 +++++++ .../ThreeSum/ChanHe/TraversalStack.lean | 342 ++++++++++++++++++ .../ThreeSum/ChanHe/TraversalStep.lean | 94 +++++ .../ThreeSum/ChanHe/TraversalTime.lean | 96 +++++ .../ThreeSum/ChanHe/TraversalVisit.lean | 138 +++++++ .../Computability/WordRAM/GhostLoop.lean | 59 +++ ComplexityTests/NativeChanHeTraversal.lean | 250 +++++++++++++ 27 files changed, 4246 insertions(+), 13 deletions(-) create mode 100644 Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean create mode 100644 Complexity/Computability/WordRAM/GhostLoop.lean create mode 100644 ComplexityTests/NativeChanHeTraversal.lean diff --git a/Complexity.lean b/Complexity.lean index 3d8269bba8..52de6992e3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -45,6 +45,7 @@ public import Complexity.Algorithms.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.ThreeSum.ChanHe.Defs public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers public import Complexity.Algorithms.ThreeSum.ChanHe.Recursion +public import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes public import Complexity.Algorithms.ThreeSum.Convolution public import Complexity.Algorithms.ThreeSum.Defs @@ -140,6 +141,28 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalAdmitted +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalDeliver +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLive +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalOutput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalResources +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalSkip +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStack +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalVisit public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays @@ -1653,6 +1676,7 @@ public import Complexity.Computability.WordRAM.Defs public import Complexity.Computability.WordRAM.Dynamic public import Complexity.Computability.WordRAM.Function public import Complexity.Computability.WordRAM.Generic +public import Complexity.Computability.WordRAM.GhostLoop public import Complexity.Computability.WordRAM.Heap public import Complexity.Computability.WordRAM.ListArray public import Complexity.Computability.WordRAM.MapOp diff --git a/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean new file mode 100644 index 0000000000..e8e22d0e9e --- /dev/null +++ b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.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 + +public import Complexity.Algorithms.ThreeSum.ChanHe.Defs + +/-! +# Size bounds for the Chan–He selectors, including their default + +Bounding the modulus does not require either filter to succeed: a failed filter returns one. +These bounds therefore apply to native execution with arbitrary nonnegative collision thresholds, +including empty input sets. They do not assert the collision-shrinking property of a successful +search. + +## Main results + +* `minOrOne_filter_bounds`: a positive bounded candidate set keeps its filtered selector bounded. +* `modulus_bounds_of_candidates`: both selections, including defaults, have product in `[1,m²]`. + +The bounds are original elementary consequences of the existing finite selectors. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ThreeSum.ChanHe + +open Finset + +/-- Filtering positive candidates bounded by `m` keeps the default-one selector in `[1,m]`. +The candidate set and the filtered set may both be empty. -/ +theorem minOrOne_filter_bounds {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (A : ℕ → Prop) [DecidablePred A] : + 1 ≤ minOrOne {q ∈ Q | A q} ∧ minOrOne {q ∈ Q | A q} ≤ m := by + let S := {q ∈ Q | A q} + by_cases h : S.Nonempty + · have hmem : S.min' h ∈ Q := (mem_filter.mp (min'_mem S h)).1 + change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_left h] + exact ⟨hpos _ hmem, hle _ hmem⟩ + · change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_right h] + exact ⟨le_rfl, hm⟩ + +/-- Positive bounded candidates bound the modulus regardless of whether either filter succeeds. +This is a size bound only; no magnitude or successful-search premise is needed. -/ +theorem modulus_bounds_of_candidates {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (Λ : ℕ) (S₁ S₂ S₃ : Finset ℤ) : + 1 ≤ modulus Q Λ S₁ S₂ S₃ ∧ modulus Q Λ S₁ S₂ S₃ ≤ m ^ 2 := by + have hp : 1 ≤ firstP Q Λ S₁ S₂ S₃ ∧ firstP Q Λ S₁ S₂ S₃ ≤ m := + minOrOne_filter_bounds hm hpos hle _ + have hq : 1 ≤ secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ∧ + secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ≤ m := + minOrOne_filter_bounds hm hpos hle _ + exact ⟨Nat.mul_le_mul hp.1 hq.1, + (Nat.mul_le_mul hp.2 hq.2).trans_eq (pow_two m).symm⟩ + +end Cslib.Algorithms.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean index d02fc2523e..7a829cb9d6 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search +import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds /-! # Native setup of the two Chan–He prime searches @@ -115,19 +116,9 @@ theorem exec_secondSetup {w : ℕ} (x : Data 64) (h3 : Fits w 3) private theorem selector_bounds {m : ℕ} (hm : 1 ≤ m) (A : ℕ → Prop) [DecidablePred A] : 1 ≤ minOrOne {q ∈ Nat.primesLE m | A q} ∧ minOrOne {q ∈ Nat.primesLE m | A q} ≤ m := by - let S := {q ∈ Nat.primesLE m | A q} - by_cases h : S.Nonempty - · have hmem : S.min' h ∈ S := Finset.min'_mem _ _ - have hprime : (S.min' h).Prime ∧ S.min' h < m + 1 := by - have hh := (Finset.mem_filter.mp hmem).1 - rw [Nat.primesLE_eq_filter_range, Finset.mem_filter, Finset.mem_range] at hh - exact ⟨hh.2, hh.1⟩ - change 1 ≤ minOrOne S ∧ minOrOne S ≤ m - rw [minOrOne, dite_eq_left h] - exact ⟨by have := hprime.1.two_le; omega, by omega⟩ - · change 1 ≤ minOrOne S ∧ minOrOne S ≤ m - rw [minOrOne, dite_eq_right h] - exact ⟨le_refl _, hm⟩ + exact minOrOne_filter_bounds hm + (fun q hq => (Nat.prime_of_mem_primesLE hq).two_le.trans' (by decide)) + (fun _ hq => Nat.le_of_mem_primesLE hq) A private theorem forall_three {P : Fin 3 → Prop} : (∀ j, P j) ↔ P 0 ∧ P 1 ∧ P 2 := by constructor diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean new file mode 100644 index 0000000000..57f495459b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission + +/-! +# Deriving every native visit fit from the worklist envelope + +The fixed arena envelope covers the current heap, every live source block and every candidate +product. This discharges the actual selector and node calls uniformly, without asking the loop +invariant to carry per-call execution advice or a separate width for each selected modulus. + +## Main results + +* `emit_admitted`: exact native emission from a loaded admissible entry and the common envelope. + +These resource and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The worklist's one signed envelope derives every local native kernel fit. -/ +theorem emit_admitted {a : Arena} {w : ℕ} (x : Data 128) (e : Entry) + (ha : a.Separated) (hw : a.Ready w) (hc : a.Controls x) + (hl : e.Loaded x) (hv : e.Valid a.n a.U) (hs : e.Stored x.mem) + (hbitmap : a.Bitmap x.mem) (hfloor : a.floor ≤ x.regs heap) + (hheap : x.regs heap ≤ a.ceiling) + (hsrc : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ y, ExecWithin (w + 2) visit x y (visitTime a.n a.m a.N) ∧ + Nonempty (Emission (Nat.primesLE a.m) a.Λ e x y) := by + rcases hc with ⟨hm, hΛ, hU, hN, hb, hcard, ht, hstack, hcap, hz, hone⟩ + have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have hbound {z : ℤ} (hz : z.natAbs ≤ envelope a.ceiling a.n a.m a.Λ a.U a.N) : + GenericRAM.Signed.Fits w z := NodePacking.signed_fits_of_natAbs_le hw.2.2 hz + have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + have hlen (j : Fin 3) : x.regs (arrayLength j) ≤ a.n := by + rw [hl.2.2] + exact hv.length_le j + have hsource (j : Fin 3) : x.regs (arraySource j) + x.regs (arrayLength j) ≤ a.ceiling := by + rw [hl.2.1, hl.2.2] + exact (hsrc j).trans hheap + have hbitmapBase : x.regs bitmap ≤ a.ceiling := by + rw [hb] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega + obtain ⟨y, he, hem⟩ := emit_entry x e hl (by simpa only [hU] using hv) hs + (by rw [hm]; exact hw.1) + (by apply hbound; simp only [hm, Int.natAbs_natCast]; omega) + (hunsigned.of_le (modulus_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N hheap + hsource hlen hm.le hΛ.le + (by rw [hcard]; exact Nat.card_primesLE_le a.m) hbitmapBase)) + (by + intro M _ hM + apply hbound + simp only [Int.natAbs_natCast] + exact node_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N M hheap hlen + (by simpa only [hm] using hM) hU.le hN.le) + (by rw [hm, hN]; exact hw.2.1) + (by rw [hm, hcard]) + (by + rw [hb, hm] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega) + (by simpa only [Arena.Bitmap, hm, hb] using hbitmap) + (by + intro j i hi + apply hbound + exact (hv.natAbs_le j i hi).trans (by omega)) + hsrc hone htable + exact ⟨y, by simpa only [hm, hN] using he, by simpa only [hm, hΛ] using hem⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean new file mode 100644 index 0000000000..6d4b27dbff --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean @@ -0,0 +1,271 @@ +/- +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.TraversalProgram +import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds + +/-! +# Actual native argument delivery in the Chan–He traversal + +The current record and persistent parameters are delivered to the existing complete modulus +and node programs in registers 0–63. Every copy and histogram-base addition is a native +instruction. The record, controller parameters and native word-stack registers survive setup. + +## Main definitions + +* `modulusState`, `nodeState`: exact states after the two native argument blocks. + +## Main results + +* `prepareModulus_exec`: fourteen charged instructions, including all histogram-base additions. +* `prepareNode_exec`: ten charged native register copies. +* `modulusState_preserves`, `nodeState_preserves`: all registers above the kernel survive. + +These calling-convention proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The exact state after the fourteen-instruction modulus argument block. -/ +def modulusState (x : Data 128) : Data 128 := + (((((((((((((x.set (kernel (CountCall.source 0)) (x.regs (slot 1))).set + (kernel (CountCall.length 0)) (x.regs (slot 2))).set + (kernel (CountCall.source 1)) (x.regs (slot 3))).set + (kernel (CountCall.length 1)) (x.regs (slot 4))).set + (kernel (CountCall.source 2)) (x.regs (slot 5))).set + (kernel (CountCall.length 2)) (x.regs (slot 6))).set + (kernel CountCall.scratch) (x.regs heap)).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2))).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2) + x.regs (slot 4))).set + (kernel CountCall.histogram) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))).set + (kernel Search.limit) (x.regs limit)).set (kernel Search.primes) (x.regs bitmap)).set + (kernel CollisionTest.card) (x.regs primeCount)).set (kernel Modulus.digits) (x.regs digits) + +/-- The exact state after the ten-instruction node argument block. -/ +def nodeState (x : Data 128) : Data 128 := + (((((((((x.set (kernel (NodePacking.source 0)) (x.regs (slot 1))).set + (kernel (NodePacking.length 0)) (x.regs (slot 2))).set + (kernel (NodePacking.source 1)) (x.regs (slot 3))).set + (kernel (NodePacking.length 1)) (x.regs (slot 4))).set + (kernel (NodePacking.source 2)) (x.regs (slot 5))).set + (kernel (NodePacking.length 2)) (x.regs (slot 6))).set + (kernel NodePacking.base) (x.regs heap)).set + (kernel NodePacking.groups) (x.regs groups)).set + (kernel NodePacking.magnitude) (x.regs magnitude)).set + (kernel NodePacking.modulus) (x.regs selected) + +/-- The existing node layout instantiated by the traversal record and one computed modulus. -/ +def recordLayout (x : Data 128) (M : ℕ) : NodePacking.Layout := + ⟨x.regs heap, fun j => x.regs (arrayLength j), x.regs groups, M, x.regs magnitude⟩ + +/-- The delivered node ABI has exactly the current record's layout. -/ +theorem nodeState_layout (x : Data 128) : + NodePacking.layout ((nodeState x).comap kernel) = recordLayout x (x.regs selected) := by + unfold NodePacking.layout recordLayout + congr 1 + funext j + fin_cases j <;> rfl + +/-- The layout reads only persistent record/controller fields outside the selected-modulus word. -/ +theorem recordLayout_congr (x y : Data 128) (M : ℕ) + (h : ∀ i, 64 ≤ i.val → i ≠ selected → y.regs i = x.regs i) : + recordLayout y M = recordLayout x M := by + unfold recordLayout + congr 1 + · exact h heap (by decide) (by decide) + · funext j + fin_cases j <;> exact h _ (by decide) (by decide) + · exact h groups (by decide) (by decide) + · exact h magnitude (by decide) (by decide) + +/-- The final endpoint formula expressed entirely in the persistent traversal ABI. -/ +theorem recordLayout_endpoint (x : Data 128) (M : ℕ) : + (recordLayout x M).endpoint = x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + 4 * x.regs groups + M := by + rw [NodePacking.Layout.endpoint_eq] + rfl + +private theorem kernel_ne {i : Fin 128} (hi : 64 ≤ i.val) (j : Fin 64) : i ≠ kernel j := by + intro h + have := congrArg Fin.val h + dsimp [kernel] at this + omega + +private theorem set_kernel_regs (i : Fin 128) (hi : 64 ≤ i.val) + (x : Data 128) (j : Fin 64) (v : ℕ) : + (x.set (kernel j) v).regs i = x.regs i := Function.update_of_ne (kernel_ne hi j) .. + +/-- Modulus argument delivery preserves every traversal and stack register. -/ +theorem modulusState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (modulusState x).regs i = x.regs i := by + simp only [modulusState, set_kernel_regs i hi] + +/-- Node argument delivery preserves every traversal and stack register. -/ +theorem nodeState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (nodeState x).regs i = x.regs i := by + simp only [nodeState, set_kernel_regs i hi] + +/-- Native modulus argument delivery charges the three histogram-base additions too. +A fit for their largest value derives all intermediate address fits. -/ +theorem prepareModulus_exec {w : ℕ} (x : Data 128) + (h : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))) : + Exec w prepareModulus x (modulusState x) 14 := by + have h₀ : Fits w (x.regs heap + x.regs (slot 2)) := h.of_le (by omega) + have h₁ : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4)) := h.of_le (by omega) + dsimp only [heap, slot] at h₀ h₁ h + ram_eval [prepareModulus, modulusState, kernel, slot, CountCall.source, CountCall.length, + CountCall.scratch, CountCall.histogram, Search.limit, Search.primes, CollisionTest.card, + Modulus.digits, heap, limit, bitmap, primeCount, digits, h₀, h₁, h] + +/-- Complete node argument delivery is ten native copies; no address arithmetic is hidden. -/ +theorem prepareNode_exec {w : ℕ} (x : Data 128) : + Exec w prepareNode x (nodeState x) 10 := by + ram_eval [prepareNode, nodeState, kernel, slot, NodePacking.source, NodePacking.length, + NodePacking.base, NodePacking.groups, NodePacking.magnitude, NodePacking.modulus, + heap, groups, magnitude, selected] + +private theorem kernel_injective : Function.Injective kernel := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 128 => i.val) h) + +private theorem extend_kernel_regs (y : Data 64) (x : Data 128) (i : Fin 128) + (hi : 64 ≤ i.val) : (y.extend kernel x).regs i = x.regs i := by + change Function.extend kernel y.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact kernel_ne hi j hj.symm + +/-- Actual argument delivery and complete two-prime computation inside the traversal's fixed +register file. The mathematical filters may fail; their default-one selections are still bounded. +The bitmap/count are the outputs of the separate native prime-table routine. -/ +theorem callModulus_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (arrayLength j) → GenericRAM.Signed.Fits w (values j i)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) (.seq prepareModulus (Modulus.program.rename kernel)) x y + (14 + Modulus.cost ((modulusState x).comap kernel)) ∧ + (∀ i, 64 ≤ i.val → y.regs i = x.regs i) ∧ + (∀ a < x.regs heap, y.mem a = x.mem a) ∧ + y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) ∧ + 1 ≤ y.regs (kernel Modulus.output) ∧ y.regs (kernel Modulus.output) ≤ x.regs limit ^ 2 := by + let a := modulusState x + let z := a.comap kernel + have hn (j : Fin 3) : z.regs (CountCall.length j) = x.regs (arrayLength j) := by + fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (CountCall.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hzm : z.regs Search.limit = x.regs limit := rfl + have hzd : z.regs Modulus.digits = x.regs digits := rfl + have hhist : Fits (w + 2) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := by + change Fits (w + 2) (z.regs CountCall.histogram) + have hbz : Fits (w + 2) (Modulus.budget z) := hbudget + apply hbz.of_le + dsimp only [Modulus.budget] + omega + have ha := prepareModulus_exec x hhist + obtain ⟨b, hb, _, hprefix, _, hout⟩ := Modulus.exec z values hm hmod hbudget hcard hbefore + hbitmap + (by intro j i hi; exact hvalues j i ((hn j) ▸ hi)) + (by intro j; change z.regs (CountCall.source j) + z.regs (CountCall.length j) ≤ x.regs heap + rw [hs j, hn j]; exact hsrc j) + (by + intro j + change x.regs heap + z.regs (CountCall.length j) ≤ + x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + rw [hn j] + fin_cases j + · change x.regs heap + x.regs (slot 2) ≤ _ + omega + · change x.regs heap + x.regs (slot 4) ≤ _ + omega + · change x.regs heap + x.regs (slot 6) ≤ _ + omega) + (by intro j i hi; change x.mem (z.regs (CountCall.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) + (by intro j; simpa only [hn j] using hinj j) + let y := b.extend kernel a + have hout' : y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) := by + change Function.extend kernel b.regs a.regs (kernel Modulus.output) = _ + rw [kernel_injective.extend_apply] + simpa only [hn, hzm, hzd] using hout + refine ⟨y, ha.within.seq (hb.rename kernel_injective), ?_, ?_, hout', ?_⟩ + · intro i hi + exact (extend_kernel_regs b a i hi).trans (modulusState_preserves x i hi) + · intro p hp + exact hprefix p hp + · rw [hout'] + exact Cslib.Algorithms.ThreeSum.ChanHe.modulus_bounds_of_candidates (by omega) + (fun q hq => (Nat.prime_of_mem_primesLE hq).two_le.trans' (by decide)) + (fun _ hq => Nat.le_of_mem_primesLE hq) _ _ _ _ + +/-- Ten actual argument copies followed by the complete native heavy/packed-node program. +The existing exact semantic output and the full allocation frame survive register renaming. -/ +theorem callNode_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < x.regs selected) (hN : 2 * x.regs selected ≤ x.regs groups) + (hwidth : GenericRAM.Signed.Fits w + ((NodePacking.layout ((nodeState x).comap kernel)).budget : ℤ)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hbdd : ∀ j i, i < x.regs (arrayLength j) → (values j i).natAbs ≤ x.regs magnitude) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + let L := NodePacking.layout ((nodeState x).comap kernel) + ∃ y, ExecWithin (w + 2) (.seq prepareNode (NodePacking.program.rename kernel)) x y + (10 + 66 * L.total + 15 * L.M + 12 * L.N + 228) ∧ + NodePacking.Output L values x.mem y.mem ∧ + ∀ i, 64 ≤ i.val → y.regs i = x.regs i := by + let a := nodeState x + let z := a.comap kernel + let L := NodePacking.layout z + have hn (j : Fin 3) : L.n j = x.regs (arrayLength j) := by fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (NodePacking.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hinj' (j : Fin 3) : Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ) := by + simpa only [hn] using hinj j + obtain ⟨b, hb, hmem, _⟩ := NodePacking.exec z values hM hN hwidth + (by intro j; rw [hs j]; change x.regs (arraySource j) + L.n j ≤ x.regs heap + rw [hn j]; exact hsrc j) + (by intro j i hi; exact hbdd j i ((hn j) ▸ hi)) + (by intro j i hi; change x.mem (z.regs (NodePacking.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) hinj' + refine ⟨b.extend kernel a, ?_, ?_, ?_⟩ + · simpa only [Nat.add_assoc] using + (prepareNode_exec (w := w + 2) x).within.seq (hb.rename kernel_injective) + · change NodePacking.Output L values z.mem b.mem + rw [hmem] + exact NodePacking.memory_output L values z.mem hM hN hinj' + · intro i hi + exact (extend_kernel_regs b a i hi).trans (nodeState_preserves x i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean new file mode 100644 index 0000000000..4773b8adfe --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean @@ -0,0 +1,166 @@ +/- +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.TraversalProgram + +/-! +# Native preparation of the three Chan–He child records + +The third child is pushed first. Original descriptors are retained above the stack dispatcher +and restored before replacing another column. All descriptor moves and the fuel decrement are +native instructions; the same finite code is used at every recursion level. + +## Main definitions + +* `thirdState`, `secondState`, `firstState`: exact states after the native child-argument blocks. +* `childWords`: the seven actual words of one child record, in its pop order. + +## Main results + +* `prepareThird_exec`, `prepareSecond_exec`, `prepareFirst_exec`: charged native argument blocks. + +The native stack controller and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Save both descriptors needed for restoration and prepare the third child. -/ +def thirdState (x : Data 128) : Data 128 := + ((((((x.set (slot 0) (x.regs (slot 0) - 1)).set (original 0) (x.regs (slot 3))).set + (original 1) (x.regs (slot 4))).set (original 2) (x.regs (slot 5))).set + (original 3) (x.regs (slot 6))).set (slot 5) (x.regs (heavyBase 2))).set + (slot 6) (x.regs (heavyLength 2)) + +/-- Restore the third original column and prepare the second child. -/ +def secondState (x : Data 128) : Data 128 := + (((x.set (slot 5) (x.regs (original 2))).set (slot 6) (x.regs (original 3))).set + (slot 3) (x.regs (heavyBase 1))).set (slot 4) (x.regs (heavyLength 1)) + +/-- Restore the second original column and prepare the first child. -/ +def firstState (x : Data 128) : Data 128 := + (((x.set (slot 3) (x.regs (original 0))).set (slot 4) (x.regs (original 1))).set + (slot 1) (x.regs (heavyBase 0))).set (slot 2) (x.regs (heavyLength 0)) + +/-- Exact child-record serialization. Only the selected column is replaced by its heavy output. -/ +def childWords (x : Data 128) (j : Fin 3) : List ℕ := + [x.regs (slot 0) - 1, + if j = 0 then x.regs (heavyBase 0) else x.regs (slot 1), + if j = 0 then x.regs (heavyLength 0) else x.regs (slot 2), + if j = 1 then x.regs (heavyBase 1) else x.regs (slot 3), + if j = 1 then x.regs (heavyLength 1) else x.regs (slot 4), + if j = 2 then x.regs (heavyBase 2) else x.regs (slot 5), + if j = 2 then x.regs (heavyLength 2) else x.regs (slot 6)] + +/-- Every child occupies exactly one seven-word native record. -/ +theorem length_childWords (x : Data 128) (j : Fin 3) : (childWords x j).length = 7 := rfl + +/-- The fuel subtraction and all six descriptor copies cost seven native instructions. -/ +theorem prepareThird_exec {w : ℕ} (x : Data 128) (hx : x.Bounded w) (hone : x.regs one = 1) : + Exec w prepareThird x (thirdState x) 7 := by + have hfit : Fits w (x.regs (slot 0) - 1) := (hx.1 (slot 0)).of_le (Nat.sub_le _ _) + dsimp only [slot, one] at hfit hone + ram_eval [prepareThird, thirdState, slot, one, original, heavyBase, heavyLength, hone, hfit] + +/-- Restoring the third column and replacing the second costs four native copies. -/ +theorem prepareSecond_exec {w : ℕ} (x : Data 128) : + Exec w prepareSecond x (secondState x) 4 := by + ram_eval [prepareSecond, secondState, slot, original, heavyBase, heavyLength] + +/-- Restoring the second column and replacing the first costs four native copies. -/ +theorem prepareFirst_exec {w : ℕ} (x : Data 128) : + Exec w prepareFirst x (firstState x) 4 := by + ram_eval [prepareFirst, firstState, slot, original, heavyBase, heavyLength] + +private theorem thirdState_comap (x : Data 128) : + (thirdState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [thirdState, Data.comap, Data.set, slot, original, registers] + · rfl + +private theorem secondState_comap (x : Data 128) : + (secondState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [secondState, Data.comap, Data.set, slot, registers] + · rfl + +private theorem firstState_comap (x : Data 128) : + (firstState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [firstState, Data.comap, Data.set, slot, registers] + · rfl + +/-- Child pushes preserve every persistent controller parameter outside the record and stack. -/ +theorem pushChildren_preserves : ∀ i : Fin 128, + ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → pushChildren.Preserves i := by decide + +private def words (x : Data 128) : List ℕ := + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] + +/-- All three child records are pushed by native instructions in reverse order. The resulting +top-first stack contains first, second, third, followed by the original pending suffix. -/ +theorem pushChildren_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (hx : x.Bounded w) (hone : x.regs one = 1) (h2 : Fits w 2) + (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hcapacity : 21 + vs.length ≤ capacity) : + ∃ y, Exec w pushChildren x y 207 ∧ + Stack.Rep w base capacity (childWords x 0 ++ childWords x 1 ++ childWords x 2 ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ i, ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → y.regs i = x.regs i := by + let a := thirdState x + have ha := prepareThird_exec x hx hone + have hra : Stack.Rep w base capacity vs (a.comap registers) := by + simpa only [a, thirdState_comap] using hr + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a vs h2 hcap hra + (fun i => (ha.bounded hx).1 (slot i)) (by omega) + have hwa : words a = childWords x 2 := by + simp +decide [words, a, thirdState, childWords, Data.set, slot, original, + heavyBase, heavyLength] + change Stack.Rep w base capacity (words a ++ vs) (b.comap registers) at hrb + rw [hwa] at hrb + let c := secondState b + have hc := prepareSecond_exec (w := w) b + have hrc : Stack.Rep w base capacity (childWords x 2 ++ vs) (c.comap registers) := by + simpa only [c, secondState_comap] using hrb + obtain ⟨d, hd, hrd, hfd, hpd⟩ := pushFrame_exec c (childWords x 2 ++ vs) h2 hcap hrc + (fun i => (hc.bounded (hb.bounded (ha.bounded hx))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwc : words c = childWords x 1 := by + simp +decide [words, c, secondState, hpb, Outside, a, thirdState, childWords, Data.set, + slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words c ++ (childWords x 2 ++ vs)) (d.comap registers) at hrd + rw [hwc] at hrd + let e := firstState d + have he := prepareFirst_exec (w := w) d + have hre : Stack.Rep w base capacity (childWords x 1 ++ (childWords x 2 ++ vs)) + (e.comap registers) := by + simpa only [e, firstState_comap] using hrd + obtain ⟨z, hz, hrz, hfz, _⟩ := pushFrame_exec e (childWords x 1 ++ (childWords x 2 ++ vs)) + h2 hcap hre + (fun i => (he.bounded (hd.bounded (hc.bounded (hb.bounded (ha.bounded hx))))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwe : words e = childWords x 0 := by + simp +decide [words, e, firstState, hpd, Outside, c, secondState, hpb, a, thirdState, + childWords, Data.set, slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words e ++ (childWords x 1 ++ (childWords x 2 ++ vs))) + (z.comap registers) at hrz + rw [hwe] at hrz + have hrun : Exec w pushChildren x z 207 := ha.seq (hb.seq (hc.seq (hd.seq (he.seq hz)))) + refine ⟨z, hrun, ?_, ?_, fun i hi => hrun.regs_eq (pushChildren_preserves i hi)⟩ + · simpa only [List.append_assoc] using hrz + · intro p hp + exact (hfz p hp).trans ((hfd p hp).trans (hfb p hp)) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean new file mode 100644 index 0000000000..7f0fc7f323 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean @@ -0,0 +1,245 @@ +/- +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.TraversalCall + +/-! +# Charged native output delivery for one traversal node + +The three actual heavy counts are read from the node allocation. The packed pointer is appended +to the reserved output table and the fresh heap advances past all node output and scratch cells. +Address computation is repeated explicitly instead of assuming scratch-register outputs that +the node builder's public contract does not export. + +## Main definitions + +* `deliveryState`: exact state after all 24 native delivery instructions. + +## Main results + +* `deliver_exec`: charged output delivery with derived address and counter fits. + +The layout and native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- All arithmetic results of the native delivery block, including its sole memory store. -/ +def deliveryState (x : Data 128) : Data 128 := + let b := x.regs heap + let n₀ := x.regs (slot 2) + let n₁ := x.regs (slot 4) + let n₂ := x.regs (slot 6) + let N := x.regs groups + let M := x.regs selected + let p := b + 3 + n₀ + n₁ + n₂ + let o := x.regs outputs + x.regs outputCount + let d₁ := b + 1 + n₀ + let d₂ := d₁ + 1 + n₁ + let a := ((((x.set packed 3).set packed (b + 3)).set packed (b + 3 + n₀)).set + packed (b + 3 + n₀ + n₁)).set packed p + let a := (((a.set temporary o).write o p).set outputCount (x.regs outputCount + 1)).set + (heavyLength 0) (x.mem b) + let a := (((a.set (heavyBase 0) (b + 1)).set temporary d₁).set + (heavyLength 1) (x.mem d₁)).set (heavyBase 1) (d₁ + 1) + let a := (((a.set temporary d₂).set (heavyLength 2) (x.mem d₂)).set + (heavyBase 2) (d₂ + 1)).set endpoint (N + N) + let a := ((((((a.set endpoint (N + N + (N + N))).set endpoint (p + 4 * N)).set + endpoint (p + 4 * N + n₀)).set endpoint (p + 4 * N + n₀ + n₁)).set + endpoint (p + 4 * N + n₀ + n₁ + n₂)).set endpoint (p + 4 * N + n₀ + n₁ + n₂ + M)).set + heap (p + 4 * N + n₀ + n₁ + n₂ + M) + a + +/-- Delivery writes exactly one output-pointer cell; all other memory retains the node output. -/ +theorem deliveryState_mem (x : Data 128) : (deliveryState x).mem = + Function.update x.mem (x.regs outputs + x.regs outputCount) + (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := rfl + +/-- The appended pointer is the existing node layout's packed base. -/ +theorem deliveryState_packed (x : Data 128) : (deliveryState x).regs packed = + (NodePacking.layout ((nodeState x).comap kernel)).packed := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + omega + +/-- All allocated node output, residues and histogram lie below the new fresh heap. -/ +theorem deliveryState_heap (x : Data 128) : (deliveryState x).regs heap = + (NodePacking.layout ((nodeState x).comap kernel)).endpoint := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + x.regs selected = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + x.regs selected + omega + +/-- Exactly one node is appended to the output table. -/ +theorem deliveryState_count (x : Data 128) : + (deliveryState x).regs outputCount = x.regs outputCount + 1 := rfl + +/-- Each delivered count was read from its actual count-prefixed heavy array. -/ +theorem deliveryState_heavy_length (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + x.mem ((NodePacking.layout ((nodeState x).comap kernel)).descriptor j) := by + fin_cases j + · rfl + · rfl + · change x.mem (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) = + x.mem (x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4))) + congr 1 + omega + +/-- Each delivered data pointer is the existing heavy-array base. -/ +theorem deliveryState_heavy_base (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyBase j) = + (NodePacking.layout ((nodeState x).comap kernel)).heavy j := by + fin_cases j + · rfl + · rfl + · change x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1 = + x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4)) + 1 + omega + +/-- Output delivery preserves the node builder's exact heavy/packed readback and allocation +frame. The sole external output-pointer store is recorded explicitly in the before-memory. -/ +theorem delivery_output (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + let L := NodePacking.layout ((nodeState x).comap kernel) + NodePacking.Output L values + (Function.update before (x.regs outputs + x.regs outputCount) L.packed) + (deliveryState x).mem := by + let L := NodePacking.layout ((nodeState x).comap kernel) + let o := x.regs outputs + x.regs outputCount + change NodePacking.Output L values before x.mem at h + change NodePacking.Output L values (Function.update before o L.packed) (deliveryState x).mem + have hm := deliveryState_mem x + change (deliveryState x).mem = Function.update x.mem o ((deliveryState x).regs packed) at hm + rw [deliveryState_packed] at hm + have ho : o < L.base := htable + have hkeep (a : ℕ) (ha : L.base ≤ a) : (deliveryState x).mem a = x.mem a := by + rw [hm, Function.update_of_ne (show a ≠ o by omega)] + refine ⟨?_, ?_, ?_⟩ + · intro j + obtain ⟨zs, hnd, hs, hc, hd⟩ := h.heavy j + refine ⟨zs, hnd, hs, (hkeep _ (L.base_le_descriptor j)).trans hc, ?_⟩ + intro k hk + have hb := L.base_le_descriptor j + have hbase : L.base ≤ L.heavy j + k := by + dsimp only [NodePacking.Layout.heavy] + omega + exact (hkeep _ hbase).trans (hd k hk) + · intro a ha + have hbase : L.base ≤ L.packed + a := by + dsimp only [NodePacking.Layout.packed] + omega + exact (hkeep _ hbase).trans (h.packed a ha) + · intro a ha + rw [hm] + by_cases he : a = o + · subst a + simp only [Function.update_self] + rfl + · simp only [Function.update_of_ne he] + exact h.frame a ha + +/-- The delivered register is the existing heavy-set cardinality, not a capacity or pair count. -/ +theorem delivery_heavy_card (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + (Cslib.Algorithms.ThreeSum.ChanHe.heavy + ((Finset.range ((NodePacking.layout ((nodeState x).comap kernel)).n j)).image (values j)) + (NodePacking.layout ((nodeState x).comap kernel)).M).card := by + rw [deliveryState_heavy_length] + exact h.heavy_card j + +/-- All non-output controller parameters and the entire stack workspace survive delivery. -/ +theorem deliver_preserves : ∀ i : Fin 128, i ≠ heap → i ≠ outputCount → i ≠ temporary → + (i.val < 88 ∨ 96 ≤ i.val) → deliver.Preserves i := by decide + +/-- The explicit delivered state retains every non-output controller and stack register. -/ +theorem deliveryState_preserves (x : Data 128) (i : Fin 128) + (hh : i ≠ heap) (hc : i ≠ outputCount) (ht : i ≠ temporary) + (hi : i.val < 88 ∨ 96 ≤ i.val) : (deliveryState x).regs i = x.regs i := by + have hp : i ≠ packed := by intro he; subst i; norm_num [packed] at hi + have he : i ≠ endpoint := by intro he; subst i; norm_num [endpoint] at hi + have hl (j : Fin 3) : i ≠ heavyLength j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyLength] at hv + have := j.isLt + omega + have hb (j : Fin 3) : i ≠ heavyBase j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyBase] at hv + have := j.isLt + omega + simp only [deliveryState, Data.set_regs, Data.write_regs, Function.update_of_ne hh, + Function.update_of_ne hc, Function.update_of_ne ht, Function.update_of_ne hp, + Function.update_of_ne he, Function.update_of_ne (hl 0), Function.update_of_ne (hl 1), + Function.update_of_ne (hl 2), Function.update_of_ne (hb 0), Function.update_of_ne (hb 1), + Function.update_of_ne (hb 2)] + +/-- Every intermediate address and the output-count increment fit once the final endpoint fits. +The output table precedes the fresh allocation, so its store cannot change a heavy-count load. -/ +theorem deliver_exec {w : ℕ} (x : Data 128) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) + (hfit : Fits w (x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected)) : + Exec w deliver x (deliveryState x) 24 := by + have hbound (v : ℕ) (hv : v ≤ x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected) : Fits w v := hfit.of_le hv + have h3 : Fits w 3 := hbound _ (by omega) + have hp₀ := hbound (x.regs heap + 3) (by omega) + have hp₁ := hbound (x.regs heap + 3 + x.regs (slot 2)) (by omega) + have hp₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have hp := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have ho := hbound (x.regs outputs + x.regs outputCount) (by omega) + have hc := hbound (x.regs outputCount + 1) (by omega) + have hd₀ := hbound (x.regs heap + 1) (by omega) + have hd₁ := hbound (x.regs heap + 1 + x.regs (slot 2)) (by omega) + have hd₁' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1) (by omega) + have hd₂ := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) (by omega) + have hd₂' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1) (by omega) + have he₀ := hbound (x.regs groups + x.regs groups) (by omega) + have he₁ := hbound (4 * x.regs groups) (by omega) + have he₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups) (by omega) + have he₃ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2)) (by omega) + have he₄ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have he₅ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have he := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + x.regs selected) (by omega) + have hne₀ : x.regs heap ≠ x.regs outputs + x.regs outputCount := by omega + have hne₁ : x.regs heap + 1 + x.regs (slot 2) ≠ + x.regs outputs + x.regs outputCount := by omega + have hne₂ : x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) ≠ + x.regs outputs + x.regs outputCount := by omega + dsimp only [heap, slot, groups, selected, outputs, outputCount, one] at * + ram_eval [deliver, deliveryState, heavyBase, heavyLength, heap, slot, groups, selected, + outputs, outputCount, packed, temporary, endpoint, one, hone, h3, hp₀, hp₁, hp₂, hp, ho, + hc, hd₀, hd₁, hd₁', hd₂, hd₂', he₀, he₁, he₂, he₃, he₄, he₅, he, + hne₀, hne₁, hne₂, Function.update_of_ne, Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc, + show ∀ n : ℕ, n + n + (n + n) = 4 * n by omega] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean new file mode 100644 index 0000000000..d21e5ff860 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean @@ -0,0 +1,167 @@ +/- +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.TraversalFrames +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Exact emissions from the complete native traversal visit + +This packages the executed modulus and node calls into the worklist's semantic entry format. +The heavy lists are witnesses to actual native memory, not program inputs or advice. The old +prefix survives before the single output-pointer store, and every new packed cell equals the +existing finite node array. + +## Main definitions + +* `Emission`: exact metadata, old-prefix frame and heavy/packed readback of an actual live visit. + +## Main results + +* `emit_entry`: the complete native visit produces an exact emission with its charged bound. + +These native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Semantic readback of one completed actual native visit. -/ +structure Emission (Q : Finset ℕ) (Λ : ℕ) (e : Entry) (x y : Data 128) where + /-- The actual selected unsigned modulus. -/ + M : ℕ + /-- Selection agrees with the existing finite node. -/ + exactModulus : M = (e.task.node Q Λ).M + /-- The native fallback is positive. -/ + positive : 1 ≤ M + /-- Both selected candidates lie within the supplied bound. -/ + upper : M ≤ x.regs limit ^ 2 + /-- Actual native compact heavy enumerations. -/ + lists : Fin 3 → List ℤ + /-- Each enumeration is duplicate-free. -/ + nodup : ∀ j, (lists j).Nodup + /-- The enumerated sets are exactly the existing heavy subsets. -/ + sets : ∀ j, (lists j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M + /-- A heavy enumeration never exceeds its parent column. -/ + length_le : ∀ j, (lists j).length ≤ (e.data j).length + /-- The delivered unsigned lengths describe these actual lists. -/ + lengths : ∀ j, y.regs (heavyLength j) = (lists j).length + /-- The delivered unsigned bases are the concrete heavy-array bases. -/ + bases : ∀ j, y.regs (heavyBase j) = (recordLayout x M).heavy j + /-- Exact signed-encoded heavy-array readback. -/ + heavy : ∀ j, (Block.mk ((recordLayout x M).heavy j) (lists j).length).Realizes y.mem + (fun i => Signed.encode (lists j)[i.val]) + /-- Exact signed-encoded packed-node readback. -/ + packed : (Block.mk (recordLayout x M).packed (4 * x.regs groups)).Realizes y.mem + (fun i => Signed.encode ((e.task.node Q Λ).oneArray (x.regs magnitude) i.val)) + /-- The actual count increment. -/ + count : y.regs outputCount = x.regs outputCount + 1 + /-- The actual advanced allocation frontier. -/ + heap_eq : y.regs heap = (recordLayout x M).endpoint + /-- The complete node output before its single external pointer-table store. -/ + before : ℕ → ℕ + /-- The two actual modulus searches and the node call preserve every old prefix cell. -/ + prefixMemory : ∀ p < x.regs heap, before p = x.mem p + /-- The sole pointer-table store is an actual native memory update. -/ + memory : y.mem = Function.update before (x.regs outputs + x.regs outputCount) + (recordLayout x M).packed + /-- The current record, persistent controller parameters and stack dispatcher survive. -/ + registers : ∀ i : Fin 128, 64 ≤ i.val → i ≠ selected → i ≠ heap → + i ≠ outputCount → i ≠ temporary → (i.val < 88 ∨ 96 ≤ i.val) → y.regs i = x.regs i + +/-- Execute both complete searches and the complete node construction for an actual loaded +entry. All heavy witnesses come from the executed memory and all control work is charged. -/ +theorem emit_entry {w n : ℕ} (x : Data 128) (e : Entry) (hl : e.Loaded x) + (hv : e.Valid n (x.regs magnitude)) (hs : e.Stored x.mem) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hnode : ∀ M, 1 ≤ M → M ≤ x.regs limit ^ 2 → + GenericRAM.Signed.Fits w ((recordLayout x M).budget : ℤ)) + (hN : 2 * x.regs limit ^ 2 ≤ x.regs groups) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < (e.data j).length → GenericRAM.Signed.Fits w (e.read j i)) + (hsrc : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ y, ExecWithin (w + 2) visit x y + (visitTime n (x.regs limit) (x.regs groups)) ∧ + Nonempty (Emission (Nat.primesLE (x.regs limit)) (x.regs digits) e x y) := by + classical + obtain ⟨M, u, b, he, hu, hus, hprefix, hout, hb, hMpos, hMle, hM⟩ := + visit_exec x e.read hm hmod hbudget hnode hN hcard hbefore hbitmap + (by simpa only [hl.2.2] using hvalues) + (by intro j i hi; exact hv.natAbs_le j i ((hl.2.2 j) ▸ hi)) + (by simpa only [hl.2.1, hl.2.2] using hsrc) + (by simpa only [hl.2.1, hl.2.2] using hs.input) + (by intro j; simpa only [hl.2.2] using e.read_injOn j (hv.1 j)) hone htable + have hbp (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + b.regs i = x.regs i := (hb i hi).trans (hu i hi hne) + have hL : NodePacking.layout ((nodeState b).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hb selected (by decide), hus] + exact recordLayout_congr x b M hbp + have htableb : b.regs outputs + b.regs outputCount < b.regs heap := by + rw [hbp outputs (by decide) (by decide), hbp outputCount (by decide) (by decide), + hbp heap (by decide) (by decide)] + exact htable + have houtb : NodePacking.Output (NodePacking.layout ((nodeState b).comap kernel)) + e.read u.mem b.mem := by simpa only [hL] using hout + have hd := delivery_output b e.read u.mem houtb htableb + rw [hL] at hd + have hMexact : M = (e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits)).M := by + simpa only [hl.2.2, e.image_read, Math.Task.node, Entry.task] using hM + have hnodeExact : NodePacking.node (recordLayout x M) e.read = + e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits) := by + simp only [NodePacking.node, recordLayout, hl.2.2, e.image_read, Math.Task.node, + Entry.task] + exact congrArg (fun q => Cslib.Algorithms.ThreeSum.ChanHe.Node.mk + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset q) hMexact + choose zs hnd hsets hcounts hdata using hd.heavy + have hsets' (j : Fin 3) : (zs j).toFinset = + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M := by + simpa only [recordLayout, hl.2.2, e.image_read, Entry.task] using hsets j + have hlen (j : Fin 3) : (zs j).length ≤ (e.data j).length := by + have hh := Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset (e.task.sets j) M + rw [← hsets' j] at hh + have hc := Finset.card_le_card hh + simpa only [Entry.task, List.toFinset_card_of_nodup (hnd j), + List.toFinset_card_of_nodup (hv.1 j)] using hc + refine ⟨deliveryState b, he.mono (visit_cost_le x n (x.regs limit) (x.regs groups) M + (by intro j; rw [hl.2.2]; exact hv.length_le j) le_rfl le_rfl hMle), ?_⟩ + refine ⟨⟨M, hMexact, hMpos, hMle, zs, hnd, hsets', hlen, ?_, ?_, ?_, ?_, ?_, ?_, b.mem, + ?_, ?_, ?_⟩⟩ + · intro j + rw [deliveryState_heavy_length, hL, hout.heavy_card j, ← hsets j] + exact List.toFinset_card_of_nodup (hnd j) + · intro j + rw [deliveryState_heavy_base, hL] + · intro j i + exact hdata j i.val i.isLt + · intro i + have hp := hd.packed i.val i.isLt + rw [hnodeExact] at hp + simpa only [Block.read, recordLayout] using hp + · rw [deliveryState_count, hbp outputCount (by decide) (by decide)] + · rw [deliveryState_heap, hL] + · intro p hp + exact (hout.frame p (Or.inl hp)).trans (hprefix p hp) + · rw [deliveryState_mem, hbp outputs (by decide) (by decide), + hbp outputCount (by decide) (by decide)] + congr 1 + exact (deliveryState_packed b).trans (congrArg NodePacking.Layout.packed hL) + · intro i hi hsel hh hc ht hw + exact (deliveryState_preserves b i hh hc ht hw).trans (hbp i hi hsel) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean new file mode 100644 index 0000000000..bdd66b224d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean @@ -0,0 +1,272 @@ +/- +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.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission + +/-! +# Retained memory and pending children after a native emission + +The actual pointer-table store is below the fresh heap and outside every live input block. +These frame lemmas retain old arrays and controller parameters, and identify the three actual +compact heavy buffers with the existing finite recursion's children. + +## Main results + +* `Emission.controls`, `Emission.loaded`: retained caller registers and current entry. +* `Emission.stored`: preservation of any live array around the reserved gap. +* `Emission.children_tasks`: the exact ordered mathematical worklist after child pushes. + +These memory-frame and representation proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- An actual emission preserves each old-prefix cell except its one explicit table store. -/ +theorem Emission.prefix_preserved {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (p : ℕ) (hp : p < x.regs heap) + (hne : p ≠ x.regs outputs + x.regs outputCount) : y.mem p = x.mem p := by + rw [h.memory, Function.update_of_ne hne] + exact h.prefixMemory p hp + +/-- All persistent caller parameters survive the actual complete visit. -/ +theorem Emission.controls {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) : a.Controls y := by + apply hc.congr + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + apply h.registers i <;> + first | omega | (intro he; have := congrArg Fin.val he; norm_num at this; omega) + +/-- The current root record survives the visit until the actual child-preparation instructions. -/ +theorem Emission.loaded {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : e.Loaded y := by + have hs (i : Fin 7) : y.regs (slot i) = x.regs (slot i) := by + apply h.registers <;> + first | (dsimp [slot]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [slot] at this; omega) + refine ⟨(hs 0).trans hl.1, ?_, ?_⟩ + · intro j + fin_cases j + · exact (hs 1).trans (hl.2.1 0) + · exact (hs 3).trans (hl.2.1 1) + · exact (hs 5).trans (hl.2.1 2) + · intro j + fin_cases j + · exact (hs 2).trans (hl.2.2 0) + · exact (hs 4).trans (hl.2.2 1) + · exact (hs 6).trans (hl.2.2 2) + +/-- Old input columns below the stack or above the output table cannot be overwritten by the +single native pointer store, and both actual kernels preserve the old prefix. -/ +theorem Emission.stored {Q : Finset ℕ} {Λ : ℕ} {e d : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hs : d.Stored x.mem) (hg : d.Around a.stack (a.floor - a.stack)) + (hend : ∀ j, d.source j + (d.data j).length ≤ x.regs heap) + (htable : x.regs outputs + x.regs outputCount < a.floor) : d.Stored y.mem := by + apply hs.congr + intro j i hi + apply h.prefix_preserved _ (by have := hend j; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hstack := ha.2.1 + have hfloor := ha.2.2.1 + rcases hg j with hj | hj <;> omega + +/-- Top-first native child entries with the three actual compact heavy buffers. -/ +def Emission.children {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : List Entry := + [e.child 0 ((recordLayout x h.M).heavy 0) (h.lists 0), + e.child 1 ((recordLayout x h.M).heavy 1) (h.lists 1), + e.child 2 ((recordLayout x h.M).heavy 2) (h.lists 2)] + +/-- Actual heavy-buffer readback represents exactly the existing ordered three-child recursion. -/ +theorem Emission.children_tasks {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : + h.children.map Entry.task = e.task.children Q Λ (e.fuel - 1) := by + simp only [Emission.children, List.map_cons, List.map_nil] + apply e.task_children Q Λ (recordLayout x h.M).heavy h.lists + intro j + rw [h.sets j, h.exactModulus] + +/-- Each of the actual three heavy-child arrays retains the existing admissibility promises. -/ +theorem Emission.children_valid {Q : Finset ℕ} {Λ n U : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hv : e.Valid n U) : ∀ d ∈ h.children, d.Valid n U := by + intro d hd + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Valid n U := by + apply hv.child j _ _ (h.nodup j) + rw [h.sets j] + exact Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset _ _ + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- Each child reuses the two surviving original columns and one actual compact heavy buffer. -/ +theorem Emission.children_stored {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hs : e.Stored y.mem) : ∀ d ∈ h.children, d.Stored y.mem := by + intro d hd + have hc (j : Fin 3) := hs.child j ((recordLayout x h.M).heavy j) (h.lists j) (h.heavy j) + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The actual heap pointer advances past the old frontier. -/ +theorem Emission.heap_mono {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : x.regs heap ≤ y.regs heap := by + rw [h.heap_eq, recordLayout_endpoint] + omega + +/-- Every actual child column lies below the advanced native allocation frontier. -/ +theorem Emission.children_ends {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) + (hend : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) : + ∀ d ∈ h.children, ∀ k, d.source k + (d.data k).length ≤ y.regs heap := by + have hpacked : (recordLayout x h.M).packed ≤ (recordLayout x h.M).endpoint := by + dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, NodePacking.Layout.scratch] + omega + have heavyEnd (j : Fin 3) : + (recordLayout x h.M).heavy j + (h.lists j).length ≤ y.regs heap := by + rw [h.heap_eq] + calc (recordLayout x h.M).heavy j + (h.lists j).length + ≤ (recordLayout x h.M).heavy j + (recordLayout x h.M).n j := by + apply Nat.add_le_add_left + simpa only [recordLayout, hl.2.2] using h.length_le j + _ ≤ (recordLayout x h.M).packed := (recordLayout x h.M).heavy_end_le_packed j + _ ≤ (recordLayout x h.M).endpoint := hpacked + have hc (j k : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).source k + + ((e.child j ((recordLayout x h.M).heavy j) (h.lists j)).data k).length ≤ + y.regs heap := by + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using heavyEnd j + · simpa only [Entry.child, Function.update_of_ne he] using (hend k).trans h.heap_mono + intro d hd k + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 k + · exact hc 1 k + · exact hc 2 k + +/-- Every new heavy child begins above the reserved stack/table gap. -/ +theorem Emission.children_around {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {base capacity : ℕ} (hg : e.Around base capacity) + (hfloor : base + capacity ≤ x.regs heap) : + ∀ d ∈ h.children, d.Around base capacity := by + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Around base capacity := by + apply hg.child + have hb := (recordLayout x h.M).base_le_descriptor j + change x.regs heap ≤ (recordLayout x h.M).descriptor j at hb + dsimp only [NodePacking.Layout.heavy] + omega + intro d hd + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The exact native words prepared for the three pushes are the new semantic child records. -/ +theorem Emission.children_words {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : + childWords y 0 ++ childWords y 1 ++ childWords y 2 = h.children.flatMap Entry.words := by + rw [Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 0, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 1, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 2] + simp only [Emission.children, List.flatMap_cons, List.flatMap_nil, List.append_nil, + List.append_assoc] + +/-- The complete visit retains the actual pending stack, including its native pointer and +length registers; its sole external memory store lies above the stack reservation. -/ +theorem Emission.stack {Q : Finset ℕ} {Λ w : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hy : y.Bounded w) (vs : List ℕ) + (hr : Stack.Rep w a.stack a.capacity vs (x.comap Traversal.registers)) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + Stack.Rep w a.stack a.capacity vs (y.comap Traversal.registers) := by + have hregs (i : Fin 9) : y.regs (Traversal.registers i) = x.regs (Traversal.registers i) := by + apply h.registers <;> + first | (dsimp [Traversal.registers]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [Traversal.registers] at this; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + refine ⟨⟨fun i => hy.1 (Traversal.registers i), hy.2⟩, (hregs Stack.ptr).trans hr.pointer, + (hregs Stack.len).trans hr.length_eq, hr.capacity_le, ?_⟩ + intro i + have hi : i.val < a.capacity := lt_of_lt_of_le i.isLt hr.capacity_le + change y.mem (a.stack + i.val) = _ + rw [h.prefix_preserved _ (by have := ha.2.1; omega) + (by have := ha.2.1; omega)] + exact hr.contents i + +/-- Append the actual newly emitted packed pointer and array to the retained output sequence. -/ +theorem Emission.output {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) + (ns : List (ℕ × Cslib.Algorithms.ThreeSum.ChanHe.Node)) + (hs : StoredNodes a.U a.N a.table ns x.mem) + (hcount : x.regs outputCount = ns.length) + (hbounds : ∀ i : Fin ns.length, + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ x.regs heap) + (htable : a.table + ns.length < a.floor) (hfloor : a.floor ≤ x.regs heap) : + StoredNodes a.U a.N a.table + (ns ++ [((recordLayout x h.M).packed, e.task.node Q Λ)]) y.mem := by + have hU : x.regs magnitude = a.U := hc.2.2.1 + have hN : x.regs groups = a.N := hc.2.2.2.1 + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hpacked : x.regs heap ≤ (recordLayout x h.M).packed := by + dsimp only [recordLayout, NodePacking.Layout.packed] + omega + have hbefore : StoredNodes a.U a.N a.table ns h.before := by + apply hs.congr + · intro p _ hp + exact h.prefixMemory p (by omega) + · intro i u hu + exact h.prefixMemory _ (by have := (hbounds i).2; omega) + have hnew : (Block.mk (recordLayout x h.M).packed (4 * a.N)).Realizes h.before + (fun i => Signed.encode ((e.task.node Q Λ).oneArray a.U i.val)) := by + intro i + have hi : i.val < 4 * x.regs groups := by simpa only [hN] using i.isLt + have hp := h.packed ⟨i.val, hi⟩ + change y.mem ((recordLayout x h.M).packed + i.val) = _ at hp + rw [h.memory, Function.update_of_ne (show + (recordLayout x h.M).packed + i.val ≠ x.regs outputs + x.regs outputCount by + rw [ht, hcount]; omega)] at hp + simpa only [Block.read, hU] using hp + rw [h.memory, ht, hcount] + exact hbefore.append hnew (by intro i; have := (hbounds i).1; omega) (by omega) + +/-- For a live entry, the actual heavy children and emitted node are the exact finite step. -/ +theorem Emission.math_step {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (es : List Entry) (hf : e.fuel ≠ 0) + (hn : ∀ j, (e.data j).length ≠ 0) : + Math.step Q Λ e.task (es.map Entry.task) = + ((h.children ++ es).map Entry.task, some (e.task.node Q Λ)) := by + have hempty : ¬e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + intro he + rcases he with he | he | he + · exact hn 0 (by rw [he]; rfl) + · exact hn 1 (by rw [he]; rfl) + · exact hn 2 (by rw [he]; rfl) + rw [List.map_append, h.children_tasks] + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => + simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte, + Nat.add_sub_cancel] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean new file mode 100644 index 0000000000..174fb9c3b9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean @@ -0,0 +1,93 @@ +/- +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.TraversalProgram + +/-! +# Charged native execution of the Chan–He traversal controller + +The zero-fuel case executes setup, a complete record push, a complete record pop, both loop +tests, and the skip branch. It preserves the accumulated output count, heap and memory outside +the reserved stack. It does not silently replace a negative 3SUM instance by an empty output. + +## Main results + +* `oneRoot_zero`: exact 173-instruction execution of the complete controller at zero fuel. + +The controller composition proofs are original. General traversal correctness and the global +preprocessing composition remain part of the ongoing native construction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Even zero fuel executes all native setup and stack work. No node is emitted, and the root +record is recovered exactly. Existing output arrays and all non-stack memory are preserved. -/ +theorem oneRoot_zero {w capacity : ℕ} (x : Data 128) (hx : x.Bounded w) + (h2 : Fits w 2) (hcap : Fits w (x.regs stackBase + capacity)) + (hcapacity : 7 ≤ capacity) (hf : x.regs (slot 0) = 0) : + ∃ y, Exec w oneRoot x y 173 ∧ + Stack.Rep w (x.regs stackBase) capacity [] (y.comap registers) ∧ + (∀ i, y.regs (slot i) = x.regs (slot i)) ∧ + y.regs outputCount = x.regs outputCount ∧ y.regs heap = x.regs heap ∧ + Modifies (Stack.footprint (x.regs stackBase) capacity) x.mem y.mem := by + let a := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have ha : Exec (B := .standard) w + (.seq (.op (.const zero 0)) (.seq (.op (.const one 1)) + (.seq (.op (.copy (registers Stack.ptr) stackBase)) + (.op (.const (registers Stack.len) 0))))) x a 4 := by + ram_eval [a, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have haslots (i : Fin 7) : a.regs (slot i) = x.regs (slot i) := by + fin_cases i <;> simp +decide [a, slot, registers, Stack.ptr, Stack.len, zero, one] + have har : Stack.Rep w (x.regs stackBase) capacity [] (a.comap registers) := by + refine ⟨⟨fun i => (ha.bounded hx).1 (registers i), (ha.bounded hx).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a [] h2 hcap har + (fun i => (ha.bounded hx).1 (slot i)) (by simpa using hcapacity) + obtain ⟨c, hc, hrc, hcs, hfc, hpc⟩ := popFrame_exec b [] (fun i => a.regs (slot i)) h2 hcap hrb + have hbzero : b.regs zero = 0 := by + rw [hpb zero (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + have hczero : c.regs zero = 0 := by + rw [hpc zero (Or.inl (by decide)) (by decide), hbzero] + have hcf : c.regs (slot 0) = 0 := (hcs 0).trans ((haslots 0).trans hf) + have hblen : b.regs (registers Stack.len) = 7 := by + simpa only [Data.comap_regs, List.length_append, List.length_cons, List.length_nil, + Nat.reduceAdd] using hrb.length_eq + have hclen : c.regs (registers Stack.len) = 0 := hrc.length_eq + have hcopy : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) c c 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := c) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + have hbody : Exec w body b c 101 := by + exact hc.seq (Exec.branch_pos (by change c.regs (slot 0) = c.regs zero; omega) hcopy) + have hl : Exec w loop b c 105 := by + apply Exec.loop_once + · change b.regs zero < b.regs (registers Stack.len) + omega + · exact hbody + · change ¬c.regs zero < c.regs (registers Stack.len) + omega + refine ⟨c, ha.seq (hb.seq hl), hrc, fun i => (hcs i).trans (haslots i), ?_, ?_, ?_⟩ + · rw [hpc outputCount (Or.inl (by decide)) (by decide), + hpb outputCount (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, outputCount, zero, one] + · rw [hpc heap (Or.inl (by decide)) (by decide), hpb heap (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, heap, zero, one] + · intro p hp + exact (hfc p hp).trans (hfb p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean new file mode 100644 index 0000000000..24503ab278 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean @@ -0,0 +1,277 @@ +/- +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.TraversalModel +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Mathlib.Data.List.Nodup + +/-! +# Semantic entries of the native Chan–He work stack + +An entry pairs the seven unsigned stack words with the three signed integer lists stored at +their bases. Memory readback reuses `Block.Realizes`; set contents use `List.toFinset`; and the +mathematical task is the existing Chan–He recursion. This is a proof invariant for native code, +not an additional machine or execution semantics. + +## Main definitions + +* `Entry`: fuel, three native bases and their represented integer lists. +* `Entry.words`, `Entry.task`: native serialization and the existing mathematical task. +* `Entry.Stored`, `Entry.Loaded`: block readback and the current record's register ABI. + +## Main results + +* `Entry.image_read`: the injective-array API represents exactly the listed finite set. +* `Entry.read_injOn`: duplicate-free lists meet the actual modulus/node call premise. +* `Entry.Stored.input`: exact signed-encoded native array readback. +* `Entry.loaded_words`: the seven register words match the pending entry's serialization. + +The native worklist invariants and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One pending native record and the integer arrays represented by its pointers and lengths. -/ +structure Entry where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three data bases. -/ + source : Fin 3 → ℕ + /-- The lists stored at those bases, in their actual enumeration order. -/ + data : Fin 3 → List ℤ + +/-- Seven unsigned stack words in the exact native pop order. -/ +def Entry.words (e : Entry) : List ℕ := + [e.fuel, e.source 0, (e.data 0).length, e.source 1, (e.data 1).length, + e.source 2, (e.data 2).length] + +/-- The unchanged finite recursion task represented by an entry. -/ +def Entry.task (e : Entry) : Math.Task := ⟨e.fuel, fun j => (e.data j).toFinset⟩ + +/-- Total extension of the compact list for the existing native array APIs. -/ +def Entry.read (e : Entry) (j : Fin 3) (i : ℕ) : ℤ := (e.data j).getD i 0 + +/-- Each signed-encoded list occupies its concrete block, using the generic memory API. -/ +def Entry.Stored (e : Entry) (mem : ℕ → ℕ) : Prop := + ∀ j, (Block.mk (e.source j) (e.data j).length).Realizes mem + (fun i => Signed.encode (e.data j)[i.val]) + +/-- The current seven record registers contain this entry's actual unsigned metadata. -/ +def Entry.Loaded (e : Entry) (x : Data 128) : Prop := + x.regs (slot 0) = e.fuel ∧ + (∀ j, x.regs (arraySource j) = e.source j) ∧ + ∀ j, x.regs (arrayLength j) = (e.data j).length + +/-- Replace exactly one column by its actual compact heavy list and decrease the fuel. -/ +def Entry.child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : Entry := + ⟨e.fuel - 1, Function.update e.source j base, Function.update e.data j zs⟩ + +/-- A pending entry always occupies seven physical stack cells, including empty arrays. -/ +theorem Entry.length_words (e : Entry) : e.words.length = 7 := rfl + +/-- Inside its valid range the total read function is the actual list element. -/ +theorem Entry.read_eq (e : Entry) (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + e.read j i = (e.data j)[i] := by + simp only [Entry.read, List.getD, List.getElem?_eq_getElem hi, Option.getD_some] + +/-- The finite-set input of each existing array kernel is exactly the entry's listed set. -/ +theorem Entry.image_read (e : Entry) (j : Fin 3) : + (Finset.range (e.data j).length).image (e.read j) = (e.data j).toFinset := by + ext z + constructor + · intro hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp hz + have hi' := Finset.mem_range.mp hi + rw [e.read_eq j i hi', List.mem_toFinset] + exact List.getElem_mem hi' + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp (List.mem_toFinset.mp hz) + exact Finset.mem_image.mpr ⟨i, Finset.mem_range.mpr hi, (e.read_eq j i hi).trans he⟩ + +/-- A duplicate-free compact list meets the actual native collision-counting premise. -/ +theorem Entry.read_injOn (e : Entry) (j : Fin 3) (h : (e.data j).Nodup) : + Set.InjOn (e.read j) (↑(Finset.range (e.data j).length) : Set ℕ) := by + intro i hi k hk he + have hi' := Finset.mem_range.mp hi + have hk' := Finset.mem_range.mp hk + rw [e.read_eq j i hi', e.read_eq j k hk'] at he + exact h.getElem_inj_iff.mp he + +/-- Exact readback in the native array-call format, derived from the generic block realization. -/ +theorem Entry.Stored.input {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + mem (e.source j + i) = Signed.encode (e.read j i) := by + rw [e.read_eq j i hi] + exact h j ⟨i, hi⟩ + +/-- Readback transfers along a memory equality on the three live blocks. -/ +theorem Entry.Stored.congr {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + (hf : ∀ j i, i < (e.data j).length → after (e.source j + i) = before (e.source j + i)) : + e.Stored after := by + intro j i + exact (hf j i.val i.isLt).trans (h j i) + +/-- A prefix frame preserves every pending list whose block ends before the fresh allocation. -/ +theorem Entry.Stored.of_prefix {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + {frontier : ℕ} (hsrc : ∀ j, e.source j + (e.data j).length ≤ frontier) + (hf : ∀ a < frontier, after a = before a) : e.Stored after := by + apply h.congr + intro j i hi + exact hf _ (by have := hsrc j; omega) + +/-- The seven actual register words match the exact work-stack serialization. -/ +theorem Entry.loaded_words {e : Entry} {x : Data 128} (h : e.Loaded x) : + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] = e.words := by + rcases h with ⟨hf, hs, hn⟩ + change [x.regs (slot 0), x.regs (arraySource 0), x.regs (arrayLength 0), + x.regs (arraySource 1), x.regs (arrayLength 1), + x.regs (arraySource 2), x.regs (arrayLength 2)] = _ + simp only [hf, hs, hn, Entry.words] + +/-- Replacing a native column changes exactly the corresponding mathematical set. -/ +theorem Entry.task_child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : + (e.child j base zs).task = + ⟨e.fuel - 1, Function.update e.task.sets j zs.toFinset⟩ := by + unfold Entry.child Entry.task + congr 1 + funext k + by_cases he : k = j + · subst k + simp only [Function.update_self] + · simp only [Function.update_of_ne he] + +/-- The native child-record words represent exactly the changed data block, with the two +other columns and their original enumeration orders preserved. -/ +theorem Entry.child_words {e : Entry} {x : Data 128} (h : e.Loaded x) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hb : ∀ j, x.regs (heavyBase j) = bases j) + (hn : ∀ j, x.regs (heavyLength j) = (zs j).length) (j : Fin 3) : + childWords x j = (e.child j (bases j) (zs j)).words := by + obtain ⟨hf, hs, hl⟩ := h + have hs₀ : x.regs (slot 1) = e.source 0 := hs 0 + have hs₁ : x.regs (slot 3) = e.source 1 := hs 1 + have hs₂ : x.regs (slot 5) = e.source 2 := hs 2 + have hl₀ : x.regs (slot 2) = (e.data 0).length := hl 0 + have hl₁ : x.regs (slot 4) = (e.data 1).length := hl 1 + have hl₂ : x.regs (slot 6) = (e.data 2).length := hl 2 + fin_cases j <;> simp +decide [childWords, Entry.child, Entry.words, hf, + hs₀, hs₁, hs₂, hl₀, hl₁, hl₂, hb, hn] + +/-- The size and magnitude assumptions are the existing finite three-set admissibility notion; +duplicate-free enumeration is the additional native-array obligation. -/ +def Entry.Valid (e : Entry) (n U : ℕ) : Prop := + (∀ j, (e.data j).Nodup) ∧ Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset + +/-- Native input lengths equal finite-set cardinalities under the duplicate-free obligation. -/ +theorem Entry.Valid.length_le {e : Entry} {n U : ℕ} (h : e.Valid n U) (j : Fin 3) : + (e.data j).length ≤ n := by + have he := List.toFinset_card_of_nodup (h.1 j) + rw [← he] + fin_cases j + · exact h.2.card₁ + · exact h.2.card₂ + · exact h.2.card₃ + +/-- The existing finite-set magnitude promise bounds every actual native array element. -/ +theorem Entry.Valid.natAbs_le {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : (e.read j i).natAbs ≤ U := by + have hz : e.read j i ∈ (e.data j).toFinset := by + rw [e.read_eq j i hi, List.mem_toFinset] + exact List.getElem_mem hi + have hb : |e.read j i| ≤ (U : ℤ) := by + fin_cases j + · exact h.2.bdd₁ _ hz + · exact h.2.bdd₂ _ hz + · exact h.2.bdd₃ _ hz + rw [← Int.natCast_natAbs] at hb + exact_mod_cast hb + +/-- Replacing a column by a duplicate-free subset preserves the existing size/magnitude promises. -/ +theorem Entry.Valid.child {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (base : ℕ) (zs : List ℤ) (hnd : zs.Nodup) + (hsub : zs.toFinset ⊆ (e.data j).toFinset) : (e.child j base zs).Valid n U := by + refine ⟨?_, ?_⟩ + · intro k + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using hnd + · simpa only [Entry.child, Function.update_of_ne he] using h.1 k + · fin_cases j + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U zs.toFinset + (e.data 1).toFinset (e.data 2).toFinset + exact ⟨(Finset.card_le_card hsub).trans h.2.card₁, h.2.card₂, h.2.card₃, + h.2.bdd₁.mono hsub, h.2.bdd₂, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + zs.toFinset (e.data 2).toFinset + exact ⟨h.2.card₁, (Finset.card_le_card hsub).trans h.2.card₂, h.2.card₃, + h.2.bdd₁, h.2.bdd₂.mono hsub, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + (e.data 1).toFinset zs.toFinset + exact ⟨h.2.card₁, h.2.card₂, (Finset.card_le_card hsub).trans h.2.card₃, + h.2.bdd₁, h.2.bdd₂, h.2.bdd₃.mono hsub⟩ + +/-- A child reuses both untouched data blocks and the actual newly written heavy block. -/ +theorem Entry.Stored.child {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (base : ℕ) (zs : List ℤ) + (hz : (Block.mk base zs.length).Realizes mem (fun i => Signed.encode zs[i.val])) : + (e.child j base zs).Stored mem := by + intro k i + by_cases he : k = j + · subst k + have hi : i.val < zs.length := by + simpa only [Entry.child, Function.update_self] using i.isLt + simpa only [Entry.child, Block.read, Function.update_self] using hz ⟨i.val, hi⟩ + · have hi : i.val < (e.data k).length := by + simpa only [Entry.child, Function.update_of_ne he] using i.isLt + simpa only [Entry.child, Block.read, Function.update_of_ne he] using h k ⟨i.val, hi⟩ + +/-- Actual heavy lists and bases give exactly the existing ordered three-child worklist. -/ +theorem Entry.task_children (e : Entry) (Q : Finset ℕ) (Λ : ℕ) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hz : ∀ j, (zs j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy + (e.task.sets j) (e.task.node Q Λ).M) : + [(e.child 0 (bases 0) (zs 0)).task, (e.child 1 (bases 1) (zs 1)).task, + (e.child 2 (bases 2) (zs 2)).task] = e.task.children Q Λ (e.fuel - 1) := by + let M := (e.task.node Q Λ).M + have h₀ : Function.update e.task.sets 0 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M) = + ![Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M, + e.task.sets 1, e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₁ : Function.update e.task.sets 1 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M) = + ![e.task.sets 0, Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M, + e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₂ : Function.update e.task.sets 2 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M) = + ![e.task.sets 0, e.task.sets 1, + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M] := by + funext j + fin_cases j <;> rfl + rw [Entry.task_child, Entry.task_child, Entry.task_child, hz 0, hz 1, hz 2, h₀, h₁, h₂] + rfl + +/-- The native word-stack length records exactly seven cells per pending entry. -/ +theorem length_serialized (es : List Entry) : (es.flatMap Entry.words).length = 7 * es.length := by + induction es with + | nil => rfl + | cons e es ih => + simp only [List.flatMap_cons, List.length_append, Entry.length_words, ih, List.length_cons] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean new file mode 100644 index 0000000000..eb0a8b0a19 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean @@ -0,0 +1,181 @@ +/- +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.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalOutput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalResources +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Concrete worklist invariant for the native Chan–He traversal + +The invariant connects the reserved native stack, existing finite recursion, retained arrays, +output-pointer table and fresh heap. Remaining work pays for both future output cells and future +allocations. In particular, allocation bounds decrease with the mathematical work measure rather +than granting an unbounded heap to each kernel call. + +## Main definitions + +* `Arena`: fixed caller parameters and disjoint stack/table/heap reservations. +* `Cursor`: represented pending entries and the already emitted native pointers and nodes. +* `Cursor.Invariant`: readback, frames, exact remaining output and concrete space reservations. + +These proof invariants are original and do not add a machine model or operational oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Fixed parameters and concrete memory reservations of a complete native root traversal. -/ +structure Arena where + /-- Maximum column cardinality. -/ + n : ℕ + /-- Candidate-prime bound. -/ + m : ℕ + /-- Label-bit parameter. -/ + Λ : ℕ + /-- Node input magnitude. -/ + U : ℕ + /-- Common number of four-cell packed groups. -/ + N : ℕ + /-- Root recursion fuel. -/ + fuel : ℕ + /-- Prime bitmap base. -/ + primes : ℕ + /-- Reserved native word-stack base. -/ + stack : ℕ + /-- Reserved native word-stack capacity. -/ + capacity : ℕ + /-- Unsigned output-pointer table base. -/ + table : ℕ + /-- Reserved output-pointer table capacity. -/ + outputCapacity : ℕ + /-- First address permitted for allocated heavy and packed arrays. -/ + floor : ℕ + /-- Final upper bound on the fresh heap pointer. -/ + ceiling : ℕ + /-- Protected original memory below the stack, including the complete frontend forest. -/ + retained : ℕ → ℕ + +/-- Persistent caller registers, including actual zero and one used by native branches. -/ +def Arena.Controls (a : Arena) (x : Data 128) : Prop := + x.regs limit = a.m ∧ x.regs digits = a.Λ ∧ x.regs magnitude = a.U ∧ + x.regs groups = a.N ∧ x.regs bitmap = a.primes ∧ + x.regs primeCount = (Nat.primesLE a.m).card ∧ x.regs outputs = a.table ∧ + x.regs stackBase = a.stack ∧ x.regs stackCapacity = a.capacity ∧ + x.regs zero = 0 ∧ x.regs one = 1 + +/-- Exact register preservation transfers all persistent caller parameters. The heap and +accumulated count are deliberately absent from this fixed register set. -/ +theorem Arena.Controls.congr {a : Arena} {x y : Data 128} (h : a.Controls x) + (hp : ∀ i : Fin 128, i.val ∈ [71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 83] → + y.regs i = x.regs i) : a.Controls y := by + rcases h with ⟨hm, hΛ, hU, hN, hb, hc, ht, hs, hcap, hz, ho⟩ + exact ⟨(hp _ (by decide)).trans hm, (hp _ (by decide)).trans hΛ, + (hp _ (by decide)).trans hU, (hp _ (by decide)).trans hN, + (hp _ (by decide)).trans hb, (hp _ (by decide)).trans hc, + (hp _ (by decide)).trans ht, (hp _ (by decide)).trans hs, + (hp _ (by decide)).trans hcap, (hp _ (by decide)).trans hz, + (hp _ (by decide)).trans ho⟩ + +/-- Concrete non-overlap and capacity obligations for the reserved native layout. -/ +def Arena.Separated (a : Arena) : Prop := + a.primes + a.m < a.stack ∧ a.stack + a.capacity ≤ a.table ∧ + a.table + a.outputCapacity ≤ a.floor ∧ 7 * (2 * a.fuel + 1) ≤ a.capacity + +/-- A single signed envelope covers both actual native kernels at every worklist transition. -/ +def Arena.Ready (a : Arena) (w : ℕ) : Prop := + 2 ≤ a.m ∧ 2 * a.m ^ 2 ≤ a.N ∧ + GenericRAM.Signed.Fits w ((envelope a.ceiling a.n a.m a.Λ a.U a.N : ℕ) : ℤ) + +/-- The actual native bitmap is retained below the reserved stack. -/ +def Arena.Bitmap (a : Arena) (mem : ℕ → ℕ) : Prop := + ∀ q ≤ a.m, mem (a.primes + q) = if q.Prime then 1 else 0 + +/-- Proof state of the actual stack loop; no part of this structure is evaluated by the program. -/ +structure Cursor where + /-- Top-first pending entries. -/ + pending : List Entry + /-- Packed base pointers and their existing mathematical nodes in emitted order. -/ + emitted : List (ℕ × Node) + +/-- Remaining transitions include empty entries and zero-fuel leaves. -/ +def Cursor.work (s : Cursor) : ℕ := Math.potential (s.pending.map Entry.task) + +/-- Full readback and allocation invariant at the actual native loop header. -/ +structure Cursor.Invariant (a : Arena) (w : ℕ) (target : List Node) + (s : Cursor) (x : Data 128) : Prop where + /-- All actual native words fit. -/ + bounded : x.Bounded (w + 2) + /-- Persistent caller parameters have not changed. -/ + controls : a.Controls x + /-- The actual bitmap still represents all candidate primes. -/ + bitmap : a.Bitmap x.mem + /-- The actual word stack represents exactly the pending entries. -/ + stack : Stack.Rep (w + 2) a.stack a.capacity + (s.pending.flatMap Entry.words) (x.comap registers) + /-- The existing finite depth invariant bounds pending entries. -/ + depth : Math.Bounded a.fuel (s.pending.map Entry.task) + /-- Every pending entry retains its duplicate-free existing admissibility promise. -/ + valid : ∀ e ∈ s.pending, e.Valid a.n a.U + /-- Every pending array has exact signed-encoded native readback. -/ + stored : ∀ e ∈ s.pending, e.Stored x.mem + /-- Pending arrays avoid the entire reserved stack and output-table gap. -/ + around : ∀ e ∈ s.pending, e.Around a.stack (a.floor - a.stack) + /-- Every pending array lies below the current fresh allocation. -/ + ends : ∀ e ∈ s.pending, ∀ j, e.source j + (e.data j).length ≤ x.regs heap + /-- The fresh heap never enters the reserved table or stack. -/ + floor : a.floor ≤ x.regs heap + /-- Remaining work reserves enough space for every later allocation. -/ + heapBudget : x.regs heap + s.work * allocationSize a.n a.m a.N ≤ a.ceiling + /-- The accumulated native count equals the actual emitted sequence length. -/ + count : x.regs outputCount = s.emitted.length + /-- Remaining work reserves enough output-table cells. -/ + tableBudget : s.emitted.length + s.work ≤ a.outputCapacity + /-- Every emitted pointer and every packed cell has exact existing-node readback. -/ + output : StoredNodes a.U a.N a.table s.emitted x.mem + /-- Retained packed arrays lie above the reserved output table and below the fresh heap. -/ + outputBounds : ∀ i : Fin s.emitted.length, + a.floor ≤ s.emitted[i.val].1 ∧ s.emitted[i.val].1 + 4 * a.N ≤ x.regs heap + /-- The emitted prefix followed by the exact pending recursion is the target sequence. -/ + exactOutput : s.emitted.map Prod.snd ++ + Math.output (Nat.primesLE a.m) a.Λ (s.pending.map Entry.task) = target + /-- Every original cell below the reserved stack remains available to subsequent roots. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- A live loop has at least one pending entry, since each entry occupies seven actual words. -/ +theorem Cursor.Invariant.nonempty {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : (Cond.lt zero (registers Stack.len)).Holds x) : s.pending ≠ [] := by + intro he + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + change x.regs zero < x.regs (registers Stack.len) at hc + simp only [he, List.flatMap_nil, List.length_nil, Data.comap_regs] at hl + omega + +/-- A finished actual loop has emitted the complete target, with every array already read back. -/ +theorem Cursor.Invariant.finished {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : ¬(Cond.lt zero (registers Stack.len)).Holds x) : + s.pending = [] ∧ s.emitted.map Prod.snd = target := by + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + rw [length_serialized] at hl + change ¬x.regs zero < x.regs (registers Stack.len) at hc + change x.regs (registers Stack.len) = 7 * s.pending.length at hl + have he : s.pending = [] := List.eq_nil_of_length_eq_zero (by omega) + refine ⟨he, ?_⟩ + simpa only [he, List.map_nil, Math.output, List.flatMap_nil, List.append_nil] using h.exactOutput + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean new file mode 100644 index 0000000000..efed94f724 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean @@ -0,0 +1,259 @@ +/- +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.TraversalAdmitted +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete live transitions of the native Chan–He worklist + +One transition executes the seven pops, both complete modulus searches, all three actual +histogram/packing calls, output delivery and the three reversed child pushes. Its invariant +preserves exact existing-node output, live arrays, the actual prime bitmap, and the space +reservations paid for by the strictly decreasing work measure. + +## Main results + +* `live_transition`: complete actual native body execution and full invariant preservation. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Execute and verify the whole live native worklist transition. -/ +theorem live_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) : + ∃ (s : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s.Invariant a w target y ∧ s.work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨p, hp, hstack, hl, hframe, hpr⟩ := pop_entry x e es h2 hcap h.stack + have preg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + p.regs i = x.regs i := hpr i (Or.inl hi') (Or.inr hi) + have ph : p.regs heap = x.regs heap := preg _ (by decide) (by decide) + have pc : p.regs outputCount = ns.length := (preg _ (by decide) (by decide)).trans h.count + have hc : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact preg i (by omega) (by omega)) + have frame (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : p.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hh := ha.2.2.1 + omega + have oldStored (d : Entry) (hd : d ∈ e :: es) : d.Stored p.mem := + (h.stored d hd).of_stack_frame ((h.around d hd).mono_capacity gap) hframe + have oldEnds (d : Entry) (hd : d ∈ e :: es) (j : Fin 3) : + d.source j + (d.data j).length ≤ p.regs heap := by rw [ph]; exact h.ends d hd j + have oldBitmap : a.Bitmap p.mem := by + intro q hq + rw [frame _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + have oldOutput : StoredNodes a.U a.N a.table ns p.mem := by + apply h.output.congr + · intro q hq _ + exact frame _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact frame _ (Or.inr (by + have hh := (h.outputBounds i).1 + have ht := ha.2.1 + have hh' := ha.2.2.1 + omega)) + have oldBounds (i : Fin ns.length) : + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ p.regs heap := by + rw [ph] + exact h.outputBounds i + have hwork : 1 ≤ (Cursor.mk (e :: es) ns).work := by + have hw := Math.work_pos e.fuel + change 1 ≤ Math.work e.fuel + Math.potential (es.map Entry.task) + omega + have htable : a.table + ns.length < a.floor := by + have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + have hh := ha.2.2.1 + omega + have hfloor : a.floor ≤ p.regs heap := by rw [ph]; exact h.floor + have hheap : p.regs heap ≤ a.ceiling := by + rw [ph] + have hb := h.heapBudget + omega + have htablep : p.regs outputs + p.regs outputCount < a.floor := by + rw [hc.2.2.2.2.2.2.1, pc] + exact htable + obtain ⟨v, hv, ⟨em⟩⟩ := emit_admitted p e ha hw hc hl (h.valid e (by simp)) + (oldStored e (by simp)) oldBitmap hfloor hheap (oldEnds e (by simp)) (by omega) + let s : Cursor := ⟨em.children ++ es, ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)]⟩ + have mathStep := em.math_step es hf hn + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at hdec + change s.work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + have depth : Math.Bounded a.fuel (s.pending.map Entry.task) := by + have hd := Math.bounded_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) h.depth + rw [mathStep] at hd + exact hd + have capacity : 21 + (es.flatMap Entry.words).length ≤ a.capacity := by + have hd := depth.length_le + rw [List.length_map] at hd + change (em.children ++ es).length ≤ 2 * a.fuel + 1 at hd + simp only [Emission.children, List.length_append, List.length_cons, List.length_nil] at hd + rw [length_serialized] + have hh := ha.2.2.2 + omega + have vcontrols := em.controls hc + have vstack := em.stack ha hc (hv.bounded (hp.bounded h.bounded)) + (es.flatMap Entry.words) hstack (by omega) + obtain ⟨y, hy, hystack, hyframe, hyr⟩ := pushChildren_exec v (es.flatMap Entry.words) + (hv.bounded (hp.bounded h.bounded)) vcontrols.2.2.2.2.2.2.2.2.2.2 h2 hcap vstack capacity + have yreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = v.regs i := hyr i (Or.inl ⟨hi, hi'⟩) + have yframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : y.mem q = v.mem q := by + apply hyframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hbody := body_live hp hl hc.2.2.2.2.2.2.2.2.2.1 hf hn hv hy + have vsStored := em.stored ha hc (oldStored e (by simp)) (h.around e (by simp)) + (oldEnds e (by simp)) htablep + have newStored : ∀ d ∈ s.pending, d.Stored v.mem := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_stored vsStored d hd + · exact em.stored ha hc (oldStored d (List.mem_cons_of_mem e hd)) + (h.around d (List.mem_cons_of_mem e hd)) (oldEnds d (List.mem_cons_of_mem e hd)) htablep + have newAround : ∀ d ∈ s.pending, d.Around a.stack (a.floor - a.stack) := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_around (h.around e (by simp)) (by omega) d hd + · exact h.around d (List.mem_cons_of_mem e hd) + have newEnds : ∀ d ∈ s.pending, ∀ j, d.source j + (d.data j).length ≤ v.regs heap := by + intro d hd j + rcases List.mem_append.mp hd with hd | hd + · exact em.children_ends hl (oldEnds e (by simp)) d hd j + · exact (oldEnds d (List.mem_cons_of_mem e hd) j).trans em.heap_mono + have houtput := em.output hc ns oldOutput pc oldBounds htable hfloor + have yh : y.regs heap = v.regs heap := yreg _ (by decide) (by decide) + have heapAllocation : v.regs heap ≤ p.regs heap + allocationSize a.n a.m a.N := by + rw [em.heap_eq] + apply allocation_le + · intro j + rw [hl.2.2] + exact (h.valid e (by simp)).length_le j + · exact hc.2.2.2.1.le + · simpa only [hc.1] using em.upper + refine ⟨s, y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, vcontrols.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact yreg i (by omega) (by omega)), ?_, ?_, depth, ?_, ?_, newAround, ?_, ?_, ?_, ?_, + ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [yframe _ (Or.inl (by have := ha.1; omega)), + em.prefix_preserved _ (by have := ha.1; have := ha.2.1; omega) (by + have ht := hc.2.2.2.2.2.2.1 + have hq' := ha.1 + have hh := ha.2.1 + omega)] + exact oldBitmap q hq + · simpa only [s, em.children_words hl, List.flatMap_append, List.append_assoc] using hystack + · intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_valid (h.valid e (by simp)) d hd + · exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (newStored d hd).of_stack_frame ((newAround d hd).mono_capacity gap) hyframe + · intro d hd j + rw [yh] + exact newEnds d hd j + · rw [yh] + exact hfloor.trans em.heap_mono + · rw [yh] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) hdec + rw [Nat.add_mul, Nat.one_mul] at hd + rw [ph] at heapAllocation + omega + · rw [yreg outputCount (by decide) (by decide), em.count, pc] + simp only [s, List.length_append, List.length_singleton] + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change (ns ++ [_]).length + s.work ≤ a.outputCapacity + simp only [List.length_append, List.length_singleton] + omega + · apply houtput.congr + · intro q hq _ + exact yframe _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + have low : a.floor ≤ s.emitted[i.val].1 := by + by_cases hi : i.val < ns.length + · simpa only [s, List.getElem_append_left hi] using (oldBounds ⟨i.val, hi⟩).1 + · have hi' := i.isLt + have he : i.val = ns.length := by simp only + [List.length_append, List.length_singleton] at hi'; omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 + simp only [he, hg] + dsimp only [NodePacking.Layout.packed, recordLayout] + omega + change a.floor ≤ (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[i.val].1 at low + exact yframe _ (Or.inr (by have ht := ha.2.1; have hh := ha.2.2.1; omega)) + · intro i + rw [yh, em.heap_eq] + by_cases hi : i.val < ns.length + · simp only [s, List.getElem_append_left hi] + exact ⟨(oldBounds ⟨i.val, hi⟩).1, + (oldBounds ⟨i.val, hi⟩).2.trans (em.heap_eq ▸ em.heap_mono)⟩ + · have hi' := i.isLt + have he : i.val = ns.length := by + dsimp only [s] at hi' + simp only [List.length_append, List.length_singleton] at hi' + omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 ∧ (ns ++ [_])[i.val].1 + 4 * a.N ≤ _ + simp only [he, hg] + constructor + · dsimp only [NodePacking.Layout.packed, recordLayout] + omega + · dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, + NodePacking.Layout.scratch, recordLayout] + have hN := hc.2.2.2.1 + omega + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at he + simpa only [s, List.map_append, List.map_singleton, List.map_cons, Option.toList_some, + List.singleton_append, List.append_assoc, List.map_nil, List.nil_append, he] + using h.exactOutput + · intro q hq + rw [yframe q (Or.inl hq), em.prefix_preserved q (by + have ht := ha.2.1 + have hf' := ha.2.2.1 + omega) (by + have ht := ha.2.1 + have hp' := hc.2.2.2.2.2.2.1 + omega), frame q (Or.inl hq)] + exact h.protectedMemory q hq + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean new file mode 100644 index 0000000000..12a14f3727 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean @@ -0,0 +1,84 @@ +/- +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.TraversalLive +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalSkip +public import Complexity.Computability.WordRAM.GhostLoop + +/-! +# Complete bounded native Chan–He worklist loop + +Each body execution is discharged by the actual pop, modulus, histogram, packing and push +programs. The explicit proof-state loop rule then counts every pending entry, including skipped +calls. Termination returns the exact finite preorder, whose native pointer table and packed +arrays have full readback. The arena bounds cover all live memory throughout the loop. + +## Main results + +* `transition`: actual native body execution and invariant preservation for every pending entry. +* `loop_exec`: the complete actual native loop terminates with exact finite node output. + +These native composition and resource proofs are original. Initialized-input preprocessing +composition is a separate obligation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Every actual live loop header admits a native body execution with the same finite program. -/ +theorem transition {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) (hc : (Cond.lt zero (registers Stack.len)).Holds x) : + ∃ (s' : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s'.Invariant a w target y ∧ s'.work < s.work := by + rcases s with ⟨pending, ns⟩ + cases pending with + | nil => exact (h.nonempty hc rfl).elim + | cons e es => + by_cases hs : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0 + · obtain ⟨y, hy, hi, hd⟩ := skip_transition e es ns x ha h2 hcap h hs + exact ⟨⟨es, ns⟩, y, hy.mono (by omega), hi, hd⟩ + · apply live_transition e es ns x ha hw h2 hcap h + · intro he + exact hs (Or.inl he) + · intro j + fin_cases j + · exact fun he => hs (Or.inr (Or.inl he)) + · exact fun he => hs (Or.inr (Or.inr (Or.inl he))) + · exact fun he => hs (Or.inr (Or.inr (Or.inr he))) + +/-- Execute the complete actual native loop. Every call and fit is discharged by `transition`; +the final emitted list equals the entire target, with exact pointer and packed-array readback. -/ +theorem loop_exec {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) : + ∃ (ns : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) loop x y (s.work * (visitTime a.n a.m a.N + 316) + 2) ∧ + (Cursor.mk [] ns).Invariant a w target y ∧ ns.map Prod.snd = target := by + obtain ⟨s', y, he, hi, hexit⟩ := ExecWithin.loop_invariant_state + (fun s x => s.Invariant a w target x) Cursor.work + (transition ha hw h2 hcap) h + obtain ⟨hp, hout⟩ := hi.finished hexit + refine ⟨s'.emitted, y, ?_, ?_, hout⟩ + · simpa only [loop, Nat.add_assoc, Nat.reduceAdd] using he + · cases s' with + | mk pending ns => + change pending = [] at hp + subst pending + exact hi + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean new file mode 100644 index 0000000000..3281b4adb9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean @@ -0,0 +1,169 @@ +/- +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.Recursion + +/-! +# A bounded worklist for the exact Chan–He recursion + +The worklist expands the existing `ChanHe.nodes` in its existing preorder. Empty triples and +zero fuel are popped without emitting an instance. Three children replace one set each by its +existing heavy subset, in the order used by the finite reduction. + +This is the mathematical invariant for the native traversal, not a native execution contract. +In particular, expanding a worklist entry here does not discharge a native modulus or node call. +The stack bound counts pending entries, including empty and zero-fuel entries. Those entries +still require charged native pop and branch instructions. + +## Main definitions + +* `Task`: one pending three-set call, including its remaining fuel. +* `step`: one exact worklist transition and its optional emitted node. +* `potential`: a natural-valued bound on all remaining transitions, including skipped entries. +* `Bounded`: a stack-depth invariant giving at most `2f+1` pending entries for root fuel `f`. + +## Main results + +* `output_step`: one transition preserves the exact preorder output. +* `potential_step`: every transition strictly decreases the charged-transition bound. +* `bounded_step`, `Bounded.length_le`: the explicit stack has linear capacity in root fuel. + +These worklist invariants are original. They reuse the finite recursion of [ChanHe2020]. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +namespace Math + +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- One pending call of the existing finite recursion. -/ +structure Task where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three mathematical sets represented by the native descriptor. -/ + sets : Fin 3 → Finset ℤ + +/-- The exact existing preorder associated with one pending call. -/ +def Task.output (Q : Finset ℕ) (Λ : ℕ) (t : Task) : List Node := + nodes Q Λ t.fuel (t.sets 0) (t.sets 1) (t.sets 2) + +/-- The exact existing modulus and three sets of a nonempty call. -/ +def Task.node (Q : Finset ℕ) (Λ : ℕ) (t : Task) : Node := + ⟨t.sets 0, t.sets 1, t.sets 2, modulus Q Λ (t.sets 0) (t.sets 1) (t.sets 2)⟩ + +/-- Whether a call is skipped because one of its sets is empty. -/ +abbrev Task.empty (t : Task) : Prop := t.sets 0 = ∅ ∨ t.sets 1 = ∅ ∨ t.sets 2 = ∅ + +/-- The three children, with exactly the mathematical reduction's order and heavy sets. -/ +def Task.children (Q : Finset ℕ) (Λ f : ℕ) (t : Task) : List Task := + let M := (t.node Q Λ).M + [⟨f, ![heavy (t.sets 0) M, t.sets 1, t.sets 2]⟩, + ⟨f, ![t.sets 0, heavy (t.sets 1) M, t.sets 2]⟩, + ⟨f, ![t.sets 0, t.sets 1, heavy (t.sets 2) M]⟩] + +/-- The sequence still to be emitted from a top-first worklist. -/ +def output (Q : Finset ℕ) (Λ : ℕ) (ts : List Task) : List Node := + ts.flatMap (Task.output Q Λ) + +/-- One pop, followed either by a skip or by one emission and three children. -/ +def step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : List Task × Option Node := + match t.fuel with + | 0 => (ts, none) + | f + 1 => if t.empty then (ts, none) else (t.children Q Λ f ++ ts, some (t.node Q Λ)) + +/-- One worklist transition preserves the exact output list, including order and skipped calls. -/ +theorem output_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + output Q Λ (t :: ts) = + (step Q Λ t ts).2.toList ++ output Q Λ (step Q Λ t ts).1 := by + cases t with + | mk f S => + cases f with + | zero => simp [output, step, Task.output, nodes] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes] + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes, Task.children, Task.node, + List.append_assoc] + +/-- Number of transitions for the full ternary tree, counting its zero-fuel leaves. -/ +def work : ℕ → ℕ + | 0 => 1 + | f + 1 => 1 + 3 * work f + +/-- Every pending entry costs at least one pop/skip transition. -/ +theorem work_pos (f : ℕ) : 1 ≤ work f := by + cases f <;> simp [work] + +/-- Closed form without a truncated quotient. -/ +theorem work_eq (f : ℕ) : 2 * work f + 1 = 3 ^ (f + 1) := by + induction f with + | zero => decide + | succ f ih => + simp only [work, pow_succ] at * + omega + +/-- The sum of the transition budgets of all pending entries. -/ +def potential (ts : List Task) : ℕ := (ts.map fun t => work t.fuel).sum + +/-- One transition spends at least one unit, including the empty and zero-fuel cases. -/ +theorem potential_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + potential (step Q Λ t ts).1 + 1 ≤ potential (t :: ts) := by + cases t with + | mk f S => + cases f with + | zero => simp [step, potential, work] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [step, he, ↓reduceIte, potential, List.map_cons, List.sum_cons] + have := work_pos (f + 1) + omega + · simp only [step, he, ↓reduceIte, potential, Task.children, List.map_append, + List.map_cons, List.map_nil, List.sum_append, List.sum_cons, List.sum_nil, work] + omega + +/-- Each entry's fuel plus the pending suffix gives a bound measured from the stack bottom. -/ +def Bounded (f : ℕ) : List Task → Prop + | [] => True + | t :: ts => 2 * t.fuel + ts.length ≤ 2 * f ∧ Bounded f ts + +/-- A single root of fuel `f` satisfies the stack invariant. -/ +theorem bounded_singleton (t : Task) : Bounded t.fuel [t] := by + simp [Bounded] + +/-- The explicit stack requires at most `2f+1` entries, even with zero-fuel entries present. -/ +theorem Bounded.length_le {f : ℕ} {ts : List Task} (h : Bounded f ts) : + ts.length ≤ 2 * f + 1 := by + cases ts with + | nil => simp + | cons t ts => + simp only [Bounded] at h + simp only [List.length_cons] + omega + +/-- A native pop/skip or three-child push preserves the same fixed stack capacity. -/ +theorem bounded_step {f : ℕ} (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) + (h : Bounded f (t :: ts)) : Bounded f (step Q Λ t ts).1 := by + obtain ⟨hf, hts⟩ := h + cases ht : t.fuel with + | zero => simpa only [step, ht] using hts + | succ g => + by_cases he : t.empty + · simpa only [step, ht, he, ↓reduceIte] using hts + · simp only [step, ht, he, ↓reduceIte, Task.children, List.cons_append, + List.nil_append, Bounded, List.length_cons] at * + exact ⟨by omega, by omega, by omega, hts⟩ + +end Math + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean new file mode 100644 index 0000000000..390e750a8e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean @@ -0,0 +1,94 @@ +/- +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.FromNumbers +public import Complexity.Computability.WordRAM.ListArray +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Exact memory output of native Chan–He traversal + +The pointer table is an unsigned word list. Each pointed-to block contains the existing +`Node.oneArray`, encoded with the signed integer encoding, at the common finite length `4N`. +This reuses the generic block/list realization API and introduces no alternative convolution +predicate or execution model. + +## Main definitions + +* `StoredNodes`: exact pointer-table and packed-array readback for the emitted preorder. + +## Main results + +* `StoredNodes.congr`: transfer exact readback along a frame on its live cells. +* `StoredNodes.append`: one actual pointer-table store appends a represented native node. + +The concrete output invariant and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- The exact finite output table and its signed-encoded packed node arrays. -/ +structure StoredNodes (U N table : ℕ) (ns : List (ℕ × Node)) (mem : ℕ → ℕ) : Prop where + /-- The table contains exactly the native base pointers in emitted order. -/ + pointers : Block.HoldsList table (ns.map Prod.fst) mem + /-- Every pointed-to word equals the existing mathematical packed array cell. -/ + cells : ∀ i : Fin ns.length, (Block.mk ns[i.val].1 (4 * N)).Realizes mem + (fun u => Signed.encode (ns[i.val].2.oneArray U u.val)) + +/-- Empty traversal output imposes no pointed-to array obligations. -/ +theorem storedNodes_nil (U N table : ℕ) (mem : ℕ → ℕ) : StoredNodes U N table [] mem := + ⟨Block.holdsList_nil table mem, fun i => Fin.elim0 i⟩ + +/-- Pointer-table and packed-array readback survive a frame on all their live cells. -/ +theorem StoredNodes.congr {U N table : ℕ} {ns : List (ℕ × Node)} {before after : ℕ → ℕ} + (h : StoredNodes U N table ns before) + (ht : ∀ a, table ≤ a → a < table + ns.length → after a = before a) + (hc : ∀ i : Fin ns.length, ∀ u < 4 * N, + after (ns[i.val].1 + u) = before (ns[i.val].1 + u)) : + StoredNodes U N table ns after := by + refine ⟨h.pointers.frame (by simpa only [List.length_map] using ht), ?_⟩ + intro i u + exact (hc i u.val u.isLt).trans (h.cells i u) + +/-- Appending one native pointer preserves all packed readback. The stored pointer lies below +both the old arrays and the fresh array, so the actual store cannot overwrite array data. -/ +theorem StoredNodes.append {U N table p : ℕ} {ν : Node} {ns : List (ℕ × Node)} + {mem : ℕ → ℕ} (h : StoredNodes U N table ns mem) + (hnew : (Block.mk p (4 * N)).Realizes mem (fun u => Signed.encode (ν.oneArray U u.val))) + (hold : ∀ i : Fin ns.length, table + ns.length < ns[i.val].1) + (hp : table + ns.length < p) : + StoredNodes U N table (ns ++ [(p, ν)]) (Function.update mem (table + ns.length) p) := by + refine ⟨?_, ?_⟩ + · simpa only [List.map_append, List.map_singleton, List.length_map] using + h.pointers.append_one p + · intro i + by_cases hi : i.val < ns.length + · rw [List.getElem_append_left hi] + apply (h.cells ⟨i.val, hi⟩).update_of_not_owns + intro ha + have hb := hold ⟨i.val, hi⟩ + exact (not_le_of_gt hb) ha.1 + · have hil := i.isLt + simp only [List.length_append, List.length_singleton] at hil + have he : i.val = ns.length := by omega + have hg : (ns ++ [(p, ν)])[ns.length] = (p, ν) := by simp + intro u + change Function.update mem (table + ns.length) p + ((ns ++ [(p, ν)])[i.val].1 + u.val) = + Signed.encode ((ns ++ [(p, ν)])[i.val].2.oneArray U u.val) + simp only [he, hg] + rw [Function.update_of_ne (show p + u.val ≠ table + ns.length by omega)] + exact hnew u + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean new file mode 100644 index 0000000000..0b56da19f9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean @@ -0,0 +1,98 @@ +/- +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.TraversalFrames + +/-! +# Live arrays around the native traversal stack + +The word stack occupies a reserved gap between the original arrays and the fresh heap. +Pending arrays lie entirely below the gap or start above it. Actual stack writes therefore +preserve their signed readback. Popping a serialized entry restores its seven registers and +retains every pending array. + +## Main definitions + +* `Entry.Around`: an array ends below the stack or starts above its reserved end. + +## Main results + +* `Entry.Stored.of_stack_frame`: stack writes preserve live arrays. +* `pop_entry`: the actual seven-pop program restores the semantic entry. + +These memory-layout and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Every column lies entirely on one side of the reserved stack interval. -/ +def Entry.Around (e : Entry) (base capacity : ℕ) : Prop := + ∀ j, e.source j + (e.data j).length ≤ base ∨ base + capacity ≤ e.source j + +/-- Avoiding a larger reserved gap also avoids each smaller gap with the same base. -/ +theorem Entry.Around.mono_capacity {e : Entry} {base small large : ℕ} + (h : e.Around base large) (hle : small ≤ large) : e.Around base small := by + intro j + rcases h j with hj | hj + · exact Or.inl hj + · exact Or.inr (by omega) + +/-- A concrete stack frame cannot alter any live column around its reserved interval. -/ +theorem Entry.Stored.of_stack_frame {e : Entry} {before after : ℕ → ℕ} + {base capacity : ℕ} (h : e.Stored before) (hg : e.Around base capacity) + (hf : Modifies (Stack.footprint base capacity) before after) : e.Stored after := by + apply h.congr + intro j i hi + apply hf + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + rcases hg j with hj | hj <;> omega + +/-- A newly allocated heavy block remains above the stack; untouched columns retain their gap. -/ +theorem Entry.Around.child {e : Entry} {base capacity p : ℕ} (h : e.Around base capacity) + (j : Fin 3) (zs : List ℤ) (hp : base + capacity ≤ p) : + (e.child j p zs).Around base capacity := by + intro k + by_cases he : k = j + · subst k + exact Or.inr (by simpa only [Entry.child, Function.update_self] using hp) + · simpa only [Entry.child, Function.update_of_ne he] using h k + +/-- Actual native popping restores the semantic entry, preserves the remaining stack and +changes memory only inside the reserved stack interval. -/ +theorem pop_entry {w base capacity : ℕ} (x : Data 128) (e : Entry) (es : List Entry) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity ((e :: es).flatMap Entry.words) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ + Stack.Rep w base capacity (es.flatMap Entry.words) (y.comap registers) ∧ + e.Loaded y ∧ Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + let a : Fin 7 → ℕ := ![e.fuel, e.source 0, (e.data 0).length, e.source 1, + (e.data 1).length, e.source 2, (e.data 2).length] + have hrep : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ es.flatMap Entry.words) + (x.comap registers) := by + simpa +decide [a, List.flatMap_cons, Entry.words] using hr + obtain ⟨y, hy, hry, hslots, hframe, hp⟩ := popFrame_exec x _ a h2 hcap hrep + refine ⟨y, hy, hry, ⟨hslots 0, ?_, ?_⟩, hframe, hp⟩ + · intro j + fin_cases j + · exact hslots 1 + · exact hslots 3 + · exact hslots 5 + · intro j + fin_cases j + · exact hslots 2 + · exact hslots 4 + · exact hslots 6 + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean new file mode 100644 index 0000000000..7a634d2f6d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean @@ -0,0 +1,234 @@ +/- +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.TraversalStack +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec + +/-! +# Fixed native controller for one Chan–He recursion tree + +The controller pops actual seven-word records, skips zero fuel or empty inputs, computes the +modulus, constructs the complete heavy and packed outputs, records the packed pointer, and +pushes the three children. Children are pushed third, second, first so the existing native +stack visits them in the mathematical preorder. Fresh allocations follow every live node; +all earlier arrays, the prime bitmap, stack reservation and output table precede the heap. + +This module defines executable code. Its correctness and resource proofs belong to the +traversal development; these definitions alone do not certify a native preprocessing bound. +The frontend and initialized-input composition remain separate. + +## Main definitions + +* `oneRoot`: the finite native traversal controller, independent of fuel, input sizes and width. +* `visit`: complete native modulus and node construction, including output delivery. +* `pushChildren`: actual reverse-order stack pushes preserving the original three descriptors. + +The native code is original and reuses the existing finite Chan–He construction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The complete node kernels occupy registers 0–63. -/ +def kernel (i : Fin 64) : Fin 128 := ⟨i.val, by omega⟩ + +/-- The supplied candidate-prime bound. -/ +abbrev limit : Fin 128 := 71 +/-- The supplied label-bit parameter. -/ +abbrev digits : Fin 128 := 72 +/-- The common input-magnitude bound of the tree. -/ +abbrev magnitude : Fin 128 := 73 +/-- The common number of packed four-cell groups. -/ +abbrev groups : Fin 128 := 74 +/-- The prime bitmap, retained below the heap. -/ +abbrev bitmap : Fin 128 := 75 +/-- The exact number of candidate primes. -/ +abbrev primeCount : Fin 128 := 76 +/-- The current fresh allocation base. -/ +abbrev heap : Fin 128 := 77 +/-- The reserved output-pointer table. -/ +abbrev outputs : Fin 128 := 78 +/-- Accumulated output count; this is not reset between roots. -/ +abbrev outputCount : Fin 128 := 79 +/-- The reserved word-stack base. -/ +abbrev stackBase : Fin 128 := 80 +/-- The reserved word-stack capacity, used by the safety specification. -/ +abbrev stackCapacity : Fin 128 := 81 +/-- Controller constant zero. -/ +abbrev zero : Fin 128 := 82 +/-- Controller constant one. -/ +abbrev one : Fin 128 := 83 +/-- Controller address temporary. -/ +abbrev temporary : Fin 128 := 85 +/-- The computed unsigned modulus. -/ +abbrev selected : Fin 128 := 87 +/-- The three computed heavy lengths. -/ +def heavyLength (j : Fin 3) : Fin 128 := ⟨88 + j.val, by omega⟩ +/-- The three computed heavy bases. -/ +def heavyBase (j : Fin 3) : Fin 128 := ⟨91 + j.val, by omega⟩ +/-- Current packed-array base. -/ +abbrev packed : Fin 128 := 94 +/-- One-past-end of the current node allocation. -/ +abbrev endpoint : Fin 128 := 95 +/-- Saved original descriptor words, outside both kernels and the stack dispatcher. -/ +def original (i : Fin 4) : Fin 128 := ⟨105 + i.val, by omega⟩ + +/-- Fourteen actual instructions deliver the modulus program's complete caller ABI. -/ +def prepareModulus : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (CountCall.source 0) + let l₀ := kernel (CountCall.length 0) + let s₁ := kernel (CountCall.source 1) + let l₁ := kernel (CountCall.length 1) + let s₂ := kernel (CountCall.source 2) + let l₂ := kernel (CountCall.length 2) + let scratch := kernel CountCall.scratch + let histogram := kernel CountCall.histogram + let m := kernel Search.limit + let b := kernel Search.primes + let c := kernel CollisionTest.card + let d := kernel Modulus.digits + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + scratch ← heap; histogram ← heap + n₀; histogram ← histogram + n₁; + histogram ← histogram + n₂; m ← limit; b ← bitmap; c ← primeCount; d ← digits ] + +/-- Ten actual copies deliver the complete node builder's caller ABI. -/ +def prepareNode : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (NodePacking.source 0) + let l₀ := kernel (NodePacking.length 0) + let s₁ := kernel (NodePacking.source 1) + let l₁ := kernel (NodePacking.length 1) + let s₂ := kernel (NodePacking.source 2) + let l₂ := kernel (NodePacking.length 2) + let b := kernel NodePacking.base + let n := kernel NodePacking.groups + let u := kernel NodePacking.magnitude + let m := kernel NodePacking.modulus + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + b ← heap; n ← groups; u ← magnitude; m ← selected ] + +/-- Read the three actual count cells, record the packed pointer, and advance the fresh heap. -/ +def deliver : Code .standard 128 := + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let h₀ := heavyBase 0 + let h₁ := heavyBase 1 + let h₂ := heavyBase 2 + let c₀ := heavyLength 0 + let c₁ := heavyLength 1 + let c₂ := heavyLength 2 + ram[ packed ← #3; packed ← heap + packed; packed ← packed + n₀; + packed ← packed + n₁; packed ← packed + n₂; + temporary ← outputs + outputCount; *temporary ← packed; + outputCount ← outputCount + one; c₀ ← *heap; h₀ ← heap + one; + temporary ← h₀ + n₀; c₁ ← *temporary; h₁ ← temporary + one; + temporary ← h₁ + n₁; c₂ ← *temporary; h₂ ← temporary + one; + endpoint ← groups + groups; endpoint ← endpoint + endpoint; + endpoint ← packed + endpoint; endpoint ← endpoint + n₀; endpoint ← endpoint + n₁; + endpoint ← endpoint + n₂; endpoint ← endpoint + selected; heap ← endpoint ] + +/-- Complete actual native calls, with argument preparation, modulus copy and output delivery. -/ +def visit : Code .standard 128 := + .seq prepareModulus (.seq (Modulus.program.rename kernel) + (.seq (.op (.copy selected (kernel Modulus.output))) + (.seq prepareNode (.seq (NodePacking.program.rename kernel) deliver)))) + +/-- Decrease fuel, retain both descriptors needed for restoration, and prepare the third child. -/ +def prepareThird : Code .standard 128 := + let f := slot 0 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₁ := original 0 + let on₁ := original 1 + let op₂ := original 2 + let on₂ := original 3 + let h₂ := heavyBase 2 + let c₂ := heavyLength 2 + ram[ f ← f - one; op₁ ← p₁; on₁ ← n₁; op₂ ← p₂; on₂ ← n₂; + p₂ ← h₂; n₂ ← c₂ ] + +/-- Restore the third original descriptor and prepare the second child. -/ +def prepareSecond : Code .standard 128 := + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₂ := original 2 + let on₂ := original 3 + let h₁ := heavyBase 1 + let c₁ := heavyLength 1 + ram[ p₂ ← op₂; n₂ ← on₂; p₁ ← h₁; n₁ ← c₁ ] + +/-- Restore the second original descriptor and prepare the first child. -/ +def prepareFirst : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let op₁ := original 0 + let on₁ := original 1 + let h₀ := heavyBase 0 + let c₀ := heavyLength 0 + ram[ p₁ ← op₁; n₁ ← on₁; p₀ ← h₀; n₀ ← c₀ ] + +/-- Push third, second, first. Every child retains the two other original set descriptors. -/ +def pushChildren : Code .standard 128 := + .seq prepareThird (.seq pushFrame (.seq prepareSecond + (.seq pushFrame (.seq prepareFirst pushFrame)))) + +/-- A popped record either skips exactly the mathematical guard or executes the complete node. -/ +def body : Code .standard 128 := + let f := slot 0 + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let skip := Code.op (B := Basis.standard) (.copy f f) + .seq popFrame (.branch (.eq f zero) skip + (.branch (.eq n₀ zero) skip (.branch (.eq n₁ zero) skip + (.branch (.eq n₂ zero) skip (.seq visit pushChildren))))) + +/-- The actual pending-word stack, including zero-fuel and empty entries, controls the loop. -/ +def loop : Code .standard 128 := .loop (.lt zero (registers Stack.len)) body + +/-- The complete finite native controller for one supplied root. It appends to the caller's +output table, retaining the accumulated count and advancing the fresh allocation base. -/ +def oneRoot : Code .standard 128 := + let ptr := registers Stack.ptr + let len := registers Stack.len + .seq (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) + (.seq pushFrame loop) + +/-- The same finite controller is used at every admitted word width. -/ +theorem oneRoot_not_readsWidth : ¬oneRoot.ReadsWidth := by decide + +/-- The native controller has a fixed finite instruction list, independent of recursion fuel. -/ +theorem oneRoot_size : oneRoot.size ≤ 65536 := by decide + +/-- Registers 109–127 are retained for the outer frontend-forest controller. -/ +theorem oneRoot_preserves : ∀ i : Fin 128, 109 ≤ i.val → oneRoot.Preserves i := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean new file mode 100644 index 0000000000..36ec20a7bf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean @@ -0,0 +1,134 @@ +/- +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.TraversalCall + +/-! +# A shared polynomial envelope for native traversal calls + +The dynamic heap and all input blocks lie below `H`; each list has at most `n` elements. +The candidate bound, label parameter, magnitude and common group count have fixed bounds +`m,Λ,U,N`. A fourth-degree polynomial covers both existing kernel envelopes, including their +native setup. This bound is later instantiated at the worklist's global heap frontier. + +## Main definitions + +* `envelope`: a common polynomial bound for the actual native modulus and node calls. + +## Main results + +* `modulus_budget_le_envelope`: the concrete selector budget is covered. +* `node_budget_le_envelope`: every selected modulus at most `m²` is covered. + +The envelope and elementary inequalities are original resource estimates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One fixed polynomial in the heap, list, candidate, label, magnitude and output-size bounds. -/ +def envelope (H n m Λ U N : ℕ) : ℕ := + 65536 * (H + n + m ^ 2 + Λ + m + U + N + 100) ^ 4 + +/-- The common envelope covers each size parameter and every smaller native word. -/ +theorem envelope_ge_sum (H n m Λ U N : ℕ) : + H + n + m ^ 2 + Λ + m + U + N + 100 ≤ envelope H n m Λ U N := by + have h := Nat.le_self_pow (by decide : 4 ≠ 0) + (H + n + m ^ 2 + Λ + m + U + N + 100) + dsimp only [envelope] + omega + +private theorem square_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 2 ≤ S ^ 4 := by + have hsq : 1 ≤ S ^ 2 := Nat.one_le_pow _ _ hS + calc S ^ 2 ≤ S ^ 2 * S ^ 2 := Nat.le_mul_of_pos_right _ hsq + _ = S ^ 4 := by ring + +private theorem cube_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 3 ≤ S ^ 4 := by + calc S ^ 3 ≤ S ^ 3 * S := Nat.le_mul_of_pos_right _ hS + _ = S ^ 4 := by ring + +/-- The exact selector envelope, including all copied source pointers and histogram addresses, +is bounded by one polynomial in the runtime size bounds. -/ +theorem modulus_budget_le_envelope (x : Data 128) (H n m Λ U N : ℕ) + (hheap : x.regs heap ≤ H) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ H) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hm : x.regs limit ≤ m) (hΛ : x.regs digits ≤ Λ) + (hcard : x.regs primeCount ≤ m) (hbitmap : x.regs bitmap ≤ H) : + Modulus.budget ((modulusState x).comap kernel) ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hline : 7 * H + 3 * n + m ^ 2 + m + 100 ≤ 7 * S := by dsimp [S]; omega + have ha : 3 * n + 1 ≤ 3 * S := by dsimp [S]; omega + have hb : 3 * Λ + m + 1 ≤ 3 * S := by dsimp [S]; omega + have hprod : (3 * n + 1) ^ 2 * (3 * Λ + m + 1) ≤ 27 * S ^ 3 := by + calc (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + ≤ (3 * S) ^ 2 * (3 * S) := Nat.mul_le_mul (Nat.pow_le_pow_left ha 2) hb + _ = 27 * S ^ 3 := by ring + have hns : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hss : x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) ≤ 3 * H := by + have h₀ := hsrc 0 + have h₁ := hsrc 1 + have h₂ := hsrc 2 + change x.regs (slot 1) + x.regs (slot 2) ≤ H at h₀ + change x.regs (slot 3) + x.regs (slot 4) ≤ H at h₁ + change x.regs (slot 5) + x.regs (slot 6) ≤ H at h₂ + omega + have hbudget : Modulus.budget ((modulusState x).comap kernel) ≤ + 7 * H + 3 * n + (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + m ^ 2 + m + 100 := by + change x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + x.regs heap + x.regs bitmap + x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) + + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + 1) ^ 2 * + (3 * x.regs digits + x.regs primeCount + 1) + x.regs limit ^ 2 + x.regs limit + 100 ≤ _ + have hp := Nat.mul_le_mul + (Nat.pow_le_pow_left (Nat.add_le_add_right hns 1) 2) + (show 3 * x.regs digits + x.regs primeCount + 1 ≤ 3 * Λ + m + 1 by omega) + have hsq := Nat.pow_le_pow_left hm 2 + omega + have hsf : S ≤ S ^ 4 := Nat.le_self_pow (by decide) S + have hcf := cube_le_fourth hS + change _ ≤ 65536 * S ^ 4 + omega + +/-- The node layout, including every allocated output/scratch cell and tag value, is covered +uniformly for every selected product at most `m²`. -/ +theorem node_budget_le_envelope (x : Data 128) (H n m Λ U N M : ℕ) + (hheap : x.regs heap ≤ H) (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hM : M ≤ m ^ 2) (hU : x.regs magnitude ≤ U) (hN : x.regs groups ≤ N) : + (recordLayout x M).budget ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hns : (recordLayout x M).total ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (arrayLength 0) + x.regs (arrayLength 1) + x.regs (arrayLength 2) ≤ 3 * n + omega + have hinner : (recordLayout x M).base + (recordLayout x M).total + M + + (recordLayout x M).N + (recordLayout x M).U + 1 ≤ 3 * S := by + change x.regs heap + (recordLayout x M).total + M + x.regs groups + + x.regs magnitude + 1 ≤ 3 * S + dsimp only [S] + omega + calc (recordLayout x M).budget + ≤ 4096 * (3 * S) ^ 2 := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hinner 2) + _ = 36864 * S ^ 2 := by ring + _ ≤ 65536 * S ^ 4 := by have := square_le_fourth hS; omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean new file mode 100644 index 0000000000..851a2ff7f4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean @@ -0,0 +1,215 @@ +/- +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.TraversalLoop + +/-! +# Complete native Chan–He traversal from a supplied root + +The finite 128-register program initializes its actual stack and constants, pushes the supplied +seven-word root and executes the complete bounded traversal. Every emitted array is the existing +finite node's `oneArray`, in preorder. The protected frontend prefix survives for later roots. + +## Main definitions + +* `RootInput`: concrete native array, metadata and disjoint-memory obligations of a root call. + +## Main results + +* `root_exec`: complete actual native execution with exact output and a charged bound. + +These native composition proofs are original. The initialized-input forest and global +logarithmic-width/subquadratic-cost composition remain separate obligations. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Concrete caller obligations before the complete native root program initializes its stack. -/ +structure RootInput (a : Arena) (w : ℕ) (e : Entry) (ns : List (ℕ × Node)) + (x : Data 128) : Prop where + /-- Initial actual words fit. -/ + bounded : x.Bounded (w + 2) + /-- Persistent caller metadata. -/ + controls : a.Controls x + /-- Exact root register metadata. -/ + loaded : e.Loaded x + /-- Exact root data readback. -/ + stored : e.Stored x.mem + /-- Duplicate-free existing three-set admissibility. -/ + valid : e.Valid a.n a.U + /-- The actual prime bitmap is ready. -/ + bitmap : a.Bitmap x.mem + /-- Original arrays avoid the complete reserved stack and output-table gap. -/ + around : e.Around a.stack (a.floor - a.stack) + /-- All root arrays end below the fresh allocation. -/ + ends : ∀ j, e.source j + (e.data j).length ≤ x.regs heap + /-- The supplied fuel agrees with the fixed stack reservation. -/ + fuel : e.fuel = a.fuel + /-- The initial fresh allocation lies above the reserved gap. -/ + floor : a.floor ≤ x.regs heap + /-- The full tree's transitions reserve sufficient fresh memory. -/ + heapBudget : x.regs heap + Math.work e.fuel * allocationSize a.n a.m a.N ≤ a.ceiling + /-- Actual accumulated output count. -/ + count : x.regs outputCount = ns.length + /-- The full tree's transitions reserve sufficient output-pointer cells. -/ + tableBudget : ns.length + Math.work e.fuel ≤ a.outputCapacity + /-- Existing output retains its exact native readback. -/ + output : StoredNodes a.U a.N a.table ns x.mem + /-- Existing packed arrays lie above the gap and below the fresh allocation. -/ + outputBounds : ∀ i : Fin ns.length, + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ x.regs heap + /-- The entire frontend prefix is available. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- Execute the fixed complete native root program. There are no supplied kernel executions: +all calls are proved from the actual arrays, bitmap, finite bounds and one signed envelope. -/ +theorem root_exec {a : Arena} {w : ℕ} (e : Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h : RootInput a w e ns x) : + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) oneRoot x y (rootTime e.fuel a.n a.m a.N) ∧ + (Cursor.mk [] out).Invariant a w + (ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) y ∧ + out.map Prod.snd = ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ ∧ + (∀ p < a.stack, y.mem p = x.mem p) ∧ + ∀ i : Fin 128, 109 ≤ i.val → y.regs i = x.regs i := by + have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have h2 : Fits (w + 2) 2 := hunsigned.of_le (by omega) + have hcap : Fits (w + 2) (a.stack + a.capacity) := hunsigned.of_le (by + have hb := h.heapBudget + have hf := h.floor + have ht := ha.2.1 + have hgap := ha.2.2.1 + omega) + let p := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + let ptr := registers Stack.ptr + let len := registers Stack.len + have h1 : Fits (w + 2) 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) (w + 2) + (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) x p 4 := by + ram_eval [p, ptr, len, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have preg (i : Fin 128) (hi : i.val < 96) : + p.regs i = ((x.set zero 0).set one 1).regs i := by + have hp' : i ≠ registers Stack.ptr := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.ptr] at this; omega + have hl' : i ≠ registers Stack.len := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.len] at this; omega + simp only [p, Data.set_regs, Function.update_of_ne hp', Function.update_of_ne hl'] + have pmeta (i : Fin 128) (hi : i.val < 96) : p.regs i = x.regs i := by + rw [preg i hi] + by_cases hz : i = zero + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.1.symm + · by_cases ho : i = one + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.2.symm + · simp only [Data.set_regs, Function.update_of_ne ho, Function.update_of_ne hz] + have pcontrols : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact pmeta i (by omega)) + have ploaded : e.Loaded p := by + refine ⟨(pmeta _ (by decide)).trans h.loaded.1, ?_, ?_⟩ + · intro j + exact (pmeta _ (by dsimp [arraySource, slot]; omega)).trans (h.loaded.2.1 j) + · intro j + exact (pmeta _ (by dsimp [arrayLength, slot]; omega)).trans (h.loaded.2.2 j) + have pstack : Stack.Rep (w + 2) a.stack a.capacity [] (p.comap registers) := by + refine ⟨⟨fun i => (hp.bounded h.bounded).1 (registers i), (hp.bounded h.bounded).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simpa +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + using h.controls.2.2.2.2.2.2.2.1 + · simp +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hstack, hframe, hbr⟩ := pushFrame_exec p [] h2 hcap pstack + (fun i => (hp.bounded h.bounded).1 (slot i)) (by + have := ha.2.2.2 + simp only [List.length_nil] + omega) + have breg (i : Fin 128) (hi : i.val < 96) : b.regs i = x.regs i := + (hbr i (Or.inl hi)).trans (pmeta i hi) + have bframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : b.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + let s : Cursor := ⟨[e], ns⟩ + have work : s.work = Math.work e.fuel := by + simp only [s, Cursor.work, List.map_cons, List.map_nil, Math.potential, List.sum_cons, + List.sum_nil, Nat.add_zero, Entry.task] + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hg := ha.2.2.1 + omega + have hi : s.Invariant a w + (ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) b := by + refine ⟨hb.bounded (hp.bounded h.bounded), h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact breg i (by omega)), ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [bframe _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · simpa only [s, List.flatMap_cons, List.flatMap_nil, Entry.loaded_words ploaded, + List.append_nil] using hstack + · rw [← h.fuel] + exact Math.bounded_singleton e.task + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.valid + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + exact h.stored.of_stack_frame (h.around.mono_capacity gap) hframe + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.around + · intro d hd j + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + rw [breg heap (by decide)] + exact h.ends j + · rw [breg heap (by decide)] + exact h.floor + · rw [breg heap (by decide), work] + exact h.heapBudget + · exact (breg outputCount (by decide)).trans h.count + · rw [work] + exact h.tableBudget + · apply h.output.congr + · intro q hq _ + exact bframe q (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact bframe _ (Or.inr (by + have hp' := (h.outputBounds i).1 + have ht := ha.2.1 + have hg := ha.2.2.1 + omega)) + · intro i + rw [breg heap (by decide)] + exact h.outputBounds i + · simp only [s, List.map_cons, List.map_nil, Math.output, List.flatMap_cons, + List.flatMap_nil, List.append_nil] + · intro q hq + exact (bframe q (Or.inl hq)).trans (h.protectedMemory q hq) + obtain ⟨out, y, he, hfinal, hout⟩ := loop_exec ha hw h2 hcap s b hi + have hexec : ExecWithin (w + 2) oneRoot x y (rootTime e.fuel a.n a.m a.N) := by + have hc := hp.within.seq (hb.within.seq he) + exact hc.mono (by rw [work]; dsimp only [rootTime]; omega) + refine ⟨out, y, hexec, hfinal, hout, ?_, ?_⟩ + · intro q hq + exact (hfinal.protectedMemory q hq).trans (h.protectedMemory q hq).symm + · intro i hi + exact hexec.regs_eq (oneRoot_preserves i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean new file mode 100644 index 0000000000..d52730a9ba --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean @@ -0,0 +1,128 @@ +/- +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.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete skipped transitions of the native worklist + +Zero-fuel and empty entries still execute the seven native pops and their actual branch paths. +The remaining arrays, bitmap and previously emitted packed output retain exact readback. +Both space reservations improve because the transition measure decreases without allocation. + +## Main results + +* `skip_transition`: the actual native body preserves the full worklist invariant on every + skipped entry and strictly decreases its remaining charged-transition measure. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Popping a skipped entry executes the complete native body and preserves every part of the +worklist invariant, including exact output, all live memory and concrete space reservations. -/ +theorem skip_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ∃ y, ExecWithin (w + 2) body x y 107 ∧ + (Cursor.mk es ns).Invariant a w target y ∧ + (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨y, hy, hstack, hloaded, hframe, hp⟩ := pop_entry x e es h2 hcap h.stack + have hreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = x.regs i := hp i (Or.inl hi') (Or.inr hi) + have hheap : y.regs heap = x.regs heap := hreg _ (by decide) (by decide) + have hcount : y.regs outputCount = x.regs outputCount := hreg _ (by decide) (by decide) + have hcontrols : a.Controls y := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact hreg i (by omega) (by omega)) + have hbody := body_skip hy hloaded hcontrols.2.2.2.2.2.2.2.2.2.1 hskip + have hmem (p : ℕ) (hp : p < a.stack ∨ a.stack + a.capacity ≤ p) : y.mem p = x.mem p := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hstep : Math.step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) = + (es.map Entry.task, none) := by + by_cases hf : e.fuel = 0 + · simp only [Math.step, Entry.task, hf] + · have hempty : e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + rcases hskip with hf' | h₀ | h₁ | h₂ + · exact (hf hf').elim + · exact Or.inl (List.eq_nil_of_length_eq_zero h₀) + · exact Or.inr (Or.inl (List.eq_nil_of_length_eq_zero h₁)) + · exact Or.inr (Or.inr (List.eq_nil_of_length_eq_zero h₂)) + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte] + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at hdec + change (Cursor.mk es ns).work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + refine ⟨y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, hcontrols, ?_, hstack, ?_, ?_, ?_, ?_, ?_, ?_, ?_, + hcount.trans h.count, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [hmem _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · exact h.depth.2 + · intro d hd + exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (h.stored d (List.mem_cons_of_mem e hd)).of_stack_frame + ((h.around d (List.mem_cons_of_mem e hd)).mono_capacity (by + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) hframe + · intro d hd + exact h.around d (List.mem_cons_of_mem e hd) + · intro d hd j + rw [hheap] + exact h.ends d (List.mem_cons_of_mem e hd) j + · rw [hheap] + exact h.floor + · rw [hheap] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) (Nat.le_of_lt + (show (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work by omega)) + omega + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change ns.length + (Cursor.mk es ns).work ≤ a.outputCapacity + omega + · apply h.output.congr + · intro p hp _ + exact hmem p (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact hmem _ (Or.inr (by + have hi := (h.outputBounds i).1 + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) + · intro i + rw [hheap] + exact h.outputBounds i + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at he + simp only [Option.toList_none, List.nil_append] at he + simpa only [Cursor.pending, Cursor.emitted, List.map_cons, he] using h.exactOutput + · intro p hp + exact (hmem p (Or.inl hp)).trans (h.protectedMemory p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean new file mode 100644 index 0000000000..5efb4c814b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean @@ -0,0 +1,342 @@ +/- +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.Stack +import Mathlib.Tactic.FinCases + +/-! +# Native stack calls for Chan–He traversal records + +Traversal keeps a seven-word record in registers 64–70 and the existing bounded stack in +registers 96–104. Registers below 64 remain available to the complete modulus and node programs. +Each record contains fuel followed by three base/length pairs. The stack stores words, so a +record is pushed in reverse order and popped in forward order. + +The dispatcher, representation and memory frame are reused from `Cslib.WordRAM.Stack`. +Argument delivery and result copies here are actual native instructions and are charged. + +## Main definitions + +* `slot`: the seven persistent traversal-record registers. +* `registers`: the existing nine-register stack dispatcher inside the 128-register controller. +* `pushSlot`: native argument delivery followed by the complete bounded-stack dispatcher. +* `popSlot`: native dispatch and result delivery to one traversal-record register. + +## Main results + +* `pushSlot_exec`: a nine-instruction push, with the exact stack, memory and register frames. +* `popSlot_exec`: a fourteen-instruction nonempty pop with the same exact frames. +* `pushFrame_exec`, `popFrame_exec`: complete seven-word record transfers in 64 and 99 instructions. + +The native controller and its composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Fuel and the three base/length pairs, retained across both 64-register native kernels. -/ +def slot (i : Fin 7) : Fin 128 := ⟨64 + i.val, by omega⟩ + +/-- Pointer word of the selected pending set. -/ +def arraySource (j : Fin 3) : Fin 128 := slot ⟨1 + 2 * j.val, by omega⟩ + +/-- Length word of the selected pending set. -/ +def arrayLength (j : Fin 3) : Fin 128 := slot ⟨2 + 2 * j.val, by omega⟩ + +/-- The existing stack's workspace lies above every 64-register node-kernel register. -/ +def registers (i : Fin 9) : Fin 128 := ⟨96 + i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp [registers] at this + omega + +/-- Caller-owned registers outside the stack dispatcher. -/ +abbrev Outside (i : Fin 128) : Prop := i.val < 96 ∨ 105 ≤ i.val + +private theorem outside_ne (i : Fin 128) (hi : Outside i) (j : Fin 9) : + i ≠ registers j := by + intro h + have := congrArg Fin.val h + dsimp [registers, Outside] at * + omega + +private theorem extend_outside (a : Data 9) (x : Data 128) (i : Fin 128) (hi : Outside i) : + (a.extend registers x).regs i = x.regs i := by + change Function.extend registers a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact outside_ne i hi j hj.symm + +/-- Deliver the push tag and actual record word, then execute the existing stack program. -/ +def pushSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + let src := slot i + .seq (ram[ tag ← #0; arg ← src ]) (Stack.program.rename registers) + +/-- Each native push includes both argument instructions, not a host-level stack operation. +Every register outside 96–104 and every memory cell outside the reserved stack is preserved. -/ +theorem pushSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : Fits w (x.regs (slot i))) (hv : vs.length < capacity) : + ∃ y, Exec w (pushSlot i) x y 9 ∧ + Stack.Rep w base capacity (x.regs (slot i) :: vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 0).set (registers Stack.arg) (x.regs (slot i)) + have hs : slot i ≠ registers Stack.tag := outside_ne _ (Or.inl (by dsimp [slot]; omega)) _ + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 0)) + (.op (.copy (registers Stack.arg) (slot i)))) x q 2 := by + ram_eval [q, hs] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare (.push (x.regs (slot i))) + (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, _, hra, hframe⟩ := Stack.execute h2 hcap hr (.push (x.regs (slot i))) ⟨hv, hw⟩ + have hcall : Exec w Stack.program (q.comap registers) a 7 := by + simpa only [hq, Stack.bodyCost] using ha + let y := a.extend registers q + refine ⟨y, ?_, ?_, hframe, ?_⟩ + · exact hp.seq (hcall.rename registers_injective) + · simpa only [y, Data.comap_extend registers_injective] using hra + · intro j hj + rw [extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def pushSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (pushSlot i) (pushSlots is) + +private theorem pushSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (x : Data 128) + (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : is.length + vs.length ≤ capacity) : + ∃ y, Exec w (pushSlots is) x y (9 * is.length + 1) ∧ + Stack.Rep w base capacity (is.reverse.map (fun i => x.regs (slot i)) ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + induction is generalizing x vs with + | nil => + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + have hv' : vs.length < capacity := by simp only [List.length_cons] at hv; omega + obtain ⟨z, hz, hrz, hfz, hpz⟩ := pushSlot_exec i x vs h2 hcap hr (hw i) hv' + have hslots : (fun i => z.regs (slot i)) = fun i => x.regs (slot i) := by + funext i + exact hpz _ (Or.inl (by dsimp [slot]; omega)) + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih z (x.regs (slot i) :: vs) hrz + (by intro j; rw [hpz _ (Or.inl (by dsimp [slot]; omega))]; exact hw j) + (by simp only [List.length_cons] at hv ⊢; omega) + refine ⟨y, ?_, ?_, ?_, fun j hj => (hpy j hj).trans (hpz j hj)⟩ + · simpa only [pushSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · simpa only [hslots, List.reverse_cons, List.map_append, List.map_singleton, + List.append_assoc, List.singleton_append] using hry + · intro a ha + exact (hfy a ha).trans (hfz a ha) + +/-- Push all seven words in reverse order, so the next seven pops recover the record in order. +The trailing self-copy gives the fixed empty suffix an ordinary native instruction. -/ +def pushFrame : Code .standard 128 := + .seq (pushSlot 6) (.seq (pushSlot 5) (.seq (pushSlot 4) (.seq (pushSlot 3) + (.seq (pushSlot 2) (.seq (pushSlot 1) (.seq (pushSlot 0) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete native frame push charges all seven dispatches and argument delivery. +There is no host-level record write, and the exact seven-word sequence is at the stack top. -/ +theorem pushFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : 7 + vs.length ≤ capacity) : + ∃ y, Exec w pushFrame x y 64 ∧ + Stack.Rep w base capacity + ([x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] ++ vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + simpa only [pushFrame, pushSlots, List.length_cons, List.length_nil, List.reverse_cons, + List.reverse_nil, List.nil_append, List.cons_append, List.append_assoc, List.singleton_append, + List.map_cons, List.map_nil, List.map_append, List.map_singleton, Nat.reduceAdd, + Nat.reduceMul] using pushSlots_exec [6, 5, 4, 3, 2, 1, 0] x vs h2 hcap hr hw hv + +private theorem comap_set_slot (x : Data 128) (i : Fin 7) (v : ℕ) : + (x.set (slot i) v).comap registers = x.comap registers := by + ext j + · change Function.update x.regs (slot i) v (registers j) = x.regs (registers j) + apply Function.update_of_ne + exact (outside_ne _ (Or.inl (by dsimp [slot]; omega)) j).symm + · rfl + +/-- Deliver the pop tag, execute the complete dispatcher, and copy its answer to the record. -/ +def popSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + .seq (ram[ tag ← #1; arg ← #0 ]) + (.seq (Stack.program.rename registers) (.op (.copy (slot i) (registers Stack.out)))) + +/-- A nonempty native pop includes both argument instructions and the actual result copy. +Only the selected record register, stack workspace and reserved stack memory may change. -/ +theorem popSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (v : ℕ) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (v :: vs) (x.comap registers)) : + ∃ y, Exec w (popSlot i) x y 14 ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ y.regs (slot i) = v ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → j ≠ slot i → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 1).set (registers Stack.arg) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 1)) + (.op (.const (registers Stack.arg) 0))) x q 2 := by + ram_eval [q, h1] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare .pop (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, hans, hra, hframe⟩ := Stack.execute h2 hcap hr .pop trivial + have hcall : Exec w Stack.program (q.comap registers) a 11 := by + simpa only [hq, Stack.bodyCost, List.cons_ne_nil, ↓reduceIte] using ha + have hout : a.regs Stack.out = v := by + change some (if a.regs Stack.present = 0 then none else some (a.regs Stack.out)) = + some (some v) at hans + split at hans + · cases hans + · exact Option.some.inj (Option.some.inj hans) + have hread : (a.extend registers q).regs (registers Stack.out) = v := by + change Function.extend registers a.regs q.regs (registers Stack.out) = v + rw [registers_injective.extend_apply, hout] + let y := (a.extend registers q).set (slot i) v + have hcopy : Exec (B := .standard) w + (.op (.copy (slot i) (registers Stack.out))) (a.extend registers q) y 1 := by + simpa only [y, hread] using + (Exec.copy (B := Basis.standard) (w := w) (x := a.extend registers q) + (d := slot i) (s := registers Stack.out)) + refine ⟨y, hp.seq ((hcall.rename registers_injective).seq hcopy), ?_, ?_, hframe, ?_⟩ + · simpa only [y, comap_set_slot, Data.comap_extend registers_injective, List.tail_cons] using hra + · exact Function.update_self .. + · intro j hj hji + change Function.update (a.extend registers q).regs (slot i) v j = x.regs j + rw [Function.update_of_ne hji, extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def popSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (popSlot i) (popSlots is) + +private def restore : List (Fin 7) → List ℕ → (Fin 128 → ℕ) → Fin 128 → ℕ + | i :: is, v :: vs, rs => restore is vs (Function.update rs (slot i) v) + | _, _, rs => rs + +private theorem restore_congr_at (is : List (Fin 7)) (vs : List ℕ) + (rs ss : Fin 128 → ℕ) (j : Fin 128) (h : rs j = ss j) : + restore is vs rs j = restore is vs ss j := by + induction is generalizing vs rs ss with + | nil => exact h + | cons i is ih => + cases vs with + | nil => exact h + | cons v vs => + apply ih + by_cases he : j = slot i + · simp [he] + · simpa only [Function.update_of_ne he] using h + +private theorem popSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (ws : List ℕ) + (x : Data 128) (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (ws ++ vs) (x.comap registers)) + (hlen : ws.length = is.length) : + ∃ y, Exec w (popSlots is) x y (14 * is.length + 1) ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = restore is ws x.regs j := by + induction is generalizing ws x with + | nil => + have hws : ws = [] := List.length_eq_zero_iff.mp hlen + subst ws + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + cases ws with + | nil => simp at hlen + | cons v ws => + obtain ⟨z, hz, hrz, hout, hfz, hpz⟩ := popSlot_exec i x v (ws ++ vs) h2 hcap hr + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih ws z hrz (by simpa using hlen) + refine ⟨y, ?_, hry, ?_, ?_⟩ + · simpa only [popSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · intro a ha + exact (hfy a ha).trans (hfz a ha) + · intro j hj + rw [hpy j hj] + apply restore_congr_at + by_cases he : j = slot i + · simpa only [he, Function.update_self] using hout + · simpa only [Function.update_of_ne he] using hpz j hj he + +/-- Pop all seven words into the record, in forward order, using the native dispatcher. -/ +def popFrame : Code .standard 128 := + .seq (popSlot 0) (.seq (popSlot 1) (.seq (popSlot 2) (.seq (popSlot 3) + (.seq (popSlot 4) (.seq (popSlot 5) (.seq (popSlot 6) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete record pop charges every argument, dispatch and result-copy instruction. +The seven restored words have their original order; other caller registers are preserved. -/ +theorem popFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) (a : Fin 7 → ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ vs) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ Stack.Rep w base capacity vs (y.comap registers) ∧ + (∀ i, y.regs (slot i) = a i) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + obtain ⟨y, hy, hry, hfy, hpy⟩ := + popSlots_exec [0, 1, 2, 3, 4, 5, 6] [a 0, a 1, a 2, a 3, a 4, a 5, a 6] + x vs h2 hcap hr rfl + refine ⟨y, hy, hry, ?_, hfy, ?_⟩ + · intro i + have h := hpy (slot i) (Or.inl (by dsimp [slot]; omega)) + fin_cases i <;> simpa +decide [restore, slot] using h + · intro j hj hj' + have hne (i : Fin 7) : j ≠ slot i := by + intro h + have := congrArg Fin.val h + dsimp [slot] at this + omega + simpa only [restore, Function.update_of_ne (hne 0), Function.update_of_ne (hne 1), + Function.update_of_ne (hne 2), Function.update_of_ne (hne 3), + Function.update_of_ne (hne 4), Function.update_of_ne (hne 5), + Function.update_of_ne (hne 6)] using hpy j hj + +/-- Record transfers do not inspect the physical word width. -/ +theorem not_readsWidth : ¬pushFrame.ReadsWidth ∧ ¬popFrame.ReadsWidth := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean new file mode 100644 index 0000000000..9215e4fc86 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean @@ -0,0 +1,94 @@ +/- +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.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Native dispatch after popping a Chan–He entry + +The zero-fuel and empty-array cases execute their actual branch paths. Live calls execute the +complete visit followed by all three native child pushes. No guard is evaluated by a host +program, and skipped entries retain the same charged pop as live entries. + +## Main results + +* `body_skip`: actual pop and dispatch for every skipped entry. +* `body_live`: the four actual negative branches connect a pop to the complete live suffix. + +These control-flow composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +private theorem copy_self {w : ℕ} (x : Data 128) : + Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + +/-- Every skipped entry executes the actual seven pops and the appropriate guard path. +The resulting data is exactly the pop result; the self-copy does not change it. -/ +theorem body_skip {w : ℕ} {x y : Data 128} {e : Entry} + (hp : Exec w popFrame x y 99) (hl : e.Loaded y) (hz : y.regs zero = 0) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ExecWithin w body x y 107 := by + have hf : y.regs (slot 0) = e.fuel := hl.1 + have hn₀ : y.regs (slot 2) = (e.data 0).length := hl.2.2 0 + have hn₁ : y.regs (slot 4) = (e.data 1).length := hl.2.2 1 + have hn₂ : y.regs (slot 6) = (e.data 2).length := hl.2.2 2 + by_cases h₀ : e.fuel = 0 + · exact (hp.seq (Exec.branch_pos + (by change y.regs (slot 0) = y.regs zero; omega) (copy_self y))).within.mono (by omega) + · by_cases h₁ : (e.data 0).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 2) = y.regs zero; omega) + (copy_self y)))).within.mono (by omega) + · by_cases h₂ : (e.data 1).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 4) = y.regs zero; omega) + (copy_self y))))).within.mono (by omega) + · have h₃ : (e.data 2).length = 0 := by omega + exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 4) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 6) = y.regs zero; omega) + (copy_self y)))))).within.mono (by omega) + +/-- A live entry takes all four native negative branches before executing the supplied actual +visit and three-push executions. The bound charges the seven pops and eight branch/jump steps. -/ +theorem body_live {w T : ℕ} {x a b y : Data 128} {e : Entry} + (hp : Exec w popFrame x a 99) (hl : e.Loaded a) (hz : a.regs zero = 0) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) + (hv : ExecWithin w visit a b T) (hc : Exec w pushChildren b y 207) : + ExecWithin w body x y (T + 314) := by + obtain ⟨t, ht, he⟩ := hv + have hg₀ : ¬(Cond.eq (slot 0) zero).Holds a := by + change a.regs (slot 0) ≠ a.regs zero + rw [hl.1, hz] + exact hf + have hg (j : Fin 3) : ¬(Cond.eq (arrayLength j) zero).Holds a := by + change a.regs (arrayLength j) ≠ a.regs zero + rw [hl.2.2 j, hz] + exact hn j + have he' : Exec w body x y (99 + (t + 207 + 2 + 2 + 2 + 2)) := + hp.seq (Exec.branch_neg hg₀ (Exec.branch_neg (hg 0) (Exec.branch_neg (hg 1) + (Exec.branch_neg (hg 2) (he.seq hc))))) + exact he'.within.mono (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean new file mode 100644 index 0000000000..f20a88b5bf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean @@ -0,0 +1,96 @@ +/- +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.TraversalVisit +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel + +/-! +# Charged bounds for the native Chan–He worklist + +The per-node estimate includes both complete modulus searches and the complete packing program. +It is cubic in the candidate bound, rather than quadratic in the input length. The root estimate +also charges skipped calls, stack transfers, branches and loop control. + +## Main definitions + +* `visitTime`, `rootTime`: uniform charged bounds for one live visit and one complete root. +* `allocationSize`: maximum fresh allocation for one live visit. + +## Main results + +* `visit_cost_le`: the exact executed call bound is covered by `visitTime`. +* `allocation_le`: the concrete node allocation is covered by `allocationSize`. + +These estimates are original; their starting costs are the proved native execution bounds. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Both searches, the actual node program, argument delivery and the output-table store. -/ +def visitTime (n m N : ℕ) : ℕ := + 2 * (m + 1) * (102 * n + 15 * m ^ 2 + 102) + + 300 * n + 15 * m ^ 2 + 15 * m + 12 * N + 386 + +/-- Root setup and every possible transition, including empty and zero-fuel entries. -/ +def rootTime (f n m N : ℕ) : ℕ := 70 + Math.work f * (visitTime n m N + 316) + +/-- Three heavy count cells, three heavy buffers, four packed blocks, scratch and histogram. -/ +def allocationSize (n m N : ℕ) : ℕ := 3 + 6 * n + 4 * N + m ^ 2 + +/-- The exact charged cost from the complete native visit is uniformly bounded. -/ +theorem visit_cost_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hm : x.regs limit ≤ m) + (hN : x.regs groups ≤ N) (hM : M ≤ m ^ 2) : + Modulus.cost ((modulusState x).comap kernel) + + 66 * (recordLayout x M).total + 15 * M + 12 * x.regs groups + 277 ≤ + visitTime n m N := by + have hs : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hp := Nat.mul_le_mul (Nat.add_le_add_right hm 1) + (show 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102 ≤ 102 * n + 15 * m ^ 2 + 102 by + have := Nat.pow_le_pow_left hm 2 + omega) + change 2 * ((x.regs limit + 1) * + (34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102) + 9) + + 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit + 91 + + 66 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * M + 12 * x.regs groups + 277 ≤ _ + dsimp only [visitTime] + rw [Nat.mul_assoc] + omega + +/-- The actual next heap pointer advances by at most the uniform per-node allocation. -/ +theorem allocation_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hN : x.regs groups ≤ N) + (hM : M ≤ m ^ 2) : + (recordLayout x M).endpoint ≤ x.regs heap + allocationSize n m N := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + rw [recordLayout_endpoint] + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + dsimp only [allocationSize] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean new file mode 100644 index 0000000000..d3e7774b71 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean @@ -0,0 +1,138 @@ +/- +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.TraversalDeliver + +/-! +# Complete charged native work at one live traversal entry + +Both native modulus searches, every counting call, the complete node builder and output delivery +are composed here. The intermediate states are exposed as proof witnesses so the worklist proof +can reuse the node builder's existing exact heavy/packed readback and allocation frame. + +## Main results + +* `visit_exec`: execution of the complete native visit, charging both kernel calls, every argument + instruction, the modulus copy and all output-delivery instructions. + +These are original native composition proofs. The global traversal and initialized-input width +and asymptotic bounds remain separate from this local calling-convention theorem. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The full native live-node visit, including all setup and output delivery. The returned +intermediate states are reached by actual instructions, not supplied node or preprocessing oracles. +The node's signed envelope derives the delivery address/counter fits. -/ +theorem visit_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hnode : ∀ M, 1 ≤ M → M ≤ x.regs limit ^ 2 → + GenericRAM.Signed.Fits w ((recordLayout x M).budget : ℤ)) + (hN : 2 * x.regs limit ^ 2 ≤ x.regs groups) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (arrayLength j) → GenericRAM.Signed.Fits w (values j i)) + (hbdd : ∀ j i, i < x.regs (arrayLength j) → (values j i).natAbs ≤ x.regs magnitude) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ (M : ℕ) (u b : Data 128), + ExecWithin (w + 2) visit x (deliveryState b) + (Modulus.cost ((modulusState x).comap kernel) + + 66 * (recordLayout x M).total + 15 * M + 12 * x.regs groups + 277) ∧ + (∀ i, 64 ≤ i.val → i ≠ selected → u.regs i = x.regs i) ∧ u.regs selected = M ∧ + (∀ p < x.regs heap, u.mem p = x.mem p) ∧ + NodePacking.Output (recordLayout x M) values u.mem b.mem ∧ + (∀ i, 64 ≤ i.val → b.regs i = u.regs i) ∧ + 1 ≤ M ∧ M ≤ x.regs limit ^ 2 ∧ + M = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) := by + obtain ⟨a, ha, hpa, hprefix, hM, hMpos, hMle⟩ := + callModulus_exec x values hm hmod hbudget hcard hbefore hbitmap hvalues hsrc hinput hinj + let M := a.regs (kernel Modulus.output) + let u := a.set selected M + have hcopy : Exec (B := .standard) (w + 2) + (.op (.copy selected (kernel Modulus.output))) a u 1 := Exec.copy + have hu (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + u.regs i = x.regs i := by + simp only [u, Data.set_regs, Function.update_of_ne hne, hpa i hi] + have hus : u.regs selected = M := rfl + have huSource (j : Fin 3) : u.regs (arraySource j) = x.regs (arraySource j) := by + fin_cases j <;> exact hu _ (by decide) (by decide) + have huLength (j : Fin 3) : u.regs (arrayLength j) = x.regs (arrayLength j) := by + fin_cases j <;> exact hu _ (by decide) (by decide) + have huHeap : u.regs heap = x.regs heap := hu _ (by decide) (by decide) + have huGroups : u.regs groups = x.regs groups := hu _ (by decide) (by decide) + have huMagnitude : u.regs magnitude = x.regs magnitude := hu _ (by decide) (by decide) + have hL : NodePacking.layout ((nodeState u).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hus] + exact recordLayout_congr x u M hu + have hwidth : GenericRAM.Signed.Fits w + ((NodePacking.layout ((nodeState u).comap kernel)).budget : ℤ) := by + rw [hL] + exact hnode M hMpos hMle + obtain ⟨b, hb, hout, hpb⟩ := callNode_exec u values (by omega) + (by rw [hus, huGroups]; omega) hwidth + (by intro j; rw [huSource j, huLength j, huHeap]; exact hsrc j) + (by intro j i hi; rw [huMagnitude]; exact hbdd j i ((huLength j) ▸ hi)) + (by + intro j i hi + rw [huSource j] + have hi' : i < x.regs (arrayLength j) := (huLength j) ▸ hi + have hs := hsrc j + rw [show u.mem (x.regs (arraySource j) + i) = x.mem (x.regs (arraySource j) + i) + from hprefix _ (by omega)] + exact hinput j i hi') + (by intro j; simpa only [huLength] using hinj j) + rw [hL] at hb hout + have hbp (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + b.regs i = x.regs i := (hpb i hi).trans (hu i hi hne) + have hbL : NodePacking.layout ((nodeState b).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hpb selected (by decide), hus] + exact recordLayout_congr x b M hbp + have hbOne : b.regs one = 1 := (hbp _ (by decide) (by decide)).trans hone + have hbTable : b.regs outputs + b.regs outputCount < b.regs heap := by + rw [hbp outputs (by decide) (by decide), hbp outputCount (by decide) (by decide), + hbp heap (by decide) (by decide)] + exact htable + have hEnd : Fits (w + 2) ((recordLayout x M).endpoint) := + (NodePacking.unsigned_fits_of_signed (hnode M hMpos hMle)).of_le + (recordLayout x M).bounds.1 + have hEndEq : (recordLayout x M).endpoint = b.regs heap + 3 + 2 * + (b.regs (slot 2) + b.regs (slot 4) + b.regs (slot 6)) + + 4 * b.regs groups + b.regs selected := by + rw [← hbL, nodeState_layout, recordLayout_endpoint] + rw [hEndEq] at hEnd + have hd := deliver_exec b hbOne hbTable hEnd + refine ⟨M, u, b, ?_, hu, hus, hprefix, hout, hpb, hMpos, hMle, hM⟩ + obtain ⟨tm, htm, hem⟩ := ha + cases hem with + | seq hpM hkM => + obtain ⟨tn, htn, hen⟩ := hb + cases hen with + | seq hpN hkN => + have he := hpM.seq (hkM.seq (hcopy.seq (hpN.seq (hkN.seq hd)))) + exact he.within.mono (by dsimp only [recordLayout] at htn ⊢; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Computability/WordRAM/GhostLoop.lean b/Complexity/Computability/WordRAM/GhostLoop.lean new file mode 100644 index 0000000000..8f3b8e7d3c --- /dev/null +++ b/Complexity/Computability/WordRAM/GhostLoop.lean @@ -0,0 +1,59 @@ +/- +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 + +/-! +# Native loop invariants with explicit proof state + +An invariant may relate the native data to additional mathematical proof state, such as the +represented worklist and emitted output. The decreasing measure then applies directly to that +proof state. It need not be reconstructed by an additional machine program or a choice of +representation from the native data alone. + +## Main results + +* `ExecWithin.loop_invariant_state`: the native loop rule with explicit proof state and a natural + decreasing measure, including all loop-control instructions in the bound. + +This is an original extension of the existing native invariant/measure rule. The body premise +is actual `ExecWithin` execution of the given native code, not a source execution or time oracle. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- Native loop rule carrying explicit mathematical proof state. The body must execute the +actual native code, preserve the relation and strictly decrease the proof-state measure. -/ +theorem ExecWithin.loop_invariant_state {B : Basis} {r w T : ℕ} {σ : Type*} + (I : σ → Data r → Prop) (μ : σ → ℕ) {cond : Cond r} {body : Code B r} + (hbody : ∀ s x, I s x → cond.Holds x → + ∃ s' y, ExecWithin w body x y T ∧ I s' y ∧ μ s' < μ s) + {s : σ} {x : Data r} (hx : I s x) : + ∃ s' y, ExecWithin w (.loop cond body) x y (μ s * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + have aux : ∀ n, ∀ s x, μ s ≤ n → I s x → + ∃ s' y, ExecWithin w (.loop cond body) x y (n * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + intro n + induction n using Nat.strong_induction_on with + | h n ih => + intro s x hμ hI + by_cases hc : cond.Holds x + · obtain ⟨s₁, y, hy, hIy, hdec⟩ := hbody s x hI hc + obtain ⟨s₂, z, hz, hIz, hexit⟩ := ih (μ s₁) (by omega) s₁ y le_rfl hIy + refine ⟨s₂, z, (ExecWithin.loop_iter hc hy hz).mono ?_, hIz, hexit⟩ + have hm := Nat.mul_le_mul_right (T + 2) (show μ s₁ + 1 ≤ n by omega) + rw [Nat.add_mul, Nat.one_mul] at hm + omega + · exact ⟨s, x, (ExecWithin.loop_exit hc).mono (by omega), hI, hc⟩ + exact aux (μ s) s x le_rfl hx + +end Cslib.WordRAM diff --git a/ComplexityTests/NativeChanHeTraversal.lean b/ComplexityTests/NativeChanHeTraversal.lean new file mode 100644 index 0000000000..abb8252909 --- /dev/null +++ b/ComplexityTests/NativeChanHeTraversal.lean @@ -0,0 +1,250 @@ +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalExec +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +private def fixture : Data 128 where + regs i := if i = registers Stack.ptr then 20 else if i = registers Stack.len then 0 + else if 64 ≤ i.val ∧ i.val < 71 then i.val - 60 else 79 + mem _ := 79 + +private def roundTrip : Code .standard 128 := .seq pushFrame popFrame + +-- A complete native record round trip restores all seven words, keeps low kernel registers, +-- and preserves memory on both sides of the reserved stack. Old stack contents are nonzero. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 8 16 roundTrip fixture).map (fun (y, _) => + (List.ofFn (fun i : Fin 7 => y.regs (slot i)), + y.regs 0, y.regs (registers Stack.len), y.mem 19, y.mem 27)) = + some ([4, 5, 6, 7, 8, 9, 10], 79, 0, 79, 79)) + +-- Address arithmetic really runs in the admitted physical width. +/-- info: true -/ +#guard_msgs in +#eval (Code.eval 1 16 pushFrame fixture).isNone + +private theorem fixture_rep : Stack.Rep 8 20 7 [] (fixture.comap registers) := by + refine ⟨?_, ?_, ?_, by decide, fun i => Fin.elim0 i⟩ + · constructor + · intro i + change Fits 8 (fixture.regs (registers i)) + dsimp only [fixture] + split + · decide + · split + · decide + · split + · change (registers i).val - 60 < 256 + have := (registers i).isLt + omega + · decide + · intro a + change Fits 8 79 + decide + · decide + · decide + +-- The native transfer contracts have a fully admitted client, including old nonzero memory. +example : ∃ y, Exec 8 roundTrip fixture y 163 ∧ + Stack.Rep 8 20 7 [] (y.comap registers) ∧ + (∀ i, y.regs (slot i) = fixture.regs (slot i)) := by + have hwords (i : Fin 7) : Fits 8 (fixture.regs (slot i)) := by + dsimp only [fixture] + split + · decide + · split + · decide + · split + · change (slot i).val - 60 < 256 + have := (slot i).isLt + omega + · decide + obtain ⟨z, hz, hrz, _, _⟩ := pushFrame_exec fixture [] (by decide) (by decide) + fixture_rep hwords (by decide) + obtain ⟨y, hy, hry, hslots, _, _⟩ := popFrame_exec z [] + (fun i => fixture.regs (slot i)) (by decide) (by decide) hrz + exact ⟨y, hz.seq hy, hry, hslots⟩ + +-- Pending entries include zero-fuel calls; capacity bounds are neither empty nor universal. +private def root : Math.Task := ⟨1, fun _ => {0}⟩ +example : Math.Bounded 1 [root] ∧ ¬Math.Bounded 0 [root] ∧ Math.work 1 = 4 := by + norm_num [Math.Bounded, root, Math.work] + +-- A zero-fuel call produces no node even with three nonempty sets. +example : (Math.step {2} 1 ⟨0, fun _ => {0}⟩ []).1 = [] ∧ + (Math.step {2} 1 ⟨0, fun _ => {0}⟩ []).2 = none := by + decide + +private def treeInput (f n : ℕ) (z : ℤ) : Data 128 where + regs i := match i.val with + | 64 => f + | 65 => 1 + | 66 => n + | 67 => 2 + | 68 => n + | 69 => 3 + | 70 => n + | 71 => 2 + | 72 => 1 + | 73 => 1 + | 74 => 8 + | 75 => 10 + | 76 => 1 + | 77 => 100 + | 78 => 60 + | 79 => 0 + | 80 => 20 + | 81 => 21 + | _ => 79 + mem a := if a = 1 ∨ a = 2 ∨ a = 3 then Signed.encode z + else if a = 10 ∨ a = 11 then 0 else if a = 12 then 1 else 79 + +private def observeTree (f n : ℕ) (z : ℤ) := + (Code.eval 16 32 oneRoot (treeInput f n z)).map fun (y, _) => + (y.regs outputCount, y.regs heap, y.mem 60, y.regs (registers Stack.len), + (List.range 32).map (fun i => y.mem (106 + i))) + +private def packedSingleton (z : ℤ) : List ℕ := + let ν : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{z}, {z}, {z}, 4⟩ + (List.range 32).map fun i => Signed.encode (ν.oneArray 1 i) + +-- Zero fuel and empty sets are actually popped and skipped without calling either node kernel. +/-- info: true -/ +#guard_msgs in +#eval decide ((observeTree 0 1 0).map (fun r => (r.1, r.2.1, r.2.2.1)) = some (0, 100, 79)) + +/-- info: true -/ +#guard_msgs in +#eval decide ((observeTree 1 0 0).map (fun r => (r.1, r.2.1, r.2.2.1)) = some (0, 100, 79)) + +-- Actual full controller: both prime searches, all three node counts, all packed stores, +-- three native child pushes, and the three zero-fuel pops. Every packed cell is checked. +/-- info: true -/ +#guard_msgs in +#eval decide (observeTree 1 1 0 = some (1, 145, 106, 0, packedSingleton 0)) + +-- The same complete traversal executes negative signed input and negated-Z placement. +/-- info: true -/ +#guard_msgs in +#eval decide (observeTree 1 1 (-1) = some (1, 145, 106, 0, packedSingleton (-1))) + +private def branchingInput : Data 128 where + regs i := if i = slot 2 then 3 else if i = slot 3 then 4 else if i = slot 5 then 5 + else if i = digits then 4 else if i = magnitude then 4 + else if i = stackCapacity then 35 else (treeInput 2 1 0).regs i + mem a := match a with + | 1 => Signed.encode 0 + | 2 => Signed.encode 4 + | 3 => Signed.encode 1 + | 4 => Signed.encode 0 + | 5 => Signed.encode (-1) + | _ => (treeInput 2 1 0).mem a + +private def expectedBranching : List (List ℕ) := + let ν₀ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4, 1}, {0}, {-1}, 4⟩ + let ν₁ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4}, {0}, {-1}, 4⟩ + [ν₀, ν₁].map fun ν => (List.range 32).map fun i => Signed.encode (ν.oneArray 4 i) + +-- Only the first column has heavy elements. The child changes that descriptor and keeps the +-- other two, then its zero-fuel children and both empty siblings are popped without outputs. +-- The two packed arrays differ, so this also checks their preorder and exact readback. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 16 64 oneRoot branchingInput).map (fun (y, _) => + (y.regs outputCount, y.regs heap, y.regs (registers Stack.len), + [y.mem 60, y.mem 61], [108, 156].map fun p => (List.range 32).map fun i => y.mem (p + i))) = + some (2, 196, 0, [108, 156], expectedBranching)) + +private theorem treeInput_zero_bounded : (treeInput 0 1 0).Bounded 8 := by + constructor + · intro i + dsimp only [treeInput] + split <;> decide + · intro a + dsimp only [treeInput] + split + · decide + · split + · decide + · split <;> decide + +-- All premises of the complete-controller theorem are discharged in a concrete native state. +example : ∃ y, Exec 8 oneRoot (treeInput 0 1 0) y 173 ∧ + y.regs outputCount = 0 ∧ y.regs heap = 100 := by + obtain ⟨y, hy, _, _, hc, hh, _⟩ := oneRoot_zero (capacity := 7) (treeInput 0 1 0) + treeInput_zero_bounded (by decide) (by decide) (by decide) rfl + exact ⟨y, hy, hc, hh⟩ + +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 8 16 oneRoot (treeInput 0 1 0)).map Prod.snd = some 173) + +private def admittedTree : Data 128 where + regs i := if i = zero then 0 else if i = one then 1 else (treeInput 1 1 0).regs i + mem := (treeInput 1 1 0).mem + +private def admittedEntry : Entry := ⟨1, ![1, 2, 3], fun _ => [0]⟩ + +private def admittedArena : Arena where + n := 1 + m := 2 + Λ := 1 + U := 1 + N := 8 + fuel := 1 + primes := 10 + stack := 20 + capacity := 21 + table := 60 + outputCapacity := 4 + floor := 100 + ceiling := 280 + retained := admittedTree.mem + +private theorem admittedTree_bounded : admittedTree.Bounded 66 := by + constructor + · intro i + dsimp only [admittedTree] + split + · decide + · split + · decide + · dsimp only [treeInput] + split <;> decide + · intro p + dsimp only [admittedTree, treeInput] + split + · decide + · split + · decide + · split <;> decide + +-- All premises of the complete live root theorem hold in one concrete native allocation. +-- This includes both searches, real histograms/packing, depth, width and space reservations. +private theorem admittedRoot : RootInput admittedArena 64 admittedEntry [] admittedTree := by + refine ⟨admittedTree_bounded, by unfold Arena.Controls; decide, + by unfold Entry.Loaded; decide, by unfold Entry.Stored Block.Realizes; decide, + ?_, ?_, by unfold Entry.Around; decide, + by decide, rfl, by decide, by decide, rfl, by decide, storedNodes_nil _ _ _ _, + fun i => Fin.elim0 i, fun _ _ => rfl⟩ + · refine ⟨by decide, ?_⟩ + refine ⟨by decide, by decide, by decide, ?_, ?_, ?_⟩ <;> + simp [Cslib.Algorithms.ThreeSum.Bdd, admittedEntry] + · intro q hq + change q ≤ 2 at hq + interval_cases q <;> decide + +example : ∃ (ns : List (ℕ × Cslib.Algorithms.ThreeSum.ChanHe.Node)) (y : Data 128), + ExecWithin 66 oneRoot admittedTree y (rootTime 1 1 2 8) ∧ + ns.map Prod.snd = admittedEntry.task.output (Nat.primesLE 2) 1 ∧ + StoredNodes 1 8 60 ns y.mem ∧ y.regs outputCount = ns.length := by + obtain ⟨ns, y, he, hi, hout, _, _⟩ := root_exec admittedEntry [] admittedTree + (by unfold Arena.Separated; decide) (by unfold Arena.Ready; decide) admittedRoot + exact ⟨ns, y, he, by simpa only [List.map_nil, List.nil_append, admittedArena] using hout, + hi.output, hi.count⟩ + +-- The common envelope is a real restriction; width 1 cannot admit this live allocation. +example : ¬admittedArena.Ready 1 := by unfold Arena.Ready; decide From f6f22dac7018f00793f6aae1aa44bd04ae08cee6 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 14:59:48 -0400 Subject: [PATCH 22/45] test(wordram): remove disconnected native node checks Address the blocking #1508 review by deleting three arithmetic/layout/injectivity examples disconnected from native execution, retaining real width faults and admitted clients, and dropping unsupported ordering prose. Native code and public library contracts are unchanged. AI assistance: OpenAI Codex. --- .../WordRAM/ThreeSum/ChanHe/NodeSpec.lean | 2 +- ComplexityTests/NativeChanHeNode.lean | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean index 9cc16a520b..2762ac98ee 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean @@ -13,7 +13,7 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeExec # Exact output contract for complete native Chan–He node construction The output uses the existing mathematical heavy subsets and `Node.oneArray`. A heavy array -contains each selected integer once, in input order, preceded by its exact cardinality. The +contains each selected integer once, preceded by its exact cardinality. The packed array has `4N` cells, including every sentinel and padded cell. Memory outside the explicit allocation and caller registers 46–55 are preserved. diff --git a/ComplexityTests/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean index b5c3648e8f..bd68b60be5 100644 --- a/ComplexityTests/NativeChanHeNode.lean +++ b/ComplexityTests/NativeChanHeNode.lean @@ -10,6 +10,9 @@ module import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec meta import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec +-- Regression clients intentionally export no public declarations. +set_option linter.privateModule false + /-! # Complete native Chan–He node regression clients @@ -123,18 +126,9 @@ private def twoUnique : Fin 3 → List ℤ := ![[-1, 0], [0, 1], [-1, 0]] #guard_msgs in #eval (run 4 singleton 3 6 1).isNone --- Layout and fit hypotheses are promises of the component, and these invalid cases violate them. -public theorem invalid_group_count : ¬(2 * 3 ≤ 5) := by decide +-- The failing executed width is also below the theorem's sufficient signed-fit promise. example : ¬Cslib.GenericRAM.Signed.Fits 4 ((layout (input singleton 3 6 1)).budget : ℤ) := by decide -example : ¬((input singleton 3 6 1).regs (source 0) + - (layout (input singleton 3 6 1)).n 0 ≤ 0) := by decide - -example : ¬Set.InjOn (fun _ : ℕ => (1 : ℤ)) (↑(Finset.range 2) : Set ℕ) := by - intro h - have he := h (by decide : 0 ∈ (↑(Finset.range 2) : Set ℕ)) - (by decide : 1 ∈ (↑(Finset.range 2) : Set ℕ)) rfl - omega -- The theorem's polynomial fit/layout assumptions admit actual supplied native inputs. example : Cslib.GenericRAM.Signed.Fits 32 ((layout (input empty 1 2 0)).budget : ℤ) := by decide From d32859e8beea19d17a763e0a241463ad7c7d4aa0 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:11:23 -0400 Subject: [PATCH 23/45] refactor(wordram): reuse prime counts and test later acceptance Address #1497 review nits with Nat.primeCounting and Nat.count APIs in place of private duplicate helpers. Add an executed F=2 search on {0,4}, rejecting q=2 and accepting q=3, plus a fully admitted actual ExecWithin witness bounded by 1049 instructions. Native programs and public library statements are unchanged. Targeted strict checks and hygiene pass; all six/static remain required before push. AI assistance: OpenAI Codex. --- Complexity/Algorithms/WordRAM/PrimeTable.lean | 29 ++++-------- ComplexityTests/WordRAMChanHeSearch.lean | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/PrimeTable.lean b/Complexity/Algorithms/WordRAM/PrimeTable.lean index 09e9ce40a7..52ed1ba938 100644 --- a/Complexity/Algorithms/WordRAM/PrimeTable.lean +++ b/Complexity/Algorithms/WordRAM/PrimeTable.lean @@ -99,17 +99,6 @@ private theorem memory_succ (mem : ℕ → ℕ) (base k : ℕ) : have h : (base ≤ a ∧ a < base + (k + 1)) ↔ (base ≤ a ∧ a < base + k) := by omega simp only [memory, h] -private def primesLT (k : ℕ) : ℕ := #{p ∈ range k | p.Prime} - -private theorem primesLT_zero : primesLT 0 = 0 := by simp [primesLT] - -private theorem primesLT_succ (k : ℕ) : - primesLT (k + 1) = primesLT k + if k.Prime then 1 else 0 := by - by_cases hp : k.Prime <;> simp [primesLT, range_add_one, filter_insert, hp] - -private theorem primesLT_le (k : ℕ) : primesLT k ≤ k := - (card_filter_le _ _).trans_eq (card_range k) - private theorem exec_test {w m : ℕ} (x : Data 15) (htwo : x.regs two = 2) (hbound : x.regs cursor ≤ m) (hfit : Fits w (m + 3)) : ∃ y, ExecWithin w test x y (7 * m + 9) ∧ @@ -162,7 +151,7 @@ theorem exec {w : ℕ} (x : Data 15) let m := x.regs limit let I (y : Data 15) : Prop := y.regs limit = m ∧ y.regs base = x.regs base ∧ y.regs stop = m + 1 ∧ y.regs one = 1 ∧ y.regs zero = 0 ∧ y.regs two = 2 ∧ - y.regs cursor ≤ m + 1 ∧ y.regs count = primesLT (y.regs cursor) ∧ + y.regs cursor ≤ m + 1 ∧ y.regs count = Nat.primeCounting' (y.regs cursor) ∧ y.mem = memory x.mem (x.regs base) (y.regs cursor) have hbody (y : Data 15) (hy : I y) (hc : (Cond.lt cursor stop).Holds y) : ∃ q, ExecWithin w body y q (7 * m + 13) ∧ I q ∧ @@ -172,14 +161,16 @@ theorem exec {w : ℕ} (x : Data 15) obtain ⟨z, he, hzf, hzm, hzr⟩ := exec_test y ht (show y.regs cursor ≤ m by omega) hfit have hzb : z.regs base = x.regs base := (hzr base (by decide)).trans hb have hzi : z.regs cursor = y.regs cursor := hzr cursor (by decide) - have hzc : z.regs count = primesLT (y.regs cursor) := (hzr count (by decide)).trans hcount + have hzc : z.regs count = Nat.primeCounting' (y.regs cursor) := + (hzr count (by decide)).trans hcount have hzo : z.regs one = 1 := (hzr one (by decide)).trans ho have hzf_le : z.regs flag ≤ 1 := by rw [hzf]; split_ifs <;> omega have ha : Fits w (z.regs base + z.regs cursor) := haddr.of_le (by rw [hzb, hzi]; omega) have hn : Fits w (z.regs cursor + 1) := hfit.of_le (by rw [hzi]; change _ ≤ m + 3; omega) have hct : Fits w (z.regs count + z.regs flag) := hfit.of_le (by rw [hzc] - have hle := primesLT_le (y.regs cursor) + have hle : Nat.primeCounting' (y.regs cursor) ≤ y.regs cursor := + Nat.count_le Nat.Prime change _ ≤ m + 3 omega) refine ⟨?q, ?run, ?inv, ?decrease⟩ @@ -197,8 +188,9 @@ theorem exec {w : ℕ} (x : Data 15) · simpa +decide [Function.update_apply] using (hzr zero (by decide)).trans hz · simpa +decide [Function.update_apply] using (hzr two (by decide)).trans ht · simpa +decide [Function.update_apply, hzi] using Nat.succ_le_of_lt hlt - · change z.regs count + z.regs flag = primesLT (z.regs cursor + 1) - rw [hzc, hzf, hzi, primesLT_succ] + · change z.regs count + z.regs flag = Nat.primeCounting' (z.regs cursor + 1) + rw [hzc, hzf, hzi] + simp only [Nat.primeCounting', Nat.count_succ] · change Function.update z.mem (z.regs base + z.regs cursor) (z.regs flag) = memory x.mem (x.regs base) (z.regs cursor + 1) rw [hzm, hm, hzb, hzi, hzf, ← memory_succ] @@ -209,7 +201,7 @@ theorem exec {w : ℕ} (x : Data 15) let x₀ := (((((x.set one 1).set zero 0).set two 2).set cursor 0).set count 0).set stop (m + 1) have hx₀ : I x₀ := by simp +decide [I, x₀, m, limit, base, stop, one, zero, two, cursor, count, - primesLT_zero, memory_zero] + Nat.primeCounting', Nat.count_zero, memory_zero] obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => m + 1 - y.regs cursor) hbody hx₀ obtain ⟨hl, hb, hs, ho, hz, ht, hi, hcount, hm⟩ := hy @@ -227,7 +219,6 @@ theorem exec {w : ℕ} (x : Data 15) · have hi₀ : x₀.regs cursor = 0 := rfl have hcost : 7 * m + 13 + 2 = 7 * m + 15 := by omega exact (hinit.seq he).mono (by rw [hi₀, Nat.sub_zero, hcost]; dsimp only [m]; omega) - · rw [hcount, hend, Nat.primesLE_eq_filter_range] - rfl + · rw [hcount, hend, Nat.primesLE_card_eq_primeCounting, Nat.primeCounting] end Cslib.Algorithms.WordRAM.PrimeTable diff --git a/ComplexityTests/WordRAMChanHeSearch.lean b/ComplexityTests/WordRAMChanHeSearch.lean index 11676596cb..136b9c36f8 100644 --- a/ComplexityTests/WordRAMChanHeSearch.lean +++ b/ComplexityTests/WordRAMChanHeSearch.lean @@ -87,4 +87,51 @@ example : ∃ y, ExecWithin 8 Search.program (emptyInput 2) y (3 * 132 + 9) ∧ change y.regs Search.answer = minOrOne (Nat.primesLE 2) at ha exact ha.trans hselect +private def laterInput : Data 64 := + ⟨Function.update (Function.update (emptyInput 3).regs (CountCall.length 0) 2) Search.factor 2, + Function.update (emptyInput 3).mem 2 8⟩ + +private def laterValues (j : Fin 3) (i : ℕ) : ℤ := if j = 0 then if i = 0 then 0 else 4 else 0 + +-- Factor two queries moduli four and six. The first collides on {0,4}; the second does not. +-- The actual answer, accepted flag, encoded modulus and charged bound are all observed. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 8 8 Search.program laterInput).map (fun yn => + ([yn.1.regs Search.answer, yn.1.regs Search.found, yn.1.regs CountCall.modulus], + decide (yn.2 ≤ 1049))) = some ([3, 1, 12], true)) + +-- The same nonempty factor-two case discharges every fit, array and bitmap premise of the +-- complete native execution theorem; its output witness is the actual executed state. +example : ∃ y, ExecWithin 8 Search.program laterInput y 1049 ∧ y.regs Search.answer = 3 := by + obtain ⟨y, he, _, _, ha⟩ := Search.exec (w := 6) laterInput laterValues + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro q hq; change q ≤ 3 at hq; interval_cases q <;> decide) + (by intro j i _; unfold laterValues; split; split <;> decide; decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by decide) (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by + intro j i hi + fin_cases j + · have hi' : i < 2 := hi + interval_cases i <;> decide + · have hi' : i < 0 := hi + omega + · have hi' : i < 0 := hi + omega) + (by + intro j i hi k hk he + fin_cases j + · have hi' : i < 2 := Finset.mem_range.mp hi + have hk' : k < 2 := Finset.mem_range.mp hk + interval_cases i <;> interval_cases k <;> simp [laterValues] at he ⊢ + · have hi' : i < 0 := Finset.mem_range.mp hi + omega + · have hi' : i < 0 := Finset.mem_range.mp hi + omega) + refine ⟨y, ?_, ha.trans (by decide)⟩ + convert he using 1 + decide + end ComplexityTests.WordRAMChanHeSearch From ec8aa561feae957effa539ac6f84d64c5db903c8 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:12:47 -0400 Subject: [PATCH 24/45] test(wordram): drop disconnected radix contract example Address the nonblocking #1465 review finding by deleting the generic sorted-set existential, which did not retain an execution or memory witness. Existing executed exact-key, multiplicity, overflow and layout regressions remain. Native code and public library contracts are unchanged. Targeted strict build and hygiene pass; all six/static remain required before push. AI assistance: OpenAI Codex. --- ComplexityTests/WordRAMRadixSort.lean | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ComplexityTests/WordRAMRadixSort.lean b/ComplexityTests/WordRAMRadixSort.lean index 06b443a9ad..28c8c96cd4 100644 --- a/ComplexityTests/WordRAMRadixSort.lean +++ b/ComplexityTests/WordRAMRadixSort.lean @@ -65,11 +65,3 @@ example : RadixSort.passes 0 [0, 0] = [0, 0] ∧ -- One finite instruction list is used at every input length and word width. example : RadixSort.program.length = 81 := by decide +kernel - --- Clients recover strict unsigned key order directly from the complete input contract. -example (w : ℕ) (xs : List ℕ) (hinput : InputFits w xs) - (hfit : Fits w (1 + 4 * xs.length)) (h2 : Fits w 2) : - ∃ keys : List ℕ, keys.Pairwise (· < ·) ∧ ∀ k, k ∈ keys ↔ k ∈ xs := by - obtain ⟨_, rs, _, _, _, _, _, _, _, hordered, hset, _, _⟩ := - RadixSort.input_exec xs hinput hfit h2 - exact ⟨rs.map Prod.fst, hordered, hset⟩ From 108564889ee67ecae0ad1e2e2973aae98b401666 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:13:54 -0400 Subject: [PATCH 25/45] doc(wordram): explain the Chan-He prime-window interface Address optional #1506 review nits by documenting why the intended preprocessing interface keeps m at least 2, and reflowing the remaining-composition note. Native code and public theorem statements are unchanged. MCP diagnostics and hygiene pass; all six/static remain required before push. AI assistance: OpenAI Codex. --- Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean | 7 ++++++- UPSTREAM.md | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean index d02fc2523e..cce0b1404f 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean @@ -404,7 +404,12 @@ def cost (x : Data 64) : ℕ := /-- Compute exactly the existing Chan–He modulus with two complete native prime searches. The prime bitmap/count is a shared input built by `PrimeTable.program`. A single polynomial budget bounds selector counters, thresholds and addresses; input values and the maximum modulus -fit the source width. The final halt is excluded. -/ +fit the source width. The final halt is excluded. + +The interface requires `m ≥ 2` for a nonempty candidate-prime window. The intended preprocessing +caller uses `mPar n U = (wPar n U + 1) * (2 * log₂(wPar n U + 1) + 4) ≥ 4`. +The default-only window `m = 1` is outside this interface, although the selector bounds extend +to it; `m = 0` fails the default-selector upper bound. -/ theorem exec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) (hm : 2 ≤ x.regs Search.limit) (hmod : GenericRAM.Signed.Fits w ((x.regs Search.limit ^ 2 : ℕ) : ℤ)) diff --git a/UPSTREAM.md b/UPSTREAM.md index 3070a33aa2..f7eb24e6a6 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -167,9 +167,9 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he Native numeric parameter computation, signed-residue/histogram counting, trial-division prime tables, collision-tested least-prime scans and complete two-prime modulus selection also use the existing `WordRAM.Code`/`ExecWithin` APIs. Their explicit local fit, frame and charged-cost - contracts do not yet compose the complete - preprocessing from input through frontend, heavy sets, recursion and packed output; the global - logarithmic width and subquadratic preprocessing bound remain in #1446. + contracts do not yet compose the complete preprocessing from input through frontend, heavy sets, + recursion and packed output. Global logarithmic width and subquadratic preprocessing bounds + remain in #1446. `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 From 787aa2c02e8c7ecfa6e65231b49c88f32e62bcfe Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:19:37 -0400 Subject: [PATCH 26/45] feat(three-sum): bound charged traversal tree overhead Bound all native transitions at Chan-He fuel, including skipped entries, by a polylogarithmic tree budget. Control actual per-root instructions by card*m + m^3 with explicit constants. Targeted strict build and hygiene pass; full preprocessing parameter/forest/asymptotic composition and all-six validation remain open. AI assistance: OpenAI Codex. --- Complexity.lean | 1 + .../ThreeSum/ChanHe/TraversalCost.lean | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean diff --git a/Complexity.lean b/Complexity.lean index 52de6992e3..f996541db3 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -144,6 +144,7 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalAdmitted public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCost public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalDeliver public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean new file mode 100644 index 0000000000..d41bf32640 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean @@ -0,0 +1,70 @@ +/- +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.TraversalTime + +/-! +# Polylogarithmic traversal overhead on charged native instructions + +The full ternary transition budget includes skipped calls. At the existing Chan–He recursion +fuel it is polylogarithmic, just as the emitted-node bound is. Combining it with the actual +native visit cost gives a cubic candidate-bound term and a linear-times-candidate input term. +This is a charged instruction estimate, not the finite reduction's candidate/element pair count. + +## Main results + +* `work_fuel_le`: polylogarithmically many charged transitions, including zero-fuel leaves. +* `visitTime_square_le`: the actual visit bound is controlled by `n*m + m³` up to constants. +* `rootTime_fuel_square_le`: the complete native root bound has polylogarithmic tree overhead. + +These native resource estimates are original. Substituting the actual parameter bound and +composing all frontend roots remain obligations of initialized-input preprocessing. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.Algorithms.ThreeSum.ChanHe (fuel three_pow_fuel_le) + +/-- Full-tree work counts all native pops, not just emitted mathematical instances. -/ +theorem work_fuel_le (n : ℕ) : Math.work (fuel n) ≤ 3 * (2 * Nat.log 2 n + 2) ^ 5 := by + have he := Math.work_eq (fuel n) + rw [pow_succ] at he + have hb := Nat.mul_le_mul_right 3 (three_pow_fuel_le n) + omega + +/-- The concrete `N=2m²` visit cost has the desired input-times-candidate and cubic terms. -/ +theorem visitTime_square_le (n m : ℕ) : + visitTime n m (2 * m ^ 2) ≤ 1024 * ((n + 1) * (m + 1) + (m + 1) ^ 3) := by + dsimp only [visitTime] + nlinarith + +/-- The complete native root cost, including setup, all skipped calls and all stack/loop work, +has only a polylogarithmic factor beyond `n*m + m³`. -/ +theorem rootTime_fuel_square_le (n card m : ℕ) : + rootTime (fuel n) card m (2 * m ^ 2) ≤ + 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + ((card + 1) * (m + 1) + (m + 1) ^ 3) := by + let S := (card + 1) * (m + 1) + (m + 1) ^ 3 + have hpos : 1 ≤ S := by + have hp : 0 < S := by dsimp only [S]; positivity + omega + have hvisit := visitTime_square_le card m + have hcost : visitTime card m (2 * m ^ 2) + 316 ≤ 2048 * S := by + change visitTime card m (2 * m ^ 2) ≤ 1024 * S at hvisit + omega + have hm := Nat.mul_le_mul (work_fuel_le n) hcost + calc rootTime (fuel n) card m (2 * m ^ 2) + ≤ 70 + (3 * (2 * Nat.log 2 n + 2) ^ 5) * (2048 * S) := + Nat.add_le_add_left hm 70 + _ = 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + ((card + 1) * (m + 1) + (m + 1) ^ 3) := by dsimp only [S]; ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal From e12766c4c09d0228d14ec1690ff5d5ad34426e17 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 15:52:19 -0400 Subject: [PATCH 27/45] feat(three-sum): assemble initialized-input native Chan-He preprocessing Implementation checkpoint for #1446. Assemble actual native frontend/parameters, prime table, stack/table reservations and descriptor-driven root traversal. Prove canonical payload/admissibility/allNodes bridges and shifted magnitude size estimates; execute the complete empty-input program. Targeted strict checks and hygiene pass, aggregate regenerated. Initialized-input execution composition, derived global logarithmic width and subquadratic charged cost remain unproved; this does not complete #1446/#1394. AI assistance: OpenAI Codex. --- Complexity.lean | 3 + .../ThreeSum/ChanHe/ShiftedSizes.lean | 74 +++++++ .../ThreeSum/ChanHe/PreprocessingEntries.lean | 136 +++++++++++++ .../ThreeSum/ChanHe/PreprocessingProgram.lean | 190 ++++++++++++++++++ .../NativeChanHePreprocessing.lean | 29 +++ 5 files changed, 432 insertions(+) create mode 100644 Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean create mode 100644 ComplexityTests/NativeChanHePreprocessing.lean diff --git a/Complexity.lean b/Complexity.lean index f996541db3..ec00d9e802 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -46,6 +46,7 @@ public import Complexity.Algorithms.ThreeSum.ChanHe.Defs public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers public import Complexity.Algorithms.ThreeSum.ChanHe.Recursion public import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds +public import Complexity.Algorithms.ThreeSum.ChanHe.ShiftedSizes public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes public import Complexity.Algorithms.ThreeSum.Convolution public import Complexity.Algorithms.ThreeSum.Defs @@ -138,6 +139,8 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Place public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceExec public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingEntries +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search diff --git a/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean b/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean new file mode 100644 index 0000000000..adb73ffd09 --- /dev/null +++ b/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean @@ -0,0 +1,74 @@ +/- +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.Sizes + +/-! +# Size bounds for the initialized-input magnitude convention + +Native parameter computation uses `(n+2)^κ`, including at lengths zero and one. The existing +finite instances retain their actual input-length parameter `n`; only their quantitative upper +bounds are compared with the existing estimates at `n+2`. + +## Main results + +* `mPar_mono_input`: candidate bounds are monotone in input length. +* `mPar_shifted_pow_le`: square-root times squared-logarithm bound for the actual native modulus. +* `length_instances_shifted_pow_le`: degree-seven logarithmic instance count at every length. +* `packed_length_shifted_pow_le`: linear times fourth-logarithm common packed length. + +These elementary parameter bridges are original and reuse the existing finite Chan–He bounds. +They do not change the finite instances or replace the actual input length by a padded input. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ThreeSum.ChanHe + +/-- Increasing the length parameter increases the candidate window without changing magnitude. -/ +theorem mPar_mono_input {n n' : ℕ} (hn : n ≤ n') (U : ℕ) : mPar n U ≤ mPar n' U := by + have hs := Nat.sqrt_le_sqrt hn + unfold mPar wPar + gcongr + +/-- The actual native parameter keeps length `n` and uses shifted polynomial magnitude. -/ +theorem mPar_shifted_pow_le (n κ : ℕ) : + mPar n (2 * (n + 2) ^ κ) ≤ + (5 * κ + 16) * (10 * κ + 40) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 2 := + (mPar_mono_input (show n ≤ n + 2 by omega) _).trans (mPar_pow_le (n + 2) κ) + +/-- The unchanged actual finite instance list has a logarithmic count even at lengths zero/one. -/ +theorem length_instances_shifted_pow_le (n κ : ℕ) (x : Fin n → ℤ) : + (instances n ((n + 2) ^ κ) x).length ≤ numBound κ (n + 2) := by + have hlog : 1 ≤ Nat.log 2 (n + 2) := Nat.log_pos (by decide) (by omega) + have hΛ : labelBits (2 * (n + 2) ^ κ) ≤ (κ + 2) * (Nat.log 2 (n + 2) + 1) := + (labelBits_pow_le (n + 2) κ).trans (by rw [add_mul]; omega) + have hone : 1 ≤ ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2 := + Nat.one_le_pow _ _ (by positivity) + have hn : Nat.log 2 n ≤ Nat.log 2 (n + 2) := Nat.log_mono_right (by omega) + calc (instances n ((n + 2) ^ κ) x).length + ≤ (2 * labelBits (2 * (n + 2) ^ κ) ^ 2 + 2) * (2 * Nat.log 2 n + 2) ^ 5 := + length_instances_le .. + _ ≤ (2 * ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2 + + 2 * ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2) * + (2 * Nat.log 2 (n + 2) + 2) ^ 5 := by gcongr; omega + _ = numBound κ (n + 2) := by unfold numBound; ring + +/-- The unchanged common packed length is bounded by the old estimate evaluated at `n+2`. -/ +theorem packed_length_shifted_pow_le (κ n : ℕ) : + 8 * mPar n (2 * (n + 2) ^ κ) ^ 2 ≤ + 32 * ((5 * κ + 16) * (10 * κ + 40)) ^ 2 * (n + 2) * + (Nat.log 2 (n + 2) + 1) ^ 4 := by + calc 8 * mPar n (2 * (n + 2) ^ κ) ^ 2 ≤ lenOf κ (n + 2) := + Nat.mul_le_mul_left 8 (Nat.pow_le_pow_left + (mPar_mono_input (show n ≤ n + 2 by omega) _) 2) + _ ≤ _ := lenOf_le κ (n + 2) (by omega) + +end Cslib.Algorithms.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean new file mode 100644 index 0000000000..9614ec7ffb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean @@ -0,0 +1,136 @@ +/- +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.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers + +/-! +# Exact native frontend entries for Chan–He traversal + +The semantic entries use the actual frontend payload bases and decoded compact lists. Native +readback follows from the frontend's canonical encoding theorem. Their output is exactly the +existing finite Chan–He node list; no new 3SUM predicate or padded input is introduced. + +## Main definitions + +* `entry`, `entries`: semantic records of the actual six-word frontend descriptors. + +## Main results + +* `entry_stored`: exact signed-encoded payload readback. +* `entry_valid`, `entry_ends`: admissibility and containment in the concrete frontend allocation. +* `entries_output`: exact equality with the existing finite node list, in order. + +These native representation bridges are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- One actual frontend descriptor, with the shared runtime fuel and concrete payload bases. -/ +def entry (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) : Traversal.Entry := + ⟨f, fun j => l.pointer (3 * t + j.val), + fun j => (Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode⟩ + +/-- All actual frontend roots in their existing enumeration order. -/ +def entries (l : Frontend.Layout) (U : ℕ) (rs : List (ℕ × ℕ)) : List Traversal.Entry := + (List.range l.count).map (entry l U (Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) rs) + +/-- Each concrete root column is a valid physical frontend slot. -/ +theorem entry_slot {l : Frontend.Layout} {t : ℕ} (ht : t < l.count) (j : Fin 3) : + 3 * t + j.val < l.arrays := by + have := j.isLt + dsimp only [Frontend.Layout.arrays] + omega + +/-- A root column occupies at most its actual `n+1`-word payload capacity. -/ +theorem entry_length_le (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) + (hcard : rs.length ≤ l.n) (t : ℕ) (j : Fin 3) : + ((entry l U f rs t).data j).length ≤ l.n + 1 := by + simpa only [entry, List.length_map] using + (Frontend.Math.length_array_le U l.digits rs (3 * t + j.val)).trans (by omega) + +/-- The actual native frontend payload is exactly the signed encoding expected by traversal. -/ +theorem entry_stored {input : List ℤ} {rs : List (ℕ × ℕ)} {l : Frontend.Layout} + {U f t : ℕ} {mem : ℕ → ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (h : Frontend.Output l U rs mem) (ht : t < l.count) : + (entry l U f rs t).Stored mem := by + intro j i + have hj := entry_slot ht j + have hi : i.val < (Frontend.Math.array U l.digits rs (3 * t + j.val)).length := by + simpa only [entry, List.length_map] using i.isLt + have hm := (h _ hj).2.2.get i.val hi + change mem (l.pointer (3 * t + j.val) + i.val) = + Signed.encode (((Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode)[i.val]) + simp only [List.getElem_map] + exact hm.trans (Frontend.Math.array_canonical hkeys U l.digits _ _ + (List.getElem_mem hi)).symm + +/-- Frontend roots satisfy the existing admissibility notion, including singleton zero at n=0. -/ +theorem entry_valid {input : List ℤ} {rs : List (ℕ × ℕ)} (l : Frontend.Layout) (U f t : ℕ) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + (entry l U f rs t).Valid (l.n + 1) (2 * U) := by + have hn (j : Fin 3) : ((entry l U f rs t).data j).Nodup := + Frontend.Math.array_decoded_nodup hkeys hnd U l.digits (3 * t + j.val) + have hc (j : Fin 3) : ((entry l U f rs t).data j).toFinset.card ≤ l.n + 1 := by + rw [List.toFinset_card_of_nodup (hn j)] + exact entry_length_le l U f rs hcard t j + have hb (j : Fin 3) : Cslib.Algorithms.ThreeSum.Bdd (2 * U) + ((entry l U f rs t).data j).toFinset := by + intro z hz + change z ∈ ((Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode).toFinset + at hz + obtain ⟨u, hu, rfl⟩ := List.mem_map.mp (List.mem_toFinset.mp hz) + exact Frontend.Math.array_magnitude hkeys hvalues l.digits _ u hu + exact ⟨hn, hc 0, hc 1, hc 2, hb 0, hb 1, hb 2⟩ + +/-- Every actual root payload ends inside the frontend's concrete allocation. -/ +theorem entry_ends (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) + (hcard : rs.length ≤ l.n) {t : ℕ} (ht : t < l.count) (j : Fin 3) : + (entry l U f rs t).source j + ((entry l U f rs t).data j).length ≤ l.endpoint := by + have hj := entry_slot ht j + have hn := entry_length_le l U f rs hcard t j + calc (entry l U f rs t).source j + ((entry l U f rs t).data j).length + ≤ l.pointer (3 * t + j.val) + l.capacity := Nat.add_le_add_left hn _ + _ = l.pointer (3 * t + j.val + 1) := (l.pointer_succ _).symm + _ ≤ l.pointer l.arrays := l.pointer_mono (by omega) + +/-- Traversing these exact entries produces precisely the unchanged finite Chan–He node list. -/ +theorem entries_output {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + let l : Frontend.Layout := ⟨input.length, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + Traversal.Math.output + (Nat.primesLE (Cslib.Algorithms.ThreeSum.ChanHe.mPar input.length (2 * U))) l.digits + ((entries l U rs).map Traversal.Entry.task) = + Cslib.Algorithms.ThreeSum.ChanHe.allNodes input.length U + (fun i : Fin input.length => input[i]) := by + let l : Frontend.Layout := ⟨input.length, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + have hr := Frontend.Math.roots_indexed U rs + have hd := Frontend.Math.roots_decoded hkeys hcounts hvalues + change ((List.range l.count).map (Frontend.Math.root U l.digits rs)) = + Frontend.Math.roots U rs at hr + calc Traversal.Math.output _ _ ((entries l U rs).map Traversal.Entry.task) + = (((List.range l.count).map (Frontend.Math.root U l.digits rs)).map + Frontend.Math.decodeRoot).flatMap (fun T => + Cslib.Algorithms.ThreeSum.ChanHe.reduction input.length (2 * U) T.1 T.2.1 T.2.2) := by + simp only [Traversal.Math.output, entries, List.flatMap_map, List.map_map, + Function.comp_def] + rfl + _ = _ := by rw [hr, hd]; rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean new file mode 100644 index 0000000000..ad6aebd6e2 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean @@ -0,0 +1,190 @@ +/- +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.Frontend +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot +public import Complexity.Algorithms.WordRAM.PrimeTable + +/-! +# Native initialized-input Chan–He preprocessing program + +The complete finite instruction program first executes the native frontend and parameter +computation. It builds the prime bitmap by actual trial division, computes concrete stack and +output-table reservations, and loads and traverses every actual six-word frontend descriptor. +The ternary reservation is computed by a native multiplication loop, not supplied by a caller. + +This module defines the executable program. Its initialized-input correctness, width and global +charged bounds require companion proofs; the program definition alone does not complete them. + +## Main definitions + +* `program`: one finite 128-register program per fixed magnitude exponent. +* `flatProgram`: the complete native program with its final count-returning halt. +* `loadRoot`, `forest`: actual descriptor loads and the outer native root loop. + +## Main results + +* `not_readsWidth`: runtime physical width never controls the program's computation. +* `size_program`: code size depends only on the fixed exponent. + +The code and layout computations are original. Output metadata is unsigned; payload words use +the existing signed encoding. No solver, collision, primality or operation-count oracle is used. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Saved frontend descriptor base. -/ +abbrev descriptors : Fin 128 := 109 +/-- Saved number of roots. -/ +abbrev roots : Fin 128 := 110 +/-- Index of the next root. -/ +abbrev current : Fin 128 := 111 +/-- The computed recursion fuel shared by all roots. -/ +abbrev levels : Fin 128 := 112 +/-- The computed original-input magnitude. -/ +abbrev inputMagnitude : Fin 128 := 113 +/-- The computed candidate-prime bound. -/ +abbrev candidateLimit : Fin 128 := 114 +/-- The computed label-bit parameter. -/ +abbrev labels : Fin 128 := 115 +/-- Common packed length in words, retained as unsigned output metadata. -/ +abbrev packedLength : Fin 128 := 116 +/-- One-past-end frontend address; the shared prime bitmap begins here. -/ +abbrev firstFree : Fin 128 := 117 +/-- Six words per frontend root descriptor. -/ +abbrev stride : Fin 128 := 118 +/-- Outer-controller constant one. -/ +abbrev one : Fin 128 := 119 +/-- Outer-controller constant zero. -/ +abbrev zero : Fin 128 := 120 +/-- Descriptor-loading address temporary. -/ +abbrev address : Fin 128 := 121 +/-- Computed ternary-tree reservation. -/ +abbrev power : Fin 128 := 122 +/-- Remaining reservation multiplications. -/ +abbrev remaining : Fin 128 := 123 +/-- Reserved number of unsigned output pointers. -/ +abbrev outputCapacity : Fin 128 := 124 +/-- Setup constant, first seven and then three. -/ +abbrev scalar : Fin 128 := 125 +/-- The constant four converts packed words to groups. -/ +abbrev four : Fin 128 := 126 +/-- Initial heap frontier, above the complete stack and pointer-table reservations. -/ +abbrev heapFloor : Fin 128 := 127 + +/-- The complete 15-register prime builder runs in the low registers. -/ +def primeRegisters (i : Fin 15) : Fin 128 := ⟨i.val, by omega⟩ + +/-- Save every frontend/parameter output that the reused traversal registers will overwrite. -/ +def snapshot : Code .standard 128 := + let frontBase := Frontend.descriptorBase + let frontCount := Frontend.descriptorCount + let frontEnd := Frontend.heapEnd + let f := Frontend.parameterRegisters Parameters.fuel + let U := Frontend.parameterRegisters Parameters.magnitude + let m := Frontend.parameterRegisters Parameters.primeBound + let Λ := Frontend.parameterRegisters Parameters.digits + let N := Frontend.parameterRegisters Parameters.packedLen + ram[ descriptors ← frontBase; roots ← frontCount; levels ← f; inputMagnitude ← U; + candidateLimit ← m; labels ← Λ; packedLength ← N; firstFree ← frontEnd; + current ← #0; stride ← #6; one ← #1; zero ← #0; four ← #4 ] + +/-- Deliver the actual native prime-table inputs. -/ +def preparePrimes : Code .standard 128 := + let m := primeRegisters PrimeTable.limit + let b := primeRegisters PrimeTable.base + ram[ m ← candidateLimit; b ← firstFree ] + +/-- Compute the traversal ABI and concrete stack reservation, retaining the outer metadata. -/ +def setupTraversal : Code .standard 128 := + let m := Traversal.limit + let Λ := Traversal.digits + let U := Traversal.magnitude + let N := Traversal.groups + let bitmap := Traversal.bitmap + let card := Traversal.primeCount + let nativeCard := primeRegisters PrimeTable.count + let stack := Traversal.stackBase + let capacity := Traversal.stackCapacity + let out := Traversal.outputs + let count := Traversal.outputCount + let z := Traversal.zero + let unit := Traversal.one + ram[ m ← candidateLimit; Λ ← labels; U ← inputMagnitude + inputMagnitude; + N ← packedLength / four; bitmap ← firstFree; card ← nativeCard; + stack ← firstFree + candidateLimit; stack ← stack + one; + capacity ← levels + levels; capacity ← capacity + one; scalar ← #7; + capacity ← capacity * scalar; out ← stack + capacity; count ← #0; z ← #0; unit ← #1; + power ← #1; remaining ← levels + one; scalar ← #3 ] + +/-- One actual multiplication and decrement per ternary-reservation level. -/ +def powerBody : Code .standard 128 := ram[ + power ← power * scalar; remaining ← remaining - one ] + +/-- Compute `3^(fuel+1)` by a finite native loop. -/ +def reserve : Code .standard 128 := .loop (.lt zero remaining) powerBody + +/-- Reserve all output pointers before the initial fresh heap frontier. -/ +def finishLayout : Code .standard 128 := + let out := Traversal.outputs + let heap := Traversal.heap + ram[ outputCapacity ← roots * power; heap ← out + outputCapacity; heapFloor ← heap ] + +/-- Load all six descriptor words in their actual length/pointer order, with the shared fuel. -/ +def loadRoot : Code .standard 128 := + let f := Traversal.slot 0 + let p₀ := Traversal.slot 1 + let n₀ := Traversal.slot 2 + let p₁ := Traversal.slot 3 + let n₁ := Traversal.slot 4 + let p₂ := Traversal.slot 5 + let n₂ := Traversal.slot 6 + ram[ address ← current * stride; address ← descriptors + address; f ← levels; + n₀ ← *address; address ← address + one; p₀ ← *address; address ← address + one; + n₁ ← *address; address ← address + one; p₁ ← *address; address ← address + one; + n₂ ← *address; address ← address + one; p₂ ← *address ] + +/-- Traverse the actually loaded root, then advance the actual descriptor index. -/ +def body : Code .standard 128 := + .seq loadRoot (.seq Traversal.oneRoot (.op (.bin .add current current one))) + +/-- Enumerate every frontend root, including the exact empty and multiplicity boundary roots. -/ +def forest : Code .standard 128 := .loop (.lt current roots) body + +/-- Complete native preprocessing after the native initialized-input frontend. -/ +def afterFrontend : Code .standard 128 := + .seq snapshot (.seq preparePrimes (.seq (PrimeTable.program.rename primeRegisters) + (.seq setupTraversal (.seq reserve (.seq finishLayout forest))))) + +/-- One finite native program per fixed magnitude exponent, independent of input length/width. -/ +def program (κ : ℕ) : Code .standard 128 := .seq (Frontend.program κ) afterFrontend + +/-- Complete flat native program; it returns the unsigned output count. -/ +def flatProgram (κ : ℕ) : Program .standard 128 := (program κ).program Traversal.outputCount + +private theorem afterFrontend_not_readsWidth : ¬afterFrontend.ReadsWidth := by decide + +/-- Physical width never determines radix passes, prime candidates, recursion fuel or code. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := + not_or.mpr ⟨Frontend.not_readsWidth κ, afterFrontend_not_readsWidth⟩ + +/-- The finite controller adds a fixed code suffix to the exponent-dependent frontend. -/ +theorem size_program (κ : ℕ) : + (program κ).size = κ + 71 + Frontend.construct.size + afterFrontend.size := by + rw [program, Code.size, Frontend.size_program] + +/-- Only the current index and address scratch among registers 109–127 change during a root. -/ +theorem body_preserves : ∀ i : Fin 128, + 109 ≤ i.val → i ≠ current → i ≠ address → body.Preserves i := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/ComplexityTests/NativeChanHePreprocessing.lean b/ComplexityTests/NativeChanHePreprocessing.lean new file mode 100644 index 0000000000..676d1a5462 --- /dev/null +++ b/ComplexityTests/NativeChanHePreprocessing.lean @@ -0,0 +1,29 @@ +/- +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.PreprocessingProgram + +/-! # Executed initialized-input Chan–He preprocessing -/ + +namespace ComplexityTests.NativeChanHePreprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +-- This successful physical width is smaller than the exported frontend sufficient width. +-- All parameters, the prime bitmap, reservations, descriptor loads and root skips execute. +-- The empty frontend still has twenty roots, rather than bypassing the algorithm. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 32 256 (Preprocessing.program 0) (Data.init [])).map (fun yn => + [yn.1.regs Traversal.outputCount, yn.1.regs Preprocessing.packedLength, + yn.1.regs Preprocessing.candidateLimit, yn.1.regs Traversal.primeCount, + yn.1.regs Preprocessing.current, yn.1.regs Preprocessing.power, + yn.1.regs Preprocessing.outputCapacity, yn.1.regs Preprocessing.heapFloor, + yn.1.regs Traversal.heap, yn.1.mem 0]) = + some [0, 294912, 192, 43, 20, 9, 180, 575, 575, 0]) + +end ComplexityTests.NativeChanHePreprocessing From a49400ce31c854d74f86b62c6580c4b99c714b16 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 16:12:40 -0400 Subject: [PATCH 28/45] doc(wordram): record complete bounded Chan-He traversal scope --- ComplexityTests/NativeChanHeTraversal.lean | 6 ++++++ ROADMAP.md | 6 ++++-- UPSTREAM.md | 9 ++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ComplexityTests/NativeChanHeTraversal.lean b/ComplexityTests/NativeChanHeTraversal.lean index abb8252909..32e6ea7404 100644 --- a/ComplexityTests/NativeChanHeTraversal.lean +++ b/ComplexityTests/NativeChanHeTraversal.lean @@ -1,3 +1,9 @@ +/- +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.TraversalModel import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalExec import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot diff --git a/ROADMAP.md b/ROADMAP.md index 8e4c0ca2ce..7f4b6a27f0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -429,8 +429,10 @@ each statement against the numbered result. Organize modules by subject, not cha ([#1482]) starts from initialized signed input and produces exact compact root descriptors with derived logarithmic width and uniform `Oκ((n+1) log²(n+2))` charged cost. The complete native one-node component ([#1481]) computes all residues/histograms, three compact heavy arrays and - exact packed output in linear time. Their integration, native recursive traversal ([#1512]) - and global preprocessing width/time composition remain. It still needs + exact packed output in linear time. Native bounded recursive traversal ([#1512]) executes those + components with an explicit stack, exact preorder output, polynomial local fit/frame contracts + and charged ternary-tree bounds. Initialized-input forest integration and global preprocessing + width/time composition remain. 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 diff --git a/UPSTREAM.md b/UPSTREAM.md index f7eb24e6a6..693e18d1ea 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -167,9 +167,12 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he Native numeric parameter computation, signed-residue/histogram counting, trial-division prime tables, collision-tested least-prime scans and complete two-prime modulus selection also use the existing `WordRAM.Code`/`ExecWithin` APIs. Their explicit local fit, frame and charged-cost - contracts do not yet compose the complete preprocessing from input through frontend, heavy sets, - recursion and packed output. Global logarithmic width and subquadratic preprocessing bounds - remain in #1446. + contracts are composed by a fixed 128-register bounded traversal with an explicit seven-word + stack frame and exact preorder packed output. The traversal consumes a root descriptor and + runtime fuel; its polynomial local envelope derives kernel fits and charges every stack, + descriptor and address operation. It does not invoke a Convolution-3SUM solver. Composition + from initialized input through all frontend roots, global logarithmic width and subquadratic + preprocessing bounds remain in #1446. These components do not affect the curated headlines. `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 From 11fd4dd93aa16c40f243b6c1496c055b695f5d4d Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 17:13:04 -0400 Subject: [PATCH 29/45] feat(wordram): complete initialized Chan-He preprocessing --- Complexity.lean | 19 ++ .../ThreeSum/ChanHe/Preprocessing.lean | 39 +++ .../ThreeSum/ChanHe/PreprocessingBounds.lean | 150 ++++++++++ .../ThreeSum/ChanHe/PreprocessingExecute.lean | 129 +++++++++ .../ThreeSum/ChanHe/PreprocessingFinish.lean | 81 ++++++ .../ThreeSum/ChanHe/PreprocessingInput.lean | 107 +++++++ .../ChanHe/PreprocessingIntermediates.lean | 86 ++++++ .../ChanHe/PreprocessingInvariant.lean | 128 +++++++++ .../ThreeSum/ChanHe/PreprocessingLayout.lean | 73 +++++ .../ThreeSum/ChanHe/PreprocessingLoad.lean | 85 ++++++ .../ThreeSum/ChanHe/PreprocessingLoop.lean | 159 +++++++++++ .../ThreeSum/ChanHe/PreprocessingPrimes.lean | 79 ++++++ .../ThreeSum/ChanHe/PreprocessingProgram.lean | 8 +- .../ThreeSum/ChanHe/PreprocessingReserve.lean | 88 ++++++ .../ThreeSum/ChanHe/PreprocessingSetup.lean | 99 +++++++ .../ChanHe/PreprocessingSnapshot.lean | 104 +++++++ .../ThreeSum/ChanHe/PreprocessingStep.lean | 117 ++++++++ .../ThreeSum/ChanHe/PreprocessingTime.lean | 266 ++++++++++++++++++ .../ThreeSum/ChanHe/PreprocessingWidth.lean | 139 +++++++++ .../ThreeSum/ChanHe/TraversalExec.lean | 5 +- .../Algorithms/WordRAM/ThreeSum/Frontend.lean | 38 +-- .../WordRAM/ThreeSum/Frontend/Frame.lean | 50 ++++ .../WordRAM/ThreeSum/Frontend/Input.lean | 5 +- Complexity/Computability/WordRAM/Width.lean | 102 +++++++ .../NativeChanHePreprocessing.lean | 5 + .../NativeChanHePreprocessingContracts.lean | 148 ++++++++++ .../WordRAMThreeSumFrontendContracts.lean | 20 +- ROADMAP.md | 8 +- UPSTREAM.md | 10 +- 29 files changed, 2283 insertions(+), 64 deletions(-) create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean create mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean create mode 100644 Complexity/Computability/WordRAM/Width.lean create mode 100644 ComplexityTests/NativeChanHePreprocessingContracts.lean diff --git a/Complexity.lean b/Complexity.lean index ec00d9e802..73198a982f 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -139,8 +139,25 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Place public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceExec public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingBounds public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingEntries +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingExecute +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingFinish +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingIntermediates +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLayout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoad +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingPrimes public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingReserve +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSnapshot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingStep +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingWidth public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search @@ -172,6 +189,7 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Execute +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Frame public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model @@ -1718,6 +1736,7 @@ public import Complexity.Computability.WordRAM.Signed.State public import Complexity.Computability.WordRAM.Structured public import Complexity.Computability.WordRAM.Time public import Complexity.Computability.WordRAM.Trace +public import Complexity.Computability.WordRAM.Width public import Complexity.Crypto.Computational.Basic public import Complexity.Crypto.Computational.CollisionResistant public import Complexity.Crypto.Computational.Commitment diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean new file mode 100644 index 0000000000..e39cd4213b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.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.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingIntermediates + +/-! +# Complete native Chan–He preprocessing + +One finite 128-register program per fixed exponent computes every parameter, sorts and counts +the initialized signed input, constructs the exact distinct-position frontend, builds its prime +bitmap, executes both modulus searches and every heavy/light recursion, and returns a table of +exact packed convolution instances. Counts, pointers and the common length are unsigned; packed +payloads use the existing signed encoding. Runtime code does not read physical width. + +## Main definitions + +* `program`, `flatProgram`: actual structured and flat native preprocessing code. +* `width`: the positive threshold `(1300κ+7865) clog₂(n+2)` derives every component's fits. +* `cost`: all native instructions, including the final halt. + +## Main results + +* `runs_input`: complete `State.init` execution and exact existing `ChanHe.instances` readback. +* `cost_bound`: at most `Kκ (n+2)(sqrt(n+2)+1)(log₂(n+2)+1)^13` charged instructions. +* `intermediates_polynomial`, `space_polynomial`: every prefix at every admitted width uses only + polynomial words and a polynomial initial address range. + +The initialized-input controller, layout, native execution and resource composition are original. +The reused finite Chan–He mathematics credits Anthropic, PBC and [ChanHe2020]. No solver or source +machine simulation is assumed. This preprocessing endpoint leaves the Exact Triangle solver and +complete subquadratic 3SUM composition, including the final 1.9992 exponent, separate. +-/ diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean new file mode 100644 index 0000000000..b00a063e0c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean @@ -0,0 +1,150 @@ +/- +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.PreprocessingLayout + +/-! +# Polynomial global preprocessing reservations + +Every frontend allocation, explicit-stack reservation and root allocation is bounded by a fixed +power of the existing parameter budget. This derives local kernel fits from logarithmic width. + +## Main results + +* `arena_envelope_le`: the complete global signed envelope has polynomial magnitude. + +These coarse resource estimates are original. Charged subquadratic time is a separate bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The complete global envelope is a fixed power of the already bounded native parameter budget. -/ +theorem arena_envelope_le (κ n : ℕ) (retained : ℕ → ℕ) : + let a := arena κ n retained + Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N ≤ + Parameters.Math.budget n ((n + 2) ^ κ) ^ 52 := by + let U := (n + 2) ^ κ + let P := Parameters.Math.budget n U + let l := Frontend.layout κ n + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let N := 2 * m ^ 2 + have hP : 64 ≤ P := by dsimp only [P, Parameters.Math.budget]; omega + have hP1 : 1 ≤ P := by omega + have hsmall : 8 * U + 4 * n + 64 ≤ P := by + dsimp only [P, Parameters.Math.budget] + omega + have hms : 8 * (m + 1) ^ 2 ≤ P := by + dsimp only [P, Parameters.Math.budget, m] + omega + have hm : m ≤ P := by nlinarith + have hmp : m + 1 ≤ P := by nlinarith + have hm2 : m ^ 2 ≤ P := by nlinarith + have hN : N ≤ P := by dsimp only [N]; nlinarith + have hn : n + 1 ≤ P := by omega + have hU : 2 * U ≤ P := by omega + have hΛ : l.digits ≤ P := by + have hl := Nat.log_le_self 2 (4 * U) + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ P + dsimp only [Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + omega + have hheight : Cslib.Algorithms.ThreeSum.ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + have hlog := Nat.log_le_self 2 n + have hF : F ≤ P := by + dsimp only [F, Cslib.Algorithms.ThreeSum.ChanHe.fuel] + omega + have hwork : Traversal.Math.work F ≤ P ^ 6 := by + have hw := Traversal.work_fuel_le n + change Traversal.Math.work F ≤ 3 * (2 * Nat.log 2 n + 2) ^ 5 at hw + calc Traversal.Math.work F ≤ 3 * P ^ 5 := + hw.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) 5)) + _ ≤ P * P ^ 5 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 6 := by ring + have hpower : 3 ^ (F + 1) ≤ P ^ 7 := by + have he := Traversal.Math.work_eq F + have hpow : 1 ≤ P ^ 6 := Nat.one_le_pow _ _ hP1 + calc 3 ^ (F + 1) ≤ 3 * P ^ 6 := by omega + _ ≤ P * P ^ 6 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 7 := by ring + have hD : l.count ≤ P ^ 3 := by + have hsq := Nat.pow_le_pow_left hΛ 2 + have hpos : 1 ≤ P ^ 2 := Nat.one_le_pow _ _ hP1 + calc + l.count ≤ 4 * P ^ 2 := by + dsimp only [Frontend.Layout.count, Frontend.Layout.splits] + omega + _ ≤ P * P ^ 2 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 3 := by ring + have hfront : l.endpoint ≤ P ^ 4 := by + have hb := Frontend.array_budget_le n U + change Frontend.Arrays.budget l U ≤ P ^ 4 at hb + apply le_trans ?_ hb + dsimp only [Frontend.Arrays.budget] + omega + have hdelta : Traversal.allocationSize (n + 1) m N ≤ P ^ 2 := by + dsimp only [Traversal.allocationSize] + nlinarith + have powmono {i j : ℕ} (hij : i ≤ j) : P ^ i ≤ P ^ j := + Nat.pow_le_pow_right hP1 hij + have hstack : l.endpoint + m + 1 ≤ P ^ 5 := by + have hp := powmono (show 1 ≤ 4 by decide) + simp only [pow_one] at hp + calc l.endpoint + m + 1 ≤ 2 * P ^ 4 := by omega + _ ≤ P * P ^ 4 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 5 := by ring + have hcap : 7 * (2 * F + 1) ≤ P ^ 2 := by nlinarith + have htable : l.endpoint + m + 1 + 7 * (2 * F + 1) ≤ P ^ 6 := by + have hp := powmono (show 2 ≤ 5 by decide) + calc _ ≤ 2 * P ^ 5 := by omega + _ ≤ P * P ^ 5 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 6 := by ring + have houtcap : l.count * 3 ^ (F + 1) ≤ P ^ 10 := by + calc _ ≤ P ^ 3 * P ^ 7 := Nat.mul_le_mul hD hpower + _ = P ^ 10 := by ring + have hfloor : (arena κ n retained).floor ≤ P ^ 11 := by + have hp := powmono (show 6 ≤ 10 by decide) + change l.endpoint + m + 1 + 7 * (2 * F + 1) + l.count * 3 ^ (F + 1) ≤ _ + calc _ ≤ 2 * P ^ 10 := by omega + _ ≤ P * P ^ 10 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 11 := by ring + have halloc : l.count * (Traversal.Math.work F * + Traversal.allocationSize (n + 1) m N) ≤ P ^ 11 := by + calc _ ≤ P ^ 3 * (P ^ 6 * P ^ 2) := + Nat.mul_le_mul hD (Nat.mul_le_mul hwork hdelta) + _ = P ^ 11 := by ring + have hceil : (arena κ n retained).ceiling ≤ P ^ 12 := by + change (arena κ n retained).floor + l.count * + (Traversal.Math.work F * Traversal.allocationSize (n + 1) m N) ≤ _ + calc _ ≤ 2 * P ^ 11 := by omega + _ ≤ P * P ^ 11 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 12 := by ring + have hsum : (arena κ n retained).ceiling + (n + 1) + m ^ 2 + l.digits + m + + 2 * U + N + 100 ≤ 2 * P ^ 12 := by + have hpow : P ^ 2 ≤ P ^ 12 := powmono (by decide) + have hsmall' : (n + 1) + m ^ 2 + l.digits + m + 2 * U + N + 100 ≤ P ^ 2 := by + nlinarith + omega + change 65536 * ((arena κ n retained).ceiling + (n + 1) + m ^ 2 + l.digits + m + + 2 * U + N + 100) ^ 4 ≤ P ^ 52 + calc _ ≤ 65536 * (2 * P ^ 12) ^ 4 := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hsum 4) + _ = 1048576 * P ^ 48 := by ring + _ ≤ P ^ 4 * P ^ 48 := Nat.mul_le_mul_right _ (by + have := Nat.pow_le_pow_left hP 4 + norm_num at this + omega) + _ = P ^ 52 := by ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean new file mode 100644 index 0000000000..3b053270ee --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.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.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingPrimes +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingFinish +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingWidth + +/-! +# Composing every computed native preprocessing root + +Starting from the complete frontend's actual output, the native program computes its shared +prime table and reservations, then executes every actual descriptor and recursion tree. + +## Main results + +* `afterFrontend_exec`: the complete native suffix has exact finite node output and charged cost. + +The native composition proofs are original. No runtime algorithm or fit advice is supplied. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- The complete actual suffix consumes only computed frontend output and a global width. -/ +theorem afterFrontend_exec {κ w : ℕ} (input : List ℤ) (rs : List (ℕ × ℕ)) (x : Data 128) + (hw : width κ input.length ≤ w + 2) (hx : x.Bounded (w + 2)) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + (hfront : Frontend.Output (Frontend.layout κ input.length) + ((input.length + 2) ^ κ) rs x.mem) + (hparams : Parameters.Output input.length ((input.length + 2) ^ κ) + (x.comap Frontend.parameterRegisters)) + (hbase : x.regs Frontend.descriptorBase = (Frontend.layout κ input.length).descriptors) + (hcount : x.regs Frontend.descriptorCount = (Frontend.layout κ input.length).count) + (hend : x.regs Frontend.heapEnd = (Frontend.layout κ input.length).endpoint) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ input.length) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let l := Frontend.layout κ n + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) afterFrontend x y + ((m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + l.count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17)) ∧ + out.map Prod.snd = Traversal.Math.output (Nat.primesLE m) l.digits + ((entries l U rs).map Traversal.Entry.task) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n x.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n x.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 := by + let n := input.length + let l := Frontend.layout κ n + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + have hfits := setup_fits hw x.mem + obtain ⟨p, hp, ps, pm⟩ := snapshot_exec x hparams hbase hcount hend hfits.2.2.2 + obtain ⟨q, hq, qs, qm, qcount, qbitmap, qprefix⟩ := primes_exec p ps hfits.2.1 + obtain ⟨r, hr, rsaved, rcontrols, rcount, rpower, rremaining, rscalar, rm⟩ := + setupTraversal_exec q qs qcount (setup_fits hw q.mem).1 + obtain ⟨b, hb, bs, bcontrols, bcount, bheap, _, _, bm⟩ := + finish_exec r rsaved rcontrols rcount rpower rremaining rscalar + (setup_fits hw r.mem).2.2.1 + ((setup_fits hw r.mem).1.of_le (by omega)) + let a := arena κ n q.mem + have bmem : b.mem = q.mem := bm.trans rm + have bcontrol : a.Controls b := by simpa only [rm] using bcontrols + have bh : b.regs Traversal.heap = a.floor := by simpa only [rm] using bheap + have bfront : Frontend.Output l U rs b.mem := by + rw [bmem] + apply hfront.frame hcard + intro v hv + exact (qprefix v hv).trans (congrFun pm v) + have bbounded := hb.bounded (hr.bounded (hq.bounded (hp.bounded hx))) + have hi : (ForestCursor.mk 0 []).Invariant a w l U rs b := by + constructor + · exact bbounded + · exact bcontrol + · exact bfront + · exact bs.base + · exact bs.count + · exact bs.index + · exact bs.fuel + · exact bs.stride + · exact bs.one + · exact Nat.zero_le _ + · intro v hv + rw [bmem] + exact qbitmap v hv + · exact bh.ge + · rw [bh] + exact le_rfl + · exact bcount + · simp only [List.length_nil, Nat.sub_zero, Nat.zero_add] + exact Nat.mul_le_mul_left l.count (work_le_power F) + · exact Traversal.storedNodes_nil _ _ _ _ + · intro i + exact Fin.elim0 i + · simp only [List.map_nil, List.drop_zero, List.nil_append] + · intro v _ + exact congrFun bmem v + have hready : a.Ready w := by + have hh := arena_ready hw q.mem + simpa only [Nat.add_sub_cancel] using hh + obtain ⟨s, y, he, hy, _, hout⟩ := forest_exec (arena_separated κ n q.mem) hready hi + rfl rfl rfl (arena_frontend_before κ n q.mem) hkeys hnd hcard hvalues + refine ⟨s.nodes, y, ?_, hout, hy.output, hy.count, hy.controls.2.2.2.2.2.2.1, ?_⟩ + · have htotal := hp.within.seq (hq.seq (hr.within.seq (hb.seq he))) + exact htotal.mono (by + dsimp only [n, l, U, m, F, a, arena] at * + simp only [Nat.sub_zero] at * + omega) + · have hpersist : forest.Preserves packedLength := body_preserves _ (by decide) + (by decide) (by decide) + exact (he.regs_eq hpersist).trans bs.length + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean new file mode 100644 index 0000000000..d0ee490e66 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean @@ -0,0 +1,81 @@ +/- +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.PreprocessingSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingReserve + +/-! +# Completing the native forest reservations + +The actual power loop computes the per-root tree reservation. Three further instructions multiply +by the root count and place the initial heap above the complete pointer table. + +## Main results + +* `finish_exec`: execute reservation and layout completion with exact native heap/table metadata. + +The native composition and memory-frame proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Compute all root reservations and the actual first fresh heap address by native code. -/ +theorem finish_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hc : (arena κ n x.mem).Controls x) (hcount : x.regs Traversal.outputCount = 0) + (hp : x.regs power = 1) + (hr : x.regs remaining = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1) + (hscalar : x.regs scalar = 3) + (hpow : Fits w (3 ^ (Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1))) + (hfloor : Fits w (arena κ n x.mem).floor) : + ∃ y, ExecWithin w (.seq reserve finishLayout) x y + (4 * (Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1) + 5) ∧ + Saved κ n y ∧ (arena κ n x.mem).Controls y ∧ + y.regs Traversal.outputCount = 0 ∧ y.regs Traversal.heap = (arena κ n x.mem).floor ∧ + y.regs outputCapacity = (arena κ n x.mem).outputCapacity ∧ + y.regs heapFloor = (arena κ n x.mem).floor ∧ y.mem = x.mem := by + let a := arena κ n x.mem + obtain ⟨p, he, hpower, _, hmem, hregs⟩ := reserve_exec x hp hr hs.zero hs.one hscalar hpow + have ps : Saved κ n p := hs.congr (fun i hi hj => hregs i + (by intro hh; subst i; norm_num [power] at hj) + (by intro hh; subst i; norm_num [remaining] at hj)) + have pc : a.Controls p := hc.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact hregs i (by intro hh; subst i; norm_num [power] at hi) + (by intro hh; subst i; norm_num [remaining] at hi)) + have pcount : p.regs Traversal.outputCount = 0 := + (hregs _ (by decide) (by decide)).trans hcount + let y := p |>.set outputCapacity a.outputCapacity |>.set Traversal.heap a.floor + |>.set heapFloor a.floor + have hcap : Fits w a.outputCapacity := hfloor.of_le (by dsimp only [a, arena]; omega) + have hout := pc.2.2.2.2.2.2.1 + dsimp only [a, arena] at hcap hfloor hout + have hz : Exec w finishLayout p y 3 := by + ram_eval [finishLayout, y, a, arena, ps.count, hpower, hout, hcap, hfloor, + outputCapacity, roots, power, Traversal.outputs, Traversal.heap, heapFloor] + have ys : Saved κ n y := ps.congr (fun i hi hj => hz.within.regs_eq (by + have hh : ∀ i : Fin 128, 109 ≤ i.val → (i.val ≤ 120 ∨ i.val = 126) → + finishLayout.Preserves i := by decide + exact hh i hi hj)) + have yc : a.Controls y := pc.congr (by + intro i hi + apply hz.within.regs_eq + have hh : ∀ i : Fin 128, i.val ∈ [71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 83] → + finishLayout.Preserves i := by decide + exact hh i hi) + refine ⟨y, (he.seq hz.within).mono (by omega), ys, yc, ?_, ?_, ?_, ?_, hmem⟩ + · simpa +decide [y, outputCapacity, Traversal.heap, heapFloor, + Traversal.outputCount] using pcount + all_goals simp +decide [y, a, outputCapacity, Traversal.heap, heapFloor] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean new file mode 100644 index 0000000000..88c214c766 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean @@ -0,0 +1,107 @@ +/- +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.PreprocessingExecute + +/-! +# Complete native Chan–He preprocessing from initialized input + +Every parameter, sorted multiplicity table, frontend descriptor, prime bit, heavy list and packed +output is computed by one finite native program for the fixed magnitude exponent. The output +list is exactly the existing finite instances, including negative and small-input cases. + +## Main definitions + +* `cost`: charged complete-program bound, including the final halt. + +## Main results + +* `exec_input`: complete actual native execution and exact finite packed output. +* `runs_input`: native halting execution and the same actual pre-halt memory witness. + +These initialized-input composition proofs are original; existing finite reduction mathematics, +native kernels and native compilation APIs are reused without an algorithm or operation oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- All actual native instructions, including the complete initialized frontend and final halt. -/ +def cost (κ n : ℕ) : ℕ := + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + Frontend.cost κ n + (m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) + +/-- Complete actual native preprocessing, uniformly over every admitted sufficiently wide input. -/ +theorem exec_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y (cost κ n - 1) ∧ + out.map (fun p => p.2.oneArray (2 * U)) = + Cslib.Algorithms.ThreeSum.ChanHe.instances n U (fun i : Fin n => input[i]) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n y.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n y.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 := by + obtain ⟨x, rs, hf, ho, hbase, hcount, hend, hnd, _, hkeys, hcounts, _, _, hp, _⟩ := + Frontend.exec_input input ((frontend_width_le κ input.length).trans hw) hvalues + have hcard : rs.length ≤ input.length := by + have ht := hnd.length_le_of_subset (fun k hk => (hkeys k).mp hk) + simpa only [List.length_map] using ht + have hb : x.Bounded w := hf.bounded (Data.Bounded.init (input_fits input hw hvalues)) + have hw2 : w - 2 + 2 = w := by have := (three_le_width κ input.length).trans hw; omega + obtain ⟨out, y, hs, hout, hstored, hlen, htable, hpacked⟩ := + afterFrontend_exec (w := w - 2) input rs x (by simpa only [hw2] using hw) + (by simpa only [hw2] using hb) hvalues ho hp hbase hcount hend hkeys hnd hcard + rw [hw2] at hs + have hnodes := entries_output hkeys (fun p hp => (hcounts p hp).2) hvalues + have heq : out.map Prod.snd = Cslib.Algorithms.ThreeSum.ChanHe.allNodes input.length + ((input.length + 2) ^ κ) (fun i : Fin input.length => input[i]) := hout.trans hnodes + refine ⟨out, y, (hf.seq hs).mono ?_, ?_, hstored, hlen, htable, hpacked⟩ + · have hpos : 0 < Frontend.cost κ input.length := by dsimp only [Frontend.cost]; omega + dsimp only [cost] + omega + · rw [Cslib.Algorithms.ThreeSum.ChanHe.instances, ← heq, List.map_map] + rfl + +/-- The native flat program halts, and its actual pre-halt state retains the exact output. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes w (flatProgram κ) (State.init (input.map Signed.encode)) out.length steps ∧ + Runs w (flatProgram κ) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ (steps - 1) ∧ steps ≤ cost κ n ∧ + out.map (fun p => p.2.oneArray (2 * U)) = + Cslib.Algorithms.ThreeSum.ChanHe.instances n U (fun i : Fin n => input[i]) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n y.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n y.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 ∧ y.Bounded w := by + obtain ⟨out, y, ⟨t, ht, he⟩, hout, hstored, hlen, htable, hpacked⟩ := + exec_input input hw hvalues + have hpos : 0 < cost κ input.length := by dsimp only [cost]; omega + refine ⟨out, y, t + 1, ?_, ?_, by omega, hout, hstored, hlen, htable, hpacked, ?_⟩ + · simpa only [flatProgram, hlen, State.init] using he.executes Traversal.outputCount + · simpa only [flatProgram, State.init, Nat.zero_add, Nat.add_sub_cancel] using + he.runs (pc := 0) (p := flatProgram κ) (Program.Contains.self _).append_left + · exact he.bounded (Data.Bounded.init (input_fits input hw hvalues)) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean new file mode 100644 index 0000000000..b6610a1063 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean @@ -0,0 +1,86 @@ +/- +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.PreprocessingInput +public import Complexity.Computability.WordRAM.Width +import Mathlib.Data.List.GetD + +/-! +# Polynomial native preprocessing intermediates at every admitted width + +The program never reads physical width. Its complete execution at the derived minimum width +therefore identifies every prefix at every larger admitted width. Every actual register and +memory word, including address and arithmetic temporaries, has polynomial magnitude throughout. + +## Main results + +* `two_pow_width_le`: the minimum-width word universe has polynomial magnitude. +* `intermediates_polynomial`: uniform polynomial bounds on every actual run-prefix word. +* `space_polynomial`: every actual run prefix has zero memory beyond the same polynomial range. + +These native representation bounds are original and use the generic width-prefix API. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The derived minimum-width word universe is bounded by a fixed polynomial. -/ +theorem two_pow_width_le (κ n : ℕ) : + 2 ^ width κ n ≤ (n + 2) ^ (2720 * κ + 16456) := by + have hw : width κ n ≤ 17 * Frontend.width κ n := by + dsimp only [width, Frontend.width] + omega + calc 2 ^ width κ n ≤ 2 ^ (17 * Frontend.width κ n) := Nat.pow_le_pow_right (by decide) hw + _ = (2 ^ Frontend.width κ n) ^ 17 := by rw [← pow_mul, Nat.mul_comm] + _ ≤ ((n + 2) ^ (160 * κ + 968)) ^ 17 := + Nat.pow_le_pow_left (Frontend.two_pow_width_le κ n) 17 + _ = _ := by + rw [← pow_mul] + exact congrArg (fun e : ℕ => (n + 2) ^ e) (by omega) + +/-- Every register, temporary and memory word has polynomial magnitude at every admitted width. -/ +theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps : ℕ} + (hr : Runs w (flatProgram κ) (State.init (input.map Signed.encode)) s steps) : + (∀ i, s.data.regs i < (input.length + 2) ^ (2720 * κ + 16456)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (2720 * κ + 16456) := by + obtain ⟨_, _, _, he, _, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := Runs.prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) Traversal.outputCount) hw hr he + have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) + have hpoly := two_pow_width_le κ input.length + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +/-- Every prefix uses a polynomial initial interval of memory, even at wider physical widths. -/ +theorem space_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps a : ℕ} + (hr : Runs w (flatProgram κ) (State.init (input.map Signed.encode)) s steps) + (ha : (input.length + 2) ^ (2720 * κ + 16456) ≤ a) : s.data.mem a = 0 := by + obtain ⟨_, _, _, he, _, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := Runs.prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) Traversal.outputCount) hw hr he + have hi := input_fits input (κ := κ) le_rfl hvalues + have hword : 2 ^ width κ input.length ≤ a := (two_pow_width_le κ input.length).trans ha + rw [hsmall.mem_above (Data.Bounded.init hi) hword] + have hn : input.length < 2 ^ width κ input.length := by + simpa only [Fits, List.length_map] using hi.1 + have hpos : a ≠ 0 := by omega + obtain ⟨k, rfl⟩ := Nat.exists_eq_succ_of_ne_zero hpos + change (Data.init (input.map Signed.encode) : Data 128).mem (k + 1) = 0 + rw [Data.init_mem_succ] + exact List.getD_eq_default _ _ (by simp only [List.length_map]; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean new file mode 100644 index 0000000000..a44bc9b855 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean @@ -0,0 +1,128 @@ +/- +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.PreprocessingLoad +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Frame + +/-! +# Shared resources across all native frontend roots + +The outer loop reserves heap and pointer cells for every remaining root. A particular root gets +only its own remaining allocation, so its execution theorem cannot consume another root's budget. + +## Main definitions + +* `rootArena`: the existing traversal arena restricted to one root's actual remaining budget. +* `ForestCursor`, `ForestCursor.Invariant`: exact root index, output and global resource relation. + +## Main results + +* `rootArena_ready`: the global signed envelope derives each dynamic root envelope. + +These native composition invariants are original; the program remains the existing native code. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Restrict a root call to its own heap and pointer-table allowance. All addresses stay fixed. -/ +def rootArena (a : Traversal.Arena) (x : Data 128) (ns : List (ℕ × Node)) : Traversal.Arena := + { a with + ceiling := x.regs Traversal.heap + + Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N + outputCapacity := ns.length + Traversal.Math.work a.fuel } + +/-- Monotonicity of the existing polynomial envelope derives every root's actual local fit. -/ +theorem rootArena_ready {a : Traversal.Arena} {w : ℕ} {x : Data 128} + {ns : List (ℕ × Node)} (hw : a.Ready w) + (hh : (rootArena a x ns).ceiling ≤ a.ceiling) : (rootArena a x ns).Ready w := by + refine ⟨hw.1, hw.2.1, ?_⟩ + apply NodePacking.signed_fits_of_natAbs_le hw.2.2 + simp only [Int.natAbs_natCast] + dsimp only [Traversal.envelope] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by dsimp only [rootArena] at *; omega) 4) + +/-- Mathematical proof state of the native outer descriptor loop. -/ +structure ForestCursor where + /-- Number of root descriptors already executed. -/ + index : ℕ + /-- Actual native pointer and existing-node pairs already emitted. -/ + nodes : List (ℕ × Node) + +/-- Exact frontend output, native controller registers and all remaining-root reservations. -/ +structure ForestCursor.Invariant (a : Traversal.Arena) (w : ℕ) (l : Frontend.Layout) + (U : ℕ) (rs : List (ℕ × ℕ)) (s : ForestCursor) (x : Data 128) : Prop where + /-- Every actual word fits. -/ + bounded : x.Bounded (w + 2) + /-- The traversal constants remain actual registers. -/ + controls : a.Controls x + /-- Every frontend descriptor and compact array is still available. -/ + frontend : Frontend.Output l U rs x.mem + /-- Actual descriptor base and root count. -/ + descriptors : x.regs descriptors = l.descriptors + roots : x.regs roots = l.count + /-- Actual root index and shared fuel. -/ + current : x.regs current = s.index + levels : x.regs levels = a.fuel + /-- Native descriptor stride and outer increment constant. -/ + stride : x.regs stride = 6 + one : x.regs one = 1 + /-- The actual index never passes the last descriptor. -/ + index_le : s.index ≤ l.count + /-- Every candidate prime still has its actual bitmap bit. -/ + bitmap : a.Bitmap x.mem + /-- The heap avoids all shared reservations. -/ + floor : a.floor ≤ x.regs Traversal.heap + /-- All unprocessed roots retain their complete allocation budgets. -/ + heapBudget : x.regs Traversal.heap + (l.count - s.index) * + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) ≤ a.ceiling + /-- The native count agrees with the emitted list. -/ + count : x.regs Traversal.outputCount = s.nodes.length + /-- All unprocessed roots retain their complete pointer-table budgets. -/ + tableBudget : s.nodes.length + (l.count - s.index) * Traversal.Math.work a.fuel ≤ + a.outputCapacity + /-- Exact native pointer-table and packed-cell readback. -/ + output : Traversal.StoredNodes a.U a.N a.table s.nodes x.mem + /-- Every earlier packed array is safely below the current fresh allocation. -/ + outputBounds : ∀ i : Fin s.nodes.length, + a.floor ≤ s.nodes[i.val].1 ∧ s.nodes[i.val].1 + 4 * a.N ≤ x.regs Traversal.heap + /-- The emitted prefix and untouched roots give exactly the original finite forest. -/ + exactOutput : s.nodes.map Prod.snd ++ + Traversal.Math.output (Nat.primesLE a.m) a.Λ + (((entries l U rs).drop s.index).map Traversal.Entry.task) = + Traversal.Math.output (Nat.primesLE a.m) a.Λ + ((entries l U rs).map Traversal.Entry.task) + /-- Original memory below the shared stack is retained throughout the forest. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- A live outer iteration has room for the one root it is about to execute. -/ +theorem ForestCursor.Invariant.root_budgets {a : Traversal.Arena} {w U : ℕ} + {l : Frontend.Layout} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (h : s.Invariant a w l U rs x) + (hc : (Cond.lt Preprocessing.current Preprocessing.roots).Holds x) : + (rootArena a x s.nodes).ceiling ≤ a.ceiling ∧ + (rootArena a x s.nodes).outputCapacity ≤ a.outputCapacity := by + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + have hm : 1 ≤ l.count - s.index := by omega + have hh := Nat.mul_le_mul_left + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) hm + have ht := Nat.mul_le_mul_left (Traversal.Math.work a.fuel) hm + rw [Nat.mul_one, Nat.mul_comm _ (l.count - s.index)] at hh ht + constructor <;> dsimp only [rootArena] + · have := h.heapBudget + omega + · have := h.tableBudget + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean new file mode 100644 index 0000000000..bd557ecb6b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean @@ -0,0 +1,73 @@ +/- +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.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCost + +/-! +# Concrete global layout of native Chan–He preprocessing + +The prime bitmap follows the frontend. A seven-word-per-entry stack and a pointer table precede +all heavy and packed arrays. Reservations use the actual input length and actual computed fuel. + +## Main definitions + +* `arena`: complete finite memory layout, including every root's allocation reservation. + +## Main results + +* `arena_separated`: concrete stack, bitmap, table and heap reservations are disjoint. + +The layout and resource estimates are original. They do not change the existing finite instances. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- All memory regions computed by the actual native forest controller. -/ +def arena (κ n : ℕ) (retained : ℕ → ℕ) : Traversal.Arena := + let l := Frontend.layout κ n + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let N := 2 * m ^ 2 + let stack := l.endpoint + m + 1 + let cap := 7 * (2 * F + 1) + let table := stack + cap + let outcap := l.count * 3 ^ (F + 1) + let floor := table + outcap + { n := n + 1, m := m, Λ := l.digits, U := 2 * U, N := N, fuel := F, + primes := l.endpoint, stack := stack, capacity := cap, table := table, + outputCapacity := outcap, floor := floor, + ceiling := floor + l.count * + (Traversal.Math.work F * Traversal.allocationSize (n + 1) m N), + retained := retained } + +/-- The actual reservations put the bitmap, bounded stack, pointer table and arrays in order. -/ +theorem arena_separated (κ n : ℕ) (retained : ℕ → ℕ) : (arena κ n retained).Separated := by + constructor + · dsimp only [arena] + omega + · exact ⟨le_rfl, le_rfl, le_rfl⟩ + +/-- Every actual frontend payload precedes the reserved shared stack. -/ +theorem arena_frontend_before (κ n : ℕ) (retained : ℕ → ℕ) : + (Frontend.layout κ n).endpoint ≤ (arena κ n retained).stack := by + dsimp only [arena] + omega + +/-- Full ternary work never exceeds the native controller's computed power reservation. -/ +theorem work_le_power (f : ℕ) : Traversal.Math.work f ≤ 3 ^ (f + 1) := by + have := Traversal.Math.work_eq f + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean new file mode 100644 index 0000000000..ba409ecb43 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean @@ -0,0 +1,85 @@ +/- +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.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingEntries + +/-! +# Native frontend descriptor loads + +The controller executes all six memory reads and the fuel copy. The semantic entry is obtained +from the existing frontend output relation, including empty compact arrays. + +## Main results + +* `loadRoot_exec`: exact native descriptor loading, charged execution and memory/register frame. + +These representation and native execution proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- A descriptor load changes only the seven current-record slots and its address scratch. -/ +theorem loadRoot_preserves : ∀ i : Fin 128, + (i.val < 64 ∨ 71 ≤ i.val) → i ≠ address → loadRoot.Preserves i := by decide + +/-- Execute all descriptor arithmetic, six native memory reads and the runtime fuel copy. -/ +theorem loadRoot_exec {w U f t : ℕ} {l : Frontend.Layout} {rs : List (ℕ × ℕ)} + (x : Data 128) (hx : x.Bounded w) (ht : t < l.count) + (hc : x.regs current = t) (hd : x.regs descriptors = l.descriptors) + (hf : x.regs levels = f) (hs : x.regs stride = 6) (ho : x.regs one = 1) + (hfit : Fits w (l.descriptors + 6 * t + 5)) + (houtput : Frontend.Output l U rs x.mem) : + ∃ y, Exec w loadRoot x y 14 ∧ (entry l U f rs t).Loaded y ∧ y.mem = x.mem ∧ + ∀ i : Fin 128, (i.val < 64 ∨ 71 ≤ i.val) → i ≠ address → y.regs i = x.regs i := by + let a := l.descriptors + 6 * t + let y := x |>.set address (t * 6) |>.set address a |>.set (Traversal.slot 0) f + |>.set (Traversal.slot 2) (x.mem a) |>.set address (a + 1) + |>.set (Traversal.slot 1) (x.mem (a + 1)) |>.set address (a + 2) + |>.set (Traversal.slot 4) (x.mem (a + 2)) |>.set address (a + 3) + |>.set (Traversal.slot 3) (x.mem (a + 3)) |>.set address (a + 4) + |>.set (Traversal.slot 6) (x.mem (a + 4)) |>.set address (a + 5) + |>.set (Traversal.slot 5) (x.mem (a + 5)) + have ha (k : ℕ) (hk : k ≤ 5) : Fits w (a + k) := hfit.of_le (by dsimp only [a]; omega) + have ha' (k : ℕ) (hk : k ≤ 5) : Fits w (l.descriptors + (t * 6 + k)) := + hfit.of_le (by omega) + have hbase : Fits w (l.descriptors + t * 6) := by + simpa only [Nat.add_zero] using ha' 0 (by decide) + have hmul : Fits w (t * 6) := hfit.of_le (by omega) + have hF : Fits w f := hf ▸ hx.1 levels + have he : Exec w loadRoot x y 14 := by + ram_eval [loadRoot, y, a, hc, hd, hf, hs, ho, hmul, ha 0 (by decide), + ha 1 (by decide), ha 2 (by decide), ha 3 (by decide), ha 4 (by decide), + ha 5 (by decide), hF, hx.2, current, descriptors, levels, stride, one, address, + Traversal.slot, Nat.mul_comm, Nat.add_assoc, hbase, + ha' 1 (by decide), ha' 2 (by decide), ha' 3 (by decide), ha' 4 (by decide), + ha' 5 (by decide), Fin.val_zero, Fin.val_one, Fin.val_two] + refine ⟨y, he, ?_, rfl, fun i hi hj => he.within.regs_eq (loadRoot_preserves i hi hj)⟩ + have hread (j : Fin 3) := houtput (3 * t + j.val) (entry_slot ht j) + have h₀ := hread 0 + have h₁ := hread 1 + have h₂ := hread 2 + have ha₀ : l.descriptors + 2 * (3 * t + 0) = a := by dsimp only [a]; omega + have ha₁ : l.descriptors + 2 * (3 * t + 1) = a + 2 := by dsimp only [a]; omega + have ha₂ : l.descriptors + 2 * (3 * t + 2) = a + 4 := by dsimp only [a]; omega + simp only [Fin.val_zero, Fin.val_one, Fin.val_two, ha₀, ha₁, ha₂] at h₀ h₁ h₂ + refine ⟨by simp +decide [y, entry, Traversal.slot, address], ?_, ?_⟩ + · intro j + fin_cases j <;> simpa +decide [y, entry, Traversal.arraySource, Traversal.slot, + address, Nat.add_assoc] using (by first | exact h₀.2.1 | exact h₁.2.1 | exact h₂.2.1) + · intro j + fin_cases j <;> simpa +decide [y, entry, Traversal.arrayLength, Traversal.slot, + address, List.length_map, Nat.add_assoc] using + (by first | exact h₀.1 | exact h₁.1 | exact h₂.1) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean new file mode 100644 index 0000000000..f72667ba26 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean @@ -0,0 +1,159 @@ +/- +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.PreprocessingStep + +/-! +# Complete native frontend-root loop + +Every iteration loads the next actual descriptor, executes its complete bounded recursion and +increments the native index. Remaining-root reservations pay for every later iteration. + +## Main results + +* `forest_body_exec`: actual root execution preserves the global resource and output invariant. +* `forest_exec`: the entire actual outer loop emits exactly the finite frontend forest. + +These composition proofs are original and use the existing charged native loop rule. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +private theorem entries_drop_cons {l : Frontend.Layout} {U : ℕ} {rs : List (ℕ × ℕ)} + {i : ℕ} (hi : i < l.count) : + (entries l U rs).drop i = + entry l U (Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) rs i :: + (entries l U rs).drop (i + 1) := by + have hlen : i < (entries l U rs).length := by + simpa only [entries, List.length_map, List.length_range] using hi + rw [List.drop_eq_getElem_cons hlen] + simp only [entries, List.getElem_map, List.getElem_range] + +/-- An actual root execution leaves enough heap and table capacity for all later roots. -/ +theorem forest_body_exec {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hc : (Cond.lt current roots).Holds x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hf : a.fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + ∃ (s' : ForestCursor) (y : Data 128), + ExecWithin (w + 2) body x y (Traversal.rootTime a.fuel a.n a.m a.N + 15) ∧ + s'.Invariant a w l U rs y ∧ l.count - s'.index < l.count - s.index := by + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + obtain ⟨out, y, he, hy, hout, hprefix, hregs⟩ := + execute_next_root ha hw h hc hn hU hfront hkeys hnd hcard hvalues + have yi : y.regs current = s.index := (hregs _ (by decide) (by decide)).trans h.current + have yo : y.regs one = 1 := (hregs _ (by decide) (by decide)).trans h.one + have hfit : Fits (w + 2) (s.index + 1) := (h.bounded.1 roots).of_le (by + rw [h.roots] + omega) + let z := y.set current (s.index + 1) + have hz : Exec (B := .standard) (w + 2) (.op (.bin .add current current one)) y z 1 := by + ram_eval [z, yi, yo, hfit] + have zreg (i : Fin 128) (hi : i ≠ current) : z.regs i = y.regs i := by + simp only [z, Data.set_regs, Function.update_of_ne hi] + have zouter (i : Fin 128) (hi : 109 ≤ i.val) (hc : i ≠ current) (ha : i ≠ address) : + z.regs i = x.regs i := (zreg i hc).trans (hregs i hi ha) + let s' : ForestCursor := ⟨s.index + 1, out⟩ + refine ⟨s', z, ?_, ?_, by dsimp only [s']; omega⟩ + · obtain ⟨t, ht, he⟩ := he + cases he with + | seq hload hroot => + exact ⟨_, by omega, hload.seq (hroot.seq hz)⟩ + · constructor + · exact hz.bounded hy.bounded + · exact hy.controls.congr (by + intro i hi + apply zreg + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + intro he + subst i + norm_num [current] at hi) + · exact h.frontend.frame hcard (fun p hp => hprefix p (hp.trans_le hfront)) + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.descriptors + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.roots + · rfl + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.levels + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.stride + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.one + · dsimp only [s']; omega + · exact hy.bitmap + · simpa only [rootArena, zreg Traversal.heap (by decide)] using hy.floor + · have hh : y.regs Traversal.heap ≤ x.regs Traversal.heap + + Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N := by + simpa only [rootArena, Traversal.Cursor.work, List.map_nil, + Traversal.Math.potential, List.sum_nil, Nat.zero_mul, Nat.add_zero] using hy.heapBudget + have hg := h.heapBudget + have hrem : l.count - s.index = (l.count - (s.index + 1)) + 1 := by omega + rw [hrem, Nat.add_mul, Nat.one_mul] at hg + change z.regs Traversal.heap + (l.count - (s.index + 1)) * _ ≤ _ + rw [zreg _ (by decide)] + omega + · exact (zreg _ (by decide)).trans hy.count + · have ht : out.length ≤ s.nodes.length + Traversal.Math.work a.fuel := by + simpa only [rootArena, Traversal.Cursor.work, List.map_nil, + Traversal.Math.potential, List.sum_nil, Nat.add_zero] using hy.tableBudget + have hg := h.tableBudget + have hrem : l.count - s.index = (l.count - (s.index + 1)) + 1 := by omega + rw [hrem, Nat.add_mul, Nat.one_mul] at hg + change out.length + (l.count - (s.index + 1)) * _ ≤ _ + omega + · exact hy.output + · simpa only [s', rootArena, zreg Traversal.heap (by decide)] using hy.outputBounds + · change out.map Prod.snd ++ _ = _ + rw [hout, List.append_assoc] + have hm := h.exactOutput + rw [entries_drop_cons hi, List.map_cons, Traversal.Math.output, + List.flatMap_cons] at hm + rw [hf] + exact hm + · intro p hp + exact (hprefix p hp).trans (h.protectedMemory p hp) + +/-- Execute every native frontend root with exact finite output and all loop control charged. -/ +theorem forest_exec {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hf : a.fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + ∃ (s' : ForestCursor) (y : Data 128), + ExecWithin (w + 2) forest x y + ((l.count - s.index) * (Traversal.rootTime a.fuel a.n a.m a.N + 17) + 2) ∧ + s'.Invariant a w l U rs y ∧ s'.index = l.count ∧ + s'.nodes.map Prod.snd = Traversal.Math.output (Nat.primesLE a.m) a.Λ + ((entries l U rs).map Traversal.Entry.task) := by + obtain ⟨s', y, he, hy, hc⟩ := ExecWithin.loop_invariant_state + (fun (s : ForestCursor) y => s.Invariant a w l U rs y) + (fun s => l.count - s.index) + (fun s x hi hc => forest_body_exec ha hw hi hc hn hU hf hfront hkeys hnd hcard hvalues) h + have hend : s'.index = l.count := by + have hge : l.count ≤ s'.index := by + simpa only [Cond.holds_lt, hy.current, hy.roots, not_lt] using hc + exact hy.index_le.antisymm hge + refine ⟨s', y, he, hy, hend, ?_⟩ + have hlen : (entries l U rs).length = l.count := by + simp only [entries, List.length_map, List.length_range] + simpa only [hend, ← hlen, List.drop_length, List.map_nil, Traversal.Math.output, + List.flatMap_nil, List.append_nil] using hy.exactOutput + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean new file mode 100644 index 0000000000..1b170b66b9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean @@ -0,0 +1,79 @@ +/- +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.PreprocessingSnapshot + +/-! +# Computing the shared native prime bitmap + +The forest copies its computed candidate bound and fresh frontend endpoint into the existing +native trial-division program. Its actual bitmap and count are retained for every recursion root. + +## Main results + +* `primes_exec`: execute argument delivery and the complete table builder, preserving the frontend. + +This native composition proof is original and reuses the existing primality computation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Compute the actual shared bitmap from the already computed frontend and numeric parameters. -/ +theorem primes_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hfit : Fits w ((Frontend.layout κ n).endpoint + + Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + 4)) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let base := (Frontend.layout κ n).endpoint + ∃ y, ExecWithin w (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) x y + (2 + ((m + 1) * (7 * m + 15) + 8)) ∧ + Saved κ n y ∧ y.mem = PrimeTable.memory x.mem base (m + 1) ∧ + y.regs (primeRegisters PrimeTable.count) = (Nat.primesLE m).card ∧ + (∀ q ≤ m, y.mem (base + q) = if q.Prime then 1 else 0) ∧ + ∀ p < base, y.mem p = x.mem p := by + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let base := (Frontend.layout κ n).endpoint + let p := (x.set (primeRegisters PrimeTable.limit) m).set (primeRegisters PrimeTable.base) base + have hp : Exec w preparePrimes x p 2 := by + ram_eval [preparePrimes, p, hs.limit, hs.endpoint, m, base] + have hpi : Function.Injective primeRegisters := by + intro i j hij + exact Fin.ext (congrArg (fun k : Fin 128 => k.val) hij) + obtain ⟨z, hz, hmem, hcount, _, _⟩ := PrimeTable.exec (p.comap primeRegisters) + (hfit.of_le (by change m + 3 ≤ base + m + 4; omega)) + (hfit.of_le (by change base + (m + 1) ≤ base + m + 4; omega)) + let y := z.extend primeRegisters p + have he : ExecWithin w (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) x y + (2 + ((m + 1) * (7 * m + 15) + 8)) := by + simpa +decide [y, p, Data.comap, primeRegisters, PrimeTable.limit, m] using + hp.within.seq (hz.rename hpi) + have hmemory : y.mem = PrimeTable.memory x.mem base (m + 1) := by + simpa +decide [y, p, Data.extend, Data.comap, primeRegisters, PrimeTable.limit, + PrimeTable.base] using hmem + refine ⟨y, he, hs.congr (fun i hi _ => he.regs_eq ?_), hmemory, ?_, ?_, ?_⟩ + · have hh : ∀ i : Fin 128, 109 ≤ i.val → + (Code.seq preparePrimes (PrimeTable.program.rename primeRegisters)).Preserves i := by decide + exact hh i hi + · have hc := congrArg (fun d : Data 15 => d.regs PrimeTable.count) + (Data.comap_extend hpi z p) + exact hc.trans (by simpa +decide [p, Data.comap, primeRegisters, PrimeTable.limit] using hcount) + · intro q hq + change y.mem (base + q) = if q.Prime then 1 else 0 + change q ≤ m at hq + rw [hmemory] + simp only [PrimeTable.memory, show base ≤ base + q ∧ base + q < base + (m + 1) by omega, + and_self, ↓reduceIte, Nat.add_sub_cancel_left] + · intro q hq + rw [hmemory] + simp only [PrimeTable.memory, show ¬(base ≤ q ∧ q < base + (m + 1)) by omega, ↓reduceIte] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean index ad6aebd6e2..96ea089e48 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean @@ -19,8 +19,8 @@ computation. It builds the prime bitmap by actual trial division, computes concr output-table reservations, and loads and traverses every actual six-word frontend descriptor. The ternary reservation is computed by a native multiplication loop, not supplied by a caller. -This module defines the executable program. Its initialized-input correctness, width and global -charged bounds require companion proofs; the program definition alone does not complete them. +This module defines the executable program. `PreprocessingInput`, `PreprocessingWidth` and +`PreprocessingTime` prove initialized-input execution, derived width and charged global bounds. ## Main definitions @@ -163,8 +163,8 @@ def forest : Code .standard 128 := .loop (.lt current roots) body /-- Complete native preprocessing after the native initialized-input frontend. -/ def afterFrontend : Code .standard 128 := - .seq snapshot (.seq preparePrimes (.seq (PrimeTable.program.rename primeRegisters) - (.seq setupTraversal (.seq reserve (.seq finishLayout forest))))) + .seq snapshot (.seq (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) + (.seq setupTraversal (.seq (.seq reserve finishLayout) forest))) /-- One finite native program per fixed magnitude exponent, independent of input length/width. -/ def program (κ : ℕ) : Code .standard 128 := .seq (Frontend.program κ) afterFrontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean new file mode 100644 index 0000000000..47822f2afb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram + +/-! +# Executing the native ternary reservation + +The forest controller computes its output reservation by actual multiplications and a native +countdown. The loop has no supplied exponentiation instruction or resource oracle. + +## Main results + +* `reserve_exec`: exact native power, loop count, memory frame and register frame. + +The controller proof and its charged bound are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The power loop changes only its accumulator and countdown. -/ +theorem reserve_preserves : ∀ i : Fin 128, i ≠ power → i ≠ remaining → reserve.Preserves i := + by decide + +/-- Actual multiplication and subtraction compute the complete ternary-tree reservation. -/ +theorem reserve_exec {w F : ℕ} (x : Data 128) + (hp : x.regs power = 1) (hr : x.regs remaining = F + 1) + (hz : x.regs zero = 0) (ho : x.regs one = 1) (hs : x.regs scalar = 3) + (hfit : Fits w (3 ^ (F + 1))) : + ∃ y, ExecWithin w reserve x y (4 * (F + 1) + 2) ∧ + y.regs power = 3 ^ (F + 1) ∧ y.regs remaining = 0 ∧ y.mem = x.mem ∧ + ∀ i : Fin 128, i ≠ power → i ≠ remaining → y.regs i = x.regs i := by + let I (y : Data 128) : Prop := + y.regs remaining ≤ F + 1 ∧ y.regs power = 3 ^ (F + 1 - y.regs remaining) ∧ + y.regs zero = 0 ∧ y.regs one = 1 ∧ y.regs scalar = 3 ∧ y.mem = x.mem + have hbody (y : Data 128) (hy : I y) (hc : (Cond.lt zero remaining).Holds y) : + ∃ z, ExecWithin w powerBody y z 2 ∧ I z ∧ + z.regs remaining < y.regs remaining := by + rcases hy with ⟨hle, hpow, hzero, hone, hscalar, hmem⟩ + have hpos : 0 < y.regs remaining := by + simpa only [Cond.holds_lt, hzero] using hc + have hmul : Fits w (y.regs power * y.regs scalar) := by + rw [hpow, hscalar, ← pow_succ] + exact hfit.of_le (Nat.pow_le_pow_right (by decide) (by omega)) + have hsub : Fits w (y.regs remaining - 1) := hfit.of_le (by + have hp : F + 1 < 3 ^ (F + 1) := Nat.lt_pow_self (by decide) + omega) + let z := (y.set power (y.regs power * y.regs scalar)).set remaining + (y.regs remaining - 1) + have he : Exec w powerBody y z 2 := by + ram_eval [powerBody, z, hone, hmul, hsub, power, remaining, scalar, one] + refine ⟨z, he.within, ?_, ?_⟩ + · refine ⟨?_, ?_, ?_, ?_, ?_, hmem⟩ + · change y.regs remaining - 1 ≤ F + 1 + omega + · change y.regs power * y.regs scalar = 3 ^ (F + 1 - (y.regs remaining - 1)) + rw [hpow, hscalar, ← pow_succ] + congr 1 + omega + · simpa +decide [z, Data.set, zero, power, remaining] using hzero + · simpa +decide [z, Data.set, one, power, remaining] using hone + · simpa +decide [z, Data.set, scalar, power, remaining] using hscalar + · change y.regs remaining - 1 < y.regs remaining + omega + have hx : I x := by + refine ⟨by omega, ?_, hz, ho, hs, rfl⟩ + rw [hp, hr, Nat.sub_self, pow_zero] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => y.regs remaining) hbody hx + have hend : y.regs remaining = 0 := by + have hzero := hy.2.2.1 + simpa only [Cond.holds_lt, hzero, not_lt, Nat.le_zero] using hc + refine ⟨y, ?_, ?_, hend, hy.2.2.2.2.2, ?_⟩ + · simpa only [reserve, hr, Nat.reduceAdd, Nat.mul_comm] using he + · simpa only [hend, Nat.sub_zero] using hy.2.1 + · intro i hi hj + exact he.regs_eq (reserve_preserves i hi hj) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean new file mode 100644 index 0000000000..d258931715 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean @@ -0,0 +1,99 @@ +/- +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.PreprocessingSnapshot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLayout + +/-! +# Executing the native shared traversal layout + +The controller copies computed parameters, doubles the actual magnitude, divides the common +packed length into four-cell groups and computes the explicit stack reservation by instructions. + +## Main results + +* `setupTraversal_exec`: actual nineteen-instruction layout setup and exact native controls. + +The native layout and composition proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Execute all shared traversal arguments, constants and explicit-stack reservations. -/ +theorem setupTraversal_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hc : x.regs (primeRegisters PrimeTable.count) = + (Nat.primesLE (Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ))).card) + (hfit : Fits w ((arena κ n x.mem).floor + (arena κ n x.mem).U + + (arena κ n x.mem).N + (arena κ n x.mem).fuel + 16)) : + ∃ y, Exec w setupTraversal x y 19 ∧ Saved κ n y ∧ + (arena κ n x.mem).Controls y ∧ y.regs Traversal.outputCount = 0 ∧ + y.regs power = 1 ∧ + y.regs remaining = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1 ∧ + y.regs scalar = 3 ∧ y.mem = x.mem := by + let a := arena κ n x.mem + let F := a.fuel + let y := x |>.set Traversal.limit a.m |>.set Traversal.digits a.Λ + |>.set Traversal.magnitude a.U |>.set Traversal.groups a.N + |>.set Traversal.bitmap a.primes |>.set Traversal.primeCount (Nat.primesLE a.m).card + |>.set Traversal.stackBase (a.primes + a.m) + |>.set Traversal.stackBase a.stack + |>.set Traversal.stackCapacity (F + F) + |>.set Traversal.stackCapacity (F + F + 1) |>.set scalar 7 + |>.set Traversal.stackCapacity a.capacity |>.set Traversal.outputs a.table + |>.set Traversal.outputCount 0 |>.set Traversal.zero 0 |>.set Traversal.one 1 + |>.set power 1 |>.set remaining (F + 1) |>.set scalar 3 + have hfloor : a.primes + a.m + 1 + 7 * (2 * F + 1) ≤ a.floor := by + dsimp only [a, F, arena] + omega + have fit (v : ℕ) (hv : v ≤ a.floor + a.U + a.N + F + 16) : Fits w v := hfit.of_le hv + have hU : Fits w (2 * (n + 2) ^ κ) := fit _ (by change a.U ≤ _; omega) + have hN : Fits w (2 * a.m ^ 2) := fit _ (by change a.N ≤ _; omega) + have hstack₀ : Fits w (a.primes + a.m) := fit _ (by omega) + have hstack : Fits w a.stack := fit _ (by change a.primes + a.m + 1 ≤ _; omega) + have hF₀ : Fits w (F + F) := fit _ (by omega) + have hF₁ : Fits w (F + F + 1) := fit _ (by omega) + have hcap : Fits w a.capacity := fit _ (by change 7 * (2 * F + 1) ≤ _; omega) + have htable : Fits w a.table := fit _ (by change a.primes + a.m + 1 + 7 * (2 * F + 1) ≤ _; omega) + have hnext : Fits w (F + 1) := fit _ (by omega) + have h1 : Fits w 1 := fit _ (by omega) + have h3 : Fits w 3 := fit _ (by omega) + have h7 : Fits w 7 := fit _ (by omega) + have hdiv : 8 * a.m ^ 2 / 4 = 2 * a.m ^ 2 := by omega + simp only [a, F, arena, ← two_mul, Nat.mul_comm, Nat.add_assoc] + at hU hN hstack₀ hstack hF₀ hF₁ hcap htable hnext hdiv + dsimp only [primeRegisters, PrimeTable.count] at hc + have he : Exec w setupTraversal x y 19 := by + ram_eval [setupTraversal, y, a, F, arena, hs.limit, hs.digits, hs.magnitude, + hs.length, hs.endpoint, hs.fuel, hs.one, hs.four, hc, hdiv, hU, hN, + hstack₀, hstack, hF₀, hF₁, hcap, htable, hnext, h1, h3, h7, + Traversal.limit, Traversal.digits, Traversal.magnitude, Traversal.groups, + Traversal.bitmap, Traversal.primeCount, Traversal.stackBase, Traversal.stackCapacity, + Traversal.outputs, Traversal.outputCount, Traversal.zero, Traversal.one, + candidateLimit, labels, inputMagnitude, packedLength, four, firstFree, levels, + one, power, remaining, scalar, primeRegisters, PrimeTable.count, + ← two_mul, Nat.mul_comm, Nat.add_assoc] + refine ⟨y, he, hs.congr (fun i hi hj => he.within.regs_eq ?_), ?_, ?_, ?_, ?_, ?_, rfl⟩ + · have hh : ∀ i : Fin 128, 109 ≤ i.val → i ≠ power → i ≠ remaining → i ≠ scalar → + setupTraversal.Preserves i := by decide + exact hh i hi (by intro hh; subst i; norm_num [power] at hj) + (by intro hh; subst i; norm_num [remaining] at hj) + (by intro hh; subst i; norm_num [scalar] at hj) + · constructor <;> simp +decide [y, a, F, arena, Traversal.limit, Traversal.digits, + Traversal.magnitude, Traversal.groups, Traversal.bitmap, Traversal.primeCount, + Traversal.outputs, Traversal.stackBase, Traversal.stackCapacity, + Traversal.zero, Traversal.one, scalar, remaining, power] + all_goals simp +decide [y, F, a, arena, Traversal.outputCount, + Traversal.one, Traversal.zero, Traversal.outputs, Traversal.stackCapacity, + power, remaining, scalar] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean new file mode 100644 index 0000000000..426dd7e78c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean @@ -0,0 +1,104 @@ +/- +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.PreprocessingProgram + +/-! +# Saving the actual frontend and parameter outputs + +The outer controller retains every shared scalar in registers outside the complete root program's +write set. These are copies of computed frontend results, not inputs supplied by a caller. + +## Main definitions + +* `Saved`: exact native outer metadata after the snapshot instructions. + +## Main results + +* `snapshot_exec`: execute all thirteen scalar copies and constant initializations. + +The native ABI and composition proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Exact computed metadata retained outside the root program's register bank. -/ +structure Saved (κ n : ℕ) (x : Data 128) : Prop where + /-- Descriptor table and count produced by the frontend. -/ + base : x.regs descriptors = (Frontend.layout κ n).descriptors + count : x.regs roots = (Frontend.layout κ n).count + /-- Exact numeric parameter outputs. -/ + fuel : x.regs levels = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + magnitude : x.regs inputMagnitude = (n + 2) ^ κ + limit : x.regs candidateLimit = Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + digits : x.regs labels = (Frontend.layout κ n).digits + length : x.regs packedLength = + 8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) ^ 2 + endpoint : x.regs firstFree = (Frontend.layout κ n).endpoint + /-- Actual index and small constants. -/ + index : x.regs current = 0 + stride : x.regs stride = 6 + one : x.regs one = 1 + zero : x.regs zero = 0 + four : x.regs four = 4 + +/-- Programs restricted to lower registers retain all computed outer metadata. -/ +theorem Saved.congr {κ n : ℕ} {x y : Data 128} (h : Saved κ n x) + (hr : ∀ i : Fin 128, 109 ≤ i.val → (i.val ≤ 120 ∨ i.val = 126) → + y.regs i = x.regs i) : Saved κ n y := by + exact ⟨(hr _ (by decide) (by decide)).trans h.base, + (hr _ (by decide) (by decide)).trans h.count, + (hr _ (by decide) (by decide)).trans h.fuel, + (hr _ (by decide) (by decide)).trans h.magnitude, + (hr _ (by decide) (by decide)).trans h.limit, + (hr _ (by decide) (by decide)).trans h.digits, + (hr _ (by decide) (by decide)).trans h.length, + (hr _ (by decide) (by decide)).trans h.endpoint, + (hr _ (by decide) (by decide)).trans h.index, + (hr _ (by decide) (by decide)).trans h.stride, + (hr _ (by decide) (by decide)).trans h.one, + (hr _ (by decide) (by decide)).trans h.zero, + (hr _ (by decide) (by decide)).trans h.four⟩ + +/-- Saving computed scalars and initializing the outer constants changes no memory. -/ +theorem snapshot_exec {κ n w : ℕ} (x : Data 128) + (hp : Parameters.Output n ((n + 2) ^ κ) (x.comap Frontend.parameterRegisters)) + (hb : x.regs Frontend.descriptorBase = (Frontend.layout κ n).descriptors) + (hc : x.regs Frontend.descriptorCount = (Frontend.layout κ n).count) + (he : x.regs Frontend.heapEnd = (Frontend.layout κ n).endpoint) + (h6 : Fits w 6) : ∃ y, Exec w snapshot x y 13 ∧ Saved κ n y ∧ y.mem = x.mem := by + let l := Frontend.layout κ n + let y := x |>.set descriptors l.descriptors |>.set roots l.count + |>.set levels (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) + |>.set inputMagnitude ((n + 2) ^ κ) + |>.set candidateLimit (Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ)) + |>.set labels l.digits + |>.set packedLength (8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) ^ 2) + |>.set firstFree l.endpoint |>.set current 0 |>.set stride 6 + |>.set one 1 |>.set zero 0 |>.set four 4 + have hf := hp.fuel + have hU := hp.magnitude + have hm := hp.primeBound + have hΛ := hp.digits + have hN := hp.packedLen + simp only [Data.comap_regs] at hf hU hm hΛ hN + have h1 : Fits w 1 := h6.of_le (by decide) + have h4 : Fits w 4 := h6.of_le (by decide) + refine ⟨y, ?_, ?_, rfl⟩ + · ram_eval [snapshot, y, l, hb, hc, he, hf, hU, hm, hΛ, hN, h1, h4, h6, + Frontend.layout, descriptors, roots, levels, inputMagnitude, candidateLimit, + labels, packedLength, firstFree, current, stride, one, zero, four] + · constructor <;> simp +decide [y, l, descriptors, roots, levels, inputMagnitude, + candidateLimit, labels, packedLength, firstFree, current, stride, one, zero, four] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean new file mode 100644 index 0000000000..fa5c860286 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean @@ -0,0 +1,117 @@ +/- +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.PreprocessingInvariant + +/-! +# Executing the next actual frontend root + +The actual descriptor loads establish the complete root program's ABI. Global remaining-root +reservations derive its local arena, rather than supplying another assumed execution contract. + +## Main results + +* `execute_next_root`: actual loads followed by the complete native traversal, with exact output. + +The native composition and resource proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Execute the next actual descriptor and its full recursion within that root's own budget. -/ +theorem execute_next_root {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hc : (Cond.lt current roots).Holds x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + let e := entry l U a.fuel rs s.index + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) (.seq loadRoot Traversal.oneRoot) x y + (14 + Traversal.rootTime a.fuel a.n a.m a.N) ∧ + (Traversal.Cursor.mk [] out).Invariant (rootArena a x s.nodes) w + (s.nodes.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) y ∧ + out.map Prod.snd = s.nodes.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ ∧ + (∀ p < a.stack, y.mem p = x.mem p) ∧ + ∀ i : Fin 128, 109 ≤ i.val → i ≠ address → y.regs i = x.regs i := by + let e := entry l U a.fuel rs s.index + let b := rootArena a x s.nodes + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + obtain ⟨hceil, htable⟩ := h.root_budgets hc + have hheap : x.regs Traversal.heap ≤ a.ceiling := by + have := h.heapBudget + omega + have hstack : a.stack ≤ x.regs Traversal.heap := by + have ht := ha.2.1 + have hg := ha.2.2.1 + have := h.floor + omega + have haddr : l.descriptors + 6 * s.index + 5 ≤ a.ceiling := by + have hd := l.descriptor_before (entry_slot hi (2 : Fin 3)) l.arrays + change l.descriptors + 2 * (3 * s.index + 2) + 2 ≤ l.endpoint at hd + omega + have hsum := Traversal.envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have hfit := (NodePacking.unsigned_fits_of_signed hw.2.2).of_le (haddr.trans (by omega)) + obtain ⟨p, hp, hloaded, hmem, hpr⟩ := loadRoot_exec x h.bounded hi h.current + h.descriptors h.levels h.stride h.one hfit h.frontend + have pheap : p.regs Traversal.heap = x.regs Traversal.heap := hpr _ (by decide) (by decide) + have pcount : p.regs Traversal.outputCount = s.nodes.length := + (hpr _ (by decide) (by decide)).trans h.count + have hb : b.Separated := by + refine ⟨ha.1, ha.2.1, ?_, ha.2.2.2⟩ + have hg := ha.2.2.1 + change a.table + (s.nodes.length + Traversal.Math.work a.fuel) ≤ a.floor + change s.nodes.length + Traversal.Math.work a.fuel ≤ a.outputCapacity at htable + omega + have hready : b.Ready w := rootArena_ready hw hceil + have hinput : Traversal.RootInput b w e s.nodes p := by + refine ⟨hp.bounded h.bounded, ?_, hloaded, ?_, ?_, ?_, ?_, ?_, rfl, ?_, ?_, + pcount, le_rfl, ?_, ?_, ?_⟩ + · exact h.controls.congr (fun i hi => hpr i (Or.inr (by + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + omega)) (by + intro he + subst i + norm_num [address] at hi)) + · rw [hmem] + exact entry_stored hkeys h.frontend hi + · dsimp only [b, rootArena] + rw [hn, hU] + exact entry_valid l U a.fuel s.index hkeys hnd hcard hvalues + · simpa only [b, rootArena, Traversal.Arena.Bitmap, hmem] using h.bitmap + · intro j + apply Or.inl + exact (entry_ends l U a.fuel rs hcard hi j).trans hfront + · intro j + rw [pheap] + exact (entry_ends l U a.fuel rs hcard hi j).trans (hfront.trans hstack) + · simpa only [b, rootArena, pheap] using h.floor + · dsimp only [b, rootArena, e, entry] + rw [pheap] + · simpa only [b, rootArena, hmem] using h.output + · simpa only [b, rootArena, pheap] using h.outputBounds + · simpa only [b, rootArena, hmem] using h.protectedMemory + obtain ⟨out, y, he, hfinal, hout, hprefix, hregs⟩ := + Traversal.root_exec e s.nodes p hb hready hinput + refine ⟨out, y, hp.within.seq he, hfinal, hout, ?_, ?_⟩ + · intro q hq + exact (hprefix q hq).trans (congrFun hmem q) + · intro i hi hj + exact (hregs i hi).trans (hpr i (Or.inr (by omega)) hj) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean new file mode 100644 index 0000000000..a9980391a1 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean @@ -0,0 +1,266 @@ +/- +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.PreprocessingInput +public import Complexity.Algorithms.ThreeSum.ChanHe.ShiftedSizes + +/-! +# Subquadratic charged native preprocessing cost + +The bound counts complete initialized-input execution, trial division, all descriptor loads, +every recursion transition, native collision/heavy computation, array writes and the final halt. +Its polynomial factor is `(n+2)(sqrt(n+2)+1)` and its logarithm has fixed degree thirteen. + +## Main definitions + +* `rootCoefficient`, `costCoefficient`: explicit constants depending only on the fixed exponent. + +## Main results + +* `cost_bound`: the actual native instruction count is at most square-root times linear times + a fixed logarithmic power, uniformly over physical width. + +These charged estimates are original. The existing mathematical prime/instance bounds are reused. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Uniform complete-root coefficient after substituting the actual candidate bound. -/ +def rootCoefficient (κ : ℕ) : ℕ := + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + 70 + 196608 * (C + 4 * C ^ 3) + +/-- Explicit whole-program coefficient, independent of input length, data and physical width. -/ +def costCoefficient (κ : ℕ) : ℕ := + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + Frontend.costCoefficient κ + 60 * C ^ 2 + 77 + + 4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17) + +/-- The complete root cost at the actual magnitude-derived candidate window. -/ +theorem root_cost_bound (κ n : ℕ) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + Traversal.rootTime (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 11 := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have hC : 1 ≤ C := by dsimp only [C]; omega + have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) + have hmb : m + 1 ≤ C * S * L ^ 2 := by + have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ + have hu : 1 ≤ S * L ^ 2 := by + have hp : 0 < S * L ^ 2 := by positivity + omega + calc m + 1 ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := + Nat.add_le_add hm hu + _ = C * S * L ^ 2 := by dsimp only [C]; ring + have hsquare : S ^ 2 ≤ 4 * B := by + have hs := Nat.sqrt_le B + have ht := Nat.sqrt_le_self B + dsimp only [S] + nlinarith only [hs, ht, hB] + have hcube : S ^ 3 ≤ 4 * B * S := by + have hh := Nat.mul_le_mul_right S hsquare + nlinarith + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hroot : Traversal.rootTime F (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * B * S * L ^ 11 := by + have hr := Traversal.rootTime_fuel_square_le n (n + 1) m + have hlogs : 2 * Nat.log 2 n + 2 ≤ 2 * L := by dsimp only [L]; omega + calc Traversal.rootTime F (n + 1) m (2 * m ^ 2) + ≤ 70 + 6144 * (2 * L) ^ 5 * + (B * (C * S * L ^ 2) + (C * S * L ^ 2) ^ 3) := by + apply hr.trans + change 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + (B * (m + 1) + (m + 1) ^ 3) ≤ _ + gcongr + _ = 70 + 196608 * (C * B * S * L ^ 7 + C ^ 3 * S ^ 3 * L ^ 11) := by ring + _ ≤ 70 + 196608 * (C * B * S * L ^ 11 + C ^ 3 * (4 * B * S) * L ^ 11) := by + have hl : C * B * S * L ^ 7 ≤ C * B * S * L ^ 11 := + Nat.mul_le_mul_left _ (lp (show 7 ≤ 11 by decide)) + have hr : C ^ 3 * S ^ 3 * L ^ 11 ≤ C ^ 3 * (4 * B * S) * L ^ 11 := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ hcube) + exact Nat.add_le_add_left (Nat.mul_le_mul_left _ (Nat.add_le_add hl hr)) _ + _ ≤ rootCoefficient κ * B * S * L ^ 11 := by + have hu : 1 ≤ B * S * L ^ 11 := by + have hp : 0 < B * S * L ^ 11 := by positivity + omega + change _ ≤ (70 + 196608 * (C + 4 * C ^ 3)) * B * S * L ^ 11 + nlinarith only [hu] + exact hroot + +private theorem forest_time_bound (κ n : ℕ) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * + ((n + 2) * (Nat.sqrt (n + 2) + 1) * (Nat.log 2 (n + 2) + 1) ^ 13) := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let V := B * S * L ^ 13 + have hL : 1 ≤ L := by dsimp only [L]; omega + have hroot := root_cost_bound κ n + change Traversal.rootTime F (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * B * S * L ^ 11 at hroot + have hD : (Frontend.layout κ n).count ≤ 4 * (κ + 3) ^ 2 * L ^ 2 := by + have hb := Cslib.Algorithms.ThreeSum.ChanHe.labelBits_pow_le B κ + have hb' : (Frontend.layout κ n).digits ≤ (κ + 3) * L := by + exact hb.trans (by change κ * L + 3 ≤ (κ + 3) * L; nlinarith only [hL]) + have hh := Nat.pow_le_pow_left hb' 2 + have hu : 1 ≤ ((κ + 3) * L) ^ 2 := by + have hp : 0 < ((κ + 3) * L) ^ 2 := by positivity + omega + dsimp only [Frontend.Layout.count, Frontend.Layout.splits] + nlinarith only [hh, hu] + have hforest : (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V := by + have hu : 1 ≤ B * S * L ^ 11 := by + have hp : 0 < B * S * L ^ 11 := by positivity + omega + have hr : Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17 ≤ + (rootCoefficient κ + 17) * B * S * L ^ 11 := by nlinarith only [hroot, hu] + calc _ ≤ (4 * (κ + 3) ^ 2 * L ^ 2) * + ((rootCoefficient κ + 17) * B * S * L ^ 11) := Nat.mul_le_mul hD hr + _ = _ := by dsimp only [V]; ring + exact hforest + +private theorem prime_time_bound (B S C L m : ℕ) + (hB : 1 ≤ B) (hS : 1 ≤ S) (hL : 1 ≤ L) + (hmb : m + 1 ≤ C * S * L ^ 2) (hsquare : S ^ 2 ≤ 4 * B) : + (m + 1) * (7 * m + 15) + 8 ≤ + (60 * C ^ 2 + 8) * (B * S * L ^ 13) := by + let V := B * S * L ^ 13 + have hV : 1 ≤ V := by + have hp : 0 < V := by dsimp only [V]; positivity + omega + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hprime : (m + 1) * (7 * m + 15) + 8 ≤ (60 * C ^ 2 + 8) * V := by + have hm2 := Nat.pow_le_pow_left hmb 2 + have hs := Nat.mul_le_mul_right (C ^ 2 * L ^ 4) hsquare + have hsmall : B * L ^ 4 ≤ V := by + have hh := Nat.mul_le_mul_left B (lp (show 4 ≤ 13 by decide)) + calc B * L ^ 4 ≤ B * L ^ 13 := hh + _ ≤ B * S * L ^ 13 := Nat.mul_le_mul_right _ (by nlinarith only [hS]) + have hms : (m + 1) ^ 2 ≤ 4 * C ^ 2 * B * L ^ 4 := by nlinarith only [hm2, hs] + have hh := Nat.mul_le_mul_left (60 * C ^ 2) hsmall + nlinarith only [hms, hh, hV] + exact hprime + +private theorem frontend_time_bound (κ n : ℕ) : + Frontend.cost κ n ≤ Frontend.costCoefficient κ * + ((n + 2) * (Nat.sqrt (n + 2) + 1) * (Nat.log 2 (n + 2) + 1) ^ 13) := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let V := B * S * L ^ 13 + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hclog : Nat.clog 2 B ≤ L := + Nat.clog_le_of_le_pow (Nat.lt_pow_succ_log_self (by decide) B).le + have hfront : Frontend.cost κ n ≤ Frontend.costCoefficient κ * V := by + have hh := Frontend.cost_bound κ n + have hlogs := Nat.pow_le_pow_left hclog 2 + have hp := lp (show 2 ≤ 13 by decide) + have hnb : n + 1 ≤ B := by dsimp only [B]; omega + have hsmall : (n + 1) * Nat.clog 2 B ^ 2 ≤ V := by + calc (n + 1) * Nat.clog 2 B ^ 2 ≤ B * L ^ 13 := + Nat.mul_le_mul hnb (hlogs.trans hp) + _ ≤ B * S * L ^ 13 := Nat.mul_le_mul_right _ (by nlinarith only [hS]) + rw [Nat.mul_assoc] at hh + exact hh.trans (Nat.mul_le_mul_left (Frontend.costCoefficient κ) hsmall) + exact hfront + +/-- The actual native program has a genuinely subquadratic polynomial factor and fixed polylog. -/ +theorem cost_bound (κ n : ℕ) : + cost κ n ≤ costCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 13 := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let V := B * S * L ^ 13 + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have hC : 1 ≤ C := by dsimp only [C]; omega + have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) + have hmb : m + 1 ≤ C * S * L ^ 2 := by + have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ + have hu : 1 ≤ S * L ^ 2 := by + have hp : 0 < S * L ^ 2 := by positivity + omega + calc m + 1 ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := + Nat.add_le_add hm hu + _ = C * S * L ^ 2 := by dsimp only [C]; ring + have hsquare : S ^ 2 ≤ 4 * B := by + have hs := Nat.sqrt_le B + have ht := Nat.sqrt_le_self B + dsimp only [S] + nlinarith only [hs, ht, hB] + have hcube : S ^ 3 ≤ 4 * B * S := by + have hh := Nat.mul_le_mul_right S hsquare + nlinarith + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hV : 1 ≤ V := by + have hp : 0 < V := by dsimp only [V]; positivity + omega + have hforest := forest_time_bound κ n + change (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V at hforest + have hprime : (m + 1) * (7 * m + 15) + 8 ≤ (60 * C ^ 2 + 8) * V := + prime_time_bound B S C L m (by omega) hS hL hmb hsquare + have hfront : Frontend.cost κ n ≤ Frontend.costCoefficient κ * V := frontend_time_bound κ n + have hheight : Cslib.Algorithms.ThreeSum.ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + have hF : 4 * F + 45 ≤ 69 * V := by + have hLV : L ≤ V := by + have hh := lp (show 1 ≤ 13 by decide) + simp only [pow_one] at hh + calc L ≤ L ^ 13 := hh + _ ≤ B * S * L ^ 13 := Nat.le_mul_of_pos_left _ (by positivity) + have hsmall : F ≤ 6 * L := by + change 3 * Cslib.Algorithms.ThreeSum.ChanHe.height n - 2 ≤ _ + dsimp only [L] + omega + have hh : 4 * F + 45 ≤ 69 * L := by nlinarith only [hsmall, hL] + exact hh.trans (Nat.mul_le_mul_left 69 hLV) + change Frontend.cost κ n + (m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ _ + rw [show costCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 13 = costCoefficient κ * V by dsimp only [V, B, S, L]; ring] + change _ ≤ (Frontend.costCoefficient κ + 60 * C ^ 2 + 77 + + 4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V + calc _ = Frontend.cost κ n + ((m + 1) * (7 * m + 15) + 8) + + (4 * F + 45) + (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) := by omega + _ ≤ Frontend.costCoefficient κ * V + (60 * C ^ 2 + 8) * V + 69 * V + + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V := + Nat.add_le_add (Nat.add_le_add (Nat.add_le_add hfront hprime) hF) hforest + _ = _ := by ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean new file mode 100644 index 0000000000..219241a0bc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean @@ -0,0 +1,139 @@ +/- +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.PreprocessingBounds + +/-! +# Derived logarithmic width for native Chan–He preprocessing + +The positive physical-width threshold depends only on the fixed magnitude exponent and actual +input length. Its signed envelope covers every root, including all shared memory reservations. + +## Main definitions + +* `width`: positive physical width sufficient for the complete preprocessing envelope. + +## Main results + +* `arena_ready`: global logarithmic width derives the complete traversal fit contract. +* `input_fits`: the same width derives actual signed-encoded input fitting. + +These coarse native resource bounds are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- One positive logarithmic physical width for every native preprocessing component. -/ +def width (κ n : ℕ) : ℕ := 65 * Parameters.width κ n + +/-- The threshold has a positive exponent-dependent coefficient and no dependence on input data. -/ +theorem width_eq (κ n : ℕ) : + width κ n = (1300 * κ + 7865) * Nat.clog 2 (n + 2) := by + dsimp only [width, Parameters.width] + ring + +/-- Empty inputs and exponent zero also receive a strictly positive word width. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := Parameters.width_pos κ n + dsimp only [width] + omega + +/-- The signed compiler's two representation bits are always available. -/ +theorem three_le_width (κ n : ℕ) : 3 ≤ width κ n := by + have hL : 1 ≤ Nat.clog 2 (n + 2) := Nat.clog_pos (by decide) (by omega) + rw [width_eq] + nlinarith + +/-- The whole-frontend threshold is covered by the global preprocessing threshold. -/ +theorem frontend_width_le (κ n : ℕ) : Frontend.width κ n ≤ width κ n := by + dsimp only [Frontend.width, width] + omega + +/-- The global logarithmic width derives all local signed kernel fits and allocation bounds. -/ +theorem arena_ready {κ n w : ℕ} (hw : width κ n ≤ w) (retained : ℕ → ℕ) : + (arena κ n retained).Ready (w - 2) := by + let P := Parameters.Math.budget n ((n + 2) ^ κ) + let p := Parameters.width κ n + let a := arena κ n retained + have hL : 1 ≤ Nat.clog 2 (n + 2) := Nat.clog_pos (by decide) (by omega) + have hp : 3 ≤ p := by dsimp only [p, Parameters.width]; nlinarith + have hwide : 65 * p ≤ w := hw + have hP : P < 2 ^ p := Parameters.budget_fits le_rfl + have hpow : P ^ 52 < 2 ^ (52 * p) := by + have hh := Nat.pow_lt_pow_left hP (by decide : (52 : ℕ) ≠ 0) + simpa only [← pow_mul, Nat.mul_comm] using hh + have henv := arena_envelope_le κ n retained + change Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N ≤ P ^ 52 at henv + have hfit : Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N < 2 ^ (w - 3) := + (henv.trans_lt hpow).trans_le (Nat.pow_le_pow_right (by decide) (by omega)) + have hfitZ : ((Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N : ℕ) : ℤ) < + (2 : ℤ) ^ (w - 3) := by exact_mod_cast hfit + refine ⟨?_, le_rfl, ?_⟩ + · change 2 ≤ Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.mPar] + nlinarith + · refine ⟨by omega, ?_, ?_⟩ + all_goals rw [show w - 2 - 1 = w - 3 by omega] + all_goals omega + +/-- The actual native unsigned length header and every signed payload fit the derived width. -/ +theorem input_fits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := + Frontend.input_fits input ((frontend_width_le κ input.length).trans hw) hvalues + +/-- The same global width covers every scalar and address of the native layout controller. -/ +theorem setup_fits {κ n w : ℕ} (hw : width κ n ≤ w) (retained : ℕ → ℕ) : + let a := arena κ n retained + Fits w (a.floor + a.U + a.N + a.fuel + 16) ∧ + Fits w ((Frontend.layout κ n).endpoint + a.m + 4) ∧ + Fits w (3 ^ (a.fuel + 1)) ∧ Fits w 6 := by + let a := arena κ n retained + change Fits w (a.floor + a.U + a.N + a.fuel + 16) ∧ + Fits w ((Frontend.layout κ n).endpoint + a.m + 4) ∧ + Fits w (3 ^ (a.fuel + 1)) ∧ Fits w 6 + let S := a.ceiling + a.n + a.m ^ 2 + a.Λ + a.m + a.U + a.N + 100 + have hS : 1 ≤ S := by dsimp only [S]; omega + have hs : S ≤ S ^ 4 := Nat.le_self_pow (by decide) S + have hf : a.fuel ≤ a.floor := by dsimp only [a, arena]; omega + have hfloor : a.floor ≤ a.ceiling := by dsimp only [a, arena]; omega + have hfront : (Frontend.layout κ n).endpoint ≤ a.ceiling := by + dsimp only [a, arena] + omega + have hready := arena_ready hw retained + have hword := NodePacking.unsigned_fits_of_signed hready.2.2 + have hw3 : 3 ≤ w := (three_le_width κ n).trans hw + rw [show w - 2 + 2 = w by omega] at hword + have cover {v : ℕ} (hv : v ≤ 3 * S) : Fits w v := hword.of_le (by + change v ≤ 65536 * S ^ 4 + omega) + refine ⟨cover (by dsimp only [S]; omega), cover (by dsimp only [S]; omega), ?_, + cover (by dsimp only [S]; omega)⟩ + have hD : 2 ≤ (Frontend.layout κ n).count := by + dsimp only [Frontend.Layout.count] + omega + have hdelta : 3 ≤ Traversal.allocationSize a.n a.m a.N := by + dsimp only [Traversal.allocationSize] + omega + have hmul := Nat.mul_le_mul hD + (Nat.mul_le_mul_left (Traversal.Math.work a.fuel) hdelta) + have hwork := Traversal.Math.work_pos a.fuel + have he := Traversal.Math.work_eq a.fuel + have hceil : (Frontend.layout κ n).count * + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) ≤ a.ceiling := by + exact Nat.le_add_left _ _ + apply cover + dsimp only [S] + nlinarith + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean index 174fb9c3b9..7010706bfe 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean @@ -20,8 +20,9 @@ the reserved stack. It does not silently replace a negative 3SUM instance by an * `oneRoot_zero`: exact 173-instruction execution of the complete controller at zero fuel. -The controller composition proofs are original. General traversal correctness and the global -preprocessing composition remain part of the ongoing native construction. +The controller composition proofs are original. General supplied-root traversal correctness is +proved in `TraversalRoot.root_exec`; complete initialized-input preprocessing is composed in +`PreprocessingInput.runs_input`. -/ @[expose] public section diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean index 7c33fcf39b..e2157518c9 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Computability.WordRAM.Width public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Time /-! @@ -44,18 +45,9 @@ namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend open Cslib.WordRAM -private theorem readsWidth_rename {r r' : ℕ} (f : Fin r → Fin r') (c : Code .standard r) : - (c.rename f).ReadsWidth ↔ c.ReadsWidth := by - induction c with - | op o => cases o <;> rfl - | seq c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] - | branch b c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] - | loop b c hc => exact hc - | rand => rfl - /-- Physical word width is never read, so it never determines the radix iteration count. -/ theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by - have hp := (readsWidth_rename parameterRegisters (ChanHe.Parameters.program κ)).not.mpr + have hp := (Code.readsWidth_rename parameterRegisters (ChanHe.Parameters.program κ)).not.mpr (ChanHe.Parameters.not_readsWidth κ) exact not_or.mpr ⟨hp, by decide⟩ @@ -91,30 +83,6 @@ theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length · exact ho.readback hkeys (fun p hp => (hcounts p hp).2) hvalues · exact hexec.bounded (Data.Bounded.init (input_fits input hw hvalues)) -private theorem prefix_at_smaller_width {w w' r : ℕ} {p : Program .standard r} - (hp : ¬p.ReadsWidth) (hw : w ≤ w') {s t : State r} {k v total : ℕ} - (hwide : Runs w' p s t k) (hlow : Executes w p s v total) : Runs w p s t k := by - induction hwide using Relation.RelatesInSteps.head_induction_on generalizing total with - | hrefl => exact .refl _ - | @hhead a c k hstep hrest ih => - obtain ⟨z, m, hm, hz, _⟩ := hlow - cases m with - | zero => - have he := hm.zero - subst z - have hh : step w' p a = .halt v := step_halt_mono_width hz - rw [show step w' p a = .next c from hstep] at hh - cases hh - | succ m => - obtain ⟨u, hu, htail⟩ := hm.succ' - have he : u = c := by - have hmono := hu.mono_width hp hw - change step w' p a = .next u at hmono - rw [show step w' p a = .next c from hstep] at hmono - exact Outcome.next.inj hmono.symm - subst u - exact (ih (total := m + 1) ⟨z, m, htail, hz, rfl⟩).head a c t k hu - /-- Every run prefix at every admitted width uses polynomially bounded words. This includes all memory words and registers, rather than merely the final output or a pair-count metric. -/ theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) @@ -126,7 +94,7 @@ theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) (∀ i, s.data.regs i < (input.length + 2) ^ (160 * κ + 968)) ∧ ∀ a, s.data.mem a < (input.length + 2) ^ (160 * κ + 968) := by obtain ⟨_, _, he, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues - have hsmall := prefix_at_smaller_width + have hsmall := Runs.prefix_at_smaller_width (Code.not_readsWidth_program (not_readsWidth κ) descriptorCount) hw hr he have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) have hpoly := two_pow_width_le κ input.length diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean new file mode 100644 index 0000000000..7c0fa31f7e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean @@ -0,0 +1,50 @@ +/- +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.Frontend.Layout + +/-! +# Preserving complete frontend output + +Later native programs may allocate beyond the frontend's endpoint. Its exact descriptor and +payload interpretation survives a frame on the concrete allocated prefix. + +## Main results + +* `Output.frame`: preserve all descriptors and compact payloads below the frontend endpoint. + +This memory-frame bridge is original and reuses the existing output and block predicates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- A frame on the frontend allocation preserves every descriptor and actual payload word. -/ +theorem Output.frame {l : Layout} {U : ℕ} {rs : List (ℕ × ℕ)} {before after : ℕ → ℕ} + (h : Output l U rs before) (hcard : rs.length ≤ l.n) + (hp : ∀ p < l.endpoint, after p = before p) : Output l U rs after := by + intro j hj + have hlen : (Math.array U l.digits rs j).length ≤ l.capacity := + (Math.length_array_le U l.digits rs j).trans (by dsimp only [Layout.capacity]; omega) + have hend : l.pointer j + (Math.array U l.digits rs j).length ≤ l.endpoint := by + calc l.pointer j + (Math.array U l.digits rs j).length + ≤ l.pointer j + l.capacity := Nat.add_le_add_left hlen _ + _ = l.pointer (j + 1) := (l.pointer_succ j).symm + _ ≤ l.pointer l.arrays := l.pointer_mono (by omega) + have hdesc := l.descriptor_before hj j + refine ⟨?_, ?_, (h j hj).2.2.frame ?_⟩ + · exact (hp _ (by omega)).trans (h j hj).1 + · exact (hp _ (by omega)).trans (h j hj).2.1 + · intro p _ hlt + exact hp p (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean index 0d6dc16694..d2662e8de4 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean @@ -18,7 +18,10 @@ preserves full multiplicities; the complete descriptor program enumerates the ex ## Main results -Initialized-input execution uses a positive logarithmic physical width and fixed code per exponent. +* `exec_input`: complete initialized-input execution, exact descriptors and full multiplicities. +* `construct_preserves_parameters`: computed parameters remain available to the traversal caller. + +Execution uses a positive logarithmic physical width and fixed code per exponent. -/ @[expose] public section diff --git a/Complexity/Computability/WordRAM/Width.lean b/Complexity/Computability/WordRAM/Width.lean new file mode 100644 index 0000000000..4c96d67331 --- /dev/null +++ b/Complexity/Computability/WordRAM/Width.lean @@ -0,0 +1,102 @@ +/- +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 + +/-! +# Width-independent native program prefixes + +An execution that halts at one width identifies every successful prefix at a larger width when +the program does not read physical width. These rules apply to both complete native frontends +and programs that compose them. + +## Main results + +* `Code.readsWidth_rename`: register renaming does not change width observation. +* `Runs.prefix_at_smaller_width`: wider prefixes agree with an admitted smaller-width execution. + +These determinism and representation lemmas are original. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- Register renaming never changes whether a structured program observes physical width. -/ +theorem Code.readsWidth_rename {B : Basis} {r r' : ℕ} (f : Fin r → Fin r') + (c : Code B r) : (c.rename f).ReadsWidth ↔ c.ReadsWidth := by + induction c with + | op o => cases o <;> rfl + | seq c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | branch b c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | loop b c hc => exact hc + | rand => rfl + +/-- Every wider prefix is the same smaller-width prefix if that smaller execution halts. -/ +theorem Runs.prefix_at_smaller_width {B : Basis} {w w' r : ℕ} {p : Program B r} + (hp : ¬p.ReadsWidth) (hw : w ≤ w') {s t : State r} {k v total : ℕ} + (hwide : Runs w' p s t k) (hlow : Executes w p s v total) : Runs w p s t k := by + induction hwide using Relation.RelatesInSteps.head_induction_on generalizing total with + | hrefl => exact .refl _ + | @hhead a c k hstep hrest ih => + obtain ⟨z, m, hm, hz, _⟩ := hlow + cases m with + | zero => + have he := hm.zero + subst z + have hh : step w' p a = .halt v := step_halt_mono_width hz + rw [show step w' p a = .next c from hstep] at hh + cases hh + | succ m => + obtain ⟨u, hu, htail⟩ := hm.succ' + have he : u = c := by + have hmono := hu.mono_width hp hw + change step w' p a = .next u at hmono + rw [show step w' p a = .next c from hstep] at hmono + exact Outcome.next.inj hmono.symm + subst u + exact (ih (total := m + 1) ⟨z, m, htail, hz, rfl⟩).head a c t k hu + +/-- A native instruction cannot write memory above the admitted address-word universe. -/ +theorem Op.eval_mem_above {B : Basis} {r w a : ℕ} {o : Op B r} {x y : Data r} + (h : o.eval w x = some y) (hx : x.Bounded w) (ha : 2 ^ w ≤ a) : + y.mem a = x.mem a := by + cases o with + | const => obtain ⟨_, rfl⟩ := Data.put_eq_some.1 h; rfl + | alu => obtain ⟨_, _, _, rfl⟩ := Op.eval_alu_eq_some.1 h; rfl + | copy => cases h; rfl + | load => cases h; rfl + | store d s => + cases h + change Function.update x.mem (x.regs d) (x.regs s) a = x.mem a + exact Function.update_of_ne (by have := hx.1 d; dsimp only [Fits] at this; omega) _ _ + +/-- A complete native step preserves memory beyond the admitted address-word universe. -/ +theorem Next.mem_above {B : Basis} {r w a : ℕ} {p : Program B r} {s t : State r} + (h : Next w p s t) (hs : s.data.Bounded w) (ha : 2 ^ w ≤ a) : + t.data.mem a = s.data.mem a := by + unfold Next WordRAM.step at h + split at h + · cases h + · next o _ => + split at h + · next x hx => cases h; exact Op.eval_mem_above hx hs ha + · cases h + all_goals first | (cases h; rfl) | cases h + +/-- A native run preserves every cell above its admitted address-word universe. -/ +theorem Runs.mem_above {B : Basis} {r w a : ℕ} {p : Program B r} {s t : State r} {n : ℕ} + (h : Runs w p s t n) (hs : s.data.Bounded w) (ha : 2 ^ w ≤ a) : + t.data.mem a = s.data.mem a := by + induction h with + | refl => rfl + | @tail t u n hpre hstep ih => + exact (hstep.mem_above (Runs.bounded hpre hs) ha).trans ih + +end Cslib.WordRAM diff --git a/ComplexityTests/NativeChanHePreprocessing.lean b/ComplexityTests/NativeChanHePreprocessing.lean index 676d1a5462..36590bab62 100644 --- a/ComplexityTests/NativeChanHePreprocessing.lean +++ b/ComplexityTests/NativeChanHePreprocessing.lean @@ -26,4 +26,9 @@ open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe yn.1.regs Traversal.heap, yn.1.mem 0]) = some [0, 294912, 192, 43, 20, 9, 180, 575, 575, 0]) +-- The complete native program faults during actual parameter arithmetic at an invalid width. +/-- info: true -/ +#guard_msgs in +#eval (Code.eval 1 256 (Preprocessing.program 0) (Data.init [])).isNone + end ComplexityTests.NativeChanHePreprocessing diff --git a/ComplexityTests/NativeChanHePreprocessingContracts.lean b/ComplexityTests/NativeChanHePreprocessingContracts.lean new file mode 100644 index 0000000000..3502b60268 --- /dev/null +++ b/ComplexityTests/NativeChanHePreprocessingContracts.lean @@ -0,0 +1,148 @@ +/- +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.Preprocessing + +-- This test module exports no library declarations. +set_option linter.privateModule false + +/-! +# Admitted whole-program native preprocessing clients + +These clients discharge the width and input promises and retain the actual complete native +execution and memory witness. In particular, the negative two-position case still emits an +instance; it is never replaced by an empty output as a shortcut. +-/ + +namespace ComplexityTests.NativeChanHePreprocessingContracts + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum +open Cslib.Algorithms.ThreeSum.ChanHe +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +private theorem promise {κ : ℕ} (input : List ℤ) + (h : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width κ input.length) (flatProgram κ) + (State.init (input.map Signed.encode)) out.length steps ∧ + Runs (width κ input.length) (flatProgram κ) + (State.init (input.map Signed.encode)) ⟨(program κ).size, y⟩ (steps - 1) ∧ + steps ≤ cost κ input.length ∧ + out.map (fun p : ℕ × Node => p.2.oneArray (2 * (input.length + 2) ^ κ)) = + instances input.length ((input.length + 2) ^ κ) (fun i => input[i]) ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes + (2 * (input.length + 2) ^ κ) + (2 * mPar input.length (2 * (input.length + 2) ^ κ) ^ 2) + (arena κ input.length y.mem).table out y.mem := by + obtain ⟨out, y, steps, he, hr, ht, ho, hm, _, _, _, _⟩ := runs_input input le_rfl h + exact ⟨out, y, steps, he, hr, ht, ho, hm⟩ + +-- Empty input retains the complete initialization/prime/forest execution, with no output. +example : ∃ y steps, + Executes (width 0 0) (flatProgram 0) (State.init []) 0 steps ∧ + Runs (width 0 0) (flatProgram 0) (State.init []) ⟨(program 0).size, y⟩ (steps - 1) ∧ + steps ≤ cost 0 0 := by + obtain ⟨out, y, steps, he, hr, ht, ho, _⟩ := promise (κ := 0) [] (by simp) + have hempty : instances 0 1 (Fin.elim0 : Fin 0 → ℤ) = [] := by decide + have hfun : (fun i : Fin 0 => ([] : List ℤ)[i]) = (Fin.elim0 : Fin 0 → ℤ) := by + funext i + exact Fin.elim0 i + have hlen := congrArg List.length ho + simp only [List.length_nil, Nat.zero_add, pow_zero, hfun, hempty, List.length_map] at hlen + simp only [List.length_nil, List.map_nil] at he hr ht + rw [hlen] at he + exact ⟨y, steps, he, hr, ht⟩ + +-- One negative input also runs the complete program rather than supplying empty descriptors. +example : ∃ steps, Executes (width 0 1) (flatProgram 0) + (State.init [Signed.encode (-1)]) 0 steps ∧ steps ≤ cost 0 1 := by + obtain ⟨out, y, steps, he, _, ht, ho, _⟩ := promise (κ := 0) [-1] (by + intro z hz + simp only [List.mem_singleton] at hz + subst z + decide) + have hempty : instances 1 1 (fun _ : Fin 1 => (-1 : ℤ)) = [] := by decide + have hfun : (fun i : Fin 1 => ([-1] : List ℤ)[i]) = (fun _ : Fin 1 => (-1 : ℤ)) := by + funext i + fin_cases i + rfl + change out.map (fun p : ℕ × Node => p.2.oneArray 2) = + instances 1 1 (fun i : Fin 1 => ([-1] : List ℤ)[i]) at ho + rw [hfun, hempty] at ho + have hlen := congrArg List.length ho + simp only [List.length_map, List.length_nil] at hlen + simp only [List.length_cons, List.length_nil, List.map_cons, List.map_nil] at he ht + rw [hlen] at he + exact ⟨steps, he, ht⟩ + +private def pair : Fin 2 → ℤ := fun _ => 1 + +private def pairNode : Node := + ⟨{2}, {0}, {1}, modulus (Nat.primesLE (mPar 2 2)) (labelBits 2) {2} {0} {1}⟩ + +private theorem pair_node_mem : pairNode ∈ allNodes 2 1 pair := by + apply mem_allNodes_iff.mpr + refine ⟨{2}, {0}, {1}, ?_, ?_⟩ + · have ht : twiceSet pair = {2} := by decide + have hi : Finset.univ.image pair = {1} := by decide + simpa only [ht, hi] using (IsInput.twice (U := 1) (x := pair)) + · change pairNode ∈ nodes (Nat.primesLE (mPar 2 2)) (labelBits 2) 4 {2} {0} {1} + rw [nodes] + simp only [Finset.singleton_ne_empty, false_or, ↓reduceIte, List.mem_cons] + exact Or.inl rfl + +-- Two equal values have no distinct-position 3SUM solution, yet their exact reduction is nonempty. +example : ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width 0 2) (flatProgram 0) (State.init [Signed.encode 1, Signed.encode 1]) + out.length steps ∧ steps ≤ cost 0 2 ∧ out ≠ [] ∧ + out.map (fun p : ℕ × Node => p.2.oneArray 2) = instances 2 1 pair ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes 2 + (2 * mPar 2 2 ^ 2) (arena 0 2 y.mem).table out y.mem ∧ + ¬∃ f ∈ out.map (fun p : ℕ × Node => p.2.oneArray 2), Convolution (8 * mPar 2 2 ^ 2) f := by + obtain ⟨out, y, steps, he, _, ht, ho, hm⟩ := promise (κ := 0) [1, 1] (by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false, or_self] at hz + subst z + decide) + change out.map (fun p : ℕ × Node => p.2.oneArray 2) = instances 2 1 pair at ho + have hmem : pairNode.oneArray 2 ∈ instances 2 1 pair := + List.mem_map.mpr ⟨pairNode, pair_node_mem, rfl⟩ + refine ⟨out, y, steps, he, ht, ?_, ho, hm, ?_⟩ + · intro hnil + rw [← ho, hnil, List.map_nil] at hmem + exact List.not_mem_nil hmem + · rw [ho, ← instances_correct 2 1 (by decide) pair (by unfold pair; decide)] + unfold HasSolution pair + decide + +-- The same program covers a genuine equal-value positive instance and its native memory. +example : ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width 1 3) (flatProgram 1) + (State.init ([(-2 : ℤ), 1, 1].map Signed.encode)) out.length steps ∧ + steps ≤ cost 1 3 ∧ + out.map (fun p : ℕ × Node => p.2.oneArray 10) = + instances 3 5 (fun i : Fin 3 => ([(-2 : ℤ), 1, 1] : List ℤ)[i]) ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes 10 + (2 * mPar 3 10 ^ 2) (arena 1 3 y.mem).table out y.mem ∧ + ∃ f ∈ out.map (fun p : ℕ × Node => p.2.oneArray 10), Convolution (8 * mPar 3 10 ^ 2) f := by + have hv : ∀ z ∈ ([(-2 : ℤ), 1, 1] : List ℤ), |z| ≤ ((3 + 2) ^ 1 : ℕ) := by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false, or_self] at hz + rcases hz with rfl | rfl <;> decide + obtain ⟨out, y, steps, he, _, ht, ho, hm⟩ := promise (κ := 1) [-2, 1, 1] hv + change out.map (fun p : ℕ × Node => p.2.oneArray 10) = + instances 3 5 (fun i : Fin 3 => ([(-2 : ℤ), 1, 1] : List ℤ)[i]) at ho + refine ⟨out, y, steps, he, ht, ho, hm, ?_⟩ + rw [ho] + apply (instances_correct 3 5 (by decide) _ (by decide)).mp + unfold HasSolution + decide + +end ComplexityTests.NativeChanHePreprocessingContracts diff --git a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean index 08cf0b89d7..b2161e9cfc 100644 --- a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean +++ b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean @@ -71,23 +71,9 @@ example (κ n : ℕ) : (layout κ n).endpoint ≤ (n + 2) ^ (80 * κ + 480) := b exact (Arrays.layout_bounds (layout κ n) ((n + 2) ^ κ)).2.2.2.2.2.2.2.2.1 |>.trans (array_budget_polynomial κ n) --- The original distinct-position problem admits equal values, but never reuses a position. -example : Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => - ([1, 1, -2] : List ℤ)[i]) := by - unfold Cslib.Algorithms.ThreeSum.HasSolution - decide +kernel - -example : ¬Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => - ([1, 2, 3] : List ℤ)[i]) := by - unfold Cslib.Algorithms.ThreeSum.HasSolution - decide +kernel - --- The two width promises are explicit; a physical width of three is not globally admissible. -example : ¬width 0 0 ≤ 3 ∧ - Cslib.GenericRAM.Signed.Fits (4096 - 2) (-8) := by - constructor - · decide - · exact signed_output_fits (κ := 1) (n := 2) (by decide) (by decide) +-- A checked frontend value fits the signed kernel ABI at the derived physical width. +example : Cslib.GenericRAM.Signed.Fits (4096 - 2) (-8) := + signed_output_fits (κ := 1) (n := 2) (by decide) (by decide) -- Complete array construction also accepts arbitrary old output and scratch memory. example (mem : ℕ → ℕ) : diff --git a/ROADMAP.md b/ROADMAP.md index 7f4b6a27f0..f6066660fe 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -431,10 +431,12 @@ each statement against the numbered result. Organize modules by subject, not cha one-node component ([#1481]) computes all residues/histograms, three compact heavy arrays and exact packed output in linear time. Native bounded recursive traversal ([#1512]) executes those components with an explicit stack, exact preorder output, polynomial local fit/frame contracts - and charged ternary-tree bounds. Initialized-input forest integration and global preprocessing - width/time composition remain. It still needs + and charged ternary-tree bounds. Complete native preprocessing ([#1446]) composes every root + from actual initialized input, returns the exact finite packed instances and derives positive + logarithmic width, polynomial space/intermediates and an `Oκ(n^1.5 log¹³(n+2))` charged bound. + 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/ + Chan–He/Exact Triangle host composition, 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 diff --git a/UPSTREAM.md b/UPSTREAM.md index 693e18d1ea..556c781fe1 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -171,8 +171,14 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he stack frame and exact preorder packed output. The traversal consumes a root descriptor and runtime fuel; its polynomial local envelope derives kernel fits and charges every stack, descriptor and address operation. It does not invoke a Convolution-3SUM solver. Composition - from initialized input through all frontend roots, global logarithmic width and subquadratic - preprocessing bounds remain in #1446. These components do not affect the curated headlines. + from initialized input through every frontend root is proved in + `Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean`. One finite 128-register program per + fixed magnitude exponent returns the existing exact finite instance list. All fits derive from + `(1300κ+7865) clog₂(n+2)` physical width; every run prefix uses polynomial words/addresses even + at wider admitted widths. Its charged cost is at most + `Kκ (n+2)(sqrt(n+2)+1)(log₂(n+2)+1)^13`, including the final halt. The complete native + preprocessing milestone is #1446; the Exact Triangle solver and full final 3SUM composition + remain #1394. These components do not affect the curated headlines. `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 From f276db1174333d3e6253b93878be8b95c1fcc713 Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 17:56:09 -0400 Subject: [PATCH 30/45] refactor(wordram): place signed fit and count frame APIs with their models --- Complexity.lean | 3 +- .../WordRAM/ThreeSum/ChanHe/Count.lean | 7 ++++ .../WordRAM/ThreeSum/ChanHe/CountCall.lean | 2 +- .../WordRAM/ThreeSum/ChanHe/CountFrame.lean | 35 ---------------- .../WordRAM/ThreeSum/ChanHe/NodeExec.lean | 5 ++- .../WordRAM/ThreeSum/ChanHe/NodeLayout.lean | 14 ------- .../WordRAM/ThreeSum/ChanHe/NodeSpec.lean | 10 +---- .../ChanHe/PreprocessingInvariant.lean | 2 +- .../ThreeSum/ChanHe/PreprocessingStep.lean | 2 +- .../ThreeSum/ChanHe/PreprocessingWidth.lean | 2 +- .../ThreeSum/ChanHe/TraversalAdmitted.lean | 4 +- .../ThreeSum/ChanHe/TraversalRoot.lean | 2 +- .../ThreeSum/ChanHe/TraversalVisit.lean | 2 +- .../Computability/GenericRAM/Signed/Fits.lean | 39 ++++++++++++++++++ .../Computability/WordRAM/Signed/Fits.lean | 35 ++++++++++++++++ ComplexityTests/NativeChanHeNode.lean | 41 +++++++++++-------- 16 files changed, 120 insertions(+), 85 deletions(-) delete mode 100644 Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean create mode 100644 Complexity/Computability/GenericRAM/Signed/Fits.lean create mode 100644 Complexity/Computability/WordRAM/Signed/Fits.lean diff --git a/Complexity.lean b/Complexity.lean index 73198a982f..ee5bf3ba1e 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -121,7 +121,6 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CollisionTest public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall -public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlaceMemory public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple @@ -1072,6 +1071,7 @@ public import Complexity.Computability.GenericRAM.Native.MeasurementFold public import Complexity.Computability.GenericRAM.Native.Nat public import Complexity.Computability.GenericRAM.Native.SubsetPairFold public import Complexity.Computability.GenericRAM.Signed +public import Complexity.Computability.GenericRAM.Signed.Fits public import Complexity.Computability.GenericRAM.Simulation.Finite public import Complexity.Computability.GenericRAM.Simulation.Run public import Complexity.Computability.GenericRAM.Simulation.Turing @@ -1730,6 +1730,7 @@ public import Complexity.Computability.WordRAM.Signed.Arithmetic public import Complexity.Computability.WordRAM.Signed.Comparison public import Complexity.Computability.WordRAM.Signed.Compile public import Complexity.Computability.WordRAM.Signed.Encoding +public import Complexity.Computability.WordRAM.Signed.Fits public import Complexity.Computability.WordRAM.Signed.Input public import Complexity.Computability.WordRAM.Signed.Operations public import Complexity.Computability.WordRAM.Signed.State diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean index 5a8a697325..427dfe83ae 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -28,6 +28,7 @@ For injective input arrays its counter is exactly the existing Chan–He `coll`. * `Count.exec`: execution in at most `34n + 5M + 18` native instructions. * `Count.exec_coll`: the counter equals the existing mathematical collision count. +* `Count.memory_before`: cells below the two workspace blocks retain their contents. ## References @@ -102,6 +103,12 @@ 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 +/-- Neither the residue array nor the histogram modifies an earlier memory cell. -/ +theorem memory_before (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst a : ℕ) + (hs : a < scratch) (hd : a < dst) : memory mem values n M scratch dst a = mem a := by + simp [memory, Histogram.memory, Residues.memory, show ¬(dst ≤ a ∧ a < dst + M) by omega, + show ¬(scratch ≤ a ∧ a < scratch + n) by omega] + /-- 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 diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean index de574e2935..a9b32a3402 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean @@ -7,7 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate -public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count import Mathlib.Tactic.FinCases /-! diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean deleted file mode 100644 index ba7f1a1e05..0000000000 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean +++ /dev/null @@ -1,35 +0,0 @@ -/- -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.Count - -/-! -# Collision-counting register and memory frames - -Prime-search clients reuse the same fixed counting program on successive candidates. -Its input parameters and all memory below its scratch blocks are preserved. - -## Main results - -* `Count.memory_before`: cells below the two workspace blocks retain their contents. --/ - -@[expose] public section - -namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count - -open Cslib.WordRAM - -/-- Neither the residue array nor the histogram modifies an earlier memory cell. -/ -theorem memory_before (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst a : ℕ) - (hs : a < scratch) (hd : a < dst) : memory mem values n M scratch dst a = mem a := by - simp [memory, Histogram.memory, Residues.memory, show ¬(dst ≤ a ∧ a < dst + M) by omega, - show ¬(scratch ≤ a ∧ a < scratch + n) by omega] - -end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean index 1927476414..0eeb3337ac 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean @@ -8,6 +8,7 @@ module -- upstream: candidate public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeRound +public import Complexity.Computability.WordRAM.Signed.Fits /-! # Complete native Chan–He node construction @@ -55,13 +56,13 @@ theorem exec {w : ℕ} (x : Data 64) (values : Fin 3 → ℕ → ℤ) have hwidth' : GenericRAM.Signed.Fits w (L.budget : ℤ) := hwidth have hM' : 0 < L.M := hM have hN' : 2 * L.M ≤ L.N := hN - have hmod : GenericRAM.Signed.Fits w (L.M : ℤ) := signed_fits_of_natAbs_le hwidth' + have hmod : GenericRAM.Signed.Fits w (L.M : ℤ) := hwidth'.of_natAbs_le (by simpa only [Int.natAbs_natCast] using (show L.M ≤ L.budget by have := L.bounds.2.2; omega)) have hu : L.U ≤ L.budget := by have := L.bounds.2.2; omega have hsigned (j : Fin 3) (i : ℕ) (hi : i < L.n j) : GenericRAM.Signed.Fits w (values j i) := - signed_fits_of_natAbs_le hwidth' ((hbdd j i hi).trans hu) + hwidth'.of_natAbs_le ((hbdd j i hi).trans hu) let R (y : Data 64) : Prop := Parameters x y ∧ Prepared L y ∧ ∀ a < L.base, y.mem a = x.mem a obtain ⟨a, ha, hap, har, ham⟩ := exec_initialize x hfit diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean index 74f0ccbde6..11a3fb3e7f 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean @@ -159,18 +159,4 @@ theorem Layout.tag_bounds (L : Layout) (j : Fin 3) : L.U ≤ L.tag j ∧ L.tag j ≤ 4 * L.gap := by fin_cases j <;> simp +decide [Layout.tag, Layout.gap] <;> omega -/-- A signed fit for a nonnegative envelope also covers every integer bounded by it. -/ -theorem signed_fits_of_natAbs_le {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) - {z : ℤ} (hz : z.natAbs ≤ B) : GenericRAM.Signed.Fits w z := by - obtain ⟨hw, hlo, hhi⟩ := hfit - have habs := abs_le.mp (show |z| ≤ (B : ℤ) by - rw [← Int.natCast_natAbs] - exact_mod_cast hz) - exact ⟨hw, by omega, by omega⟩ - -/-- One signed envelope suffices for every unsigned intermediate at the execution width. -/ -theorem unsigned_fits_of_signed {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) : - Fits (w + 2) B := - ((encode_fits hfit).mono (by omega)).of_le (by simp [Signed.encode]; omega) - end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean index 2762ac98ee..dcf81eeb18 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean @@ -20,7 +20,7 @@ explicit allocation and caller registers 46–55 are preserved. ## Main results * `NodePacking.exec_spec`: complete native execution and exact semantic readback. -* `NodePacking.packed_length_square`: the preprocessing specialization has `8m²` cells. +* `NodePacking.Output.packed_square`: exact native readback at the common `8m²` length. * `NodePacking.abs_oneArray_le`: the node magnitude bound is `60U+40`. These programs and composition proofs are original. The mathematical comparator is the @@ -103,17 +103,11 @@ theorem abs_oneArray_le (L : Layout) (values : Fin 3 → ℕ → ℤ) Cslib.Algorithms.ThreeSum.ChanHe.Node.abs_oneArray_le (bdd_image L values hbdd 0) (bdd_image L values hbdd 1) (bdd_image L values hbdd 2) u -/-- The full preprocessing's common group count gives exactly its existing packed length. -/ -theorem packed_length_square (m : ℕ) : 4 * (2 * m ^ 2) = 8 * m ^ 2 := by omega - -/-- The square specialization is admissible whenever the parent supplies `M ≤ m²`. -/ -theorem square_admissible {M m : ℕ} (hM : M ≤ m ^ 2) : 2 * M ≤ 2 * m ^ 2 := by omega - /-- Exact readback at the full preprocessing's common length, with no extra padding assumption. -/ theorem Output.packed_square {L : Layout} {values : Fin 3 → ℕ → ℤ} {before after : ℕ → ℕ} (h : Output L values before after) {m : ℕ} (hN : L.N = 2 * m ^ 2) {u : ℕ} (hu : u < 8 * m ^ 2) : after (L.packed + u) = encode ((node L values).oneArray L.U u) := - h.packed u (by rw [hN, packed_length_square]; exact hu) + h.packed u (by rw [hN]; omega) end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean index a44bc9b855..efdeeb8e1d 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean @@ -47,7 +47,7 @@ theorem rootArena_ready {a : Traversal.Arena} {w : ℕ} {x : Data 128} {ns : List (ℕ × Node)} (hw : a.Ready w) (hh : (rootArena a x ns).ceiling ≤ a.ceiling) : (rootArena a x ns).Ready w := by refine ⟨hw.1, hw.2.1, ?_⟩ - apply NodePacking.signed_fits_of_natAbs_le hw.2.2 + apply GenericRAM.Signed.Fits.of_natAbs_le hw.2.2 simp only [Int.natAbs_natCast] dsimp only [Traversal.envelope] exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by dsimp only [rootArena] at *; omega) 4) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean index fa5c860286..1791528800 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean @@ -66,7 +66,7 @@ theorem execute_next_root {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout change l.descriptors + 2 * (3 * s.index + 2) + 2 ≤ l.endpoint at hd omega have hsum := Traversal.envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N - have hfit := (NodePacking.unsigned_fits_of_signed hw.2.2).of_le (haddr.trans (by omega)) + have hfit := (Signed.unsigned_fits_of_signed hw.2.2).of_le (haddr.trans (by omega)) obtain ⟨p, hp, hloaded, hmem, hpr⟩ := loadRoot_exec x h.bounded hi h.current h.descriptors h.levels h.stride h.one hfit h.frontend have pheap : p.regs Traversal.heap = x.regs Traversal.heap := hpr _ (by decide) (by decide) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean index 219241a0bc..1c9b786b00 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean @@ -111,7 +111,7 @@ theorem setup_fits {κ n w : ℕ} (hw : width κ n ≤ w) (retained : ℕ → dsimp only [a, arena] omega have hready := arena_ready hw retained - have hword := NodePacking.unsigned_fits_of_signed hready.2.2 + have hword := Signed.unsigned_fits_of_signed hready.2.2 have hw3 : 3 ≤ w := (three_le_width κ n).trans hw rw [show w - 2 + 2 = w by omega] at hword have cover {v : ℕ} (hv : v ≤ 3 * S) : Fits w v := hword.of_le (by diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean index 57f495459b..bfa4e752bb 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean @@ -43,8 +43,8 @@ theorem emit_admitted {a : Arena} {w : ℕ} (x : Data 128) (e : Entry) rcases hc with ⟨hm, hΛ, hU, hN, hb, hcard, ht, hstack, hcap, hz, hone⟩ have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N have hbound {z : ℤ} (hz : z.natAbs ≤ envelope a.ceiling a.n a.m a.Λ a.U a.N) : - GenericRAM.Signed.Fits w z := NodePacking.signed_fits_of_natAbs_le hw.2.2 hz - have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + GenericRAM.Signed.Fits w z := GenericRAM.Signed.Fits.of_natAbs_le hw.2.2 hz + have hunsigned := Signed.unsigned_fits_of_signed hw.2.2 have hlen (j : Fin 3) : x.regs (arrayLength j) ≤ a.n := by rw [hl.2.2] exact hv.length_le j diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean index 851a2ff7f4..7b23a1eaa6 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean @@ -83,7 +83,7 @@ theorem root_exec {a : Arena} {w : ℕ} (e : Entry) (ns : List (ℕ × Node)) (x out.map Prod.snd = ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ ∧ (∀ p < a.stack, y.mem p = x.mem p) ∧ ∀ i : Fin 128, 109 ≤ i.val → y.regs i = x.regs i := by - have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + have hunsigned := Signed.unsigned_fits_of_signed hw.2.2 have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N have h2 : Fits (w + 2) 2 := hunsigned.of_le (by omega) have hcap : Fits (w + 2) (a.stack + a.capacity) := hunsigned.of_le (by diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean index d3e7774b71..f02451568a 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean @@ -117,7 +117,7 @@ theorem visit_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) hbp heap (by decide) (by decide)] exact htable have hEnd : Fits (w + 2) ((recordLayout x M).endpoint) := - (NodePacking.unsigned_fits_of_signed (hnode M hMpos hMle)).of_le + (Signed.unsigned_fits_of_signed (hnode M hMpos hMle)).of_le (recordLayout x M).bounds.1 have hEndEq : (recordLayout x M).endpoint = b.regs heap + 3 + 2 * (b.regs (slot 2) + b.regs (slot 4) + b.regs (slot 6)) + diff --git a/Complexity/Computability/GenericRAM/Signed/Fits.lean b/Complexity/Computability/GenericRAM/Signed/Fits.lean new file mode 100644 index 0000000000..aa069549a0 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Signed/Fits.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.GenericRAM.Signed +import Mathlib.Algebra.Order.Group.Abs + +/-! +# Signed fits from magnitude envelopes + +A representable nonnegative envelope covers every integer whose absolute value is at most it. +The envelope itself must fit: the asymmetric signed range still excludes its positive endpoint. + +## Main results + +* `Fits.of_natAbs_le`: derive signed fitting from a representable natural magnitude envelope. + +This elementary bound is original. +-/ + +@[expose] public section + +namespace Cslib.GenericRAM.Signed + +/-- A signed fit for a nonnegative envelope also covers every integer bounded by it. -/ +theorem Fits.of_natAbs_le {w B : ℕ} (hfit : Fits w (B : ℤ)) + {z : ℤ} (hz : z.natAbs ≤ B) : Fits w z := by + obtain ⟨hw, hlo, hhi⟩ := hfit + have habs := abs_le.mp (show |z| ≤ (B : ℤ) by + rw [← Int.natCast_natAbs] + exact_mod_cast hz) + exact ⟨hw, by omega, by omega⟩ + +end Cslib.GenericRAM.Signed diff --git a/Complexity/Computability/WordRAM/Signed/Fits.lean b/Complexity/Computability/WordRAM/Signed/Fits.lean new file mode 100644 index 0000000000..72d8b0931d --- /dev/null +++ b/Complexity/Computability/WordRAM/Signed/Fits.lean @@ -0,0 +1,35 @@ +/- +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.GenericRAM.Signed.Fits +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Unsigned envelopes at the signed compiler's execution width + +Native callers use the same nonnegative envelope for encoded signed payloads and unsigned +addresses, counts and loop temporaries. The existing compiler executes at signed width plus two. + +## Main results + +* `unsigned_fits_of_signed`: a representable natural signed envelope fits that physical width. + +This representation bound is original and reuses the existing sign-and-magnitude encoding. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.Signed + +/-- One signed envelope suffices for every unsigned intermediate at the execution width. -/ +theorem unsigned_fits_of_signed {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) : + Fits (w + 2) B := + ((encode_fits hfit).mono (by omega)).of_le (by simp [encode]; omega) + +end Cslib.WordRAM.Signed diff --git a/ComplexityTests/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean index bd68b60be5..d6acd15fde 100644 --- a/ComplexityTests/NativeChanHeNode.lean +++ b/ComplexityTests/NativeChanHeNode.lean @@ -149,22 +149,29 @@ example : ∃ y, ExecWithin 34 program (input empty 1 2 0) y private def singletonValues (j : Fin 3) (_ : ℕ) : ℤ := ![-1, 0, 1] j --- A nonempty signed instance also satisfies every premise of the complete semantic theorem. -example : ∃ y, ExecWithin 34 program (input singleton 3 6 1) y 3328 ∧ - Output (layout (input singleton 3 6 1)) singletonValues - (input singleton 3 6 1).mem y.mem ∧ Parameters (input singleton 3 6 1) y := by - apply exec_spec _ _ (by decide) (by decide) (by decide) - · intro j - fin_cases j <;> decide - · intro j i hi - rw [show (layout (input singleton 3 6 1)).U = 1 by decide] - fin_cases j <;> norm_num [singletonValues] - · intro j i hi - have hi0 : i = 0 := by - fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi <;> omega - subst i - fin_cases j <;> decide - · intro j i hi k hk _ - fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi hk <;> omega +-- A nonempty square-length client reads the final packed cell from the actual native state. +example : ∃ y, ExecWithin 34 program (input singleton 1 2 1) y 1792 ∧ + Output (layout (input singleton 1 2 1)) singletonValues + (input singleton 1 2 1).mem y.mem ∧ Parameters (input singleton 1 2 1) y ∧ + y.mem ((layout (input singleton 1 2 1)).packed + 7) = + Signed.encode ((node (layout (input singleton 1 2 1)) singletonValues).oneArray 1 7) := by + have hclient : ∃ y, ExecWithin 34 program (input singleton 1 2 1) y 1792 ∧ + Output (layout (input singleton 1 2 1)) singletonValues + (input singleton 1 2 1).mem y.mem ∧ Parameters (input singleton 1 2 1) y := by + apply exec_spec _ _ (by decide) (by decide) (by decide) + · intro j + fin_cases j <;> decide + · intro j i hi + rw [show (layout (input singleton 1 2 1)).U = 1 by decide] + fin_cases j <;> norm_num [singletonValues] + · intro j i hi + have hi0 : i = 0 := by + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi <;> omega + subst i + fin_cases j <;> decide + · intro j i hi k hk _ + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi hk <;> omega + obtain ⟨y, he, ho, hp⟩ := hclient + exact ⟨y, he, ho, hp, ho.packed_square (m := 1) (by decide) (by decide)⟩ end ComplexityTests.NativeChanHeNode From a33aea0701bf8fcbfa6be55d9b18a91df8f1729c Mon Sep 17 00:00:00 2001 From: Erik Demaine Date: Wed, 28 Oct 2026 18:15:48 -0400 Subject: [PATCH 31/45] fix(wordram): resolve native preprocessing review findings --- .../ThreeSum/ChanHe/Preprocessing.lean | 8 ++- .../ThreeSum/ChanHe/PreprocessingTime.lean | 63 ++++++++++--------- .../ThreeSum/ChanHe/TraversalCost.lean | 2 +- .../ThreeSum/ChanHe/TraversalRoot.lean | 5 +- ComplexityTests/NativeChanHeTraversal.lean | 15 ++++- UPSTREAM.md | 18 +++++- 6 files changed, 73 insertions(+), 38 deletions(-) diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean index e39cd4213b..5a63b8f2a8 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean @@ -21,9 +21,11 @@ payloads use the existing signed encoding. Runtime code does not read physical w ## Main definitions -* `program`, `flatProgram`: actual structured and flat native preprocessing code. -* `width`: the positive threshold `(1300κ+7865) clog₂(n+2)` derives every component's fits. -* `cost`: all native instructions, including the final halt. +This umbrella re-exports these definitions and results from its imported modules. + +* `program`, `flatProgram`: structured and flat native code from `PreprocessingProgram`. +* `width`: the positive threshold `(1300κ+7865) clog₂(n+2)` from `PreprocessingWidth` derives fits. +* `cost`: all native instructions, including the final halt, from `PreprocessingInput`. ## Main results diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean index a9980391a1..601b122db7 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean @@ -46,6 +46,34 @@ def costCoefficient (κ : ℕ) : ℕ := Frontend.costCoefficient κ + 60 * C ^ 2 + 77 + 4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17) +private theorem parameter_scale_bounds (κ n : ℕ) : + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + 1 ≤ C * S * L ^ 2 ∧ + S ^ 2 ≤ 4 * B := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + constructor + · have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ + have hu : 1 ≤ S * L ^ 2 := by + have hp : 0 < S * L ^ 2 := by positivity + omega + calc _ ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := + Nat.add_le_add hm hu + _ = C * S * L ^ 2 := by dsimp only [C]; ring + · have hs := Nat.sqrt_le B + have ht := Nat.sqrt_le_self B + change S ^ 2 ≤ 4 * B + dsimp only [S] + nlinarith only [hs, ht, hB] + /-- The complete root cost at the actual magnitude-derived candidate window. -/ theorem root_cost_bound (κ n : ℕ) : let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) @@ -63,19 +91,9 @@ theorem root_cost_bound (κ n : ℕ) : have hS : 1 ≤ S := by dsimp only [S]; omega have hC : 1 ≤ C := by dsimp only [C]; omega have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) - have hmb : m + 1 ≤ C * S * L ^ 2 := by - have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ - have hu : 1 ≤ S * L ^ 2 := by - have hp : 0 < S * L ^ 2 := by positivity - omega - calc m + 1 ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := - Nat.add_le_add hm hu - _ = C * S * L ^ 2 := by dsimp only [C]; ring - have hsquare : S ^ 2 ≤ 4 * B := by - have hs := Nat.sqrt_le B - have ht := Nat.sqrt_le_self B - dsimp only [S] - nlinarith only [hs, ht, hB] + obtain ⟨hmb, hsquare⟩ := parameter_scale_bounds κ n + change m + 1 ≤ C * S * L ^ 2 at hmb + change S ^ 2 ≤ 4 * B at hsquare have hcube : S ^ 3 ≤ 4 * B * S := by have hh := Nat.mul_le_mul_right S hsquare nlinarith @@ -208,22 +226,9 @@ theorem cost_bound (κ n : ℕ) : have hS : 1 ≤ S := by dsimp only [S]; omega have hC : 1 ≤ C := by dsimp only [C]; omega have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) - have hmb : m + 1 ≤ C * S * L ^ 2 := by - have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ - have hu : 1 ≤ S * L ^ 2 := by - have hp : 0 < S * L ^ 2 := by positivity - omega - calc m + 1 ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := - Nat.add_le_add hm hu - _ = C * S * L ^ 2 := by dsimp only [C]; ring - have hsquare : S ^ 2 ≤ 4 * B := by - have hs := Nat.sqrt_le B - have ht := Nat.sqrt_le_self B - dsimp only [S] - nlinarith only [hs, ht, hB] - have hcube : S ^ 3 ≤ 4 * B * S := by - have hh := Nat.mul_le_mul_right S hsquare - nlinarith + obtain ⟨hmb, hsquare⟩ := parameter_scale_bounds κ n + change m + 1 ≤ C * S * L ^ 2 at hmb + change S ^ 2 ≤ 4 * B at hsquare have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij have hV : 1 ≤ V := by have hp : 0 < V := by dsimp only [V]; positivity diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean index d41bf32640..14d50fa184 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean @@ -47,7 +47,7 @@ theorem visitTime_square_le (n m : ℕ) : nlinarith /-- The complete native root cost, including setup, all skipped calls and all stack/loop work, -has only a polylogarithmic factor beyond `n*m + m³`. -/ +has only a polylogarithmic factor beyond `card*m + m³`. -/ theorem rootTime_fuel_square_le (n card m : ℕ) : rootTime (fuel n) card m (2 * m ^ 2) ≤ 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean index 851a2ff7f4..b998766050 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean @@ -73,7 +73,10 @@ structure RootInput (a : Arena) (w : ℕ) (e : Entry) (ns : List (ℕ × Node)) protectedMemory : ∀ p < a.stack, x.mem p = a.retained p /-- Execute the fixed complete native root program. There are no supplied kernel executions: -all calls are proved from the actual arrays, bitmap, finite bounds and one signed envelope. -/ +all calls are proved from the actual arrays, bitmap, finite bounds and one signed envelope. +The caller chooses `m`, `Λ` and fuel. Setting `m = mPar n U`, `Λ = labelBits U` and +`e.fuel = fuel n` makes `e.task.output (Nat.primesLE a.m) a.Λ` the existing Chan–He +`reduction n U` of the root sets; the initialized-input bridge is in `PreprocessingEntries`. -/ theorem root_exec {a : Arena} {w : ℕ} (e : Entry) (ns : List (ℕ × Node)) (x : Data 128) (ha : a.Separated) (hw : a.Ready w) (h : RootInput a w e ns x) : ∃ (out : List (ℕ × Node)) (y : Data 128), diff --git a/ComplexityTests/NativeChanHeTraversal.lean b/ComplexityTests/NativeChanHeTraversal.lean index 32e6ea7404..1a7781a8ba 100644 --- a/ComplexityTests/NativeChanHeTraversal.lean +++ b/ComplexityTests/NativeChanHeTraversal.lean @@ -149,10 +149,21 @@ private def branchingInput : Data 128 where | 5 => Signed.encode (-1) | _ => (treeInput 2 1 0).mem a -private def expectedBranching : List (List ℕ) := +private def branchingNodes : List Cslib.Algorithms.ThreeSum.ChanHe.Node := let ν₀ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4, 1}, {0}, {-1}, 4⟩ let ν₁ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4}, {0}, {-1}, 4⟩ - [ν₀, ν₁].map fun ν => (List.range 32).map fun i => Signed.encode (ν.oneArray 4 i) + [ν₀, ν₁] + +private instance : DecidableEq Cslib.Algorithms.ThreeSum.ChanHe.Node := fun a b => + decidable_of_iff (a.S₁ = b.S₁ ∧ a.S₂ = b.S₂ ∧ a.S₃ = b.S₃ ∧ a.M = b.M) + (by cases a; cases b; simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.mk.injEq]) + +-- The same two nodes are computed by the existing finite recursion, in the same order. +example : (Math.Task.mk 2 ![{0, 4, 1}, {0}, {-1}]).output {2} 4 = branchingNodes := by + decide + +private def expectedBranching : List (List ℕ) := + branchingNodes.map fun ν => (List.range 32).map fun i => Signed.encode (ν.oneArray 4 i) -- Only the first column has heavy elements. The child changes that descriptor and keeps the -- other two, then its zero-fuel children and both empty siblings are popped without outputs. diff --git a/UPSTREAM.md b/UPSTREAM.md index 556c781fe1..d32f9db287 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -159,8 +159,8 @@ 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 - input/time/width/asymptotic composition remain in + were copied. The full reduction host, 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. @@ -180,6 +180,20 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he preprocessing milestone is #1446; the Exact Triangle solver and full final 3SUM composition remain #1394. These components do not affect the curated headlines. + Bounded API follow-ups from the traversal/preprocessing reviews: merge the stage proof modules + `PreprocessingSnapshot`, `PreprocessingPrimes`, `PreprocessingSetup`, `PreprocessingFinish`, + `PreprocessingInvariant`, `PreprocessingStep` and `PreprocessingLoop` with their controller + consumer `PreprocessingExecute`, or use `import all` to make their proof plumbing private. + This includes `Saved`, `ForestCursor`, `snapshot_exec`, `primes_exec`, `setupTraversal_exec`, + `finish_exec`, `execute_next_root` and `forest_body_exec`; keep the initialized-input, readback, + width and resource endpoints available through `Preprocessing`. Separately, move + `modulus_bounds_of_candidates` from `SelectorBounds` beside `Collisions`' `modulus_bounds` and + derive or cross-reference the stronger bound, migrating `TraversalCall` and selector tests. + Derive `Structured`'s `ExecWithin.loop_invariant` as the `Data`-state case of + `GhostLoop`'s `ExecWithin.loop_invariant_state`, preserving the existing copy/residue/padding/ + search/reservation clients. Resolve the current import direction by keeping both rules in one + loop topic module before changing those clients. + `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 From 4f6de797203eb5dac02b2cadd9e8fa33e7427d57 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 22:22:06 -0400 Subject: [PATCH 32/45] 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 33/45] 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 34/45] 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 eb8f9255f4d60514d2db863ae989a05e242dbab0 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:29:44 -0400 Subject: [PATCH 35/45] perf(three-sum): check native query tests within default heartbeats --- ComplexityTests/WordRAMChanHeQuery.lean | 15 ++++++--------- ComplexityTests/WordRAMChanHeSearch.lean | 6 ------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/ComplexityTests/WordRAMChanHeQuery.lean b/ComplexityTests/WordRAMChanHeQuery.lean index efdddf0f61..dc16eb04c9 100644 --- a/ComplexityTests/WordRAMChanHeQuery.lean +++ b/ComplexityTests/WordRAMChanHeQuery.lean @@ -29,30 +29,27 @@ private def input : Data 64 := -- The three complete counting calls unfold all renamed loops and their register maps. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +-- Reduce the complete interpreter run directly in the kernel, avoiding elaborator normalization. /-- Arrays of lengths one, two and zero test setup/result copies and empty-array counting. -/ example : (Code.eval 8 3 CountTriple.program input).map (fun yn => [yn.1.regs (CountCall.result 0), yn.1.regs (CountCall.result 1), yn.1.regs (CountCall.result 2), yn.1.regs (40 : Fin 64), yn.1.mem 5]) = - some [0, 2, 0, 71, encode (-5)] := by decide + some [0, 2, 0, 71, encode (-5)] := by decide +kernel -- The full query additionally reduces three scaled inequalities after the counting loops. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +-- Reduce the complete interpreter run directly in the kernel, avoiding elaborator normalization. /-- Equality at the scaled threshold is accepted, and caller controls and input are framed. -/ example : (Code.eval 8 3 Query.program input).map (fun yn => [yn.1.regs CollisionTest.good, yn.1.regs (40 : Fin 64), yn.1.mem 5]) = - some [1, 71, encode (-5)] := by decide + some [1, 71, encode (-5)] := by decide +kernel -- This negative case executes the same complete native query with one smaller threshold. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the complete three-call native query exceeds the default heartbeat budget. +-- Reduce the complete interpreter run directly in the kernel, avoiding elaborator normalization. /-- A strict failure of one threshold rejects the three-set candidate. -/ example : (Code.eval 8 3 Query.program (input.set (CollisionTest.threshold 1) 5)).map (fun yn => yn.1.regs CollisionTest.good) = - some 0 := by decide + some 0 := by decide +kernel end ComplexityTests.WordRAMChanHeQuery diff --git a/ComplexityTests/WordRAMChanHeSearch.lean b/ComplexityTests/WordRAMChanHeSearch.lean index 136b9c36f8..cbdd66497f 100644 --- a/ComplexityTests/WordRAMChanHeSearch.lean +++ b/ComplexityTests/WordRAMChanHeSearch.lean @@ -33,8 +33,6 @@ example : (Code.eval 8 3 Search.program (emptyInput 1)).map -- The positive case reduces the full renamed three-set query inside the search loop. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the complete native search exceeds the default heartbeat budget. /-- The first prime is accepted at three zero thresholds for three empty sets. -/ example : (Code.eval 8 4 Search.program (emptyInput 2)).map (fun yn => [yn.1.regs Search.answer, yn.1.regs Search.found, yn.1.mem 12]) = @@ -42,8 +40,6 @@ example : (Code.eval 8 4 Search.program (emptyInput 2)).map -- After accepting two, the search executes only the charged skip branch for three. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction includes the complete query and subsequent retained-answer iteration. /-- Later accepted primes cannot overwrite the minimum or rerun the collision query. -/ example : (Code.eval 8 5 Search.program (emptyInput 3)).map (fun yn => [yn.1.regs Search.answer, yn.1.regs CountCall.modulus]) = @@ -51,8 +47,6 @@ example : (Code.eval 8 5 Search.program (emptyInput 3)).map -- The rejected prime still runs all three native collision-counting calls. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the complete rejected query exceeds the default heartbeat budget. /-- The only prime fails because zero and two collide, so the answer is the default one. -/ example : (Code.eval 8 4 Search.program ⟨Function.update (emptyInput 2).regs (CountCall.length 0) 2, From b94e77f170222506c2fbdd802110a0d11939a275 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:30:20 -0400 Subject: [PATCH 36/45] perf(three-sum): run modulus instance within default heartbeats --- ComplexityTests/WordRAMChanHeModulus.lean | 2 -- 1 file changed, 2 deletions(-) diff --git a/ComplexityTests/WordRAMChanHeModulus.lean b/ComplexityTests/WordRAMChanHeModulus.lean index 9e5f46d517..655224e245 100644 --- a/ComplexityTests/WordRAMChanHeModulus.lean +++ b/ComplexityTests/WordRAMChanHeModulus.lean @@ -24,8 +24,6 @@ private def input (n digits : ℕ) (values : ℕ → ℤ) : Data 64 := -- Each execution includes two complete prime scans and three baseline counting calls. set_option maxRecDepth 8192 in -set_option maxHeartbeats 2000000 in --- Kernel reduction of the full two-search program exceeds the default heartbeat budget. /-- Empty sets still compute the exact minimum/default specification, returning `2*2`. -/ example : (Code.eval 8 6 Modulus.program (input 0 1 (fun i => i))).map (fun yn => [yn.1.regs Modulus.first, yn.1.regs Modulus.output, From 9c8b9fcc3ccc5a6282bbd3b3e3cbcfc4c0070c62 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:31:21 -0400 Subject: [PATCH 37/45] 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 38/45] 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 aa7cebd5340b45e4febfbf4a4aa8cd5e9b9cd9a6 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:40:46 -0400 Subject: [PATCH 39/45] chore(wordram): regenerate frontend integration imports --- Complexity.lean | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 9891133fb6..39957ed8ce 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -55,11 +55,6 @@ public import Complexity.Algorithms.ThreeSum.ThreeSets public import Complexity.Algorithms.WordRAM.Deduplicate.Count public import Complexity.Algorithms.WordRAM.Deduplicate.Model public import Complexity.Algorithms.WordRAM.Deduplicate.Program -public import Complexity.Algorithms.WordRAM.Dijkstra.BinaryHeap -public import Complexity.Algorithms.WordRAM.Dijkstra.ExactNotation -public import Complexity.Algorithms.WordRAM.Dijkstra.HeapProgram -public import Complexity.Algorithms.WordRAM.Dijkstra.HeapRelax -public import Complexity.Algorithms.WordRAM.Dijkstra.HeapSift public import Complexity.Algorithms.WordRAM.Dijkstra.Fibonacci public import Complexity.Algorithms.WordRAM.Dijkstra.FibonacciStatement public import Complexity.Algorithms.WordRAM.Dijkstra.GenericPQ From 66851667d590094427dab4582c3109129b4eab76 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Wed, 28 Oct 2026 23:46:11 -0400 Subject: [PATCH 40/45] doc(three-sum): reconcile inherited preprocessing milestone status --- ROADMAP.md | 4 ++-- UPSTREAM.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d70cc744bc..3743359196 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -436,8 +436,8 @@ each statement against the numbered result. Organize modules by subject, not cha from actual initialized input, returns the exact finite packed instances and derives positive logarithmic width, polynomial space/intermediates and an `Oκ(n^1.5 log¹³(n+2))` charged bound. It still needs tensor/sparse-query programs, native Chan–He/Exact Triangle host composition, - 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 + 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 diff --git a/UPSTREAM.md b/UPSTREAM.md index 9a756eeb59..5f1d63089a 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -224,8 +224,8 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he 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. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. - Tensor/sparse query programs, the full reduction host, native Chan–He preprocessing, fast Exact - Triangle and full input/time/width/asymptotic composition remain in + Tensor/sparse query programs, the full reduction host, 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. From 48a8d3a2a8341d5797b4bb9670eaf8942a73f344 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 01:00:38 -0400 Subject: [PATCH 41/45] chore(wordram): refresh frontend imports after landed main --- Complexity.lean | 3 --- 1 file changed, 3 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 8746ab924f..b57fc49e77 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -144,9 +144,6 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search -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.Frontend public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays From 4b71a936e4f8f33ff009045bf58d9a1c3d7400c1 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 01:37:00 -0400 Subject: [PATCH 42/45] doc(roadmap): restore tensor milestone reference --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index f6faba622a..b9ef8ed0fd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -582,4 +582,5 @@ not planned, not as proved. [#1446]: https://github.com/cslib-community/complexitylib/issues/1446 [#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 +[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 [#1572]: https://github.com/cslib-community/complexitylib/issues/1572 From 4cb34a93df09ff1776a998df7565d98ad7df9644 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 02:17:44 -0400 Subject: [PATCH 43/45] doc(roadmap): separate native Chan-He progress from tensor work --- Complexity.lean | 1 - ROADMAP.md | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Complexity.lean b/Complexity.lean index 4d36c72752..55e99deac1 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -145,7 +145,6 @@ public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountTriple public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query -public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search public import Complexity.Algorithms.WordRAM.ThreeSum.Fill diff --git a/ROADMAP.md b/ROADMAP.md index 11613c0b68..1f8d746b43 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -424,6 +424,10 @@ each statement against the numbered result. Organize modules by subject, not cha kept separate; and develop general open-program composition. Use Cook–Levin and GGM as regression clients. Improve configuration views, tape partitions and handoff identities only where several clients benefit, keeping machine transitions out of global simp. +- **Native Chan–He components** ([#1446]): native parameter, collision-counting, prime-table and + collision-tested minimum-search programs have explicit local fit/frame/cost contracts. + Frontend enumeration, heavy extraction, recursive traversal, packed output and global + preprocessing width/time composition remain. - **Mathlib TM2:** sublinear work-space comparisons and a linear-space forward simulation. - **Generic RAM:** uniform arbitrary-program dispatch and general width-selection and input-fit theorems; simulating operations by programs over smaller bases; structured programs and time @@ -438,13 +442,11 @@ 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. Native parameter, collision-counting, - prime-table and collision-tested minimum-search programs have explicit local fit/frame/cost - contracts ([#1446]); frontend enumeration, heavy extraction, recursive traversal, packed output - and global preprocessing width/time composition remain. 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 + 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 From 16f2809167acb83bba4e2f8ab7582009e2086dd7 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 02:29:51 -0400 Subject: [PATCH 44/45] doc(roadmap): separate initialized frontend progress and references --- ROADMAP.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 8db204a47a..d2a0e5fd68 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -402,6 +402,10 @@ each statement against the numbered result. Organize modules by subject, not cha communication and interactive classes, with amplification, containments and complete problems. +[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 +[#1463]: https://github.com/cslib-community/complexitylib/issues/1463 +[#1482]: https://github.com/cslib-community/complexitylib/issues/1482 + ## Machines, RAM and data structures - **Native distinct-position frontend** ([#1482]): initialized signed input produces exact @@ -600,6 +604,4 @@ not planned, not as proved. [#1446]: https://github.com/cslib-community/complexitylib/issues/1446 [#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 -[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 -[#1482]: https://github.com/cslib-community/complexitylib/issues/1482 [#1572]: https://github.com/cslib-community/complexitylib/issues/1572 From 8c654382ca8e6282e6c8b62f10e0850f20d42345 Mon Sep 17 00:00:00 2001 From: Samuel Giuseppe Schlesinger Date: Thu, 29 Oct 2026 06:42:52 -0400 Subject: [PATCH 45/45] doc(wordram): restore preprocessing resource and API handoff details --- UPSTREAM.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/UPSTREAM.md b/UPSTREAM.md index 191b6337ce..9d8bf94ee7 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -184,11 +184,28 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he stack frame and exact preorder packed output. The traversal consumes a root descriptor and runtime fuel; its polynomial local envelope derives kernel fits and charges every stack, descriptor and address operation. It does not invoke a Convolution-3SUM solver. Composition - from initialized input through all frontend roots is supplied by - `Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing`, with positive logarithmic width, polynomial - space/intermediates and an `Oκ(n^1.5 log¹³(n+2))` charged bound. The fast Exact Triangle solver - and full input/time/width/asymptotic composition remain under #1394. These components do not - affect the curated headlines. + from initialized input through every frontend root is proved in + `Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean`. One finite 128-register program per + fixed magnitude exponent returns the existing exact finite instance list. All fits derive from + `(1300κ+7865) clog₂(n+2)` physical width; every run prefix uses polynomial words/addresses even + at wider admitted widths. Its charged cost is at most + `Kκ (n+2)(sqrt(n+2)+1)(log₂(n+2)+1)^13`, including the final halt. The complete native + preprocessing milestone is #1446; the Exact Triangle solver and full final 3SUM composition + remain #1394. These components do not affect the curated headlines. + + Bounded API follow-ups from the traversal/preprocessing reviews: merge the stage proof modules + `PreprocessingSnapshot`, `PreprocessingPrimes`, `PreprocessingSetup`, `PreprocessingFinish`, + `PreprocessingInvariant`, `PreprocessingStep` and `PreprocessingLoop` with their controller + consumer `PreprocessingExecute`, or use `import all` to make their proof plumbing private. + This includes `Saved`, `ForestCursor`, `snapshot_exec`, `primes_exec`, `setupTraversal_exec`, + `finish_exec`, `execute_next_root` and `forest_body_exec`; keep the initialized-input, readback, + width and resource endpoints available through `Preprocessing`. Separately, move + `modulus_bounds_of_candidates` from `SelectorBounds` beside `Collisions`' `modulus_bounds` and + derive or cross-reference the stronger bound, migrating `TraversalCall` and selector tests. + Derive `Structured`'s `ExecWithin.loop_invariant` as the `Data`-state case of + `GhostLoop`'s `ExecWithin.loop_invariant_state`, preserving the existing copy/residue/padding/ + search/reservation clients. Resolve the current import direction by keeping both rules in one + loop topic module before changing those clients. `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