diff --git a/Complexity.lean b/Complexity.lean index cfa5796f19..8c5709bfe9 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -118,6 +118,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.PriorityQueue.Basic public import Complexity.Algorithms.WordRAM.PriorityQueue.BinaryHeap public import Complexity.Algorithms.WordRAM.PriorityQueue.BinaryHeap.Model @@ -144,10 +146,32 @@ 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 +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.Modulus +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 public import Complexity.Algorithms.WordRAM.ThreeSum.Fill public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Loop public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Prepare diff --git a/Complexity/Algorithms/WordRAM/PrimeTable.lean b/Complexity/Algorithms/WordRAM/PrimeTable.lean new file mode 100644 index 0000000000..52ed1ba938 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/PrimeTable.lean @@ -0,0 +1,224 @@ +/- +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 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 = 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 ∧ + 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 = 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 : Nat.primeCounting' (y.regs cursor) ≤ y.regs cursor := + Nat.count_le Nat.Prime + 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 = 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] + 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, + 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 + 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_card_eq_primeCounting, Nat.primeCounting] + +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/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/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/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/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/Modulus.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean new file mode 100644 index 0000000000..cce0b1404f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean @@ -0,0 +1,536 @@ +/- +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. + +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 : ℕ) : ℤ)) + (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/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..2762ac98ee --- /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, 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/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/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean new file mode 100644 index 0000000000..bd68b60be5 --- /dev/null +++ b/ComplexityTests/NativeChanHeNode.lean @@ -0,0 +1,170 @@ +/- +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 + +-- Regression clients intentionally export no public declarations. +set_option linter.privateModule false + +/-! +# 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 + +-- 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 + +-- 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/ComplexityTests/WordRAMChanHeModulus.lean b/ComplexityTests/WordRAMChanHeModulus.lean new file mode 100644 index 0000000000..655224e245 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeModulus.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 +-/ + +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 +/-- 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/ComplexityTests/WordRAMChanHeQuery.lean b/ComplexityTests/WordRAMChanHeQuery.lean new file mode 100644 index 0000000000..dc16eb04c9 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeQuery.lean @@ -0,0 +1,55 @@ +/- +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 +-- 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 +kernel + +-- The full query additionally reduces three scaled inequalities after the counting loops. +set_option maxRecDepth 8192 in +-- 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 +kernel + +-- This negative case executes the same complete native query with one smaller threshold. +set_option maxRecDepth 8192 in +-- 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 +kernel + +end ComplexityTests.WordRAMChanHeQuery diff --git a/ComplexityTests/WordRAMChanHeSearch.lean b/ComplexityTests/WordRAMChanHeSearch.lean new file mode 100644 index 0000000000..cbdd66497f --- /dev/null +++ b/ComplexityTests/WordRAMChanHeSearch.lean @@ -0,0 +1,131 @@ +/- +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 +/-- 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 +/-- 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 +/-- 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 + +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 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 5fc6cb5a6f..a89a812705 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -429,6 +429,12 @@ 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 and complete two-prime modulus programs have explicit local + fit/frame/cost contracts. 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. - **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 @@ -444,7 +450,7 @@ each statement against the numbered result. Organize modules by subject, not cha witness recovery ([#1447]) are proved mathematically. The tensor identity, full recursion, shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set bounds, with the empty zero-depth root handled explicitly. The finite thin-product component - ([#1463]) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and + ([#1463](https://github.com/cslib-community/complexitylib/issues/1463)) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and sparse encoding/leaf/passed-set totals. These counts are not native instruction costs. It still needs native tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full @@ -598,7 +604,10 @@ 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 [#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 +[#1481]: https://github.com/cslib-community/complexitylib/issues/1481 [#1524]: https://github.com/cslib-community/complexitylib/issues/1524 [#1572]: https://github.com/cslib-community/complexitylib/issues/1572 diff --git a/UPSTREAM.md b/UPSTREAM.md index b009e0bce4..dc5d4e0f40 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -177,6 +177,12 @@ 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, 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. `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