diff --git a/Complexity.lean b/Complexity.lean index cfa5796f1..7a29194f5 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -61,6 +61,7 @@ public import Complexity.Algorithms.ThreeSum.ChanHe.Collisions public import Complexity.Algorithms.ThreeSum.ChanHe.Defs public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers public import Complexity.Algorithms.ThreeSum.ChanHe.Recursion +public import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes public import Complexity.Algorithms.ThreeSum.Convolution public import Complexity.Algorithms.ThreeSum.Defs @@ -118,6 +119,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,11 +147,67 @@ 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.ChanHe.TraversalAdmitted +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCost +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalDeliver +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLive +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalOutput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalResources +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalSkip +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStack +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalVisit public import Complexity.Algorithms.WordRAM.ThreeSum.Fill +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Execute +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Scan +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Select +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Time public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Loop public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Prepare public import Complexity.Algorithms.WordRAM.ThreeSum.Host.Program @@ -1717,6 +1776,7 @@ public import Complexity.Computability.WordRAM.Dynamic public import Complexity.Computability.WordRAM.ExactNotation public import Complexity.Computability.WordRAM.Function public import Complexity.Computability.WordRAM.Generic +public import Complexity.Computability.WordRAM.GhostLoop public import Complexity.Computability.WordRAM.Heap public import Complexity.Computability.WordRAM.ListArray public import Complexity.Computability.WordRAM.MapOp diff --git a/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean new file mode 100644 index 000000000..e8e22d0e9 --- /dev/null +++ b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Defs + +/-! +# Size bounds for the Chan–He selectors, including their default + +Bounding the modulus does not require either filter to succeed: a failed filter returns one. +These bounds therefore apply to native execution with arbitrary nonnegative collision thresholds, +including empty input sets. They do not assert the collision-shrinking property of a successful +search. + +## Main results + +* `minOrOne_filter_bounds`: a positive bounded candidate set keeps its filtered selector bounded. +* `modulus_bounds_of_candidates`: both selections, including defaults, have product in `[1,m²]`. + +The bounds are original elementary consequences of the existing finite selectors. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ThreeSum.ChanHe + +open Finset + +/-- Filtering positive candidates bounded by `m` keeps the default-one selector in `[1,m]`. +The candidate set and the filtered set may both be empty. -/ +theorem minOrOne_filter_bounds {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (A : ℕ → Prop) [DecidablePred A] : + 1 ≤ minOrOne {q ∈ Q | A q} ∧ minOrOne {q ∈ Q | A q} ≤ m := by + let S := {q ∈ Q | A q} + by_cases h : S.Nonempty + · have hmem : S.min' h ∈ Q := (mem_filter.mp (min'_mem S h)).1 + change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_left h] + exact ⟨hpos _ hmem, hle _ hmem⟩ + · change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_right h] + exact ⟨le_rfl, hm⟩ + +/-- Positive bounded candidates bound the modulus regardless of whether either filter succeeds. +This is a size bound only; no magnitude or successful-search premise is needed. -/ +theorem modulus_bounds_of_candidates {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (Λ : ℕ) (S₁ S₂ S₃ : Finset ℤ) : + 1 ≤ modulus Q Λ S₁ S₂ S₃ ∧ modulus Q Λ S₁ S₂ S₃ ≤ m ^ 2 := by + have hp : 1 ≤ firstP Q Λ S₁ S₂ S₃ ∧ firstP Q Λ S₁ S₂ S₃ ≤ m := + minOrOne_filter_bounds hm hpos hle _ + have hq : 1 ≤ secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ∧ + secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ≤ m := + minOrOne_filter_bounds hm hpos hle _ + exact ⟨Nat.mul_le_mul hp.1 hq.1, + (Nat.mul_le_mul hp.2 hq.2).trans_eq (pow_two m).symm⟩ + +end Cslib.Algorithms.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/PrimeTable.lean b/Complexity/Algorithms/WordRAM/PrimeTable.lean new file mode 100644 index 000000000..52ed1ba93 --- /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 000000000..ec29efe11 --- /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 000000000..aedff6aae --- /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 000000000..37545bd9a --- /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 000000000..de574e293 --- /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 000000000..ba7f1a1e0 --- /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 000000000..2fe25904c --- /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 000000000..24f7f33dc --- /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 000000000..09ea12f05 --- /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 000000000..204021629 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean @@ -0,0 +1,527 @@ +/- +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 +import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds + +/-! +# 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 + exact minOrOne_filter_bounds hm + (fun q hq => (Nat.prime_of_mem_primesLE hq).two_le.trans' (by decide)) + (fun _ hq => Nat.le_of_mem_primesLE hq) A + +private theorem forall_three {P : Fin 3 → Prop} : (∀ j, P j) ↔ P 0 ∧ P 1 ∧ P 2 := by + constructor + · 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 000000000..192747641 --- /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 000000000..ddce7d18d --- /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 000000000..74f0ccbde --- /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 000000000..4e6e4be17 --- /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 000000000..65255a32c --- /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 000000000..15ec92870 --- /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 000000000..444119ae4 --- /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 000000000..2762ac98e --- /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 000000000..ad4c2c0e4 --- /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 000000000..5fc95cc14 --- /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 000000000..d985795a0 --- /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 000000000..9753467df --- /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 000000000..9e61f1a50 --- /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 000000000..eef9c8d51 --- /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/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean new file mode 100644 index 000000000..57f495459 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission + +/-! +# Deriving every native visit fit from the worklist envelope + +The fixed arena envelope covers the current heap, every live source block and every candidate +product. This discharges the actual selector and node calls uniformly, without asking the loop +invariant to carry per-call execution advice or a separate width for each selected modulus. + +## Main results + +* `emit_admitted`: exact native emission from a loaded admissible entry and the common envelope. + +These resource and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The worklist's one signed envelope derives every local native kernel fit. -/ +theorem emit_admitted {a : Arena} {w : ℕ} (x : Data 128) (e : Entry) + (ha : a.Separated) (hw : a.Ready w) (hc : a.Controls x) + (hl : e.Loaded x) (hv : e.Valid a.n a.U) (hs : e.Stored x.mem) + (hbitmap : a.Bitmap x.mem) (hfloor : a.floor ≤ x.regs heap) + (hheap : x.regs heap ≤ a.ceiling) + (hsrc : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ y, ExecWithin (w + 2) visit x y (visitTime a.n a.m a.N) ∧ + Nonempty (Emission (Nat.primesLE a.m) a.Λ e x y) := by + rcases hc with ⟨hm, hΛ, hU, hN, hb, hcard, ht, hstack, hcap, hz, hone⟩ + have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have hbound {z : ℤ} (hz : z.natAbs ≤ envelope a.ceiling a.n a.m a.Λ a.U a.N) : + GenericRAM.Signed.Fits w z := NodePacking.signed_fits_of_natAbs_le hw.2.2 hz + have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + have hlen (j : Fin 3) : x.regs (arrayLength j) ≤ a.n := by + rw [hl.2.2] + exact hv.length_le j + have hsource (j : Fin 3) : x.regs (arraySource j) + x.regs (arrayLength j) ≤ a.ceiling := by + rw [hl.2.1, hl.2.2] + exact (hsrc j).trans hheap + have hbitmapBase : x.regs bitmap ≤ a.ceiling := by + rw [hb] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega + obtain ⟨y, he, hem⟩ := emit_entry x e hl (by simpa only [hU] using hv) hs + (by rw [hm]; exact hw.1) + (by apply hbound; simp only [hm, Int.natAbs_natCast]; omega) + (hunsigned.of_le (modulus_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N hheap + hsource hlen hm.le hΛ.le + (by rw [hcard]; exact Nat.card_primesLE_le a.m) hbitmapBase)) + (by + intro M _ hM + apply hbound + simp only [Int.natAbs_natCast] + exact node_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N M hheap hlen + (by simpa only [hm] using hM) hU.le hN.le) + (by rw [hm, hN]; exact hw.2.1) + (by rw [hm, hcard]) + (by + rw [hb, hm] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega) + (by simpa only [Arena.Bitmap, hm, hb] using hbitmap) + (by + intro j i hi + apply hbound + exact (hv.natAbs_le j i hi).trans (by omega)) + hsrc hone htable + exact ⟨y, by simpa only [hm, hN] using he, by simpa only [hm, hΛ] using hem⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean new file mode 100644 index 000000000..6d4b27dbf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean @@ -0,0 +1,271 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram +import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds + +/-! +# Actual native argument delivery in the Chan–He traversal + +The current record and persistent parameters are delivered to the existing complete modulus +and node programs in registers 0–63. Every copy and histogram-base addition is a native +instruction. The record, controller parameters and native word-stack registers survive setup. + +## Main definitions + +* `modulusState`, `nodeState`: exact states after the two native argument blocks. + +## Main results + +* `prepareModulus_exec`: fourteen charged instructions, including all histogram-base additions. +* `prepareNode_exec`: ten charged native register copies. +* `modulusState_preserves`, `nodeState_preserves`: all registers above the kernel survive. + +These calling-convention proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The exact state after the fourteen-instruction modulus argument block. -/ +def modulusState (x : Data 128) : Data 128 := + (((((((((((((x.set (kernel (CountCall.source 0)) (x.regs (slot 1))).set + (kernel (CountCall.length 0)) (x.regs (slot 2))).set + (kernel (CountCall.source 1)) (x.regs (slot 3))).set + (kernel (CountCall.length 1)) (x.regs (slot 4))).set + (kernel (CountCall.source 2)) (x.regs (slot 5))).set + (kernel (CountCall.length 2)) (x.regs (slot 6))).set + (kernel CountCall.scratch) (x.regs heap)).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2))).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2) + x.regs (slot 4))).set + (kernel CountCall.histogram) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))).set + (kernel Search.limit) (x.regs limit)).set (kernel Search.primes) (x.regs bitmap)).set + (kernel CollisionTest.card) (x.regs primeCount)).set (kernel Modulus.digits) (x.regs digits) + +/-- The exact state after the ten-instruction node argument block. -/ +def nodeState (x : Data 128) : Data 128 := + (((((((((x.set (kernel (NodePacking.source 0)) (x.regs (slot 1))).set + (kernel (NodePacking.length 0)) (x.regs (slot 2))).set + (kernel (NodePacking.source 1)) (x.regs (slot 3))).set + (kernel (NodePacking.length 1)) (x.regs (slot 4))).set + (kernel (NodePacking.source 2)) (x.regs (slot 5))).set + (kernel (NodePacking.length 2)) (x.regs (slot 6))).set + (kernel NodePacking.base) (x.regs heap)).set + (kernel NodePacking.groups) (x.regs groups)).set + (kernel NodePacking.magnitude) (x.regs magnitude)).set + (kernel NodePacking.modulus) (x.regs selected) + +/-- The existing node layout instantiated by the traversal record and one computed modulus. -/ +def recordLayout (x : Data 128) (M : ℕ) : NodePacking.Layout := + ⟨x.regs heap, fun j => x.regs (arrayLength j), x.regs groups, M, x.regs magnitude⟩ + +/-- The delivered node ABI has exactly the current record's layout. -/ +theorem nodeState_layout (x : Data 128) : + NodePacking.layout ((nodeState x).comap kernel) = recordLayout x (x.regs selected) := by + unfold NodePacking.layout recordLayout + congr 1 + funext j + fin_cases j <;> rfl + +/-- The layout reads only persistent record/controller fields outside the selected-modulus word. -/ +theorem recordLayout_congr (x y : Data 128) (M : ℕ) + (h : ∀ i, 64 ≤ i.val → i ≠ selected → y.regs i = x.regs i) : + recordLayout y M = recordLayout x M := by + unfold recordLayout + congr 1 + · exact h heap (by decide) (by decide) + · funext j + fin_cases j <;> exact h _ (by decide) (by decide) + · exact h groups (by decide) (by decide) + · exact h magnitude (by decide) (by decide) + +/-- The final endpoint formula expressed entirely in the persistent traversal ABI. -/ +theorem recordLayout_endpoint (x : Data 128) (M : ℕ) : + (recordLayout x M).endpoint = x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + 4 * x.regs groups + M := by + rw [NodePacking.Layout.endpoint_eq] + rfl + +private theorem kernel_ne {i : Fin 128} (hi : 64 ≤ i.val) (j : Fin 64) : i ≠ kernel j := by + intro h + have := congrArg Fin.val h + dsimp [kernel] at this + omega + +private theorem set_kernel_regs (i : Fin 128) (hi : 64 ≤ i.val) + (x : Data 128) (j : Fin 64) (v : ℕ) : + (x.set (kernel j) v).regs i = x.regs i := Function.update_of_ne (kernel_ne hi j) .. + +/-- Modulus argument delivery preserves every traversal and stack register. -/ +theorem modulusState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (modulusState x).regs i = x.regs i := by + simp only [modulusState, set_kernel_regs i hi] + +/-- Node argument delivery preserves every traversal and stack register. -/ +theorem nodeState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (nodeState x).regs i = x.regs i := by + simp only [nodeState, set_kernel_regs i hi] + +/-- Native modulus argument delivery charges the three histogram-base additions too. +A fit for their largest value derives all intermediate address fits. -/ +theorem prepareModulus_exec {w : ℕ} (x : Data 128) + (h : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))) : + Exec w prepareModulus x (modulusState x) 14 := by + have h₀ : Fits w (x.regs heap + x.regs (slot 2)) := h.of_le (by omega) + have h₁ : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4)) := h.of_le (by omega) + dsimp only [heap, slot] at h₀ h₁ h + ram_eval [prepareModulus, modulusState, kernel, slot, CountCall.source, CountCall.length, + CountCall.scratch, CountCall.histogram, Search.limit, Search.primes, CollisionTest.card, + Modulus.digits, heap, limit, bitmap, primeCount, digits, h₀, h₁, h] + +/-- Complete node argument delivery is ten native copies; no address arithmetic is hidden. -/ +theorem prepareNode_exec {w : ℕ} (x : Data 128) : + Exec w prepareNode x (nodeState x) 10 := by + ram_eval [prepareNode, nodeState, kernel, slot, NodePacking.source, NodePacking.length, + NodePacking.base, NodePacking.groups, NodePacking.magnitude, NodePacking.modulus, + heap, groups, magnitude, selected] + +private theorem kernel_injective : Function.Injective kernel := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 128 => i.val) h) + +private theorem extend_kernel_regs (y : Data 64) (x : Data 128) (i : Fin 128) + (hi : 64 ≤ i.val) : (y.extend kernel x).regs i = x.regs i := by + change Function.extend kernel y.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact kernel_ne hi j hj.symm + +/-- Actual argument delivery and complete two-prime computation inside the traversal's fixed +register file. The mathematical filters may fail; their default-one selections are still bounded. +The bitmap/count are the outputs of the separate native prime-table routine. -/ +theorem callModulus_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (arrayLength j) → GenericRAM.Signed.Fits w (values j i)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) (.seq prepareModulus (Modulus.program.rename kernel)) x y + (14 + Modulus.cost ((modulusState x).comap kernel)) ∧ + (∀ i, 64 ≤ i.val → y.regs i = x.regs i) ∧ + (∀ a < x.regs heap, y.mem a = x.mem a) ∧ + y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) ∧ + 1 ≤ y.regs (kernel Modulus.output) ∧ y.regs (kernel Modulus.output) ≤ x.regs limit ^ 2 := by + let a := modulusState x + let z := a.comap kernel + have hn (j : Fin 3) : z.regs (CountCall.length j) = x.regs (arrayLength j) := by + fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (CountCall.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hzm : z.regs Search.limit = x.regs limit := rfl + have hzd : z.regs Modulus.digits = x.regs digits := rfl + have hhist : Fits (w + 2) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := by + change Fits (w + 2) (z.regs CountCall.histogram) + have hbz : Fits (w + 2) (Modulus.budget z) := hbudget + apply hbz.of_le + dsimp only [Modulus.budget] + omega + have ha := prepareModulus_exec x hhist + obtain ⟨b, hb, _, hprefix, _, hout⟩ := Modulus.exec z values hm hmod hbudget hcard hbefore + hbitmap + (by intro j i hi; exact hvalues j i ((hn j) ▸ hi)) + (by intro j; change z.regs (CountCall.source j) + z.regs (CountCall.length j) ≤ x.regs heap + rw [hs j, hn j]; exact hsrc j) + (by + intro j + change x.regs heap + z.regs (CountCall.length j) ≤ + x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + rw [hn j] + fin_cases j + · change x.regs heap + x.regs (slot 2) ≤ _ + omega + · change x.regs heap + x.regs (slot 4) ≤ _ + omega + · change x.regs heap + x.regs (slot 6) ≤ _ + omega) + (by intro j i hi; change x.mem (z.regs (CountCall.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) + (by intro j; simpa only [hn j] using hinj j) + let y := b.extend kernel a + have hout' : y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) := by + change Function.extend kernel b.regs a.regs (kernel Modulus.output) = _ + rw [kernel_injective.extend_apply] + simpa only [hn, hzm, hzd] using hout + refine ⟨y, ha.within.seq (hb.rename kernel_injective), ?_, ?_, hout', ?_⟩ + · intro i hi + exact (extend_kernel_regs b a i hi).trans (modulusState_preserves x i hi) + · intro p hp + exact hprefix p hp + · rw [hout'] + exact Cslib.Algorithms.ThreeSum.ChanHe.modulus_bounds_of_candidates (by omega) + (fun q hq => (Nat.prime_of_mem_primesLE hq).two_le.trans' (by decide)) + (fun _ hq => Nat.le_of_mem_primesLE hq) _ _ _ _ + +/-- Ten actual argument copies followed by the complete native heavy/packed-node program. +The existing exact semantic output and the full allocation frame survive register renaming. -/ +theorem callNode_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < x.regs selected) (hN : 2 * x.regs selected ≤ x.regs groups) + (hwidth : GenericRAM.Signed.Fits w + ((NodePacking.layout ((nodeState x).comap kernel)).budget : ℤ)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hbdd : ∀ j i, i < x.regs (arrayLength j) → (values j i).natAbs ≤ x.regs magnitude) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + let L := NodePacking.layout ((nodeState x).comap kernel) + ∃ y, ExecWithin (w + 2) (.seq prepareNode (NodePacking.program.rename kernel)) x y + (10 + 66 * L.total + 15 * L.M + 12 * L.N + 228) ∧ + NodePacking.Output L values x.mem y.mem ∧ + ∀ i, 64 ≤ i.val → y.regs i = x.regs i := by + let a := nodeState x + let z := a.comap kernel + let L := NodePacking.layout z + have hn (j : Fin 3) : L.n j = x.regs (arrayLength j) := by fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (NodePacking.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hinj' (j : Fin 3) : Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ) := by + simpa only [hn] using hinj j + obtain ⟨b, hb, hmem, _⟩ := NodePacking.exec z values hM hN hwidth + (by intro j; rw [hs j]; change x.regs (arraySource j) + L.n j ≤ x.regs heap + rw [hn j]; exact hsrc j) + (by intro j i hi; exact hbdd j i ((hn j) ▸ hi)) + (by intro j i hi; change x.mem (z.regs (NodePacking.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) hinj' + refine ⟨b.extend kernel a, ?_, ?_, ?_⟩ + · simpa only [Nat.add_assoc] using + (prepareNode_exec (w := w + 2) x).within.seq (hb.rename kernel_injective) + · change NodePacking.Output L values z.mem b.mem + rw [hmem] + exact NodePacking.memory_output L values z.mem hM hN hinj' + · intro i hi + exact (extend_kernel_regs b a i hi).trans (nodeState_preserves x i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean new file mode 100644 index 000000000..4773b8adf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean @@ -0,0 +1,166 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram + +/-! +# Native preparation of the three Chan–He child records + +The third child is pushed first. Original descriptors are retained above the stack dispatcher +and restored before replacing another column. All descriptor moves and the fuel decrement are +native instructions; the same finite code is used at every recursion level. + +## Main definitions + +* `thirdState`, `secondState`, `firstState`: exact states after the native child-argument blocks. +* `childWords`: the seven actual words of one child record, in its pop order. + +## Main results + +* `prepareThird_exec`, `prepareSecond_exec`, `prepareFirst_exec`: charged native argument blocks. + +The native stack controller and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Save both descriptors needed for restoration and prepare the third child. -/ +def thirdState (x : Data 128) : Data 128 := + ((((((x.set (slot 0) (x.regs (slot 0) - 1)).set (original 0) (x.regs (slot 3))).set + (original 1) (x.regs (slot 4))).set (original 2) (x.regs (slot 5))).set + (original 3) (x.regs (slot 6))).set (slot 5) (x.regs (heavyBase 2))).set + (slot 6) (x.regs (heavyLength 2)) + +/-- Restore the third original column and prepare the second child. -/ +def secondState (x : Data 128) : Data 128 := + (((x.set (slot 5) (x.regs (original 2))).set (slot 6) (x.regs (original 3))).set + (slot 3) (x.regs (heavyBase 1))).set (slot 4) (x.regs (heavyLength 1)) + +/-- Restore the second original column and prepare the first child. -/ +def firstState (x : Data 128) : Data 128 := + (((x.set (slot 3) (x.regs (original 0))).set (slot 4) (x.regs (original 1))).set + (slot 1) (x.regs (heavyBase 0))).set (slot 2) (x.regs (heavyLength 0)) + +/-- Exact child-record serialization. Only the selected column is replaced by its heavy output. -/ +def childWords (x : Data 128) (j : Fin 3) : List ℕ := + [x.regs (slot 0) - 1, + if j = 0 then x.regs (heavyBase 0) else x.regs (slot 1), + if j = 0 then x.regs (heavyLength 0) else x.regs (slot 2), + if j = 1 then x.regs (heavyBase 1) else x.regs (slot 3), + if j = 1 then x.regs (heavyLength 1) else x.regs (slot 4), + if j = 2 then x.regs (heavyBase 2) else x.regs (slot 5), + if j = 2 then x.regs (heavyLength 2) else x.regs (slot 6)] + +/-- Every child occupies exactly one seven-word native record. -/ +theorem length_childWords (x : Data 128) (j : Fin 3) : (childWords x j).length = 7 := rfl + +/-- The fuel subtraction and all six descriptor copies cost seven native instructions. -/ +theorem prepareThird_exec {w : ℕ} (x : Data 128) (hx : x.Bounded w) (hone : x.regs one = 1) : + Exec w prepareThird x (thirdState x) 7 := by + have hfit : Fits w (x.regs (slot 0) - 1) := (hx.1 (slot 0)).of_le (Nat.sub_le _ _) + dsimp only [slot, one] at hfit hone + ram_eval [prepareThird, thirdState, slot, one, original, heavyBase, heavyLength, hone, hfit] + +/-- Restoring the third column and replacing the second costs four native copies. -/ +theorem prepareSecond_exec {w : ℕ} (x : Data 128) : + Exec w prepareSecond x (secondState x) 4 := by + ram_eval [prepareSecond, secondState, slot, original, heavyBase, heavyLength] + +/-- Restoring the second column and replacing the first costs four native copies. -/ +theorem prepareFirst_exec {w : ℕ} (x : Data 128) : + Exec w prepareFirst x (firstState x) 4 := by + ram_eval [prepareFirst, firstState, slot, original, heavyBase, heavyLength] + +private theorem thirdState_comap (x : Data 128) : + (thirdState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [thirdState, Data.comap, Data.set, slot, original, registers] + · rfl + +private theorem secondState_comap (x : Data 128) : + (secondState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [secondState, Data.comap, Data.set, slot, registers] + · rfl + +private theorem firstState_comap (x : Data 128) : + (firstState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [firstState, Data.comap, Data.set, slot, registers] + · rfl + +/-- Child pushes preserve every persistent controller parameter outside the record and stack. -/ +theorem pushChildren_preserves : ∀ i : Fin 128, + ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → pushChildren.Preserves i := by decide + +private def words (x : Data 128) : List ℕ := + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] + +/-- All three child records are pushed by native instructions in reverse order. The resulting +top-first stack contains first, second, third, followed by the original pending suffix. -/ +theorem pushChildren_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (hx : x.Bounded w) (hone : x.regs one = 1) (h2 : Fits w 2) + (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hcapacity : 21 + vs.length ≤ capacity) : + ∃ y, Exec w pushChildren x y 207 ∧ + Stack.Rep w base capacity (childWords x 0 ++ childWords x 1 ++ childWords x 2 ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ i, ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → y.regs i = x.regs i := by + let a := thirdState x + have ha := prepareThird_exec x hx hone + have hra : Stack.Rep w base capacity vs (a.comap registers) := by + simpa only [a, thirdState_comap] using hr + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a vs h2 hcap hra + (fun i => (ha.bounded hx).1 (slot i)) (by omega) + have hwa : words a = childWords x 2 := by + simp +decide [words, a, thirdState, childWords, Data.set, slot, original, + heavyBase, heavyLength] + change Stack.Rep w base capacity (words a ++ vs) (b.comap registers) at hrb + rw [hwa] at hrb + let c := secondState b + have hc := prepareSecond_exec (w := w) b + have hrc : Stack.Rep w base capacity (childWords x 2 ++ vs) (c.comap registers) := by + simpa only [c, secondState_comap] using hrb + obtain ⟨d, hd, hrd, hfd, hpd⟩ := pushFrame_exec c (childWords x 2 ++ vs) h2 hcap hrc + (fun i => (hc.bounded (hb.bounded (ha.bounded hx))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwc : words c = childWords x 1 := by + simp +decide [words, c, secondState, hpb, Outside, a, thirdState, childWords, Data.set, + slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words c ++ (childWords x 2 ++ vs)) (d.comap registers) at hrd + rw [hwc] at hrd + let e := firstState d + have he := prepareFirst_exec (w := w) d + have hre : Stack.Rep w base capacity (childWords x 1 ++ (childWords x 2 ++ vs)) + (e.comap registers) := by + simpa only [e, firstState_comap] using hrd + obtain ⟨z, hz, hrz, hfz, _⟩ := pushFrame_exec e (childWords x 1 ++ (childWords x 2 ++ vs)) + h2 hcap hre + (fun i => (he.bounded (hd.bounded (hc.bounded (hb.bounded (ha.bounded hx))))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwe : words e = childWords x 0 := by + simp +decide [words, e, firstState, hpd, Outside, c, secondState, hpb, a, thirdState, + childWords, Data.set, slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words e ++ (childWords x 1 ++ (childWords x 2 ++ vs))) + (z.comap registers) at hrz + rw [hwe] at hrz + have hrun : Exec w pushChildren x z 207 := ha.seq (hb.seq (hc.seq (hd.seq (he.seq hz)))) + refine ⟨z, hrun, ?_, ?_, fun i hi => hrun.regs_eq (pushChildren_preserves i hi)⟩ + · simpa only [List.append_assoc] using hrz + · intro p hp + exact (hfz p hp).trans ((hfd p hp).trans (hfb p hp)) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean new file mode 100644 index 000000000..d41bf3264 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean @@ -0,0 +1,70 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Polylogarithmic traversal overhead on charged native instructions + +The full ternary transition budget includes skipped calls. At the existing Chan–He recursion +fuel it is polylogarithmic, just as the emitted-node bound is. Combining it with the actual +native visit cost gives a cubic candidate-bound term and a linear-times-candidate input term. +This is a charged instruction estimate, not the finite reduction's candidate/element pair count. + +## Main results + +* `work_fuel_le`: polylogarithmically many charged transitions, including zero-fuel leaves. +* `visitTime_square_le`: the actual visit bound is controlled by `n*m + m³` up to constants. +* `rootTime_fuel_square_le`: the complete native root bound has polylogarithmic tree overhead. + +These native resource estimates are original. Substituting the actual parameter bound and +composing all frontend roots remain obligations of initialized-input preprocessing. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.Algorithms.ThreeSum.ChanHe (fuel three_pow_fuel_le) + +/-- Full-tree work counts all native pops, not just emitted mathematical instances. -/ +theorem work_fuel_le (n : ℕ) : Math.work (fuel n) ≤ 3 * (2 * Nat.log 2 n + 2) ^ 5 := by + have he := Math.work_eq (fuel n) + rw [pow_succ] at he + have hb := Nat.mul_le_mul_right 3 (three_pow_fuel_le n) + omega + +/-- The concrete `N=2m²` visit cost has the desired input-times-candidate and cubic terms. -/ +theorem visitTime_square_le (n m : ℕ) : + visitTime n m (2 * m ^ 2) ≤ 1024 * ((n + 1) * (m + 1) + (m + 1) ^ 3) := by + dsimp only [visitTime] + nlinarith + +/-- The complete native root cost, including setup, all skipped calls and all stack/loop work, +has only a polylogarithmic factor beyond `n*m + m³`. -/ +theorem rootTime_fuel_square_le (n card m : ℕ) : + rootTime (fuel n) card m (2 * m ^ 2) ≤ + 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + ((card + 1) * (m + 1) + (m + 1) ^ 3) := by + let S := (card + 1) * (m + 1) + (m + 1) ^ 3 + have hpos : 1 ≤ S := by + have hp : 0 < S := by dsimp only [S]; positivity + omega + have hvisit := visitTime_square_le card m + have hcost : visitTime card m (2 * m ^ 2) + 316 ≤ 2048 * S := by + change visitTime card m (2 * m ^ 2) ≤ 1024 * S at hvisit + omega + have hm := Nat.mul_le_mul (work_fuel_le n) hcost + calc rootTime (fuel n) card m (2 * m ^ 2) + ≤ 70 + (3 * (2 * Nat.log 2 n + 2) ^ 5) * (2048 * S) := + Nat.add_le_add_left hm 70 + _ = 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + ((card + 1) * (m + 1) + (m + 1) ^ 3) := by dsimp only [S]; ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean new file mode 100644 index 000000000..7f0fc7f32 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean @@ -0,0 +1,245 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall + +/-! +# Charged native output delivery for one traversal node + +The three actual heavy counts are read from the node allocation. The packed pointer is appended +to the reserved output table and the fresh heap advances past all node output and scratch cells. +Address computation is repeated explicitly instead of assuming scratch-register outputs that +the node builder's public contract does not export. + +## Main definitions + +* `deliveryState`: exact state after all 24 native delivery instructions. + +## Main results + +* `deliver_exec`: charged output delivery with derived address and counter fits. + +The layout and native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- All arithmetic results of the native delivery block, including its sole memory store. -/ +def deliveryState (x : Data 128) : Data 128 := + let b := x.regs heap + let n₀ := x.regs (slot 2) + let n₁ := x.regs (slot 4) + let n₂ := x.regs (slot 6) + let N := x.regs groups + let M := x.regs selected + let p := b + 3 + n₀ + n₁ + n₂ + let o := x.regs outputs + x.regs outputCount + let d₁ := b + 1 + n₀ + let d₂ := d₁ + 1 + n₁ + let a := ((((x.set packed 3).set packed (b + 3)).set packed (b + 3 + n₀)).set + packed (b + 3 + n₀ + n₁)).set packed p + let a := (((a.set temporary o).write o p).set outputCount (x.regs outputCount + 1)).set + (heavyLength 0) (x.mem b) + let a := (((a.set (heavyBase 0) (b + 1)).set temporary d₁).set + (heavyLength 1) (x.mem d₁)).set (heavyBase 1) (d₁ + 1) + let a := (((a.set temporary d₂).set (heavyLength 2) (x.mem d₂)).set + (heavyBase 2) (d₂ + 1)).set endpoint (N + N) + let a := ((((((a.set endpoint (N + N + (N + N))).set endpoint (p + 4 * N)).set + endpoint (p + 4 * N + n₀)).set endpoint (p + 4 * N + n₀ + n₁)).set + endpoint (p + 4 * N + n₀ + n₁ + n₂)).set endpoint (p + 4 * N + n₀ + n₁ + n₂ + M)).set + heap (p + 4 * N + n₀ + n₁ + n₂ + M) + a + +/-- Delivery writes exactly one output-pointer cell; all other memory retains the node output. -/ +theorem deliveryState_mem (x : Data 128) : (deliveryState x).mem = + Function.update x.mem (x.regs outputs + x.regs outputCount) + (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := rfl + +/-- The appended pointer is the existing node layout's packed base. -/ +theorem deliveryState_packed (x : Data 128) : (deliveryState x).regs packed = + (NodePacking.layout ((nodeState x).comap kernel)).packed := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + omega + +/-- All allocated node output, residues and histogram lie below the new fresh heap. -/ +theorem deliveryState_heap (x : Data 128) : (deliveryState x).regs heap = + (NodePacking.layout ((nodeState x).comap kernel)).endpoint := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + x.regs selected = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + x.regs selected + omega + +/-- Exactly one node is appended to the output table. -/ +theorem deliveryState_count (x : Data 128) : + (deliveryState x).regs outputCount = x.regs outputCount + 1 := rfl + +/-- Each delivered count was read from its actual count-prefixed heavy array. -/ +theorem deliveryState_heavy_length (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + x.mem ((NodePacking.layout ((nodeState x).comap kernel)).descriptor j) := by + fin_cases j + · rfl + · rfl + · change x.mem (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) = + x.mem (x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4))) + congr 1 + omega + +/-- Each delivered data pointer is the existing heavy-array base. -/ +theorem deliveryState_heavy_base (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyBase j) = + (NodePacking.layout ((nodeState x).comap kernel)).heavy j := by + fin_cases j + · rfl + · rfl + · change x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1 = + x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4)) + 1 + omega + +/-- Output delivery preserves the node builder's exact heavy/packed readback and allocation +frame. The sole external output-pointer store is recorded explicitly in the before-memory. -/ +theorem delivery_output (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + let L := NodePacking.layout ((nodeState x).comap kernel) + NodePacking.Output L values + (Function.update before (x.regs outputs + x.regs outputCount) L.packed) + (deliveryState x).mem := by + let L := NodePacking.layout ((nodeState x).comap kernel) + let o := x.regs outputs + x.regs outputCount + change NodePacking.Output L values before x.mem at h + change NodePacking.Output L values (Function.update before o L.packed) (deliveryState x).mem + have hm := deliveryState_mem x + change (deliveryState x).mem = Function.update x.mem o ((deliveryState x).regs packed) at hm + rw [deliveryState_packed] at hm + have ho : o < L.base := htable + have hkeep (a : ℕ) (ha : L.base ≤ a) : (deliveryState x).mem a = x.mem a := by + rw [hm, Function.update_of_ne (show a ≠ o by omega)] + refine ⟨?_, ?_, ?_⟩ + · intro j + obtain ⟨zs, hnd, hs, hc, hd⟩ := h.heavy j + refine ⟨zs, hnd, hs, (hkeep _ (L.base_le_descriptor j)).trans hc, ?_⟩ + intro k hk + have hb := L.base_le_descriptor j + have hbase : L.base ≤ L.heavy j + k := by + dsimp only [NodePacking.Layout.heavy] + omega + exact (hkeep _ hbase).trans (hd k hk) + · intro a ha + have hbase : L.base ≤ L.packed + a := by + dsimp only [NodePacking.Layout.packed] + omega + exact (hkeep _ hbase).trans (h.packed a ha) + · intro a ha + rw [hm] + by_cases he : a = o + · subst a + simp only [Function.update_self] + rfl + · simp only [Function.update_of_ne he] + exact h.frame a ha + +/-- The delivered register is the existing heavy-set cardinality, not a capacity or pair count. -/ +theorem delivery_heavy_card (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + (Cslib.Algorithms.ThreeSum.ChanHe.heavy + ((Finset.range ((NodePacking.layout ((nodeState x).comap kernel)).n j)).image (values j)) + (NodePacking.layout ((nodeState x).comap kernel)).M).card := by + rw [deliveryState_heavy_length] + exact h.heavy_card j + +/-- All non-output controller parameters and the entire stack workspace survive delivery. -/ +theorem deliver_preserves : ∀ i : Fin 128, i ≠ heap → i ≠ outputCount → i ≠ temporary → + (i.val < 88 ∨ 96 ≤ i.val) → deliver.Preserves i := by decide + +/-- The explicit delivered state retains every non-output controller and stack register. -/ +theorem deliveryState_preserves (x : Data 128) (i : Fin 128) + (hh : i ≠ heap) (hc : i ≠ outputCount) (ht : i ≠ temporary) + (hi : i.val < 88 ∨ 96 ≤ i.val) : (deliveryState x).regs i = x.regs i := by + have hp : i ≠ packed := by intro he; subst i; norm_num [packed] at hi + have he : i ≠ endpoint := by intro he; subst i; norm_num [endpoint] at hi + have hl (j : Fin 3) : i ≠ heavyLength j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyLength] at hv + have := j.isLt + omega + have hb (j : Fin 3) : i ≠ heavyBase j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyBase] at hv + have := j.isLt + omega + simp only [deliveryState, Data.set_regs, Data.write_regs, Function.update_of_ne hh, + Function.update_of_ne hc, Function.update_of_ne ht, Function.update_of_ne hp, + Function.update_of_ne he, Function.update_of_ne (hl 0), Function.update_of_ne (hl 1), + Function.update_of_ne (hl 2), Function.update_of_ne (hb 0), Function.update_of_ne (hb 1), + Function.update_of_ne (hb 2)] + +/-- Every intermediate address and the output-count increment fit once the final endpoint fits. +The output table precedes the fresh allocation, so its store cannot change a heavy-count load. -/ +theorem deliver_exec {w : ℕ} (x : Data 128) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) + (hfit : Fits w (x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected)) : + Exec w deliver x (deliveryState x) 24 := by + have hbound (v : ℕ) (hv : v ≤ x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected) : Fits w v := hfit.of_le hv + have h3 : Fits w 3 := hbound _ (by omega) + have hp₀ := hbound (x.regs heap + 3) (by omega) + have hp₁ := hbound (x.regs heap + 3 + x.regs (slot 2)) (by omega) + have hp₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have hp := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have ho := hbound (x.regs outputs + x.regs outputCount) (by omega) + have hc := hbound (x.regs outputCount + 1) (by omega) + have hd₀ := hbound (x.regs heap + 1) (by omega) + have hd₁ := hbound (x.regs heap + 1 + x.regs (slot 2)) (by omega) + have hd₁' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1) (by omega) + have hd₂ := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) (by omega) + have hd₂' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1) (by omega) + have he₀ := hbound (x.regs groups + x.regs groups) (by omega) + have he₁ := hbound (4 * x.regs groups) (by omega) + have he₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups) (by omega) + have he₃ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2)) (by omega) + have he₄ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have he₅ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have he := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + x.regs selected) (by omega) + have hne₀ : x.regs heap ≠ x.regs outputs + x.regs outputCount := by omega + have hne₁ : x.regs heap + 1 + x.regs (slot 2) ≠ + x.regs outputs + x.regs outputCount := by omega + have hne₂ : x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) ≠ + x.regs outputs + x.regs outputCount := by omega + dsimp only [heap, slot, groups, selected, outputs, outputCount, one] at * + ram_eval [deliver, deliveryState, heavyBase, heavyLength, heap, slot, groups, selected, + outputs, outputCount, packed, temporary, endpoint, one, hone, h3, hp₀, hp₁, hp₂, hp, ho, + hc, hd₀, hd₁, hd₁', hd₂, hd₂', he₀, he₁, he₂, he₃, he₄, he₅, he, + hne₀, hne₁, hne₂, Function.update_of_ne, Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc, + show ∀ n : ℕ, n + n + (n + n) = 4 * n by omega] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean new file mode 100644 index 000000000..d21e5ff86 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Exact emissions from the complete native traversal visit + +This packages the executed modulus and node calls into the worklist's semantic entry format. +The heavy lists are witnesses to actual native memory, not program inputs or advice. The old +prefix survives before the single output-pointer store, and every new packed cell equals the +existing finite node array. + +## Main definitions + +* `Emission`: exact metadata, old-prefix frame and heavy/packed readback of an actual live visit. + +## Main results + +* `emit_entry`: the complete native visit produces an exact emission with its charged bound. + +These native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Semantic readback of one completed actual native visit. -/ +structure Emission (Q : Finset ℕ) (Λ : ℕ) (e : Entry) (x y : Data 128) where + /-- The actual selected unsigned modulus. -/ + M : ℕ + /-- Selection agrees with the existing finite node. -/ + exactModulus : M = (e.task.node Q Λ).M + /-- The native fallback is positive. -/ + positive : 1 ≤ M + /-- Both selected candidates lie within the supplied bound. -/ + upper : M ≤ x.regs limit ^ 2 + /-- Actual native compact heavy enumerations. -/ + lists : Fin 3 → List ℤ + /-- Each enumeration is duplicate-free. -/ + nodup : ∀ j, (lists j).Nodup + /-- The enumerated sets are exactly the existing heavy subsets. -/ + sets : ∀ j, (lists j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M + /-- A heavy enumeration never exceeds its parent column. -/ + length_le : ∀ j, (lists j).length ≤ (e.data j).length + /-- The delivered unsigned lengths describe these actual lists. -/ + lengths : ∀ j, y.regs (heavyLength j) = (lists j).length + /-- The delivered unsigned bases are the concrete heavy-array bases. -/ + bases : ∀ j, y.regs (heavyBase j) = (recordLayout x M).heavy j + /-- Exact signed-encoded heavy-array readback. -/ + heavy : ∀ j, (Block.mk ((recordLayout x M).heavy j) (lists j).length).Realizes y.mem + (fun i => Signed.encode (lists j)[i.val]) + /-- Exact signed-encoded packed-node readback. -/ + packed : (Block.mk (recordLayout x M).packed (4 * x.regs groups)).Realizes y.mem + (fun i => Signed.encode ((e.task.node Q Λ).oneArray (x.regs magnitude) i.val)) + /-- The actual count increment. -/ + count : y.regs outputCount = x.regs outputCount + 1 + /-- The actual advanced allocation frontier. -/ + heap_eq : y.regs heap = (recordLayout x M).endpoint + /-- The complete node output before its single external pointer-table store. -/ + before : ℕ → ℕ + /-- The two actual modulus searches and the node call preserve every old prefix cell. -/ + prefixMemory : ∀ p < x.regs heap, before p = x.mem p + /-- The sole pointer-table store is an actual native memory update. -/ + memory : y.mem = Function.update before (x.regs outputs + x.regs outputCount) + (recordLayout x M).packed + /-- The current record, persistent controller parameters and stack dispatcher survive. -/ + registers : ∀ i : Fin 128, 64 ≤ i.val → i ≠ selected → i ≠ heap → + i ≠ outputCount → i ≠ temporary → (i.val < 88 ∨ 96 ≤ i.val) → y.regs i = x.regs i + +/-- Execute both complete searches and the complete node construction for an actual loaded +entry. All heavy witnesses come from the executed memory and all control work is charged. -/ +theorem emit_entry {w n : ℕ} (x : Data 128) (e : Entry) (hl : e.Loaded x) + (hv : e.Valid n (x.regs magnitude)) (hs : e.Stored x.mem) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hnode : ∀ M, 1 ≤ M → M ≤ x.regs limit ^ 2 → + GenericRAM.Signed.Fits w ((recordLayout x M).budget : ℤ)) + (hN : 2 * x.regs limit ^ 2 ≤ x.regs groups) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < (e.data j).length → GenericRAM.Signed.Fits w (e.read j i)) + (hsrc : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ y, ExecWithin (w + 2) visit x y + (visitTime n (x.regs limit) (x.regs groups)) ∧ + Nonempty (Emission (Nat.primesLE (x.regs limit)) (x.regs digits) e x y) := by + classical + obtain ⟨M, u, b, he, hu, hus, hprefix, hout, hb, hMpos, hMle, hM⟩ := + visit_exec x e.read hm hmod hbudget hnode hN hcard hbefore hbitmap + (by simpa only [hl.2.2] using hvalues) + (by intro j i hi; exact hv.natAbs_le j i ((hl.2.2 j) ▸ hi)) + (by simpa only [hl.2.1, hl.2.2] using hsrc) + (by simpa only [hl.2.1, hl.2.2] using hs.input) + (by intro j; simpa only [hl.2.2] using e.read_injOn j (hv.1 j)) hone htable + have hbp (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + b.regs i = x.regs i := (hb i hi).trans (hu i hi hne) + have hL : NodePacking.layout ((nodeState b).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hb selected (by decide), hus] + exact recordLayout_congr x b M hbp + have htableb : b.regs outputs + b.regs outputCount < b.regs heap := by + rw [hbp outputs (by decide) (by decide), hbp outputCount (by decide) (by decide), + hbp heap (by decide) (by decide)] + exact htable + have houtb : NodePacking.Output (NodePacking.layout ((nodeState b).comap kernel)) + e.read u.mem b.mem := by simpa only [hL] using hout + have hd := delivery_output b e.read u.mem houtb htableb + rw [hL] at hd + have hMexact : M = (e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits)).M := by + simpa only [hl.2.2, e.image_read, Math.Task.node, Entry.task] using hM + have hnodeExact : NodePacking.node (recordLayout x M) e.read = + e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits) := by + simp only [NodePacking.node, recordLayout, hl.2.2, e.image_read, Math.Task.node, + Entry.task] + exact congrArg (fun q => Cslib.Algorithms.ThreeSum.ChanHe.Node.mk + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset q) hMexact + choose zs hnd hsets hcounts hdata using hd.heavy + have hsets' (j : Fin 3) : (zs j).toFinset = + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M := by + simpa only [recordLayout, hl.2.2, e.image_read, Entry.task] using hsets j + have hlen (j : Fin 3) : (zs j).length ≤ (e.data j).length := by + have hh := Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset (e.task.sets j) M + rw [← hsets' j] at hh + have hc := Finset.card_le_card hh + simpa only [Entry.task, List.toFinset_card_of_nodup (hnd j), + List.toFinset_card_of_nodup (hv.1 j)] using hc + refine ⟨deliveryState b, he.mono (visit_cost_le x n (x.regs limit) (x.regs groups) M + (by intro j; rw [hl.2.2]; exact hv.length_le j) le_rfl le_rfl hMle), ?_⟩ + refine ⟨⟨M, hMexact, hMpos, hMle, zs, hnd, hsets', hlen, ?_, ?_, ?_, ?_, ?_, ?_, b.mem, + ?_, ?_, ?_⟩⟩ + · intro j + rw [deliveryState_heavy_length, hL, hout.heavy_card j, ← hsets j] + exact List.toFinset_card_of_nodup (hnd j) + · intro j + rw [deliveryState_heavy_base, hL] + · intro j i + exact hdata j i.val i.isLt + · intro i + have hp := hd.packed i.val i.isLt + rw [hnodeExact] at hp + simpa only [Block.read, recordLayout] using hp + · rw [deliveryState_count, hbp outputCount (by decide) (by decide)] + · rw [deliveryState_heap, hL] + · intro p hp + exact (hout.frame p (Or.inl hp)).trans (hprefix p hp) + · rw [deliveryState_mem, hbp outputs (by decide) (by decide), + hbp outputCount (by decide) (by decide)] + congr 1 + exact (deliveryState_packed b).trans (congrArg NodePacking.Layout.packed hL) + · intro i hi hsel hh hc ht hw + exact (deliveryState_preserves b i hh hc ht hw).trans (hbp i hi hsel) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean new file mode 100644 index 000000000..bdd66b224 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean @@ -0,0 +1,272 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission + +/-! +# Retained memory and pending children after a native emission + +The actual pointer-table store is below the fresh heap and outside every live input block. +These frame lemmas retain old arrays and controller parameters, and identify the three actual +compact heavy buffers with the existing finite recursion's children. + +## Main results + +* `Emission.controls`, `Emission.loaded`: retained caller registers and current entry. +* `Emission.stored`: preservation of any live array around the reserved gap. +* `Emission.children_tasks`: the exact ordered mathematical worklist after child pushes. + +These memory-frame and representation proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- An actual emission preserves each old-prefix cell except its one explicit table store. -/ +theorem Emission.prefix_preserved {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (p : ℕ) (hp : p < x.regs heap) + (hne : p ≠ x.regs outputs + x.regs outputCount) : y.mem p = x.mem p := by + rw [h.memory, Function.update_of_ne hne] + exact h.prefixMemory p hp + +/-- All persistent caller parameters survive the actual complete visit. -/ +theorem Emission.controls {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) : a.Controls y := by + apply hc.congr + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + apply h.registers i <;> + first | omega | (intro he; have := congrArg Fin.val he; norm_num at this; omega) + +/-- The current root record survives the visit until the actual child-preparation instructions. -/ +theorem Emission.loaded {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : e.Loaded y := by + have hs (i : Fin 7) : y.regs (slot i) = x.regs (slot i) := by + apply h.registers <;> + first | (dsimp [slot]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [slot] at this; omega) + refine ⟨(hs 0).trans hl.1, ?_, ?_⟩ + · intro j + fin_cases j + · exact (hs 1).trans (hl.2.1 0) + · exact (hs 3).trans (hl.2.1 1) + · exact (hs 5).trans (hl.2.1 2) + · intro j + fin_cases j + · exact (hs 2).trans (hl.2.2 0) + · exact (hs 4).trans (hl.2.2 1) + · exact (hs 6).trans (hl.2.2 2) + +/-- Old input columns below the stack or above the output table cannot be overwritten by the +single native pointer store, and both actual kernels preserve the old prefix. -/ +theorem Emission.stored {Q : Finset ℕ} {Λ : ℕ} {e d : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hs : d.Stored x.mem) (hg : d.Around a.stack (a.floor - a.stack)) + (hend : ∀ j, d.source j + (d.data j).length ≤ x.regs heap) + (htable : x.regs outputs + x.regs outputCount < a.floor) : d.Stored y.mem := by + apply hs.congr + intro j i hi + apply h.prefix_preserved _ (by have := hend j; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hstack := ha.2.1 + have hfloor := ha.2.2.1 + rcases hg j with hj | hj <;> omega + +/-- Top-first native child entries with the three actual compact heavy buffers. -/ +def Emission.children {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : List Entry := + [e.child 0 ((recordLayout x h.M).heavy 0) (h.lists 0), + e.child 1 ((recordLayout x h.M).heavy 1) (h.lists 1), + e.child 2 ((recordLayout x h.M).heavy 2) (h.lists 2)] + +/-- Actual heavy-buffer readback represents exactly the existing ordered three-child recursion. -/ +theorem Emission.children_tasks {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : + h.children.map Entry.task = e.task.children Q Λ (e.fuel - 1) := by + simp only [Emission.children, List.map_cons, List.map_nil] + apply e.task_children Q Λ (recordLayout x h.M).heavy h.lists + intro j + rw [h.sets j, h.exactModulus] + +/-- Each of the actual three heavy-child arrays retains the existing admissibility promises. -/ +theorem Emission.children_valid {Q : Finset ℕ} {Λ n U : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hv : e.Valid n U) : ∀ d ∈ h.children, d.Valid n U := by + intro d hd + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Valid n U := by + apply hv.child j _ _ (h.nodup j) + rw [h.sets j] + exact Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset _ _ + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- Each child reuses the two surviving original columns and one actual compact heavy buffer. -/ +theorem Emission.children_stored {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hs : e.Stored y.mem) : ∀ d ∈ h.children, d.Stored y.mem := by + intro d hd + have hc (j : Fin 3) := hs.child j ((recordLayout x h.M).heavy j) (h.lists j) (h.heavy j) + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The actual heap pointer advances past the old frontier. -/ +theorem Emission.heap_mono {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : x.regs heap ≤ y.regs heap := by + rw [h.heap_eq, recordLayout_endpoint] + omega + +/-- Every actual child column lies below the advanced native allocation frontier. -/ +theorem Emission.children_ends {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) + (hend : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) : + ∀ d ∈ h.children, ∀ k, d.source k + (d.data k).length ≤ y.regs heap := by + have hpacked : (recordLayout x h.M).packed ≤ (recordLayout x h.M).endpoint := by + dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, NodePacking.Layout.scratch] + omega + have heavyEnd (j : Fin 3) : + (recordLayout x h.M).heavy j + (h.lists j).length ≤ y.regs heap := by + rw [h.heap_eq] + calc (recordLayout x h.M).heavy j + (h.lists j).length + ≤ (recordLayout x h.M).heavy j + (recordLayout x h.M).n j := by + apply Nat.add_le_add_left + simpa only [recordLayout, hl.2.2] using h.length_le j + _ ≤ (recordLayout x h.M).packed := (recordLayout x h.M).heavy_end_le_packed j + _ ≤ (recordLayout x h.M).endpoint := hpacked + have hc (j k : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).source k + + ((e.child j ((recordLayout x h.M).heavy j) (h.lists j)).data k).length ≤ + y.regs heap := by + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using heavyEnd j + · simpa only [Entry.child, Function.update_of_ne he] using (hend k).trans h.heap_mono + intro d hd k + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 k + · exact hc 1 k + · exact hc 2 k + +/-- Every new heavy child begins above the reserved stack/table gap. -/ +theorem Emission.children_around {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {base capacity : ℕ} (hg : e.Around base capacity) + (hfloor : base + capacity ≤ x.regs heap) : + ∀ d ∈ h.children, d.Around base capacity := by + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Around base capacity := by + apply hg.child + have hb := (recordLayout x h.M).base_le_descriptor j + change x.regs heap ≤ (recordLayout x h.M).descriptor j at hb + dsimp only [NodePacking.Layout.heavy] + omega + intro d hd + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The exact native words prepared for the three pushes are the new semantic child records. -/ +theorem Emission.children_words {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : + childWords y 0 ++ childWords y 1 ++ childWords y 2 = h.children.flatMap Entry.words := by + rw [Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 0, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 1, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 2] + simp only [Emission.children, List.flatMap_cons, List.flatMap_nil, List.append_nil, + List.append_assoc] + +/-- The complete visit retains the actual pending stack, including its native pointer and +length registers; its sole external memory store lies above the stack reservation. -/ +theorem Emission.stack {Q : Finset ℕ} {Λ w : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hy : y.Bounded w) (vs : List ℕ) + (hr : Stack.Rep w a.stack a.capacity vs (x.comap Traversal.registers)) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + Stack.Rep w a.stack a.capacity vs (y.comap Traversal.registers) := by + have hregs (i : Fin 9) : y.regs (Traversal.registers i) = x.regs (Traversal.registers i) := by + apply h.registers <;> + first | (dsimp [Traversal.registers]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [Traversal.registers] at this; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + refine ⟨⟨fun i => hy.1 (Traversal.registers i), hy.2⟩, (hregs Stack.ptr).trans hr.pointer, + (hregs Stack.len).trans hr.length_eq, hr.capacity_le, ?_⟩ + intro i + have hi : i.val < a.capacity := lt_of_lt_of_le i.isLt hr.capacity_le + change y.mem (a.stack + i.val) = _ + rw [h.prefix_preserved _ (by have := ha.2.1; omega) + (by have := ha.2.1; omega)] + exact hr.contents i + +/-- Append the actual newly emitted packed pointer and array to the retained output sequence. -/ +theorem Emission.output {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) + (ns : List (ℕ × Cslib.Algorithms.ThreeSum.ChanHe.Node)) + (hs : StoredNodes a.U a.N a.table ns x.mem) + (hcount : x.regs outputCount = ns.length) + (hbounds : ∀ i : Fin ns.length, + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ x.regs heap) + (htable : a.table + ns.length < a.floor) (hfloor : a.floor ≤ x.regs heap) : + StoredNodes a.U a.N a.table + (ns ++ [((recordLayout x h.M).packed, e.task.node Q Λ)]) y.mem := by + have hU : x.regs magnitude = a.U := hc.2.2.1 + have hN : x.regs groups = a.N := hc.2.2.2.1 + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hpacked : x.regs heap ≤ (recordLayout x h.M).packed := by + dsimp only [recordLayout, NodePacking.Layout.packed] + omega + have hbefore : StoredNodes a.U a.N a.table ns h.before := by + apply hs.congr + · intro p _ hp + exact h.prefixMemory p (by omega) + · intro i u hu + exact h.prefixMemory _ (by have := (hbounds i).2; omega) + have hnew : (Block.mk (recordLayout x h.M).packed (4 * a.N)).Realizes h.before + (fun i => Signed.encode ((e.task.node Q Λ).oneArray a.U i.val)) := by + intro i + have hi : i.val < 4 * x.regs groups := by simpa only [hN] using i.isLt + have hp := h.packed ⟨i.val, hi⟩ + change y.mem ((recordLayout x h.M).packed + i.val) = _ at hp + rw [h.memory, Function.update_of_ne (show + (recordLayout x h.M).packed + i.val ≠ x.regs outputs + x.regs outputCount by + rw [ht, hcount]; omega)] at hp + simpa only [Block.read, hU] using hp + rw [h.memory, ht, hcount] + exact hbefore.append hnew (by intro i; have := (hbounds i).1; omega) (by omega) + +/-- For a live entry, the actual heavy children and emitted node are the exact finite step. -/ +theorem Emission.math_step {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (es : List Entry) (hf : e.fuel ≠ 0) + (hn : ∀ j, (e.data j).length ≠ 0) : + Math.step Q Λ e.task (es.map Entry.task) = + ((h.children ++ es).map Entry.task, some (e.task.node Q Λ)) := by + have hempty : ¬e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + intro he + rcases he with he | he | he + · exact hn 0 (by rw [he]; rfl) + · exact hn 1 (by rw [he]; rfl) + · exact hn 2 (by rw [he]; rfl) + rw [List.map_append, h.children_tasks] + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => + simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte, + Nat.add_sub_cancel] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean new file mode 100644 index 000000000..174fb9c3b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean @@ -0,0 +1,93 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram + +/-! +# Charged native execution of the Chan–He traversal controller + +The zero-fuel case executes setup, a complete record push, a complete record pop, both loop +tests, and the skip branch. It preserves the accumulated output count, heap and memory outside +the reserved stack. It does not silently replace a negative 3SUM instance by an empty output. + +## Main results + +* `oneRoot_zero`: exact 173-instruction execution of the complete controller at zero fuel. + +The controller composition proofs are original. General traversal correctness and the global +preprocessing composition remain part of the ongoing native construction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Even zero fuel executes all native setup and stack work. No node is emitted, and the root +record is recovered exactly. Existing output arrays and all non-stack memory are preserved. -/ +theorem oneRoot_zero {w capacity : ℕ} (x : Data 128) (hx : x.Bounded w) + (h2 : Fits w 2) (hcap : Fits w (x.regs stackBase + capacity)) + (hcapacity : 7 ≤ capacity) (hf : x.regs (slot 0) = 0) : + ∃ y, Exec w oneRoot x y 173 ∧ + Stack.Rep w (x.regs stackBase) capacity [] (y.comap registers) ∧ + (∀ i, y.regs (slot i) = x.regs (slot i)) ∧ + y.regs outputCount = x.regs outputCount ∧ y.regs heap = x.regs heap ∧ + Modifies (Stack.footprint (x.regs stackBase) capacity) x.mem y.mem := by + let a := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have ha : Exec (B := .standard) w + (.seq (.op (.const zero 0)) (.seq (.op (.const one 1)) + (.seq (.op (.copy (registers Stack.ptr) stackBase)) + (.op (.const (registers Stack.len) 0))))) x a 4 := by + ram_eval [a, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have haslots (i : Fin 7) : a.regs (slot i) = x.regs (slot i) := by + fin_cases i <;> simp +decide [a, slot, registers, Stack.ptr, Stack.len, zero, one] + have har : Stack.Rep w (x.regs stackBase) capacity [] (a.comap registers) := by + refine ⟨⟨fun i => (ha.bounded hx).1 (registers i), (ha.bounded hx).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a [] h2 hcap har + (fun i => (ha.bounded hx).1 (slot i)) (by simpa using hcapacity) + obtain ⟨c, hc, hrc, hcs, hfc, hpc⟩ := popFrame_exec b [] (fun i => a.regs (slot i)) h2 hcap hrb + have hbzero : b.regs zero = 0 := by + rw [hpb zero (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + have hczero : c.regs zero = 0 := by + rw [hpc zero (Or.inl (by decide)) (by decide), hbzero] + have hcf : c.regs (slot 0) = 0 := (hcs 0).trans ((haslots 0).trans hf) + have hblen : b.regs (registers Stack.len) = 7 := by + simpa only [Data.comap_regs, List.length_append, List.length_cons, List.length_nil, + Nat.reduceAdd] using hrb.length_eq + have hclen : c.regs (registers Stack.len) = 0 := hrc.length_eq + have hcopy : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) c c 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := c) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + have hbody : Exec w body b c 101 := by + exact hc.seq (Exec.branch_pos (by change c.regs (slot 0) = c.regs zero; omega) hcopy) + have hl : Exec w loop b c 105 := by + apply Exec.loop_once + · change b.regs zero < b.regs (registers Stack.len) + omega + · exact hbody + · change ¬c.regs zero < c.regs (registers Stack.len) + omega + refine ⟨c, ha.seq (hb.seq hl), hrc, fun i => (hcs i).trans (haslots i), ?_, ?_, ?_⟩ + · rw [hpc outputCount (Or.inl (by decide)) (by decide), + hpb outputCount (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, outputCount, zero, one] + · rw [hpc heap (Or.inl (by decide)) (by decide), hpb heap (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, heap, zero, one] + · intro p hp + exact (hfc p hp).trans (hfb p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean new file mode 100644 index 000000000..24503ab27 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Mathlib.Data.List.Nodup + +/-! +# Semantic entries of the native Chan–He work stack + +An entry pairs the seven unsigned stack words with the three signed integer lists stored at +their bases. Memory readback reuses `Block.Realizes`; set contents use `List.toFinset`; and the +mathematical task is the existing Chan–He recursion. This is a proof invariant for native code, +not an additional machine or execution semantics. + +## Main definitions + +* `Entry`: fuel, three native bases and their represented integer lists. +* `Entry.words`, `Entry.task`: native serialization and the existing mathematical task. +* `Entry.Stored`, `Entry.Loaded`: block readback and the current record's register ABI. + +## Main results + +* `Entry.image_read`: the injective-array API represents exactly the listed finite set. +* `Entry.read_injOn`: duplicate-free lists meet the actual modulus/node call premise. +* `Entry.Stored.input`: exact signed-encoded native array readback. +* `Entry.loaded_words`: the seven register words match the pending entry's serialization. + +The native worklist invariants and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One pending native record and the integer arrays represented by its pointers and lengths. -/ +structure Entry where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three data bases. -/ + source : Fin 3 → ℕ + /-- The lists stored at those bases, in their actual enumeration order. -/ + data : Fin 3 → List ℤ + +/-- Seven unsigned stack words in the exact native pop order. -/ +def Entry.words (e : Entry) : List ℕ := + [e.fuel, e.source 0, (e.data 0).length, e.source 1, (e.data 1).length, + e.source 2, (e.data 2).length] + +/-- The unchanged finite recursion task represented by an entry. -/ +def Entry.task (e : Entry) : Math.Task := ⟨e.fuel, fun j => (e.data j).toFinset⟩ + +/-- Total extension of the compact list for the existing native array APIs. -/ +def Entry.read (e : Entry) (j : Fin 3) (i : ℕ) : ℤ := (e.data j).getD i 0 + +/-- Each signed-encoded list occupies its concrete block, using the generic memory API. -/ +def Entry.Stored (e : Entry) (mem : ℕ → ℕ) : Prop := + ∀ j, (Block.mk (e.source j) (e.data j).length).Realizes mem + (fun i => Signed.encode (e.data j)[i.val]) + +/-- The current seven record registers contain this entry's actual unsigned metadata. -/ +def Entry.Loaded (e : Entry) (x : Data 128) : Prop := + x.regs (slot 0) = e.fuel ∧ + (∀ j, x.regs (arraySource j) = e.source j) ∧ + ∀ j, x.regs (arrayLength j) = (e.data j).length + +/-- Replace exactly one column by its actual compact heavy list and decrease the fuel. -/ +def Entry.child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : Entry := + ⟨e.fuel - 1, Function.update e.source j base, Function.update e.data j zs⟩ + +/-- A pending entry always occupies seven physical stack cells, including empty arrays. -/ +theorem Entry.length_words (e : Entry) : e.words.length = 7 := rfl + +/-- Inside its valid range the total read function is the actual list element. -/ +theorem Entry.read_eq (e : Entry) (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + e.read j i = (e.data j)[i] := by + simp only [Entry.read, List.getD, List.getElem?_eq_getElem hi, Option.getD_some] + +/-- The finite-set input of each existing array kernel is exactly the entry's listed set. -/ +theorem Entry.image_read (e : Entry) (j : Fin 3) : + (Finset.range (e.data j).length).image (e.read j) = (e.data j).toFinset := by + ext z + constructor + · intro hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp hz + have hi' := Finset.mem_range.mp hi + rw [e.read_eq j i hi', List.mem_toFinset] + exact List.getElem_mem hi' + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp (List.mem_toFinset.mp hz) + exact Finset.mem_image.mpr ⟨i, Finset.mem_range.mpr hi, (e.read_eq j i hi).trans he⟩ + +/-- A duplicate-free compact list meets the actual native collision-counting premise. -/ +theorem Entry.read_injOn (e : Entry) (j : Fin 3) (h : (e.data j).Nodup) : + Set.InjOn (e.read j) (↑(Finset.range (e.data j).length) : Set ℕ) := by + intro i hi k hk he + have hi' := Finset.mem_range.mp hi + have hk' := Finset.mem_range.mp hk + rw [e.read_eq j i hi', e.read_eq j k hk'] at he + exact h.getElem_inj_iff.mp he + +/-- Exact readback in the native array-call format, derived from the generic block realization. -/ +theorem Entry.Stored.input {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + mem (e.source j + i) = Signed.encode (e.read j i) := by + rw [e.read_eq j i hi] + exact h j ⟨i, hi⟩ + +/-- Readback transfers along a memory equality on the three live blocks. -/ +theorem Entry.Stored.congr {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + (hf : ∀ j i, i < (e.data j).length → after (e.source j + i) = before (e.source j + i)) : + e.Stored after := by + intro j i + exact (hf j i.val i.isLt).trans (h j i) + +/-- A prefix frame preserves every pending list whose block ends before the fresh allocation. -/ +theorem Entry.Stored.of_prefix {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + {frontier : ℕ} (hsrc : ∀ j, e.source j + (e.data j).length ≤ frontier) + (hf : ∀ a < frontier, after a = before a) : e.Stored after := by + apply h.congr + intro j i hi + exact hf _ (by have := hsrc j; omega) + +/-- The seven actual register words match the exact work-stack serialization. -/ +theorem Entry.loaded_words {e : Entry} {x : Data 128} (h : e.Loaded x) : + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] = e.words := by + rcases h with ⟨hf, hs, hn⟩ + change [x.regs (slot 0), x.regs (arraySource 0), x.regs (arrayLength 0), + x.regs (arraySource 1), x.regs (arrayLength 1), + x.regs (arraySource 2), x.regs (arrayLength 2)] = _ + simp only [hf, hs, hn, Entry.words] + +/-- Replacing a native column changes exactly the corresponding mathematical set. -/ +theorem Entry.task_child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : + (e.child j base zs).task = + ⟨e.fuel - 1, Function.update e.task.sets j zs.toFinset⟩ := by + unfold Entry.child Entry.task + congr 1 + funext k + by_cases he : k = j + · subst k + simp only [Function.update_self] + · simp only [Function.update_of_ne he] + +/-- The native child-record words represent exactly the changed data block, with the two +other columns and their original enumeration orders preserved. -/ +theorem Entry.child_words {e : Entry} {x : Data 128} (h : e.Loaded x) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hb : ∀ j, x.regs (heavyBase j) = bases j) + (hn : ∀ j, x.regs (heavyLength j) = (zs j).length) (j : Fin 3) : + childWords x j = (e.child j (bases j) (zs j)).words := by + obtain ⟨hf, hs, hl⟩ := h + have hs₀ : x.regs (slot 1) = e.source 0 := hs 0 + have hs₁ : x.regs (slot 3) = e.source 1 := hs 1 + have hs₂ : x.regs (slot 5) = e.source 2 := hs 2 + have hl₀ : x.regs (slot 2) = (e.data 0).length := hl 0 + have hl₁ : x.regs (slot 4) = (e.data 1).length := hl 1 + have hl₂ : x.regs (slot 6) = (e.data 2).length := hl 2 + fin_cases j <;> simp +decide [childWords, Entry.child, Entry.words, hf, + hs₀, hs₁, hs₂, hl₀, hl₁, hl₂, hb, hn] + +/-- The size and magnitude assumptions are the existing finite three-set admissibility notion; +duplicate-free enumeration is the additional native-array obligation. -/ +def Entry.Valid (e : Entry) (n U : ℕ) : Prop := + (∀ j, (e.data j).Nodup) ∧ Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset + +/-- Native input lengths equal finite-set cardinalities under the duplicate-free obligation. -/ +theorem Entry.Valid.length_le {e : Entry} {n U : ℕ} (h : e.Valid n U) (j : Fin 3) : + (e.data j).length ≤ n := by + have he := List.toFinset_card_of_nodup (h.1 j) + rw [← he] + fin_cases j + · exact h.2.card₁ + · exact h.2.card₂ + · exact h.2.card₃ + +/-- The existing finite-set magnitude promise bounds every actual native array element. -/ +theorem Entry.Valid.natAbs_le {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : (e.read j i).natAbs ≤ U := by + have hz : e.read j i ∈ (e.data j).toFinset := by + rw [e.read_eq j i hi, List.mem_toFinset] + exact List.getElem_mem hi + have hb : |e.read j i| ≤ (U : ℤ) := by + fin_cases j + · exact h.2.bdd₁ _ hz + · exact h.2.bdd₂ _ hz + · exact h.2.bdd₃ _ hz + rw [← Int.natCast_natAbs] at hb + exact_mod_cast hb + +/-- Replacing a column by a duplicate-free subset preserves the existing size/magnitude promises. -/ +theorem Entry.Valid.child {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (base : ℕ) (zs : List ℤ) (hnd : zs.Nodup) + (hsub : zs.toFinset ⊆ (e.data j).toFinset) : (e.child j base zs).Valid n U := by + refine ⟨?_, ?_⟩ + · intro k + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using hnd + · simpa only [Entry.child, Function.update_of_ne he] using h.1 k + · fin_cases j + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U zs.toFinset + (e.data 1).toFinset (e.data 2).toFinset + exact ⟨(Finset.card_le_card hsub).trans h.2.card₁, h.2.card₂, h.2.card₃, + h.2.bdd₁.mono hsub, h.2.bdd₂, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + zs.toFinset (e.data 2).toFinset + exact ⟨h.2.card₁, (Finset.card_le_card hsub).trans h.2.card₂, h.2.card₃, + h.2.bdd₁, h.2.bdd₂.mono hsub, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + (e.data 1).toFinset zs.toFinset + exact ⟨h.2.card₁, h.2.card₂, (Finset.card_le_card hsub).trans h.2.card₃, + h.2.bdd₁, h.2.bdd₂, h.2.bdd₃.mono hsub⟩ + +/-- A child reuses both untouched data blocks and the actual newly written heavy block. -/ +theorem Entry.Stored.child {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (base : ℕ) (zs : List ℤ) + (hz : (Block.mk base zs.length).Realizes mem (fun i => Signed.encode zs[i.val])) : + (e.child j base zs).Stored mem := by + intro k i + by_cases he : k = j + · subst k + have hi : i.val < zs.length := by + simpa only [Entry.child, Function.update_self] using i.isLt + simpa only [Entry.child, Block.read, Function.update_self] using hz ⟨i.val, hi⟩ + · have hi : i.val < (e.data k).length := by + simpa only [Entry.child, Function.update_of_ne he] using i.isLt + simpa only [Entry.child, Block.read, Function.update_of_ne he] using h k ⟨i.val, hi⟩ + +/-- Actual heavy lists and bases give exactly the existing ordered three-child worklist. -/ +theorem Entry.task_children (e : Entry) (Q : Finset ℕ) (Λ : ℕ) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hz : ∀ j, (zs j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy + (e.task.sets j) (e.task.node Q Λ).M) : + [(e.child 0 (bases 0) (zs 0)).task, (e.child 1 (bases 1) (zs 1)).task, + (e.child 2 (bases 2) (zs 2)).task] = e.task.children Q Λ (e.fuel - 1) := by + let M := (e.task.node Q Λ).M + have h₀ : Function.update e.task.sets 0 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M) = + ![Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M, + e.task.sets 1, e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₁ : Function.update e.task.sets 1 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M) = + ![e.task.sets 0, Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M, + e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₂ : Function.update e.task.sets 2 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M) = + ![e.task.sets 0, e.task.sets 1, + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M] := by + funext j + fin_cases j <;> rfl + rw [Entry.task_child, Entry.task_child, Entry.task_child, hz 0, hz 1, hz 2, h₀, h₁, h₂] + rfl + +/-- The native word-stack length records exactly seven cells per pending entry. -/ +theorem length_serialized (es : List Entry) : (es.flatMap Entry.words).length = 7 * es.length := by + induction es with + | nil => rfl + | cons e es ih => + simp only [List.flatMap_cons, List.length_append, Entry.length_words, ih, List.length_cons] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean new file mode 100644 index 000000000..eb0a8b0a1 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean @@ -0,0 +1,181 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalOutput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalResources +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Concrete worklist invariant for the native Chan–He traversal + +The invariant connects the reserved native stack, existing finite recursion, retained arrays, +output-pointer table and fresh heap. Remaining work pays for both future output cells and future +allocations. In particular, allocation bounds decrease with the mathematical work measure rather +than granting an unbounded heap to each kernel call. + +## Main definitions + +* `Arena`: fixed caller parameters and disjoint stack/table/heap reservations. +* `Cursor`: represented pending entries and the already emitted native pointers and nodes. +* `Cursor.Invariant`: readback, frames, exact remaining output and concrete space reservations. + +These proof invariants are original and do not add a machine model or operational oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Fixed parameters and concrete memory reservations of a complete native root traversal. -/ +structure Arena where + /-- Maximum column cardinality. -/ + n : ℕ + /-- Candidate-prime bound. -/ + m : ℕ + /-- Label-bit parameter. -/ + Λ : ℕ + /-- Node input magnitude. -/ + U : ℕ + /-- Common number of four-cell packed groups. -/ + N : ℕ + /-- Root recursion fuel. -/ + fuel : ℕ + /-- Prime bitmap base. -/ + primes : ℕ + /-- Reserved native word-stack base. -/ + stack : ℕ + /-- Reserved native word-stack capacity. -/ + capacity : ℕ + /-- Unsigned output-pointer table base. -/ + table : ℕ + /-- Reserved output-pointer table capacity. -/ + outputCapacity : ℕ + /-- First address permitted for allocated heavy and packed arrays. -/ + floor : ℕ + /-- Final upper bound on the fresh heap pointer. -/ + ceiling : ℕ + /-- Protected original memory below the stack, including the complete frontend forest. -/ + retained : ℕ → ℕ + +/-- Persistent caller registers, including actual zero and one used by native branches. -/ +def Arena.Controls (a : Arena) (x : Data 128) : Prop := + x.regs limit = a.m ∧ x.regs digits = a.Λ ∧ x.regs magnitude = a.U ∧ + x.regs groups = a.N ∧ x.regs bitmap = a.primes ∧ + x.regs primeCount = (Nat.primesLE a.m).card ∧ x.regs outputs = a.table ∧ + x.regs stackBase = a.stack ∧ x.regs stackCapacity = a.capacity ∧ + x.regs zero = 0 ∧ x.regs one = 1 + +/-- Exact register preservation transfers all persistent caller parameters. The heap and +accumulated count are deliberately absent from this fixed register set. -/ +theorem Arena.Controls.congr {a : Arena} {x y : Data 128} (h : a.Controls x) + (hp : ∀ i : Fin 128, i.val ∈ [71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 83] → + y.regs i = x.regs i) : a.Controls y := by + rcases h with ⟨hm, hΛ, hU, hN, hb, hc, ht, hs, hcap, hz, ho⟩ + exact ⟨(hp _ (by decide)).trans hm, (hp _ (by decide)).trans hΛ, + (hp _ (by decide)).trans hU, (hp _ (by decide)).trans hN, + (hp _ (by decide)).trans hb, (hp _ (by decide)).trans hc, + (hp _ (by decide)).trans ht, (hp _ (by decide)).trans hs, + (hp _ (by decide)).trans hcap, (hp _ (by decide)).trans hz, + (hp _ (by decide)).trans ho⟩ + +/-- Concrete non-overlap and capacity obligations for the reserved native layout. -/ +def Arena.Separated (a : Arena) : Prop := + a.primes + a.m < a.stack ∧ a.stack + a.capacity ≤ a.table ∧ + a.table + a.outputCapacity ≤ a.floor ∧ 7 * (2 * a.fuel + 1) ≤ a.capacity + +/-- A single signed envelope covers both actual native kernels at every worklist transition. -/ +def Arena.Ready (a : Arena) (w : ℕ) : Prop := + 2 ≤ a.m ∧ 2 * a.m ^ 2 ≤ a.N ∧ + GenericRAM.Signed.Fits w ((envelope a.ceiling a.n a.m a.Λ a.U a.N : ℕ) : ℤ) + +/-- The actual native bitmap is retained below the reserved stack. -/ +def Arena.Bitmap (a : Arena) (mem : ℕ → ℕ) : Prop := + ∀ q ≤ a.m, mem (a.primes + q) = if q.Prime then 1 else 0 + +/-- Proof state of the actual stack loop; no part of this structure is evaluated by the program. -/ +structure Cursor where + /-- Top-first pending entries. -/ + pending : List Entry + /-- Packed base pointers and their existing mathematical nodes in emitted order. -/ + emitted : List (ℕ × Node) + +/-- Remaining transitions include empty entries and zero-fuel leaves. -/ +def Cursor.work (s : Cursor) : ℕ := Math.potential (s.pending.map Entry.task) + +/-- Full readback and allocation invariant at the actual native loop header. -/ +structure Cursor.Invariant (a : Arena) (w : ℕ) (target : List Node) + (s : Cursor) (x : Data 128) : Prop where + /-- All actual native words fit. -/ + bounded : x.Bounded (w + 2) + /-- Persistent caller parameters have not changed. -/ + controls : a.Controls x + /-- The actual bitmap still represents all candidate primes. -/ + bitmap : a.Bitmap x.mem + /-- The actual word stack represents exactly the pending entries. -/ + stack : Stack.Rep (w + 2) a.stack a.capacity + (s.pending.flatMap Entry.words) (x.comap registers) + /-- The existing finite depth invariant bounds pending entries. -/ + depth : Math.Bounded a.fuel (s.pending.map Entry.task) + /-- Every pending entry retains its duplicate-free existing admissibility promise. -/ + valid : ∀ e ∈ s.pending, e.Valid a.n a.U + /-- Every pending array has exact signed-encoded native readback. -/ + stored : ∀ e ∈ s.pending, e.Stored x.mem + /-- Pending arrays avoid the entire reserved stack and output-table gap. -/ + around : ∀ e ∈ s.pending, e.Around a.stack (a.floor - a.stack) + /-- Every pending array lies below the current fresh allocation. -/ + ends : ∀ e ∈ s.pending, ∀ j, e.source j + (e.data j).length ≤ x.regs heap + /-- The fresh heap never enters the reserved table or stack. -/ + floor : a.floor ≤ x.regs heap + /-- Remaining work reserves enough space for every later allocation. -/ + heapBudget : x.regs heap + s.work * allocationSize a.n a.m a.N ≤ a.ceiling + /-- The accumulated native count equals the actual emitted sequence length. -/ + count : x.regs outputCount = s.emitted.length + /-- Remaining work reserves enough output-table cells. -/ + tableBudget : s.emitted.length + s.work ≤ a.outputCapacity + /-- Every emitted pointer and every packed cell has exact existing-node readback. -/ + output : StoredNodes a.U a.N a.table s.emitted x.mem + /-- Retained packed arrays lie above the reserved output table and below the fresh heap. -/ + outputBounds : ∀ i : Fin s.emitted.length, + a.floor ≤ s.emitted[i.val].1 ∧ s.emitted[i.val].1 + 4 * a.N ≤ x.regs heap + /-- The emitted prefix followed by the exact pending recursion is the target sequence. -/ + exactOutput : s.emitted.map Prod.snd ++ + Math.output (Nat.primesLE a.m) a.Λ (s.pending.map Entry.task) = target + /-- Every original cell below the reserved stack remains available to subsequent roots. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- A live loop has at least one pending entry, since each entry occupies seven actual words. -/ +theorem Cursor.Invariant.nonempty {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : (Cond.lt zero (registers Stack.len)).Holds x) : s.pending ≠ [] := by + intro he + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + change x.regs zero < x.regs (registers Stack.len) at hc + simp only [he, List.flatMap_nil, List.length_nil, Data.comap_regs] at hl + omega + +/-- A finished actual loop has emitted the complete target, with every array already read back. -/ +theorem Cursor.Invariant.finished {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : ¬(Cond.lt zero (registers Stack.len)).Holds x) : + s.pending = [] ∧ s.emitted.map Prod.snd = target := by + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + rw [length_serialized] at hl + change ¬x.regs zero < x.regs (registers Stack.len) at hc + change x.regs (registers Stack.len) = 7 * s.pending.length at hl + have he : s.pending = [] := List.eq_nil_of_length_eq_zero (by omega) + refine ⟨he, ?_⟩ + simpa only [he, List.map_nil, Math.output, List.flatMap_nil, List.append_nil] using h.exactOutput + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean new file mode 100644 index 000000000..efed94f72 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean @@ -0,0 +1,259 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalAdmitted +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete live transitions of the native Chan–He worklist + +One transition executes the seven pops, both complete modulus searches, all three actual +histogram/packing calls, output delivery and the three reversed child pushes. Its invariant +preserves exact existing-node output, live arrays, the actual prime bitmap, and the space +reservations paid for by the strictly decreasing work measure. + +## Main results + +* `live_transition`: complete actual native body execution and full invariant preservation. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Execute and verify the whole live native worklist transition. -/ +theorem live_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) : + ∃ (s : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s.Invariant a w target y ∧ s.work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨p, hp, hstack, hl, hframe, hpr⟩ := pop_entry x e es h2 hcap h.stack + have preg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + p.regs i = x.regs i := hpr i (Or.inl hi') (Or.inr hi) + have ph : p.regs heap = x.regs heap := preg _ (by decide) (by decide) + have pc : p.regs outputCount = ns.length := (preg _ (by decide) (by decide)).trans h.count + have hc : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact preg i (by omega) (by omega)) + have frame (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : p.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hh := ha.2.2.1 + omega + have oldStored (d : Entry) (hd : d ∈ e :: es) : d.Stored p.mem := + (h.stored d hd).of_stack_frame ((h.around d hd).mono_capacity gap) hframe + have oldEnds (d : Entry) (hd : d ∈ e :: es) (j : Fin 3) : + d.source j + (d.data j).length ≤ p.regs heap := by rw [ph]; exact h.ends d hd j + have oldBitmap : a.Bitmap p.mem := by + intro q hq + rw [frame _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + have oldOutput : StoredNodes a.U a.N a.table ns p.mem := by + apply h.output.congr + · intro q hq _ + exact frame _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact frame _ (Or.inr (by + have hh := (h.outputBounds i).1 + have ht := ha.2.1 + have hh' := ha.2.2.1 + omega)) + have oldBounds (i : Fin ns.length) : + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ p.regs heap := by + rw [ph] + exact h.outputBounds i + have hwork : 1 ≤ (Cursor.mk (e :: es) ns).work := by + have hw := Math.work_pos e.fuel + change 1 ≤ Math.work e.fuel + Math.potential (es.map Entry.task) + omega + have htable : a.table + ns.length < a.floor := by + have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + have hh := ha.2.2.1 + omega + have hfloor : a.floor ≤ p.regs heap := by rw [ph]; exact h.floor + have hheap : p.regs heap ≤ a.ceiling := by + rw [ph] + have hb := h.heapBudget + omega + have htablep : p.regs outputs + p.regs outputCount < a.floor := by + rw [hc.2.2.2.2.2.2.1, pc] + exact htable + obtain ⟨v, hv, ⟨em⟩⟩ := emit_admitted p e ha hw hc hl (h.valid e (by simp)) + (oldStored e (by simp)) oldBitmap hfloor hheap (oldEnds e (by simp)) (by omega) + let s : Cursor := ⟨em.children ++ es, ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)]⟩ + have mathStep := em.math_step es hf hn + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at hdec + change s.work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + have depth : Math.Bounded a.fuel (s.pending.map Entry.task) := by + have hd := Math.bounded_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) h.depth + rw [mathStep] at hd + exact hd + have capacity : 21 + (es.flatMap Entry.words).length ≤ a.capacity := by + have hd := depth.length_le + rw [List.length_map] at hd + change (em.children ++ es).length ≤ 2 * a.fuel + 1 at hd + simp only [Emission.children, List.length_append, List.length_cons, List.length_nil] at hd + rw [length_serialized] + have hh := ha.2.2.2 + omega + have vcontrols := em.controls hc + have vstack := em.stack ha hc (hv.bounded (hp.bounded h.bounded)) + (es.flatMap Entry.words) hstack (by omega) + obtain ⟨y, hy, hystack, hyframe, hyr⟩ := pushChildren_exec v (es.flatMap Entry.words) + (hv.bounded (hp.bounded h.bounded)) vcontrols.2.2.2.2.2.2.2.2.2.2 h2 hcap vstack capacity + have yreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = v.regs i := hyr i (Or.inl ⟨hi, hi'⟩) + have yframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : y.mem q = v.mem q := by + apply hyframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hbody := body_live hp hl hc.2.2.2.2.2.2.2.2.2.1 hf hn hv hy + have vsStored := em.stored ha hc (oldStored e (by simp)) (h.around e (by simp)) + (oldEnds e (by simp)) htablep + have newStored : ∀ d ∈ s.pending, d.Stored v.mem := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_stored vsStored d hd + · exact em.stored ha hc (oldStored d (List.mem_cons_of_mem e hd)) + (h.around d (List.mem_cons_of_mem e hd)) (oldEnds d (List.mem_cons_of_mem e hd)) htablep + have newAround : ∀ d ∈ s.pending, d.Around a.stack (a.floor - a.stack) := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_around (h.around e (by simp)) (by omega) d hd + · exact h.around d (List.mem_cons_of_mem e hd) + have newEnds : ∀ d ∈ s.pending, ∀ j, d.source j + (d.data j).length ≤ v.regs heap := by + intro d hd j + rcases List.mem_append.mp hd with hd | hd + · exact em.children_ends hl (oldEnds e (by simp)) d hd j + · exact (oldEnds d (List.mem_cons_of_mem e hd) j).trans em.heap_mono + have houtput := em.output hc ns oldOutput pc oldBounds htable hfloor + have yh : y.regs heap = v.regs heap := yreg _ (by decide) (by decide) + have heapAllocation : v.regs heap ≤ p.regs heap + allocationSize a.n a.m a.N := by + rw [em.heap_eq] + apply allocation_le + · intro j + rw [hl.2.2] + exact (h.valid e (by simp)).length_le j + · exact hc.2.2.2.1.le + · simpa only [hc.1] using em.upper + refine ⟨s, y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, vcontrols.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact yreg i (by omega) (by omega)), ?_, ?_, depth, ?_, ?_, newAround, ?_, ?_, ?_, ?_, + ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [yframe _ (Or.inl (by have := ha.1; omega)), + em.prefix_preserved _ (by have := ha.1; have := ha.2.1; omega) (by + have ht := hc.2.2.2.2.2.2.1 + have hq' := ha.1 + have hh := ha.2.1 + omega)] + exact oldBitmap q hq + · simpa only [s, em.children_words hl, List.flatMap_append, List.append_assoc] using hystack + · intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_valid (h.valid e (by simp)) d hd + · exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (newStored d hd).of_stack_frame ((newAround d hd).mono_capacity gap) hyframe + · intro d hd j + rw [yh] + exact newEnds d hd j + · rw [yh] + exact hfloor.trans em.heap_mono + · rw [yh] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) hdec + rw [Nat.add_mul, Nat.one_mul] at hd + rw [ph] at heapAllocation + omega + · rw [yreg outputCount (by decide) (by decide), em.count, pc] + simp only [s, List.length_append, List.length_singleton] + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change (ns ++ [_]).length + s.work ≤ a.outputCapacity + simp only [List.length_append, List.length_singleton] + omega + · apply houtput.congr + · intro q hq _ + exact yframe _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + have low : a.floor ≤ s.emitted[i.val].1 := by + by_cases hi : i.val < ns.length + · simpa only [s, List.getElem_append_left hi] using (oldBounds ⟨i.val, hi⟩).1 + · have hi' := i.isLt + have he : i.val = ns.length := by simp only + [List.length_append, List.length_singleton] at hi'; omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 + simp only [he, hg] + dsimp only [NodePacking.Layout.packed, recordLayout] + omega + change a.floor ≤ (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[i.val].1 at low + exact yframe _ (Or.inr (by have ht := ha.2.1; have hh := ha.2.2.1; omega)) + · intro i + rw [yh, em.heap_eq] + by_cases hi : i.val < ns.length + · simp only [s, List.getElem_append_left hi] + exact ⟨(oldBounds ⟨i.val, hi⟩).1, + (oldBounds ⟨i.val, hi⟩).2.trans (em.heap_eq ▸ em.heap_mono)⟩ + · have hi' := i.isLt + have he : i.val = ns.length := by + dsimp only [s] at hi' + simp only [List.length_append, List.length_singleton] at hi' + omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 ∧ (ns ++ [_])[i.val].1 + 4 * a.N ≤ _ + simp only [he, hg] + constructor + · dsimp only [NodePacking.Layout.packed, recordLayout] + omega + · dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, + NodePacking.Layout.scratch, recordLayout] + have hN := hc.2.2.2.1 + omega + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at he + simpa only [s, List.map_append, List.map_singleton, List.map_cons, Option.toList_some, + List.singleton_append, List.append_assoc, List.map_nil, List.nil_append, he] + using h.exactOutput + · intro q hq + rw [yframe q (Or.inl hq), em.prefix_preserved q (by + have ht := ha.2.1 + have hf' := ha.2.2.1 + omega) (by + have ht := ha.2.1 + have hp' := hc.2.2.2.2.2.2.1 + omega), frame q (Or.inl hq)] + exact h.protectedMemory q hq + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean new file mode 100644 index 000000000..12a14f372 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLive +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalSkip +public import Complexity.Computability.WordRAM.GhostLoop + +/-! +# Complete bounded native Chan–He worklist loop + +Each body execution is discharged by the actual pop, modulus, histogram, packing and push +programs. The explicit proof-state loop rule then counts every pending entry, including skipped +calls. Termination returns the exact finite preorder, whose native pointer table and packed +arrays have full readback. The arena bounds cover all live memory throughout the loop. + +## Main results + +* `transition`: actual native body execution and invariant preservation for every pending entry. +* `loop_exec`: the complete actual native loop terminates with exact finite node output. + +These native composition and resource proofs are original. Initialized-input preprocessing +composition is a separate obligation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Every actual live loop header admits a native body execution with the same finite program. -/ +theorem transition {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) (hc : (Cond.lt zero (registers Stack.len)).Holds x) : + ∃ (s' : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s'.Invariant a w target y ∧ s'.work < s.work := by + rcases s with ⟨pending, ns⟩ + cases pending with + | nil => exact (h.nonempty hc rfl).elim + | cons e es => + by_cases hs : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0 + · obtain ⟨y, hy, hi, hd⟩ := skip_transition e es ns x ha h2 hcap h hs + exact ⟨⟨es, ns⟩, y, hy.mono (by omega), hi, hd⟩ + · apply live_transition e es ns x ha hw h2 hcap h + · intro he + exact hs (Or.inl he) + · intro j + fin_cases j + · exact fun he => hs (Or.inr (Or.inl he)) + · exact fun he => hs (Or.inr (Or.inr (Or.inl he))) + · exact fun he => hs (Or.inr (Or.inr (Or.inr he))) + +/-- Execute the complete actual native loop. Every call and fit is discharged by `transition`; +the final emitted list equals the entire target, with exact pointer and packed-array readback. -/ +theorem loop_exec {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) : + ∃ (ns : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) loop x y (s.work * (visitTime a.n a.m a.N + 316) + 2) ∧ + (Cursor.mk [] ns).Invariant a w target y ∧ ns.map Prod.snd = target := by + obtain ⟨s', y, he, hi, hexit⟩ := ExecWithin.loop_invariant_state + (fun s x => s.Invariant a w target x) Cursor.work + (transition ha hw h2 hcap) h + obtain ⟨hp, hout⟩ := hi.finished hexit + refine ⟨s'.emitted, y, ?_, ?_, hout⟩ + · simpa only [loop, Nat.add_assoc, Nat.reduceAdd] using he + · cases s' with + | mk pending ns => + change pending = [] at hp + subst pending + exact hi + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean new file mode 100644 index 000000000..3281b4adb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean @@ -0,0 +1,169 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Recursion + +/-! +# A bounded worklist for the exact Chan–He recursion + +The worklist expands the existing `ChanHe.nodes` in its existing preorder. Empty triples and +zero fuel are popped without emitting an instance. Three children replace one set each by its +existing heavy subset, in the order used by the finite reduction. + +This is the mathematical invariant for the native traversal, not a native execution contract. +In particular, expanding a worklist entry here does not discharge a native modulus or node call. +The stack bound counts pending entries, including empty and zero-fuel entries. Those entries +still require charged native pop and branch instructions. + +## Main definitions + +* `Task`: one pending three-set call, including its remaining fuel. +* `step`: one exact worklist transition and its optional emitted node. +* `potential`: a natural-valued bound on all remaining transitions, including skipped entries. +* `Bounded`: a stack-depth invariant giving at most `2f+1` pending entries for root fuel `f`. + +## Main results + +* `output_step`: one transition preserves the exact preorder output. +* `potential_step`: every transition strictly decreases the charged-transition bound. +* `bounded_step`, `Bounded.length_le`: the explicit stack has linear capacity in root fuel. + +These worklist invariants are original. They reuse the finite recursion of [ChanHe2020]. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +namespace Math + +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- One pending call of the existing finite recursion. -/ +structure Task where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three mathematical sets represented by the native descriptor. -/ + sets : Fin 3 → Finset ℤ + +/-- The exact existing preorder associated with one pending call. -/ +def Task.output (Q : Finset ℕ) (Λ : ℕ) (t : Task) : List Node := + nodes Q Λ t.fuel (t.sets 0) (t.sets 1) (t.sets 2) + +/-- The exact existing modulus and three sets of a nonempty call. -/ +def Task.node (Q : Finset ℕ) (Λ : ℕ) (t : Task) : Node := + ⟨t.sets 0, t.sets 1, t.sets 2, modulus Q Λ (t.sets 0) (t.sets 1) (t.sets 2)⟩ + +/-- Whether a call is skipped because one of its sets is empty. -/ +abbrev Task.empty (t : Task) : Prop := t.sets 0 = ∅ ∨ t.sets 1 = ∅ ∨ t.sets 2 = ∅ + +/-- The three children, with exactly the mathematical reduction's order and heavy sets. -/ +def Task.children (Q : Finset ℕ) (Λ f : ℕ) (t : Task) : List Task := + let M := (t.node Q Λ).M + [⟨f, ![heavy (t.sets 0) M, t.sets 1, t.sets 2]⟩, + ⟨f, ![t.sets 0, heavy (t.sets 1) M, t.sets 2]⟩, + ⟨f, ![t.sets 0, t.sets 1, heavy (t.sets 2) M]⟩] + +/-- The sequence still to be emitted from a top-first worklist. -/ +def output (Q : Finset ℕ) (Λ : ℕ) (ts : List Task) : List Node := + ts.flatMap (Task.output Q Λ) + +/-- One pop, followed either by a skip or by one emission and three children. -/ +def step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : List Task × Option Node := + match t.fuel with + | 0 => (ts, none) + | f + 1 => if t.empty then (ts, none) else (t.children Q Λ f ++ ts, some (t.node Q Λ)) + +/-- One worklist transition preserves the exact output list, including order and skipped calls. -/ +theorem output_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + output Q Λ (t :: ts) = + (step Q Λ t ts).2.toList ++ output Q Λ (step Q Λ t ts).1 := by + cases t with + | mk f S => + cases f with + | zero => simp [output, step, Task.output, nodes] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes] + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes, Task.children, Task.node, + List.append_assoc] + +/-- Number of transitions for the full ternary tree, counting its zero-fuel leaves. -/ +def work : ℕ → ℕ + | 0 => 1 + | f + 1 => 1 + 3 * work f + +/-- Every pending entry costs at least one pop/skip transition. -/ +theorem work_pos (f : ℕ) : 1 ≤ work f := by + cases f <;> simp [work] + +/-- Closed form without a truncated quotient. -/ +theorem work_eq (f : ℕ) : 2 * work f + 1 = 3 ^ (f + 1) := by + induction f with + | zero => decide + | succ f ih => + simp only [work, pow_succ] at * + omega + +/-- The sum of the transition budgets of all pending entries. -/ +def potential (ts : List Task) : ℕ := (ts.map fun t => work t.fuel).sum + +/-- One transition spends at least one unit, including the empty and zero-fuel cases. -/ +theorem potential_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + potential (step Q Λ t ts).1 + 1 ≤ potential (t :: ts) := by + cases t with + | mk f S => + cases f with + | zero => simp [step, potential, work] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [step, he, ↓reduceIte, potential, List.map_cons, List.sum_cons] + have := work_pos (f + 1) + omega + · simp only [step, he, ↓reduceIte, potential, Task.children, List.map_append, + List.map_cons, List.map_nil, List.sum_append, List.sum_cons, List.sum_nil, work] + omega + +/-- Each entry's fuel plus the pending suffix gives a bound measured from the stack bottom. -/ +def Bounded (f : ℕ) : List Task → Prop + | [] => True + | t :: ts => 2 * t.fuel + ts.length ≤ 2 * f ∧ Bounded f ts + +/-- A single root of fuel `f` satisfies the stack invariant. -/ +theorem bounded_singleton (t : Task) : Bounded t.fuel [t] := by + simp [Bounded] + +/-- The explicit stack requires at most `2f+1` entries, even with zero-fuel entries present. -/ +theorem Bounded.length_le {f : ℕ} {ts : List Task} (h : Bounded f ts) : + ts.length ≤ 2 * f + 1 := by + cases ts with + | nil => simp + | cons t ts => + simp only [Bounded] at h + simp only [List.length_cons] + omega + +/-- A native pop/skip or three-child push preserves the same fixed stack capacity. -/ +theorem bounded_step {f : ℕ} (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) + (h : Bounded f (t :: ts)) : Bounded f (step Q Λ t ts).1 := by + obtain ⟨hf, hts⟩ := h + cases ht : t.fuel with + | zero => simpa only [step, ht] using hts + | succ g => + by_cases he : t.empty + · simpa only [step, ht, he, ↓reduceIte] using hts + · simp only [step, ht, he, ↓reduceIte, Task.children, List.cons_append, + List.nil_append, Bounded, List.length_cons] at * + exact ⟨by omega, by omega, by omega, hts⟩ + +end Math + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean new file mode 100644 index 000000000..390e750a8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean @@ -0,0 +1,94 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers +public import Complexity.Computability.WordRAM.ListArray +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Exact memory output of native Chan–He traversal + +The pointer table is an unsigned word list. Each pointed-to block contains the existing +`Node.oneArray`, encoded with the signed integer encoding, at the common finite length `4N`. +This reuses the generic block/list realization API and introduces no alternative convolution +predicate or execution model. + +## Main definitions + +* `StoredNodes`: exact pointer-table and packed-array readback for the emitted preorder. + +## Main results + +* `StoredNodes.congr`: transfer exact readback along a frame on its live cells. +* `StoredNodes.append`: one actual pointer-table store appends a represented native node. + +The concrete output invariant and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- The exact finite output table and its signed-encoded packed node arrays. -/ +structure StoredNodes (U N table : ℕ) (ns : List (ℕ × Node)) (mem : ℕ → ℕ) : Prop where + /-- The table contains exactly the native base pointers in emitted order. -/ + pointers : Block.HoldsList table (ns.map Prod.fst) mem + /-- Every pointed-to word equals the existing mathematical packed array cell. -/ + cells : ∀ i : Fin ns.length, (Block.mk ns[i.val].1 (4 * N)).Realizes mem + (fun u => Signed.encode (ns[i.val].2.oneArray U u.val)) + +/-- Empty traversal output imposes no pointed-to array obligations. -/ +theorem storedNodes_nil (U N table : ℕ) (mem : ℕ → ℕ) : StoredNodes U N table [] mem := + ⟨Block.holdsList_nil table mem, fun i => Fin.elim0 i⟩ + +/-- Pointer-table and packed-array readback survive a frame on all their live cells. -/ +theorem StoredNodes.congr {U N table : ℕ} {ns : List (ℕ × Node)} {before after : ℕ → ℕ} + (h : StoredNodes U N table ns before) + (ht : ∀ a, table ≤ a → a < table + ns.length → after a = before a) + (hc : ∀ i : Fin ns.length, ∀ u < 4 * N, + after (ns[i.val].1 + u) = before (ns[i.val].1 + u)) : + StoredNodes U N table ns after := by + refine ⟨h.pointers.frame (by simpa only [List.length_map] using ht), ?_⟩ + intro i u + exact (hc i u.val u.isLt).trans (h.cells i u) + +/-- Appending one native pointer preserves all packed readback. The stored pointer lies below +both the old arrays and the fresh array, so the actual store cannot overwrite array data. -/ +theorem StoredNodes.append {U N table p : ℕ} {ν : Node} {ns : List (ℕ × Node)} + {mem : ℕ → ℕ} (h : StoredNodes U N table ns mem) + (hnew : (Block.mk p (4 * N)).Realizes mem (fun u => Signed.encode (ν.oneArray U u.val))) + (hold : ∀ i : Fin ns.length, table + ns.length < ns[i.val].1) + (hp : table + ns.length < p) : + StoredNodes U N table (ns ++ [(p, ν)]) (Function.update mem (table + ns.length) p) := by + refine ⟨?_, ?_⟩ + · simpa only [List.map_append, List.map_singleton, List.length_map] using + h.pointers.append_one p + · intro i + by_cases hi : i.val < ns.length + · rw [List.getElem_append_left hi] + apply (h.cells ⟨i.val, hi⟩).update_of_not_owns + intro ha + have hb := hold ⟨i.val, hi⟩ + exact (not_le_of_gt hb) ha.1 + · have hil := i.isLt + simp only [List.length_append, List.length_singleton] at hil + have he : i.val = ns.length := by omega + have hg : (ns ++ [(p, ν)])[ns.length] = (p, ν) := by simp + intro u + change Function.update mem (table + ns.length) p + ((ns ++ [(p, ν)])[i.val].1 + u.val) = + Signed.encode ((ns ++ [(p, ν)])[i.val].2.oneArray U u.val) + simp only [he, hg] + rw [Function.update_of_ne (show p + u.val ≠ table + ns.length by omega)] + exact hnew u + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean new file mode 100644 index 000000000..0b56da19f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean @@ -0,0 +1,98 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames + +/-! +# Live arrays around the native traversal stack + +The word stack occupies a reserved gap between the original arrays and the fresh heap. +Pending arrays lie entirely below the gap or start above it. Actual stack writes therefore +preserve their signed readback. Popping a serialized entry restores its seven registers and +retains every pending array. + +## Main definitions + +* `Entry.Around`: an array ends below the stack or starts above its reserved end. + +## Main results + +* `Entry.Stored.of_stack_frame`: stack writes preserve live arrays. +* `pop_entry`: the actual seven-pop program restores the semantic entry. + +These memory-layout and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Every column lies entirely on one side of the reserved stack interval. -/ +def Entry.Around (e : Entry) (base capacity : ℕ) : Prop := + ∀ j, e.source j + (e.data j).length ≤ base ∨ base + capacity ≤ e.source j + +/-- Avoiding a larger reserved gap also avoids each smaller gap with the same base. -/ +theorem Entry.Around.mono_capacity {e : Entry} {base small large : ℕ} + (h : e.Around base large) (hle : small ≤ large) : e.Around base small := by + intro j + rcases h j with hj | hj + · exact Or.inl hj + · exact Or.inr (by omega) + +/-- A concrete stack frame cannot alter any live column around its reserved interval. -/ +theorem Entry.Stored.of_stack_frame {e : Entry} {before after : ℕ → ℕ} + {base capacity : ℕ} (h : e.Stored before) (hg : e.Around base capacity) + (hf : Modifies (Stack.footprint base capacity) before after) : e.Stored after := by + apply h.congr + intro j i hi + apply hf + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + rcases hg j with hj | hj <;> omega + +/-- A newly allocated heavy block remains above the stack; untouched columns retain their gap. -/ +theorem Entry.Around.child {e : Entry} {base capacity p : ℕ} (h : e.Around base capacity) + (j : Fin 3) (zs : List ℤ) (hp : base + capacity ≤ p) : + (e.child j p zs).Around base capacity := by + intro k + by_cases he : k = j + · subst k + exact Or.inr (by simpa only [Entry.child, Function.update_self] using hp) + · simpa only [Entry.child, Function.update_of_ne he] using h k + +/-- Actual native popping restores the semantic entry, preserves the remaining stack and +changes memory only inside the reserved stack interval. -/ +theorem pop_entry {w base capacity : ℕ} (x : Data 128) (e : Entry) (es : List Entry) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity ((e :: es).flatMap Entry.words) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ + Stack.Rep w base capacity (es.flatMap Entry.words) (y.comap registers) ∧ + e.Loaded y ∧ Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + let a : Fin 7 → ℕ := ![e.fuel, e.source 0, (e.data 0).length, e.source 1, + (e.data 1).length, e.source 2, (e.data 2).length] + have hrep : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ es.flatMap Entry.words) + (x.comap registers) := by + simpa +decide [a, List.flatMap_cons, Entry.words] using hr + obtain ⟨y, hy, hry, hslots, hframe, hp⟩ := popFrame_exec x _ a h2 hcap hrep + refine ⟨y, hy, hry, ⟨hslots 0, ?_, ?_⟩, hframe, hp⟩ + · intro j + fin_cases j + · exact hslots 1 + · exact hslots 3 + · exact hslots 5 + · intro j + fin_cases j + · exact hslots 2 + · exact hslots 4 + · exact hslots 6 + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean new file mode 100644 index 000000000..7a634d2f6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean @@ -0,0 +1,234 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStack +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec + +/-! +# Fixed native controller for one Chan–He recursion tree + +The controller pops actual seven-word records, skips zero fuel or empty inputs, computes the +modulus, constructs the complete heavy and packed outputs, records the packed pointer, and +pushes the three children. Children are pushed third, second, first so the existing native +stack visits them in the mathematical preorder. Fresh allocations follow every live node; +all earlier arrays, the prime bitmap, stack reservation and output table precede the heap. + +This module defines executable code. Its correctness and resource proofs belong to the +traversal development; these definitions alone do not certify a native preprocessing bound. +The frontend and initialized-input composition remain separate. + +## Main definitions + +* `oneRoot`: the finite native traversal controller, independent of fuel, input sizes and width. +* `visit`: complete native modulus and node construction, including output delivery. +* `pushChildren`: actual reverse-order stack pushes preserving the original three descriptors. + +The native code is original and reuses the existing finite Chan–He construction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The complete node kernels occupy registers 0–63. -/ +def kernel (i : Fin 64) : Fin 128 := ⟨i.val, by omega⟩ + +/-- The supplied candidate-prime bound. -/ +abbrev limit : Fin 128 := 71 +/-- The supplied label-bit parameter. -/ +abbrev digits : Fin 128 := 72 +/-- The common input-magnitude bound of the tree. -/ +abbrev magnitude : Fin 128 := 73 +/-- The common number of packed four-cell groups. -/ +abbrev groups : Fin 128 := 74 +/-- The prime bitmap, retained below the heap. -/ +abbrev bitmap : Fin 128 := 75 +/-- The exact number of candidate primes. -/ +abbrev primeCount : Fin 128 := 76 +/-- The current fresh allocation base. -/ +abbrev heap : Fin 128 := 77 +/-- The reserved output-pointer table. -/ +abbrev outputs : Fin 128 := 78 +/-- Accumulated output count; this is not reset between roots. -/ +abbrev outputCount : Fin 128 := 79 +/-- The reserved word-stack base. -/ +abbrev stackBase : Fin 128 := 80 +/-- The reserved word-stack capacity, used by the safety specification. -/ +abbrev stackCapacity : Fin 128 := 81 +/-- Controller constant zero. -/ +abbrev zero : Fin 128 := 82 +/-- Controller constant one. -/ +abbrev one : Fin 128 := 83 +/-- Controller address temporary. -/ +abbrev temporary : Fin 128 := 85 +/-- The computed unsigned modulus. -/ +abbrev selected : Fin 128 := 87 +/-- The three computed heavy lengths. -/ +def heavyLength (j : Fin 3) : Fin 128 := ⟨88 + j.val, by omega⟩ +/-- The three computed heavy bases. -/ +def heavyBase (j : Fin 3) : Fin 128 := ⟨91 + j.val, by omega⟩ +/-- Current packed-array base. -/ +abbrev packed : Fin 128 := 94 +/-- One-past-end of the current node allocation. -/ +abbrev endpoint : Fin 128 := 95 +/-- Saved original descriptor words, outside both kernels and the stack dispatcher. -/ +def original (i : Fin 4) : Fin 128 := ⟨105 + i.val, by omega⟩ + +/-- Fourteen actual instructions deliver the modulus program's complete caller ABI. -/ +def prepareModulus : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (CountCall.source 0) + let l₀ := kernel (CountCall.length 0) + let s₁ := kernel (CountCall.source 1) + let l₁ := kernel (CountCall.length 1) + let s₂ := kernel (CountCall.source 2) + let l₂ := kernel (CountCall.length 2) + let scratch := kernel CountCall.scratch + let histogram := kernel CountCall.histogram + let m := kernel Search.limit + let b := kernel Search.primes + let c := kernel CollisionTest.card + let d := kernel Modulus.digits + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + scratch ← heap; histogram ← heap + n₀; histogram ← histogram + n₁; + histogram ← histogram + n₂; m ← limit; b ← bitmap; c ← primeCount; d ← digits ] + +/-- Ten actual copies deliver the complete node builder's caller ABI. -/ +def prepareNode : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (NodePacking.source 0) + let l₀ := kernel (NodePacking.length 0) + let s₁ := kernel (NodePacking.source 1) + let l₁ := kernel (NodePacking.length 1) + let s₂ := kernel (NodePacking.source 2) + let l₂ := kernel (NodePacking.length 2) + let b := kernel NodePacking.base + let n := kernel NodePacking.groups + let u := kernel NodePacking.magnitude + let m := kernel NodePacking.modulus + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + b ← heap; n ← groups; u ← magnitude; m ← selected ] + +/-- Read the three actual count cells, record the packed pointer, and advance the fresh heap. -/ +def deliver : Code .standard 128 := + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let h₀ := heavyBase 0 + let h₁ := heavyBase 1 + let h₂ := heavyBase 2 + let c₀ := heavyLength 0 + let c₁ := heavyLength 1 + let c₂ := heavyLength 2 + ram[ packed ← #3; packed ← heap + packed; packed ← packed + n₀; + packed ← packed + n₁; packed ← packed + n₂; + temporary ← outputs + outputCount; *temporary ← packed; + outputCount ← outputCount + one; c₀ ← *heap; h₀ ← heap + one; + temporary ← h₀ + n₀; c₁ ← *temporary; h₁ ← temporary + one; + temporary ← h₁ + n₁; c₂ ← *temporary; h₂ ← temporary + one; + endpoint ← groups + groups; endpoint ← endpoint + endpoint; + endpoint ← packed + endpoint; endpoint ← endpoint + n₀; endpoint ← endpoint + n₁; + endpoint ← endpoint + n₂; endpoint ← endpoint + selected; heap ← endpoint ] + +/-- Complete actual native calls, with argument preparation, modulus copy and output delivery. -/ +def visit : Code .standard 128 := + .seq prepareModulus (.seq (Modulus.program.rename kernel) + (.seq (.op (.copy selected (kernel Modulus.output))) + (.seq prepareNode (.seq (NodePacking.program.rename kernel) deliver)))) + +/-- Decrease fuel, retain both descriptors needed for restoration, and prepare the third child. -/ +def prepareThird : Code .standard 128 := + let f := slot 0 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₁ := original 0 + let on₁ := original 1 + let op₂ := original 2 + let on₂ := original 3 + let h₂ := heavyBase 2 + let c₂ := heavyLength 2 + ram[ f ← f - one; op₁ ← p₁; on₁ ← n₁; op₂ ← p₂; on₂ ← n₂; + p₂ ← h₂; n₂ ← c₂ ] + +/-- Restore the third original descriptor and prepare the second child. -/ +def prepareSecond : Code .standard 128 := + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₂ := original 2 + let on₂ := original 3 + let h₁ := heavyBase 1 + let c₁ := heavyLength 1 + ram[ p₂ ← op₂; n₂ ← on₂; p₁ ← h₁; n₁ ← c₁ ] + +/-- Restore the second original descriptor and prepare the first child. -/ +def prepareFirst : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let op₁ := original 0 + let on₁ := original 1 + let h₀ := heavyBase 0 + let c₀ := heavyLength 0 + ram[ p₁ ← op₁; n₁ ← on₁; p₀ ← h₀; n₀ ← c₀ ] + +/-- Push third, second, first. Every child retains the two other original set descriptors. -/ +def pushChildren : Code .standard 128 := + .seq prepareThird (.seq pushFrame (.seq prepareSecond + (.seq pushFrame (.seq prepareFirst pushFrame)))) + +/-- A popped record either skips exactly the mathematical guard or executes the complete node. -/ +def body : Code .standard 128 := + let f := slot 0 + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let skip := Code.op (B := Basis.standard) (.copy f f) + .seq popFrame (.branch (.eq f zero) skip + (.branch (.eq n₀ zero) skip (.branch (.eq n₁ zero) skip + (.branch (.eq n₂ zero) skip (.seq visit pushChildren))))) + +/-- The actual pending-word stack, including zero-fuel and empty entries, controls the loop. -/ +def loop : Code .standard 128 := .loop (.lt zero (registers Stack.len)) body + +/-- The complete finite native controller for one supplied root. It appends to the caller's +output table, retaining the accumulated count and advancing the fresh allocation base. -/ +def oneRoot : Code .standard 128 := + let ptr := registers Stack.ptr + let len := registers Stack.len + .seq (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) + (.seq pushFrame loop) + +/-- The same finite controller is used at every admitted word width. -/ +theorem oneRoot_not_readsWidth : ¬oneRoot.ReadsWidth := by decide + +/-- The native controller has a fixed finite instruction list, independent of recursion fuel. -/ +theorem oneRoot_size : oneRoot.size ≤ 65536 := by decide + +/-- Registers 109–127 are retained for the outer frontend-forest controller. -/ +theorem oneRoot_preserves : ∀ i : Fin 128, 109 ≤ i.val → oneRoot.Preserves i := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean new file mode 100644 index 000000000..36ec20a7b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall + +/-! +# A shared polynomial envelope for native traversal calls + +The dynamic heap and all input blocks lie below `H`; each list has at most `n` elements. +The candidate bound, label parameter, magnitude and common group count have fixed bounds +`m,Λ,U,N`. A fourth-degree polynomial covers both existing kernel envelopes, including their +native setup. This bound is later instantiated at the worklist's global heap frontier. + +## Main definitions + +* `envelope`: a common polynomial bound for the actual native modulus and node calls. + +## Main results + +* `modulus_budget_le_envelope`: the concrete selector budget is covered. +* `node_budget_le_envelope`: every selected modulus at most `m²` is covered. + +The envelope and elementary inequalities are original resource estimates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One fixed polynomial in the heap, list, candidate, label, magnitude and output-size bounds. -/ +def envelope (H n m Λ U N : ℕ) : ℕ := + 65536 * (H + n + m ^ 2 + Λ + m + U + N + 100) ^ 4 + +/-- The common envelope covers each size parameter and every smaller native word. -/ +theorem envelope_ge_sum (H n m Λ U N : ℕ) : + H + n + m ^ 2 + Λ + m + U + N + 100 ≤ envelope H n m Λ U N := by + have h := Nat.le_self_pow (by decide : 4 ≠ 0) + (H + n + m ^ 2 + Λ + m + U + N + 100) + dsimp only [envelope] + omega + +private theorem square_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 2 ≤ S ^ 4 := by + have hsq : 1 ≤ S ^ 2 := Nat.one_le_pow _ _ hS + calc S ^ 2 ≤ S ^ 2 * S ^ 2 := Nat.le_mul_of_pos_right _ hsq + _ = S ^ 4 := by ring + +private theorem cube_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 3 ≤ S ^ 4 := by + calc S ^ 3 ≤ S ^ 3 * S := Nat.le_mul_of_pos_right _ hS + _ = S ^ 4 := by ring + +/-- The exact selector envelope, including all copied source pointers and histogram addresses, +is bounded by one polynomial in the runtime size bounds. -/ +theorem modulus_budget_le_envelope (x : Data 128) (H n m Λ U N : ℕ) + (hheap : x.regs heap ≤ H) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ H) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hm : x.regs limit ≤ m) (hΛ : x.regs digits ≤ Λ) + (hcard : x.regs primeCount ≤ m) (hbitmap : x.regs bitmap ≤ H) : + Modulus.budget ((modulusState x).comap kernel) ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hline : 7 * H + 3 * n + m ^ 2 + m + 100 ≤ 7 * S := by dsimp [S]; omega + have ha : 3 * n + 1 ≤ 3 * S := by dsimp [S]; omega + have hb : 3 * Λ + m + 1 ≤ 3 * S := by dsimp [S]; omega + have hprod : (3 * n + 1) ^ 2 * (3 * Λ + m + 1) ≤ 27 * S ^ 3 := by + calc (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + ≤ (3 * S) ^ 2 * (3 * S) := Nat.mul_le_mul (Nat.pow_le_pow_left ha 2) hb + _ = 27 * S ^ 3 := by ring + have hns : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hss : x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) ≤ 3 * H := by + have h₀ := hsrc 0 + have h₁ := hsrc 1 + have h₂ := hsrc 2 + change x.regs (slot 1) + x.regs (slot 2) ≤ H at h₀ + change x.regs (slot 3) + x.regs (slot 4) ≤ H at h₁ + change x.regs (slot 5) + x.regs (slot 6) ≤ H at h₂ + omega + have hbudget : Modulus.budget ((modulusState x).comap kernel) ≤ + 7 * H + 3 * n + (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + m ^ 2 + m + 100 := by + change x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + x.regs heap + x.regs bitmap + x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) + + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + 1) ^ 2 * + (3 * x.regs digits + x.regs primeCount + 1) + x.regs limit ^ 2 + x.regs limit + 100 ≤ _ + have hp := Nat.mul_le_mul + (Nat.pow_le_pow_left (Nat.add_le_add_right hns 1) 2) + (show 3 * x.regs digits + x.regs primeCount + 1 ≤ 3 * Λ + m + 1 by omega) + have hsq := Nat.pow_le_pow_left hm 2 + omega + have hsf : S ≤ S ^ 4 := Nat.le_self_pow (by decide) S + have hcf := cube_le_fourth hS + change _ ≤ 65536 * S ^ 4 + omega + +/-- The node layout, including every allocated output/scratch cell and tag value, is covered +uniformly for every selected product at most `m²`. -/ +theorem node_budget_le_envelope (x : Data 128) (H n m Λ U N M : ℕ) + (hheap : x.regs heap ≤ H) (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hM : M ≤ m ^ 2) (hU : x.regs magnitude ≤ U) (hN : x.regs groups ≤ N) : + (recordLayout x M).budget ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hns : (recordLayout x M).total ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (arrayLength 0) + x.regs (arrayLength 1) + x.regs (arrayLength 2) ≤ 3 * n + omega + have hinner : (recordLayout x M).base + (recordLayout x M).total + M + + (recordLayout x M).N + (recordLayout x M).U + 1 ≤ 3 * S := by + change x.regs heap + (recordLayout x M).total + M + x.regs groups + + x.regs magnitude + 1 ≤ 3 * S + dsimp only [S] + omega + calc (recordLayout x M).budget + ≤ 4096 * (3 * S) ^ 2 := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hinner 2) + _ = 36864 * S ^ 2 := by ring + _ ≤ 65536 * S ^ 4 := by have := square_le_fourth hS; omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean new file mode 100644 index 000000000..851a2ff7f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean @@ -0,0 +1,215 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLoop + +/-! +# Complete native Chan–He traversal from a supplied root + +The finite 128-register program initializes its actual stack and constants, pushes the supplied +seven-word root and executes the complete bounded traversal. Every emitted array is the existing +finite node's `oneArray`, in preorder. The protected frontend prefix survives for later roots. + +## Main definitions + +* `RootInput`: concrete native array, metadata and disjoint-memory obligations of a root call. + +## Main results + +* `root_exec`: complete actual native execution with exact output and a charged bound. + +These native composition proofs are original. The initialized-input forest and global +logarithmic-width/subquadratic-cost composition remain separate obligations. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Concrete caller obligations before the complete native root program initializes its stack. -/ +structure RootInput (a : Arena) (w : ℕ) (e : Entry) (ns : List (ℕ × Node)) + (x : Data 128) : Prop where + /-- Initial actual words fit. -/ + bounded : x.Bounded (w + 2) + /-- Persistent caller metadata. -/ + controls : a.Controls x + /-- Exact root register metadata. -/ + loaded : e.Loaded x + /-- Exact root data readback. -/ + stored : e.Stored x.mem + /-- Duplicate-free existing three-set admissibility. -/ + valid : e.Valid a.n a.U + /-- The actual prime bitmap is ready. -/ + bitmap : a.Bitmap x.mem + /-- Original arrays avoid the complete reserved stack and output-table gap. -/ + around : e.Around a.stack (a.floor - a.stack) + /-- All root arrays end below the fresh allocation. -/ + ends : ∀ j, e.source j + (e.data j).length ≤ x.regs heap + /-- The supplied fuel agrees with the fixed stack reservation. -/ + fuel : e.fuel = a.fuel + /-- The initial fresh allocation lies above the reserved gap. -/ + floor : a.floor ≤ x.regs heap + /-- The full tree's transitions reserve sufficient fresh memory. -/ + heapBudget : x.regs heap + Math.work e.fuel * allocationSize a.n a.m a.N ≤ a.ceiling + /-- Actual accumulated output count. -/ + count : x.regs outputCount = ns.length + /-- The full tree's transitions reserve sufficient output-pointer cells. -/ + tableBudget : ns.length + Math.work e.fuel ≤ a.outputCapacity + /-- Existing output retains its exact native readback. -/ + output : StoredNodes a.U a.N a.table ns x.mem + /-- Existing packed arrays lie above the gap and below the fresh allocation. -/ + outputBounds : ∀ i : Fin ns.length, + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ x.regs heap + /-- The entire frontend prefix is available. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- Execute the fixed complete native root program. There are no supplied kernel executions: +all calls are proved from the actual arrays, bitmap, finite bounds and one signed envelope. -/ +theorem root_exec {a : Arena} {w : ℕ} (e : Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h : RootInput a w e ns x) : + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) oneRoot x y (rootTime e.fuel a.n a.m a.N) ∧ + (Cursor.mk [] out).Invariant a w + (ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) y ∧ + out.map Prod.snd = ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ ∧ + (∀ p < a.stack, y.mem p = x.mem p) ∧ + ∀ i : Fin 128, 109 ≤ i.val → y.regs i = x.regs i := by + have hunsigned := NodePacking.unsigned_fits_of_signed hw.2.2 + have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have h2 : Fits (w + 2) 2 := hunsigned.of_le (by omega) + have hcap : Fits (w + 2) (a.stack + a.capacity) := hunsigned.of_le (by + have hb := h.heapBudget + have hf := h.floor + have ht := ha.2.1 + have hgap := ha.2.2.1 + omega) + let p := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + let ptr := registers Stack.ptr + let len := registers Stack.len + have h1 : Fits (w + 2) 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) (w + 2) + (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) x p 4 := by + ram_eval [p, ptr, len, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have preg (i : Fin 128) (hi : i.val < 96) : + p.regs i = ((x.set zero 0).set one 1).regs i := by + have hp' : i ≠ registers Stack.ptr := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.ptr] at this; omega + have hl' : i ≠ registers Stack.len := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.len] at this; omega + simp only [p, Data.set_regs, Function.update_of_ne hp', Function.update_of_ne hl'] + have pmeta (i : Fin 128) (hi : i.val < 96) : p.regs i = x.regs i := by + rw [preg i hi] + by_cases hz : i = zero + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.1.symm + · by_cases ho : i = one + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.2.symm + · simp only [Data.set_regs, Function.update_of_ne ho, Function.update_of_ne hz] + have pcontrols : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact pmeta i (by omega)) + have ploaded : e.Loaded p := by + refine ⟨(pmeta _ (by decide)).trans h.loaded.1, ?_, ?_⟩ + · intro j + exact (pmeta _ (by dsimp [arraySource, slot]; omega)).trans (h.loaded.2.1 j) + · intro j + exact (pmeta _ (by dsimp [arrayLength, slot]; omega)).trans (h.loaded.2.2 j) + have pstack : Stack.Rep (w + 2) a.stack a.capacity [] (p.comap registers) := by + refine ⟨⟨fun i => (hp.bounded h.bounded).1 (registers i), (hp.bounded h.bounded).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simpa +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + using h.controls.2.2.2.2.2.2.2.1 + · simp +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hstack, hframe, hbr⟩ := pushFrame_exec p [] h2 hcap pstack + (fun i => (hp.bounded h.bounded).1 (slot i)) (by + have := ha.2.2.2 + simp only [List.length_nil] + omega) + have breg (i : Fin 128) (hi : i.val < 96) : b.regs i = x.regs i := + (hbr i (Or.inl hi)).trans (pmeta i hi) + have bframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : b.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + let s : Cursor := ⟨[e], ns⟩ + have work : s.work = Math.work e.fuel := by + simp only [s, Cursor.work, List.map_cons, List.map_nil, Math.potential, List.sum_cons, + List.sum_nil, Nat.add_zero, Entry.task] + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hg := ha.2.2.1 + omega + have hi : s.Invariant a w + (ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) b := by + refine ⟨hb.bounded (hp.bounded h.bounded), h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact breg i (by omega)), ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [bframe _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · simpa only [s, List.flatMap_cons, List.flatMap_nil, Entry.loaded_words ploaded, + List.append_nil] using hstack + · rw [← h.fuel] + exact Math.bounded_singleton e.task + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.valid + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + exact h.stored.of_stack_frame (h.around.mono_capacity gap) hframe + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.around + · intro d hd j + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + rw [breg heap (by decide)] + exact h.ends j + · rw [breg heap (by decide)] + exact h.floor + · rw [breg heap (by decide), work] + exact h.heapBudget + · exact (breg outputCount (by decide)).trans h.count + · rw [work] + exact h.tableBudget + · apply h.output.congr + · intro q hq _ + exact bframe q (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact bframe _ (Or.inr (by + have hp' := (h.outputBounds i).1 + have ht := ha.2.1 + have hg := ha.2.2.1 + omega)) + · intro i + rw [breg heap (by decide)] + exact h.outputBounds i + · simp only [s, List.map_cons, List.map_nil, Math.output, List.flatMap_cons, + List.flatMap_nil, List.append_nil] + · intro q hq + exact (bframe q (Or.inl hq)).trans (h.protectedMemory q hq) + obtain ⟨out, y, he, hfinal, hout⟩ := loop_exec ha hw h2 hcap s b hi + have hexec : ExecWithin (w + 2) oneRoot x y (rootTime e.fuel a.n a.m a.N) := by + have hc := hp.within.seq (hb.within.seq he) + exact hc.mono (by rw [work]; dsimp only [rootTime]; omega) + refine ⟨out, y, hexec, hfinal, hout, ?_, ?_⟩ + · intro q hq + exact (hfinal.protectedMemory q hq).trans (h.protectedMemory q hq).symm + · intro i hi + exact hexec.regs_eq (oneRoot_preserves i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean new file mode 100644 index 000000000..d52730a9b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete skipped transitions of the native worklist + +Zero-fuel and empty entries still execute the seven native pops and their actual branch paths. +The remaining arrays, bitmap and previously emitted packed output retain exact readback. +Both space reservations improve because the transition measure decreases without allocation. + +## Main results + +* `skip_transition`: the actual native body preserves the full worklist invariant on every + skipped entry and strictly decreases its remaining charged-transition measure. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Popping a skipped entry executes the complete native body and preserves every part of the +worklist invariant, including exact output, all live memory and concrete space reservations. -/ +theorem skip_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ∃ y, ExecWithin (w + 2) body x y 107 ∧ + (Cursor.mk es ns).Invariant a w target y ∧ + (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨y, hy, hstack, hloaded, hframe, hp⟩ := pop_entry x e es h2 hcap h.stack + have hreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = x.regs i := hp i (Or.inl hi') (Or.inr hi) + have hheap : y.regs heap = x.regs heap := hreg _ (by decide) (by decide) + have hcount : y.regs outputCount = x.regs outputCount := hreg _ (by decide) (by decide) + have hcontrols : a.Controls y := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact hreg i (by omega) (by omega)) + have hbody := body_skip hy hloaded hcontrols.2.2.2.2.2.2.2.2.2.1 hskip + have hmem (p : ℕ) (hp : p < a.stack ∨ a.stack + a.capacity ≤ p) : y.mem p = x.mem p := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hstep : Math.step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) = + (es.map Entry.task, none) := by + by_cases hf : e.fuel = 0 + · simp only [Math.step, Entry.task, hf] + · have hempty : e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + rcases hskip with hf' | h₀ | h₁ | h₂ + · exact (hf hf').elim + · exact Or.inl (List.eq_nil_of_length_eq_zero h₀) + · exact Or.inr (Or.inl (List.eq_nil_of_length_eq_zero h₁)) + · exact Or.inr (Or.inr (List.eq_nil_of_length_eq_zero h₂)) + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte] + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at hdec + change (Cursor.mk es ns).work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + refine ⟨y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, hcontrols, ?_, hstack, ?_, ?_, ?_, ?_, ?_, ?_, ?_, + hcount.trans h.count, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [hmem _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · exact h.depth.2 + · intro d hd + exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (h.stored d (List.mem_cons_of_mem e hd)).of_stack_frame + ((h.around d (List.mem_cons_of_mem e hd)).mono_capacity (by + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) hframe + · intro d hd + exact h.around d (List.mem_cons_of_mem e hd) + · intro d hd j + rw [hheap] + exact h.ends d (List.mem_cons_of_mem e hd) j + · rw [hheap] + exact h.floor + · rw [hheap] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) (Nat.le_of_lt + (show (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work by omega)) + omega + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change ns.length + (Cursor.mk es ns).work ≤ a.outputCapacity + omega + · apply h.output.congr + · intro p hp _ + exact hmem p (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact hmem _ (Or.inr (by + have hi := (h.outputBounds i).1 + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) + · intro i + rw [hheap] + exact h.outputBounds i + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at he + simp only [Option.toList_none, List.nil_append] at he + simpa only [Cursor.pending, Cursor.emitted, List.map_cons, he] using h.exactOutput + · intro p hp + exact (hmem p (Or.inl hp)).trans (h.protectedMemory p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean new file mode 100644 index 000000000..5efb4c814 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean @@ -0,0 +1,342 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.Stack +import Mathlib.Tactic.FinCases + +/-! +# Native stack calls for Chan–He traversal records + +Traversal keeps a seven-word record in registers 64–70 and the existing bounded stack in +registers 96–104. Registers below 64 remain available to the complete modulus and node programs. +Each record contains fuel followed by three base/length pairs. The stack stores words, so a +record is pushed in reverse order and popped in forward order. + +The dispatcher, representation and memory frame are reused from `Cslib.WordRAM.Stack`. +Argument delivery and result copies here are actual native instructions and are charged. + +## Main definitions + +* `slot`: the seven persistent traversal-record registers. +* `registers`: the existing nine-register stack dispatcher inside the 128-register controller. +* `pushSlot`: native argument delivery followed by the complete bounded-stack dispatcher. +* `popSlot`: native dispatch and result delivery to one traversal-record register. + +## Main results + +* `pushSlot_exec`: a nine-instruction push, with the exact stack, memory and register frames. +* `popSlot_exec`: a fourteen-instruction nonempty pop with the same exact frames. +* `pushFrame_exec`, `popFrame_exec`: complete seven-word record transfers in 64 and 99 instructions. + +The native controller and its composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Fuel and the three base/length pairs, retained across both 64-register native kernels. -/ +def slot (i : Fin 7) : Fin 128 := ⟨64 + i.val, by omega⟩ + +/-- Pointer word of the selected pending set. -/ +def arraySource (j : Fin 3) : Fin 128 := slot ⟨1 + 2 * j.val, by omega⟩ + +/-- Length word of the selected pending set. -/ +def arrayLength (j : Fin 3) : Fin 128 := slot ⟨2 + 2 * j.val, by omega⟩ + +/-- The existing stack's workspace lies above every 64-register node-kernel register. -/ +def registers (i : Fin 9) : Fin 128 := ⟨96 + i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp [registers] at this + omega + +/-- Caller-owned registers outside the stack dispatcher. -/ +abbrev Outside (i : Fin 128) : Prop := i.val < 96 ∨ 105 ≤ i.val + +private theorem outside_ne (i : Fin 128) (hi : Outside i) (j : Fin 9) : + i ≠ registers j := by + intro h + have := congrArg Fin.val h + dsimp [registers, Outside] at * + omega + +private theorem extend_outside (a : Data 9) (x : Data 128) (i : Fin 128) (hi : Outside i) : + (a.extend registers x).regs i = x.regs i := by + change Function.extend registers a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact outside_ne i hi j hj.symm + +/-- Deliver the push tag and actual record word, then execute the existing stack program. -/ +def pushSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + let src := slot i + .seq (ram[ tag ← #0; arg ← src ]) (Stack.program.rename registers) + +/-- Each native push includes both argument instructions, not a host-level stack operation. +Every register outside 96–104 and every memory cell outside the reserved stack is preserved. -/ +theorem pushSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : Fits w (x.regs (slot i))) (hv : vs.length < capacity) : + ∃ y, Exec w (pushSlot i) x y 9 ∧ + Stack.Rep w base capacity (x.regs (slot i) :: vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 0).set (registers Stack.arg) (x.regs (slot i)) + have hs : slot i ≠ registers Stack.tag := outside_ne _ (Or.inl (by dsimp [slot]; omega)) _ + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 0)) + (.op (.copy (registers Stack.arg) (slot i)))) x q 2 := by + ram_eval [q, hs] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare (.push (x.regs (slot i))) + (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, _, hra, hframe⟩ := Stack.execute h2 hcap hr (.push (x.regs (slot i))) ⟨hv, hw⟩ + have hcall : Exec w Stack.program (q.comap registers) a 7 := by + simpa only [hq, Stack.bodyCost] using ha + let y := a.extend registers q + refine ⟨y, ?_, ?_, hframe, ?_⟩ + · exact hp.seq (hcall.rename registers_injective) + · simpa only [y, Data.comap_extend registers_injective] using hra + · intro j hj + rw [extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def pushSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (pushSlot i) (pushSlots is) + +private theorem pushSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (x : Data 128) + (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : is.length + vs.length ≤ capacity) : + ∃ y, Exec w (pushSlots is) x y (9 * is.length + 1) ∧ + Stack.Rep w base capacity (is.reverse.map (fun i => x.regs (slot i)) ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + induction is generalizing x vs with + | nil => + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + have hv' : vs.length < capacity := by simp only [List.length_cons] at hv; omega + obtain ⟨z, hz, hrz, hfz, hpz⟩ := pushSlot_exec i x vs h2 hcap hr (hw i) hv' + have hslots : (fun i => z.regs (slot i)) = fun i => x.regs (slot i) := by + funext i + exact hpz _ (Or.inl (by dsimp [slot]; omega)) + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih z (x.regs (slot i) :: vs) hrz + (by intro j; rw [hpz _ (Or.inl (by dsimp [slot]; omega))]; exact hw j) + (by simp only [List.length_cons] at hv ⊢; omega) + refine ⟨y, ?_, ?_, ?_, fun j hj => (hpy j hj).trans (hpz j hj)⟩ + · simpa only [pushSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · simpa only [hslots, List.reverse_cons, List.map_append, List.map_singleton, + List.append_assoc, List.singleton_append] using hry + · intro a ha + exact (hfy a ha).trans (hfz a ha) + +/-- Push all seven words in reverse order, so the next seven pops recover the record in order. +The trailing self-copy gives the fixed empty suffix an ordinary native instruction. -/ +def pushFrame : Code .standard 128 := + .seq (pushSlot 6) (.seq (pushSlot 5) (.seq (pushSlot 4) (.seq (pushSlot 3) + (.seq (pushSlot 2) (.seq (pushSlot 1) (.seq (pushSlot 0) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete native frame push charges all seven dispatches and argument delivery. +There is no host-level record write, and the exact seven-word sequence is at the stack top. -/ +theorem pushFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : 7 + vs.length ≤ capacity) : + ∃ y, Exec w pushFrame x y 64 ∧ + Stack.Rep w base capacity + ([x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] ++ vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + simpa only [pushFrame, pushSlots, List.length_cons, List.length_nil, List.reverse_cons, + List.reverse_nil, List.nil_append, List.cons_append, List.append_assoc, List.singleton_append, + List.map_cons, List.map_nil, List.map_append, List.map_singleton, Nat.reduceAdd, + Nat.reduceMul] using pushSlots_exec [6, 5, 4, 3, 2, 1, 0] x vs h2 hcap hr hw hv + +private theorem comap_set_slot (x : Data 128) (i : Fin 7) (v : ℕ) : + (x.set (slot i) v).comap registers = x.comap registers := by + ext j + · change Function.update x.regs (slot i) v (registers j) = x.regs (registers j) + apply Function.update_of_ne + exact (outside_ne _ (Or.inl (by dsimp [slot]; omega)) j).symm + · rfl + +/-- Deliver the pop tag, execute the complete dispatcher, and copy its answer to the record. -/ +def popSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + .seq (ram[ tag ← #1; arg ← #0 ]) + (.seq (Stack.program.rename registers) (.op (.copy (slot i) (registers Stack.out)))) + +/-- A nonempty native pop includes both argument instructions and the actual result copy. +Only the selected record register, stack workspace and reserved stack memory may change. -/ +theorem popSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (v : ℕ) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (v :: vs) (x.comap registers)) : + ∃ y, Exec w (popSlot i) x y 14 ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ y.regs (slot i) = v ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → j ≠ slot i → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 1).set (registers Stack.arg) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 1)) + (.op (.const (registers Stack.arg) 0))) x q 2 := by + ram_eval [q, h1] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare .pop (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, hans, hra, hframe⟩ := Stack.execute h2 hcap hr .pop trivial + have hcall : Exec w Stack.program (q.comap registers) a 11 := by + simpa only [hq, Stack.bodyCost, List.cons_ne_nil, ↓reduceIte] using ha + have hout : a.regs Stack.out = v := by + change some (if a.regs Stack.present = 0 then none else some (a.regs Stack.out)) = + some (some v) at hans + split at hans + · cases hans + · exact Option.some.inj (Option.some.inj hans) + have hread : (a.extend registers q).regs (registers Stack.out) = v := by + change Function.extend registers a.regs q.regs (registers Stack.out) = v + rw [registers_injective.extend_apply, hout] + let y := (a.extend registers q).set (slot i) v + have hcopy : Exec (B := .standard) w + (.op (.copy (slot i) (registers Stack.out))) (a.extend registers q) y 1 := by + simpa only [y, hread] using + (Exec.copy (B := Basis.standard) (w := w) (x := a.extend registers q) + (d := slot i) (s := registers Stack.out)) + refine ⟨y, hp.seq ((hcall.rename registers_injective).seq hcopy), ?_, ?_, hframe, ?_⟩ + · simpa only [y, comap_set_slot, Data.comap_extend registers_injective, List.tail_cons] using hra + · exact Function.update_self .. + · intro j hj hji + change Function.update (a.extend registers q).regs (slot i) v j = x.regs j + rw [Function.update_of_ne hji, extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def popSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (popSlot i) (popSlots is) + +private def restore : List (Fin 7) → List ℕ → (Fin 128 → ℕ) → Fin 128 → ℕ + | i :: is, v :: vs, rs => restore is vs (Function.update rs (slot i) v) + | _, _, rs => rs + +private theorem restore_congr_at (is : List (Fin 7)) (vs : List ℕ) + (rs ss : Fin 128 → ℕ) (j : Fin 128) (h : rs j = ss j) : + restore is vs rs j = restore is vs ss j := by + induction is generalizing vs rs ss with + | nil => exact h + | cons i is ih => + cases vs with + | nil => exact h + | cons v vs => + apply ih + by_cases he : j = slot i + · simp [he] + · simpa only [Function.update_of_ne he] using h + +private theorem popSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (ws : List ℕ) + (x : Data 128) (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (ws ++ vs) (x.comap registers)) + (hlen : ws.length = is.length) : + ∃ y, Exec w (popSlots is) x y (14 * is.length + 1) ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = restore is ws x.regs j := by + induction is generalizing ws x with + | nil => + have hws : ws = [] := List.length_eq_zero_iff.mp hlen + subst ws + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + cases ws with + | nil => simp at hlen + | cons v ws => + obtain ⟨z, hz, hrz, hout, hfz, hpz⟩ := popSlot_exec i x v (ws ++ vs) h2 hcap hr + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih ws z hrz (by simpa using hlen) + refine ⟨y, ?_, hry, ?_, ?_⟩ + · simpa only [popSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · intro a ha + exact (hfy a ha).trans (hfz a ha) + · intro j hj + rw [hpy j hj] + apply restore_congr_at + by_cases he : j = slot i + · simpa only [he, Function.update_self] using hout + · simpa only [Function.update_of_ne he] using hpz j hj he + +/-- Pop all seven words into the record, in forward order, using the native dispatcher. -/ +def popFrame : Code .standard 128 := + .seq (popSlot 0) (.seq (popSlot 1) (.seq (popSlot 2) (.seq (popSlot 3) + (.seq (popSlot 4) (.seq (popSlot 5) (.seq (popSlot 6) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete record pop charges every argument, dispatch and result-copy instruction. +The seven restored words have their original order; other caller registers are preserved. -/ +theorem popFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) (a : Fin 7 → ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ vs) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ Stack.Rep w base capacity vs (y.comap registers) ∧ + (∀ i, y.regs (slot i) = a i) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + obtain ⟨y, hy, hry, hfy, hpy⟩ := + popSlots_exec [0, 1, 2, 3, 4, 5, 6] [a 0, a 1, a 2, a 3, a 4, a 5, a 6] + x vs h2 hcap hr rfl + refine ⟨y, hy, hry, ?_, hfy, ?_⟩ + · intro i + have h := hpy (slot i) (Or.inl (by dsimp [slot]; omega)) + fin_cases i <;> simpa +decide [restore, slot] using h + · intro j hj hj' + have hne (i : Fin 7) : j ≠ slot i := by + intro h + have := congrArg Fin.val h + dsimp [slot] at this + omega + simpa only [restore, Function.update_of_ne (hne 0), Function.update_of_ne (hne 1), + Function.update_of_ne (hne 2), Function.update_of_ne (hne 3), + Function.update_of_ne (hne 4), Function.update_of_ne (hne 5), + Function.update_of_ne (hne 6)] using hpy j hj + +/-- Record transfers do not inspect the physical word width. -/ +theorem not_readsWidth : ¬pushFrame.ReadsWidth ∧ ¬popFrame.ReadsWidth := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean new file mode 100644 index 000000000..9215e4fc8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean @@ -0,0 +1,94 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Native dispatch after popping a Chan–He entry + +The zero-fuel and empty-array cases execute their actual branch paths. Live calls execute the +complete visit followed by all three native child pushes. No guard is evaluated by a host +program, and skipped entries retain the same charged pop as live entries. + +## Main results + +* `body_skip`: actual pop and dispatch for every skipped entry. +* `body_live`: the four actual negative branches connect a pop to the complete live suffix. + +These control-flow composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +private theorem copy_self {w : ℕ} (x : Data 128) : + Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + +/-- Every skipped entry executes the actual seven pops and the appropriate guard path. +The resulting data is exactly the pop result; the self-copy does not change it. -/ +theorem body_skip {w : ℕ} {x y : Data 128} {e : Entry} + (hp : Exec w popFrame x y 99) (hl : e.Loaded y) (hz : y.regs zero = 0) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ExecWithin w body x y 107 := by + have hf : y.regs (slot 0) = e.fuel := hl.1 + have hn₀ : y.regs (slot 2) = (e.data 0).length := hl.2.2 0 + have hn₁ : y.regs (slot 4) = (e.data 1).length := hl.2.2 1 + have hn₂ : y.regs (slot 6) = (e.data 2).length := hl.2.2 2 + by_cases h₀ : e.fuel = 0 + · exact (hp.seq (Exec.branch_pos + (by change y.regs (slot 0) = y.regs zero; omega) (copy_self y))).within.mono (by omega) + · by_cases h₁ : (e.data 0).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 2) = y.regs zero; omega) + (copy_self y)))).within.mono (by omega) + · by_cases h₂ : (e.data 1).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 4) = y.regs zero; omega) + (copy_self y))))).within.mono (by omega) + · have h₃ : (e.data 2).length = 0 := by omega + exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 4) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 6) = y.regs zero; omega) + (copy_self y)))))).within.mono (by omega) + +/-- A live entry takes all four native negative branches before executing the supplied actual +visit and three-push executions. The bound charges the seven pops and eight branch/jump steps. -/ +theorem body_live {w T : ℕ} {x a b y : Data 128} {e : Entry} + (hp : Exec w popFrame x a 99) (hl : e.Loaded a) (hz : a.regs zero = 0) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) + (hv : ExecWithin w visit a b T) (hc : Exec w pushChildren b y 207) : + ExecWithin w body x y (T + 314) := by + obtain ⟨t, ht, he⟩ := hv + have hg₀ : ¬(Cond.eq (slot 0) zero).Holds a := by + change a.regs (slot 0) ≠ a.regs zero + rw [hl.1, hz] + exact hf + have hg (j : Fin 3) : ¬(Cond.eq (arrayLength j) zero).Holds a := by + change a.regs (arrayLength j) ≠ a.regs zero + rw [hl.2.2 j, hz] + exact hn j + have he' : Exec w body x y (99 + (t + 207 + 2 + 2 + 2 + 2)) := + hp.seq (Exec.branch_neg hg₀ (Exec.branch_neg (hg 0) (Exec.branch_neg (hg 1) + (Exec.branch_neg (hg 2) (he.seq hc))))) + exact he'.within.mono (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean new file mode 100644 index 000000000..f20a88b5b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalVisit +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel + +/-! +# Charged bounds for the native Chan–He worklist + +The per-node estimate includes both complete modulus searches and the complete packing program. +It is cubic in the candidate bound, rather than quadratic in the input length. The root estimate +also charges skipped calls, stack transfers, branches and loop control. + +## Main definitions + +* `visitTime`, `rootTime`: uniform charged bounds for one live visit and one complete root. +* `allocationSize`: maximum fresh allocation for one live visit. + +## Main results + +* `visit_cost_le`: the exact executed call bound is covered by `visitTime`. +* `allocation_le`: the concrete node allocation is covered by `allocationSize`. + +These estimates are original; their starting costs are the proved native execution bounds. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Both searches, the actual node program, argument delivery and the output-table store. -/ +def visitTime (n m N : ℕ) : ℕ := + 2 * (m + 1) * (102 * n + 15 * m ^ 2 + 102) + + 300 * n + 15 * m ^ 2 + 15 * m + 12 * N + 386 + +/-- Root setup and every possible transition, including empty and zero-fuel entries. -/ +def rootTime (f n m N : ℕ) : ℕ := 70 + Math.work f * (visitTime n m N + 316) + +/-- Three heavy count cells, three heavy buffers, four packed blocks, scratch and histogram. -/ +def allocationSize (n m N : ℕ) : ℕ := 3 + 6 * n + 4 * N + m ^ 2 + +/-- The exact charged cost from the complete native visit is uniformly bounded. -/ +theorem visit_cost_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hm : x.regs limit ≤ m) + (hN : x.regs groups ≤ N) (hM : M ≤ m ^ 2) : + Modulus.cost ((modulusState x).comap kernel) + + 66 * (recordLayout x M).total + 15 * M + 12 * x.regs groups + 277 ≤ + visitTime n m N := by + have hs : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hp := Nat.mul_le_mul (Nat.add_le_add_right hm 1) + (show 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102 ≤ 102 * n + 15 * m ^ 2 + 102 by + have := Nat.pow_le_pow_left hm 2 + omega) + change 2 * ((x.regs limit + 1) * + (34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102) + 9) + + 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit + 91 + + 66 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * M + 12 * x.regs groups + 277 ≤ _ + dsimp only [visitTime] + rw [Nat.mul_assoc] + omega + +/-- The actual next heap pointer advances by at most the uniform per-node allocation. -/ +theorem allocation_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hN : x.regs groups ≤ N) + (hM : M ≤ m ^ 2) : + (recordLayout x M).endpoint ≤ x.regs heap + allocationSize n m N := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + rw [recordLayout_endpoint] + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + dsimp only [allocationSize] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean new file mode 100644 index 000000000..d3e7774b7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean @@ -0,0 +1,138 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalDeliver + +/-! +# Complete charged native work at one live traversal entry + +Both native modulus searches, every counting call, the complete node builder and output delivery +are composed here. The intermediate states are exposed as proof witnesses so the worklist proof +can reuse the node builder's existing exact heavy/packed readback and allocation frame. + +## Main results + +* `visit_exec`: execution of the complete native visit, charging both kernel calls, every argument + instruction, the modulus copy and all output-delivery instructions. + +These are original native composition proofs. The global traversal and initialized-input width +and asymptotic bounds remain separate from this local calling-convention theorem. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The full native live-node visit, including all setup and output delivery. The returned +intermediate states are reached by actual instructions, not supplied node or preprocessing oracles. +The node's signed envelope derives the delivery address/counter fits. -/ +theorem visit_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hnode : ∀ M, 1 ≤ M → M ≤ x.regs limit ^ 2 → + GenericRAM.Signed.Fits w ((recordLayout x M).budget : ℤ)) + (hN : 2 * x.regs limit ^ 2 ≤ x.regs groups) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (arrayLength j) → GenericRAM.Signed.Fits w (values j i)) + (hbdd : ∀ j i, i < x.regs (arrayLength j) → (values j i).natAbs ≤ x.regs magnitude) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ (M : ℕ) (u b : Data 128), + ExecWithin (w + 2) visit x (deliveryState b) + (Modulus.cost ((modulusState x).comap kernel) + + 66 * (recordLayout x M).total + 15 * M + 12 * x.regs groups + 277) ∧ + (∀ i, 64 ≤ i.val → i ≠ selected → u.regs i = x.regs i) ∧ u.regs selected = M ∧ + (∀ p < x.regs heap, u.mem p = x.mem p) ∧ + NodePacking.Output (recordLayout x M) values u.mem b.mem ∧ + (∀ i, 64 ≤ i.val → b.regs i = u.regs i) ∧ + 1 ≤ M ∧ M ≤ x.regs limit ^ 2 ∧ + M = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) := by + obtain ⟨a, ha, hpa, hprefix, hM, hMpos, hMle⟩ := + callModulus_exec x values hm hmod hbudget hcard hbefore hbitmap hvalues hsrc hinput hinj + let M := a.regs (kernel Modulus.output) + let u := a.set selected M + have hcopy : Exec (B := .standard) (w + 2) + (.op (.copy selected (kernel Modulus.output))) a u 1 := Exec.copy + have hu (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + u.regs i = x.regs i := by + simp only [u, Data.set_regs, Function.update_of_ne hne, hpa i hi] + have hus : u.regs selected = M := rfl + have huSource (j : Fin 3) : u.regs (arraySource j) = x.regs (arraySource j) := by + fin_cases j <;> exact hu _ (by decide) (by decide) + have huLength (j : Fin 3) : u.regs (arrayLength j) = x.regs (arrayLength j) := by + fin_cases j <;> exact hu _ (by decide) (by decide) + have huHeap : u.regs heap = x.regs heap := hu _ (by decide) (by decide) + have huGroups : u.regs groups = x.regs groups := hu _ (by decide) (by decide) + have huMagnitude : u.regs magnitude = x.regs magnitude := hu _ (by decide) (by decide) + have hL : NodePacking.layout ((nodeState u).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hus] + exact recordLayout_congr x u M hu + have hwidth : GenericRAM.Signed.Fits w + ((NodePacking.layout ((nodeState u).comap kernel)).budget : ℤ) := by + rw [hL] + exact hnode M hMpos hMle + obtain ⟨b, hb, hout, hpb⟩ := callNode_exec u values (by omega) + (by rw [hus, huGroups]; omega) hwidth + (by intro j; rw [huSource j, huLength j, huHeap]; exact hsrc j) + (by intro j i hi; rw [huMagnitude]; exact hbdd j i ((huLength j) ▸ hi)) + (by + intro j i hi + rw [huSource j] + have hi' : i < x.regs (arrayLength j) := (huLength j) ▸ hi + have hs := hsrc j + rw [show u.mem (x.regs (arraySource j) + i) = x.mem (x.regs (arraySource j) + i) + from hprefix _ (by omega)] + exact hinput j i hi') + (by intro j; simpa only [huLength] using hinj j) + rw [hL] at hb hout + have hbp (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + b.regs i = x.regs i := (hpb i hi).trans (hu i hi hne) + have hbL : NodePacking.layout ((nodeState b).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hpb selected (by decide), hus] + exact recordLayout_congr x b M hbp + have hbOne : b.regs one = 1 := (hbp _ (by decide) (by decide)).trans hone + have hbTable : b.regs outputs + b.regs outputCount < b.regs heap := by + rw [hbp outputs (by decide) (by decide), hbp outputCount (by decide) (by decide), + hbp heap (by decide) (by decide)] + exact htable + have hEnd : Fits (w + 2) ((recordLayout x M).endpoint) := + (NodePacking.unsigned_fits_of_signed (hnode M hMpos hMle)).of_le + (recordLayout x M).bounds.1 + have hEndEq : (recordLayout x M).endpoint = b.regs heap + 3 + 2 * + (b.regs (slot 2) + b.regs (slot 4) + b.regs (slot 6)) + + 4 * b.regs groups + b.regs selected := by + rw [← hbL, nodeState_layout, recordLayout_endpoint] + rw [hEndEq] at hEnd + have hd := deliver_exec b hbOne hbTable hEnd + refine ⟨M, u, b, ?_, hu, hus, hprefix, hout, hpb, hMpos, hMle, hM⟩ + obtain ⟨tm, htm, hem⟩ := ha + cases hem with + | seq hpM hkM => + obtain ⟨tn, htn, hen⟩ := hb + cases hen with + | seq hpN hkN => + have he := hpM.seq (hkM.seq (hcopy.seq (hpN.seq (hkN.seq hd)))) + exact he.within.mono (by dsimp only [recordLayout] at htn ⊢; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean new file mode 100644 index 000000000..7c33fcf39 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean @@ -0,0 +1,135 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Time + +/-! +# Complete native distinct-position 3SUM frontend + +One finite 128-register program per fixed exponent starts from ordinary native initialized +signed input. It computes `(n+2)^κ`, sorts with an explicit magnitude-derived bit count, +retains full multiplicities, and writes six-word descriptors of compact injective arrays. +Reading the descriptors gives exactly the existing `ThreeSum.inputs`, in its existing order, +including empty arrays and the two multiplicity triples at lengths zero, one and two. + +The mathematical definitions are reused from the frontend adaptation of +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +`PaperStatements.lean` lines 1358–1376. In particular `twiceSet` contains positive `2*a`. +Native programs, layout and charged proofs here are original; no source machine is imported. +This is the frontend component, not complete Chan–He preprocessing or a subquadratic solver. + +## Main definitions + +* `program`: native code depending only on the fixed polynomial exponent. +* `layout`: six-word descriptors followed by disjoint capacity-`n+1` payload blocks. +* `width`, `cost`: derived positive logarithmic width and all charged instructions. + +## Main results + +* `exec_input`: initialized-input execution, exact slots/frame and full multiplicities. +* `runs_input`: exact existing-root readback and complete native halting execution. +* `cost_bound`: explicit uniform logarithmic-square instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +private theorem readsWidth_rename {r r' : ℕ} (f : Fin r → Fin r') (c : Code .standard r) : + (c.rename f).ReadsWidth ↔ c.ReadsWidth := by + induction c with + | op o => cases o <;> rfl + | seq c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | branch b c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | loop b c hc => exact hc + | rand => rfl + +/-- Physical word width is never read, so it never determines the radix iteration count. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by + have hp := (readsWidth_rename parameterRegisters (ChanHe.Parameters.program κ)).not.mpr + (ChanHe.Parameters.not_readsWidth κ) + exact not_or.mpr ⟨hp, by decide⟩ + +/-- Code size depends only on the fixed exponent; the assembled final halt adds one. -/ +theorem size_program (κ : ℕ) : (program κ).size = κ + 71 + construct.size := by + simp only [program, Code.size, Code.size_rename, ChanHe.Parameters.size_program] + +/-- The complete finite program halts with its descriptor count, and following its descriptors +gives exactly the existing frontend list. The memory witness is the actual native state just +before the final halt. No slot, bit budget, sorting result or fit is advice. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (y : Data 128) (steps : ℕ), + Executes w ((program κ).program descriptorCount) (State.init (input.map Signed.encode)) + (layout κ input.length).count steps ∧ + Runs w ((program κ).program descriptorCount) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ (steps - 1) ∧ steps ≤ cost κ input.length ∧ + readRoots (layout κ input.length) y.mem = + Cslib.Algorithms.ThreeSum.inputs ((input.length + 2) ^ κ) + (fun i : Fin input.length => input[i]) ∧ + y.regs descriptorBase = (layout κ input.length).descriptors ∧ + y.regs descriptorCount = (layout κ input.length).count ∧ + y.regs heapEnd = (layout κ input.length).endpoint ∧ y.Bounded w := by + obtain ⟨y, rs, he, ho, hbase, hcount, hend, _, _, hkeys, hcounts, _, _, _, _⟩ := + exec_input input hw hvalues + obtain ⟨steps, hsteps, hexec⟩ := he + have hpos : 0 < cost κ input.length := by dsimp only [cost]; omega + refine ⟨y, steps + 1, ?_, ?_, by omega, ?_, hbase, hcount, hend, ?_⟩ + · simpa only [hcount, State.init] using hexec.executes descriptorCount + · simpa only [State.init, Nat.zero_add, Nat.add_sub_cancel] using + hexec.runs (pc := 0) (p := (program κ).program descriptorCount) + (Program.Contains.self _).append_left + · exact ho.readback hkeys (fun p hp => (hcounts p hp).2) hvalues + · exact hexec.bounded (Data.Bounded.init (input_fits input hw hvalues)) + +private theorem prefix_at_smaller_width {w w' r : ℕ} {p : Program .standard r} + (hp : ¬p.ReadsWidth) (hw : w ≤ w') {s t : State r} {k v total : ℕ} + (hwide : Runs w' p s t k) (hlow : Executes w p s v total) : Runs w p s t k := by + induction hwide using Relation.RelatesInSteps.head_induction_on generalizing total with + | hrefl => exact .refl _ + | @hhead a c k hstep hrest ih => + obtain ⟨z, m, hm, hz, _⟩ := hlow + cases m with + | zero => + have he := hm.zero + subst z + have hh : step w' p a = .halt v := step_halt_mono_width hz + rw [show step w' p a = .next c from hstep] at hh + cases hh + | succ m => + obtain ⟨u, hu, htail⟩ := hm.succ' + have he : u = c := by + have hmono := hu.mono_width hp hw + change step w' p a = .next u at hmono + rw [show step w' p a = .next c from hstep] at hmono + exact Outcome.next.inj hmono.symm + subst u + exact (ih (total := m + 1) ⟨z, m, htail, hz, rfl⟩).head a c t k hu + +/-- Every run prefix at every admitted width uses polynomially bounded words. This includes +all memory words and registers, rather than merely the final output or a pair-count metric. -/ +theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps : ℕ} + (hr : Runs w ((program κ).program descriptorCount) + (State.init (input.map Signed.encode)) s steps) : + (∀ i, s.data.regs i < (input.length + 2) ^ (160 * κ + 968)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (160 * κ + 968) := by + obtain ⟨_, _, he, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) descriptorCount) hw hr he + have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) + have hpoly := two_pow_width_le κ input.length + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean new file mode 100644 index 000000000..368f2ec4c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean @@ -0,0 +1,343 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program + +/-! +# Native frontend array and descriptor execution + +## Main results + +The compact scan contract is composed with every array setup and descriptor store. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Local arithmetic budget covers array addresses, native labels and loop setup. -/ +def budget (l : Layout) (U : ℕ) : ℕ := l.endpoint + 8 * U + 8 * l.digits + 64 + +/-- The hypotheses of a concrete compact scan, before choosing its bit coordinates and mode. -/ +structure ScanReady (w : ℕ) (rs : List (ℕ × ℕ)) (x : Data 48) : Prop where + /-- The source length is the distinct-key count. -/ + length : x.regs len = rs.length + /-- Native small constants. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + /-- Both radix arrays have exact contents. -/ + keys : Block.HoldsList (x.regs Scan.keys) (rs.map Prod.fst) x.mem + counts : Block.HoldsList (x.regs Scan.counts) (rs.map Prod.snd) x.mem + /-- Every key and the native label arithmetic fit. -/ + words : ∀ p ∈ rs, Fits w p.1 + absolute : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude + double : ∀ p ∈ rs, Fits w (2 * p.1) + labels : Fits w (4 * x.regs magnitude + 2) + small : Fits w 5 + /-- Source and output endpoints fit. -/ + keyAddress : Fits w (x.regs Scan.keys + rs.length) + countAddress : Fits w (x.regs Scan.counts + rs.length) + outputAddress : Fits w (x.regs dst + rs.length + 1) + /-- Both source blocks precede the output. -/ + keysBefore : x.regs Scan.keys + rs.length ≤ x.regs dst + countsBefore : x.regs Scan.counts + rs.length ≤ x.regs dst + +/-- Parameters changed by split/mode setup. -/ +def ScanParametersPreserved (x y : Data 48) : Prop := + y.mem = x.mem ∧ ∀ j, j.val < 27 → j ≠ beta → j ≠ beta' → j ≠ digit → j ≠ mode → + y.regs j = x.regs j + +/-- A mode/bit setup preserves every actual scan precondition. -/ +theorem ScanReady.transfer {w : ℕ} {rs : List (ℕ × ℕ)} {x y : Data 48} + (h : ScanReady w rs x) (he : ScanParametersPreserved x y) : ScanReady w rs y := by + obtain ⟨hm, hr⟩ := he + have eq (j : Fin 48) (hlt : j.val < 27) (hj : j ≠ beta ∧ j ≠ beta' ∧ j ≠ digit ∧ j ≠ mode) : + y.regs j = x.regs j := hr j hlt hj.1 hj.2.1 hj.2.2.1 hj.2.2.2 + constructor + · simpa only [eq len (by decide) (by decide)] using h.length + · simpa only [eq zero (by decide) (by decide), eq one (by decide) (by decide), + eq two (by decide) (by decide), + eq three (by decide) (by decide), eq four (by decide) (by decide), + eq five (by decide) (by decide)] using h.constants + · simpa only [eq Scan.keys (by decide) (by decide), hm] using h.keys + · simpa only [eq Scan.counts (by decide) (by decide), hm] using h.counts + · exact h.words + · simpa only [eq magnitude (by decide) (by decide)] using h.absolute + · exact h.double + · simpa only [eq magnitude (by decide) (by decide)] using h.labels + · exact h.small + · simpa only [eq Scan.keys (by decide) (by decide)] using h.keyAddress + · simpa only [eq Scan.counts (by decide) (by decide)] using h.countAddress + · simpa only [eq dst (by decide) (by decide)] using h.outputAddress + · simpa only [eq Scan.keys (by decide) (by decide), + eq dst (by decide) (by decide)] using h.keysBefore + · simpa only [eq Scan.counts (by decide) (by decide), + eq dst (by decide) (by decide)] using h.countsBefore + +/-- Invoke the proved native scan from its actual arithmetic/layout context. -/ +theorem ScanReady.exec {w : ℕ} {rs : List (ℕ × ℕ)} {x : Data 48} + (h : ScanReady w rs x) : + ∃ y, ExecWithin w Scan.program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, Scan.body.Preserves j → y.regs j = x.regs j) := + Scan.exec rs x h.length h.constants.1 h.constants.2.1 h.constants.2.2.1 + h.constants.2.2.2.1 h.constants.2.2.2.2.1 h.constants.2.2.2.2.2 + h.keys h.counts h.words h.absolute h.double h.labels h.small h.keyAddress + h.countAddress h.outputAddress h.keysBefore h.countsBefore + +private theorem exec_mode {w m : ℕ} (r : Fin 48) (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hr : x.regs r = m) : + ∃ y, ExecWithin w (.seq (ram[ mode ← r ]) Scan.program) x y (43 * rs.length + 5) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem := by + let a := x.set mode m + have ha : ExecWithin (B := .standard) w (ram[ mode ← r ]) x a 1 := by + ram_eval [a, hr] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro j _ _ _ _ hj + exact Function.update_of_ne hj _ _ + obtain ⟨y, hy, hmem, hout, hframe, _⟩ := (h.transfer har).exec + refine ⟨y, (ha.seq hy).mono (by omega), ?_, ?_, hframe⟩ + · simpa +decide [a] using hmem + · simpa +decide [a] using hout + +private theorem exec_singleton {w : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) : + ∃ y, ExecWithin w singleton x y 2 ∧ + Block.HoldsList (x.regs dst) [0] y.mem ∧ y.regs out = 1 ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have f1 : Fits w 1 := h.small.of_le (by decide) + have fd : Fits w (x.regs dst) := h.outputAddress.of_le (by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [singleton, h.constants.1, h.constants.2.1, f1, fd] + · intro i + simp [Block.read, Data.set_mem, Data.write_mem] + · simp + · intro a ha + have hne : a ≠ x.regs dst := by + intro he + apply ha + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + (rs.length + 1) + omega + simp only [Data.set_mem, Data.write_mem, Function.update_of_ne hne] + +/-- Select exactly the array named by one physical slot. Every split/multiplicity setup is +executed; the width and label budget are scalar runtime values, independent of code size. -/ +theorem pick_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hU : x.regs magnitude = U) + (hΛ : x.regs digits = Λ) (hs : x.regs splits = 2 * Λ ^ 2) + (ht : x.regs triple = j / 3) (hslot : x.regs slot = j % 3) + (hjfit : Fits w j) : + ∃ y, ExecWithin w pick x y (43 * rs.length + 20) ∧ + Block.HoldsList (x.regs dst) (Math.array U Λ rs j) y.mem ∧ + y.regs out = (Math.array U Λ rs j).length ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have hz := h.constants.1 + have h1 := h.constants.2.1 + have h2 := h.constants.2.2.1 + have h3 := h.constants.2.2.2.1 + have h4 := h.constants.2.2.2.2.1 + have h5 := h.constants.2.2.2.2.2 + have frame {mem : ℕ → ℕ} + (hf : Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem mem) : + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem mem := by + intro a ha + apply hf a + intro hmem + apply ha + rw [Block.mem_footprint] at hmem ⊢ + dsimp only [Block.Owns] at hmem ⊢ + omega + by_cases hsplit : j / 3 < 2 * Λ ^ 2 + · let a := ((((x.set beta (j / 3 / 2)).set beta' (j / 3 / 2 % Λ)).set + beta (j / 3 / 2 / Λ)).set digit (j / 3 % 2)).set mode (j % 3) + have f0 := hjfit.of_le (show j / 3 / 2 ≤ j by omega) + have f1' := hjfit.of_le (show j / 3 / 2 % Λ ≤ j from + (Nat.mod_le _ _).trans (by omega)) + have f2' := hjfit.of_le (show j / 3 / 2 / Λ ≤ j from + (Nat.div_le_self _ _).trans (by omega)) + have f3' := h.small.of_le (show j / 3 % 2 ≤ 5 by omega) + have f4' := h.small.of_le (show j % 3 ≤ 5 by omega) + have ha : ExecWithin w splitSetup x a 5 := by + ram_eval [splitSetup, a, h2, ht, hslot, hΛ, f0, f1', f2', f3', f4'] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro i _ hb hb' hv hm + simp only [a, Data.set_regs, Function.update_of_ne hb, Function.update_of_ne hb', + Function.update_of_ne hv, Function.update_of_ne hm] + obtain ⟨y, hy, hm, ho, hf, _⟩ := (h.transfer har).exec + have he : Math.array U Λ rs j = Math.select U (j / 3 / 2 / Λ) + (j / 3 / 2 % Λ) (j / 3 % 2) (j % 3) rs := by + have hn : j % 3 ≠ 6 := by omega + simp [Math.array, Math.mode, Math.beta, Math.beta', Math.digit, hsplit, hn] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((ha.seq hy).branch_pos (by simpa only [Cond.holds_lt, ht, hs] using hsplit)).mono + (by omega) + · simpa +decide [a, hU, he] using hm + · simpa +decide [a, hU, he] using ho + · have hcond : ¬(Cond.lt triple splits).Holds x := by + simpa only [Cond.holds_lt, ht, hs] using hsplit + by_cases htwice : j / 3 = 2 * Λ ^ 2 + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode three x rs h h3 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 3 rs := by + simp [Math.array, Math.mode, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hone : j % 3 = 1 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, htwice, hone] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact ((((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode five x rs h h5 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 5 rs := by + simp [Math.array, Math.mode, htwice, hzero, hone, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, + h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, + hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode four x rs h h4 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 4 rs := by + simp [Math.array, Math.mode, hsplit, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, hsplit, htwice, hzero] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact (((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + +private theorem write_exec {w : ℕ} (x : Data 48) (h1 : x.regs one = 1) + (ha : Fits w (x.regs descriptors + 2 * x.regs current + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) + (hi : Fits w (x.regs current + 1)) : + ∃ y, ExecWithin w writeDescriptor x y 7 ∧ + y.regs current = x.regs current + 1 ∧ + y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem = Function.update (Function.update x.mem + (x.regs descriptors + 2 * x.regs current) (x.regs out)) + (x.regs descriptors + 2 * x.regs current + 1) (x.regs dst) := by + have f0 := ha.of_le (show x.regs current + x.regs current ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f1 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f2 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors + 1 ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [writeDescriptor, h1, f0, f1, f2, hh, hi] + all_goals simp +decide [Data.set_regs, Data.set_mem, Data.write_mem, + show x.regs current + x.regs current + x.regs descriptors = + x.regs descriptors + 2 * x.regs current by omega] + +/-- One complete array, its exact descriptor words, allocation advance and local frame. -/ +theorem body_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (hready : ScanReady w rs (x.set dst (x.regs heap))) + (hU : x.regs magnitude = U) (hΛ : x.regs digits = Λ) + (hs : x.regs splits = 2 * Λ ^ 2) (hj : x.regs current = j) + (hcap : rs.length + 1 ≤ x.regs capacity) + (hbefore : x.regs descriptors + 2 * j + 2 ≤ x.regs heap) + (ha : Fits w (x.regs descriptors + 2 * j + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) (hi : Fits w (j + 1)) : + ∃ y, ExecWithin w body x y (43 * rs.length + 30) ∧ + y.regs current = j + 1 ∧ y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem (x.regs descriptors + 2 * j) = (Math.array U Λ rs j).length ∧ + y.mem (x.regs descriptors + 2 * j + 1) = x.regs heap ∧ + Block.HoldsList (x.regs heap) (Math.array U Λ rs j) y.mem ∧ + (∀ a, (a < x.regs heap ∨ x.regs heap + x.regs capacity ≤ a) → + a ≠ x.regs descriptors + 2 * j → a ≠ x.regs descriptors + 2 * j + 1 → + y.mem a = x.mem a) := by + have h3 : x.regs three = 3 := by + simpa +decide using hready.constants.2.2.2.1 + have h1 : x.regs one = 1 := by simpa +decide using hready.constants.2.1 + let a := ((x.set triple (j / 3)).set slot (j % 3)).set dst (x.regs heap) + have f0 := hi.of_le (show j / 3 ≤ j + 1 by omega) + have f1 := hi.of_le (show j % 3 ≤ j + 1 from (Nat.mod_le _ _).trans (by omega)) + have he : ExecWithin w + (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) x a 3 := by + ram_eval [a, h3, hj, f0, f1] + have hr : ScanReady w rs a := by + apply hready.transfer + refine ⟨rfl, ?_⟩ + intro i hil _ _ _ _ + have hit : i ≠ triple := by intro h; subst i; norm_num [triple] at hil + have his : i ≠ slot := by intro h; subst i; norm_num [slot] at hil + simp only [a, Data.set_regs, Function.update_apply] + by_cases hid : i = dst + · subst i; simp +decide + · simp only [hid, hit, his, ↓reduceIte] + obtain ⟨u, hu, hmem, hout, hf⟩ := pick_exec (j := j) a rs hr + (by simpa +decide [a] using hU) (by simpa +decide [a] using hΛ) + (by simpa +decide [a] using hs) (by simp +decide [a]) (by simp +decide [a]) + (hi.of_le (by omega)) + have hreg (i : Fin 48) (hp : pick.Preserves i) + (ht : i ≠ triple) (hs : i ≠ slot) (hd : i ≠ dst) : u.regs i = x.regs i := by + simpa only [a, Data.set_regs, Function.update_of_ne ht, Function.update_of_ne hs, + Function.update_of_ne hd] using hu.regs_eq hp + have huc : u.regs current = j := (hreg current (by decide) (by decide) (by decide) + (by decide)).trans hj + have huh : u.regs heap = x.regs heap := hreg heap (by decide) (by decide) (by decide) (by decide) + have hucap := hreg capacity (by decide) (by decide) (by decide) (by decide) + have hudesc := hreg descriptors (by decide) (by decide) (by decide) (by decide) + have hu1 : u.regs one = 1 := (hreg one (by decide) (by decide) (by decide) (by decide)).trans h1 + have hud : u.regs dst = x.regs heap := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hum : Block.HoldsList (x.regs heap) (Math.array U Λ rs j) u.mem := by + simpa +decide [a] using hmem + obtain ⟨y, hy, hyc, hyh, hym⟩ := write_exec u hu1 + (by simpa only [huc, hudesc] using ha) (by simpa only [huh, hucap] using hh) + (by simpa only [huc] using hi) + rw [huc, hudesc, hout, hud] at hym + refine ⟨y, (he.seq (hu.seq hy)).mono (by omega), by simpa only [huc] using hyc, + by simpa only [huh, hucap] using hyh, ?_, ?_, ?_, ?_⟩ + · rw [hym, Function.update_of_ne (by omega), Function.update_self] + · rw [hym, Function.update_self] + · apply hum.frame + intro b hlo hhi + rw [hym, Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + · intro b hb hb0 hb1 + rw [hym, Function.update_of_ne hb1, Function.update_of_ne hb0] + apply hf b + rw [Block.mem_footprint] + change ¬(a.regs dst ≤ b ∧ b < a.regs dst + (rs.length + 1)) + have had : a.regs dst = x.regs heap := by simp [a] + rw [had] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean new file mode 100644 index 000000000..7a778c6c4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean @@ -0,0 +1,184 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Execute + +/-! +# Derived native frontend word width and charged cost + +The physical unsigned width is four times the existing complete parameter width. This coarse +positive logarithmic threshold covers all descriptor/payload addresses and native intermediates. +The bit budget passed to radix is the explicit magnitude-derived label count, never physical width. + +## Main definitions + +* `layout`: concrete runtime layout at fixed exponent and input length. +* `width`: positive logarithmic physical word-width threshold. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Layout computed from the actual unsigned input-length header and polynomial promise. -/ +def layout (κ n : ℕ) : Layout := + ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * (n + 2) ^ κ)⟩ + +/-- Physical unsigned width, including the two bits reserved when composing signed kernels. -/ +def width (κ n : ℕ) : ℕ := 4 * ChanHe.Parameters.width κ n + +/-- Concrete charged instruction bound, including the final halt. -/ +def cost (κ n : ℕ) : ℕ := + let U := (n + 2) ^ κ + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ChanHe.Parameters.time κ n U + RadixSort.deduplicateTime n Λ + + (layout κ n).arrays * (43 * n + 32) + 38 + +/-- The width coefficient is positive, including exponent zero and empty input. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := ChanHe.Parameters.width_pos κ n + dsimp only [width] + omega + +/-- The physical width is explicitly `Cκ * clog₂(n+2)` with a positive coefficient. -/ +theorem width_eq (κ n : ℕ) : + width κ n = (80 * κ + 484) * Nat.clog 2 (n + 2) := by + dsimp only [width, ChanHe.Parameters.width] + ring + +/-- All frontend arithmetic is bounded by the fourth power of the existing parameter budget. -/ +theorem array_budget_le (n U : ℕ) : + Arrays.budget ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U ≤ + ChanHe.Parameters.Math.budget n U ^ 4 := by + let P := ChanHe.Parameters.Math.budget n U + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + let A := 3 * (2 * Λ ^ 2 + 2) + have hP : 8 * U + 4 * n + 64 ≤ P := by dsimp [P, ChanHe.Parameters.Math.budget]; omega + have h64 : 64 ≤ P := by omega + have hn : n + 3 ≤ P := by omega + have hΛ : Λ ≤ P := by + have hlog := Nat.log_le_self 2 (4 * U) + dsimp only [Λ, Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + omega + have hsq : Λ ^ 2 ≤ P ^ 2 := Nat.pow_le_pow_left hΛ 2 + have hA : A ≤ 12 * P ^ 2 := by dsimp only [A]; nlinarith + have hm := Nat.mul_le_mul hn hA + have hbudget : Arrays.budget ⟨n, Λ⟩ U = 1 + 4 * n + (n + 3) * A + 8 * U + 8 * Λ + 64 := by + dsimp only [Arrays.budget, Layout.endpoint, Layout.pointer, Layout.payloads, + Layout.descriptors, Layout.capacity, Layout.arrays, Layout.count, Layout.splits, A] + ring + have hP3 : P ≤ P ^ 3 := by nlinarith + have hP4 : 64 * P ^ 3 ≤ P ^ 4 := by + have := Nat.mul_le_mul_right (P ^ 3) h64 + nlinarith + change Arrays.budget ⟨n, Λ⟩ U ≤ P ^ 4 + rw [hbudget] + nlinarith + +/-- The derived physical width discharges the complete native frontend budget. -/ +theorem array_budget_fits {κ n w : ℕ} (hw : width κ n ≤ w) : + Fits w (Arrays.budget (layout κ n) ((n + 2) ^ κ)) := by + have hp : Fits (ChanHe.Parameters.width κ n) + (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ)) := ChanHe.Parameters.budget_fits le_rfl + have hpow := Nat.pow_lt_pow_left hp (by decide : (4 : ℕ) ≠ 0) + have hf : Fits (width κ n) (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4) := by + simpa only [Fits, width, ← pow_mul, Nat.mul_comm] using hpow + exact (hf.of_le (array_budget_le n ((n + 2) ^ κ))).mono hw + +/-- Input/header fitting follows from the same global magnitude and width promises. -/ +theorem input_fits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := by + apply ChanHe.Parameters.inputFits input (le_trans ?_ hw) hvalues + dsimp only [width] + omega + +/-- The lower-level radix bit bound includes the largest signed key, even when `κ=0`. -/ +theorem key_lt_two_pow {U : ℕ} (hU : 1 ≤ U) {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z < 2 ^ Cslib.Algorithms.ThreeSum.labelBits (2 * U) := by + have hk := (Math.key_bounds hz).1 + have hp := Nat.lt_pow_succ_log_self (by decide : 1 < 2) (4 * U) + change Signed.encode z < 2 ^ (Nat.log 2 (2 * (2 * U)) + 1) + rw [show 2 * (2 * U) = 4 * U by omega] + exact lt_of_le_of_lt (show Signed.encode z ≤ 4 * U by omega) hp + +/-- The explicit radix bit count has a logarithmic bound independent of admitted physical width. -/ +theorem digits_bound (κ n : ℕ) : + (layout κ n).digits ≤ (20 * κ + 121) * Nat.clog 2 (n + 2) := by + let E := 20 * κ + 120 + let L := Nat.clog 2 (n + 2) + let U := (n + 2) ^ κ + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hp : ChanHe.Parameters.Math.budget n U ≤ 2 ^ (E * L) := by + calc + _ ≤ (n + 2) ^ E := ChanHe.Parameters.Math.budget_polynomial κ n + _ ≤ (2 ^ L) ^ E := Nat.pow_le_pow_left (Nat.le_pow_clog (by decide) _) _ + _ = _ := by rw [← pow_mul]; congr 1; ring + have h4 : 4 * U ≤ ChanHe.Parameters.Math.budget n U := by + dsimp only [ChanHe.Parameters.Math.budget] + omega + have hlog : Nat.log 2 (4 * U) ≤ E * L := by + calc + _ ≤ Nat.log 2 (2 ^ (E * L)) := Nat.log_mono_right (h4.trans hp) + _ = _ := Nat.log_pow (by decide) _ + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ _ + dsimp only [Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + dsimp only [E, L] at * + nlinarith + +/-- The entire allocated region and arithmetic budget are polynomial in actual input length. -/ +theorem array_budget_polynomial (κ n : ℕ) : + Arrays.budget (layout κ n) ((n + 2) ^ κ) ≤ (n + 2) ^ (80 * κ + 480) := by + calc + _ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4 := array_budget_le _ _ + _ ≤ ((n + 2) ^ (20 * κ + 120)) ^ 4 := + Nat.pow_le_pow_left (ChanHe.Parameters.Math.budget_polynomial κ n) 4 + _ = _ := by rw [← pow_mul]; congr 1; omega + +/-- Every compact output integer fits the parent's signed kernel width `physicalWidth-2`. +Thus the parent's native signed arithmetic compiler runs at exactly the physical width here. -/ +theorem signed_output_fits {κ n w : ℕ} (hw : width κ n ≤ w) {z : ℤ} + (hz : |z| ≤ (2 * (n + 2) ^ κ : ℕ)) : GenericRAM.Signed.Fits (w - 2) z := by + have hP := ChanHe.Parameters.budget_fits (κ := κ) (n := n) (w := ChanHe.Parameters.width κ n) + le_rfl + have hpos := ChanHe.Parameters.width_pos κ n + have hW : ChanHe.Parameters.width κ n + 3 ≤ w := by dsimp only [width] at hw; omega + have hfit : 2 * (n + 2) ^ κ < 2 ^ (w - 3) := by + have hmag : 2 * (n + 2) ^ κ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) := by + dsimp only [ChanHe.Parameters.Math.budget]; omega + exact (hmag.trans_lt hP).trans_le (Nat.pow_le_pow_right (by decide) (by omega)) + have habs := abs_le.mp hz + have hfi : ((2 * (n + 2) ^ κ : ℕ) : ℤ) < (2 : ℤ) ^ (w - 3) := by exact_mod_cast hfit + refine ⟨by omega, ?_, ?_⟩ + all_goals rw [show w - 2 - 1 = w - 3 by omega] + all_goals omega + +/-- A word at the canonical physical width has polynomial magnitude. -/ +theorem two_pow_width_le (κ n : ℕ) : 2 ^ width κ n ≤ (n + 2) ^ (160 * κ + 968) := by + let L := Nat.clog 2 (n + 2) + have hL : 0 < L := Nat.clog_pos (by decide) (by omega) + have hp : 2 ^ (L - 1) < n + 2 := + Nat.pow_pred_clog_lt_self (b := 2) (x := n + 2) (by decide) (by omega) + have heq : 2 ^ L = 2 * 2 ^ (L - 1) := by + conv_lhs => rw [show L = (L - 1) + 1 by omega] + rw [pow_succ, Nat.mul_comm] + have hceil : 2 ^ L ≤ (n + 2) ^ 2 := by rw [heq]; nlinarith + rw [width_eq] + calc + _ = (2 ^ L) ^ (80 * κ + 484) := by rw [← pow_mul]; congr 1; ring + _ ≤ ((n + 2) ^ 2) ^ (80 * κ + 484) := Nat.pow_le_pow_left hceil _ + _ = _ := by + rw [← pow_mul] + exact congrArg (fun e : ℕ => (n + 2) ^ e) (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean new file mode 100644 index 000000000..578321d60 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean @@ -0,0 +1,315 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +/-! +# Complete descriptor-table execution + +The invariant retains every completed descriptor and compact payload, both full radix arrays, +and all memory outside the allocated frontend region. + +## Main results + +The enumeration loop charges each native array scan, descriptor store and allocation advance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Immutable registers of the complete enumeration loop. -/ +structure Registers (l : Layout) (U k : ℕ) (x : Data 48) : Prop where + /-- Original magnitude promise. -/ + magnitude : x.regs Select.magnitude = U + /-- Original input length and explicit bit budget. -/ + inputLen : x.regs Arrays.inputLen = l.n + digits : x.regs Arrays.digits = l.digits + /-- Exact radix key/count pointers and distinct-key length. -/ + keys : x.regs Scan.keys = 1 + 2 * l.n + counts : x.regs Scan.counts = 1 + 3 * l.n + len : x.regs Scan.len = k + /-- Computed layout values. -/ + splits : x.regs Arrays.splits = l.splits + triples : x.regs Arrays.triples = l.count + arrays : x.regs Arrays.arrays = l.arrays + capacity : x.regs Arrays.capacity = l.capacity + descriptors : x.regs Arrays.descriptors = l.descriptors + heapEnd : x.regs Arrays.heapEnd = l.endpoint + /-- All small constants are real native registers. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + +/-- The body preserves every immutable layout and source register. -/ +theorem Registers.transfer {l : Layout} {U k : ℕ} {x y : Data 48} + (h : Registers l U k x) (hr : ∀ i, body.Preserves i → y.regs i = x.regs i) : + Registers l U k y := by + constructor + · exact (hr _ (by decide)).trans h.magnitude + · exact (hr _ (by decide)).trans h.inputLen + · exact (hr _ (by decide)).trans h.digits + · exact (hr _ (by decide)).trans h.keys + · exact (hr _ (by decide)).trans h.counts + · exact (hr _ (by decide)).trans h.len + · exact (hr _ (by decide)).trans h.splits + · exact (hr _ (by decide)).trans h.triples + · exact (hr _ (by decide)).trans h.arrays + · exact (hr _ (by decide)).trans h.capacity + · exact (hr _ (by decide)).trans h.descriptors + · exact (hr _ (by decide)).trans h.heapEnd + · obtain ⟨h0, h1, h2, h3, h4, h5⟩ := h.constants + exact ⟨(hr _ (by decide)).trans h0, (hr _ (by decide)).trans h1, + (hr _ (by decide)).trans h2, (hr _ (by decide)).trans h3, + (hr _ (by decide)).trans h4, (hr _ (by decide)).trans h5⟩ + +/-- Every layout scalar and partial allocation is below the explicit arithmetic budget. -/ +theorem layout_bounds (l : Layout) (U : ℕ) : + l.n + 1 ≤ budget l U ∧ l.digits * l.digits ≤ budget l U ∧ + l.splits ≤ budget l U ∧ l.count ≤ budget l U ∧ l.arrays ≤ budget l U ∧ + 2 * l.arrays ≤ budget l U ∧ l.payloads ≤ budget l U ∧ + l.arrays * l.capacity ≤ budget l U ∧ l.endpoint ≤ budget l U ∧ + 4 * U + 2 ≤ budget l U ∧ 5 ≤ budget l U := by + have hb : budget l U = l.endpoint + 8 * U + 8 * l.digits + 64 := rfl + have he : l.endpoint = l.payloads + l.capacity * l.arrays := rfl + have hp : l.payloads = l.descriptors + 2 * l.arrays := rfl + have hd : l.descriptors = 1 + 4 * l.n := rfl + have hs : l.splits = 2 * l.digits ^ 2 := rfl + have hc : l.count = l.splits + 2 := rfl + have ha : l.arrays = 3 * l.count := rfl + have hmul : l.arrays * l.capacity = l.capacity * l.arrays := Nat.mul_comm _ _ + have hsq : l.digits * l.digits = l.digits ^ 2 := (pow_two _).symm + omega + +/-- Setup computes all constants and addresses with actual instructions, preserving all memory. -/ +theorem setup_exec {w U : ℕ} (l : Layout) (k : ℕ) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hk : x.regs Scan.keys = 1 + 2 * l.n) + (hc : x.regs Scan.counts = 1 + 3 * l.n) (hlen : x.regs len = k) + (hd : x.regs descriptors = l.descriptors) (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w setup x y 19 ∧ Registers l U k y ∧ + y.regs current = 0 ∧ y.regs heap = l.pointer 0 ∧ y.mem = x.mem := by + obtain ⟨fN, fSq, fS, fD, fA, f2A, fP, fAC, fE, _, f5⟩ := layout_bounds l U + have f0 : Fits w 0 := fits_zero w + have f1 : Fits w 1 := hfit.of_le (by omega) + have f2 : Fits w 2 := hfit.of_le (by omega) + have f3 : Fits w 3 := hfit.of_le (by omega) + have f4 : Fits w 4 := hfit.of_le (by omega) + have f5' : Fits w 5 := hfit.of_le f5 + have fn := hfit.of_le fN + have fsq := hfit.of_le fSq + have fs : Fits w (l.digits * l.digits * 2) := by + simpa only [Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fS + have fd : Fits w (l.digits * l.digits * 2 + 2) := by + simpa only [Layout.count, Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fD + have fa : Fits w ((l.digits * l.digits * 2 + 2) * 3) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm] + using hfit.of_le fA + have f2a : Fits w ((l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le f2A + have fp : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.payloads, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fP + have fac : Fits w ((l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.capacity, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm] using hfit.of_le fAC + have fe : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2 + + (l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.capacity, + Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fE + refine ⟨?y, ?run, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [setup, hn, hΛ, hd, f0, f1, f2, f3, f4, f5', fn, fsq, + fs, fd, fa, f2a, fp, fac, fe] + · constructor + all_goals simp +decide [Data.set_regs, hU, hn, hΛ, hk, hc, hlen, hd, + Layout.splits, Layout.count, Layout.arrays, Layout.capacity, Layout.endpoint, + Layout.pointer, Layout.payloads, pow_two, Nat.mul_comm, Nat.mul_assoc] + · simp + · simp +decide [Data.set_regs, Layout.pointer, Layout.payloads, Layout.arrays, + Layout.count, Layout.splits, pow_two, Nat.mul_comm, Nat.mul_assoc] + · rfl + +/-- All compact arrays and six-word descriptors are produced by the complete native loop. -/ +theorem loop_exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hr : Registers l U rs.length x) (hidx : x.regs current = 0) + (hheap : x.regs heap = l.pointer 0) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w (.loop (.lt current arrays) body) x y + (l.arrays * (43 * rs.length + 32) + 2) ∧ + Output l U rs y.mem ∧ y.regs current = l.arrays ∧ y.regs heap = l.endpoint ∧ + Registers l U rs.length y ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + have hb := layout_bounds l U + have he := hb.2.2.2.2.2.2.2.2.1 + have hlabel := hb.2.2.2.2.2.2.2.2.2.1 + have hsmall := hb.2.2.2.2.2.2.2.2.2.2 + have hbase : l.descriptors ≤ l.pointer 0 := by dsimp [Layout.pointer, Layout.payloads]; omega + have hN : 1 + 4 * l.n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors] + omega + let I (y : Data 48) : Prop := + Registers l U rs.length y ∧ y.regs current ≤ l.arrays ∧ + y.regs heap = l.pointer (y.regs current) ∧ + (∀ i < y.regs current, + y.mem (l.descriptors + 2 * i) = (Math.array U l.digits rs i).length ∧ + y.mem (l.descriptors + 2 * i + 1) = l.pointer i ∧ + Block.HoldsList (l.pointer i) (Math.array U l.digits rs i) y.mem) ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt current arrays).Holds y) : + ∃ z, ExecWithin w body y z (43 * rs.length + 30) ∧ I z ∧ + l.arrays - z.regs current < l.arrays - y.regs current := by + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hy + have hj : y.regs current < l.arrays := by + simpa only [Cond.holds_lt, hyr.arrays] using ht + have hnext : l.pointer (y.regs current + 1) ≤ l.endpoint := + l.pointer_mono (by omega) + have hcur : l.pointer (y.regs current) ≤ l.endpoint := l.pointer_mono (by omega) + have hcappos : 0 < l.capacity := by dsimp [Layout.capacity]; omega + have hsource : 1 + 4 * l.n ≤ l.pointer (y.regs current) := by + have := l.pointer_mono (i := 0) (j := y.regs current) (by omega) + change l.descriptors ≤ _ + omega + have hready : ScanReady w rs (y.set dst (y.regs heap)) := by + constructor + · simpa +decide using hyr.len + · simpa +decide using hyr.constants + · simpa +decide [hyr.keys] using hykeys + · simpa +decide [hyr.counts] using hycounts + · intro p hp + exact hfit.of_le ((hkeys p hp).1.trans (by omega)) + · intro p hp + simpa +decide [hyr.magnitude] using (hkeys p hp).2.trans (show U ≤ 2 * U by omega) + · intro p hp + exact hfit.of_le (show 2 * p.1 ≤ budget l U by have := (hkeys p hp).1; omega) + · simpa +decide [hyr.magnitude] using hfit.of_le hlabel + · exact hfit.of_le hsmall + · simpa +decide [hyr.keys] using hfit.of_le + (show 1 + 2 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyr.counts] using hfit.of_le + (show 1 + 3 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyheap] using hfit.of_le + (show l.pointer (y.regs current) + rs.length + 1 ≤ budget l U by + rw [Layout.pointer_succ] at hnext + dsimp only [Layout.capacity] at hnext + omega) + · simpa +decide [hyr.keys, hyheap] using + (show 1 + 2 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + · simpa +decide [hyr.counts, hyheap] using + (show 1 + 3 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + obtain ⟨z, hz, hzi, hzh, hzd0, hzd1, hzp, hzf⟩ := body_exec + (j := y.regs current) y rs hready hyr.magnitude hyr.digits + (by simpa only [Layout.splits] using hyr.splits) rfl + (by rw [hyr.capacity]; dsimp [Layout.capacity]; omega) + (by rw [hyr.descriptors, hyheap]; exact l.descriptor_before hj _) + (hfit.of_le (by + rw [hyr.descriptors] + have := l.descriptor_before hj 0 + have hp0 : l.pointer 0 ≤ l.endpoint := l.pointer_mono (i := 0) (j := l.arrays) (by omega) + omega)) + (by rw [hyheap, hyr.capacity, ← Layout.pointer_succ]; exact hfit.of_le (hnext.trans he)) + (hfit.of_le (by have := hb.2.2.2.2.1; omega)) + simp only [hyr.descriptors, hyheap, hyr.capacity] at hzd0 hzd1 hzp hzf hzh + have hf (b : ℕ) (houtside : b < l.pointer (y.regs current) ∨ + l.pointer (y.regs current + 1) ≤ b) + (hb0 : b ≠ l.descriptors + 2 * y.regs current) + (hb1 : b ≠ l.descriptors + 2 * y.regs current + 1) : z.mem b = y.mem b := by + apply hzf b _ hb0 hb1 + simpa only [Layout.pointer_succ] using houtside + refine ⟨z, hz, ?_, by omega⟩ + refine ⟨hyr.transfer (fun i hi => hz.regs_eq hi), by omega, ?_, ?_, ?_, ?_, ?_⟩ + · rw [hzi, Layout.pointer_succ] + exact hzh + · intro i hi + by_cases hij : i = y.regs current + · subst i + exact ⟨hzd0, hzd1, hzp⟩ + · have hi' : i < y.regs current := by omega + obtain ⟨h0, h1, hp⟩ := hyout i hi' + have hbefore := l.descriptor_before (hi'.trans hj) (y.regs current) + refine ⟨(hf _ (.inl (by omega)) (by omega) (by omega)).trans h0, + (hf _ (.inl (by omega)) (by omega) (by omega)).trans h1, ?_⟩ + apply hp.frame + intro b hlo hhi + have hlen' := (Math.length_array_le U l.digits rs i).trans + (show rs.length + 1 ≤ l.capacity by dsimp [Layout.capacity]; omega) + have hptr := l.pointer_mono (show i + 1 ≤ y.regs current by omega) + rw [Layout.pointer_succ] at hptr + have htable := l.descriptor_before hj i + exact hf b (.inl (by omega)) (by omega) (by omega) + · apply hykeys.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · apply hycounts.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · intro b hnot + have hb : b < l.descriptors ∨ l.endpoint ≤ b := by + rw [Block.mem_footprint] at hnot + dsimp only [Block.Owns] at hnot + have := hbase.trans (l.pointer_mono (show 0 ≤ l.arrays by omega)) + omega + have htable := l.descriptor_before hj (y.regs current) + have hb0 : b ≠ l.descriptors + 2 * y.regs current := by omega + have hb1 : b ≠ l.descriptors + 2 * y.regs current + 1 := by omega + exact (hf b (by rcases hb with hb | hb <;> omega) hb0 hb1).trans (hyframe b hnot) + have hx : I x := by + refine ⟨hr, by rw [hidx]; omega, by simpa only [hidx] using hheap, ?_, hk, hc, + Modifies.refl _ _⟩ + intro i hi + rw [hidx] at hi + omega + obtain ⟨y, hy, hI, ht⟩ := ExecWithin.loop_invariant I + (fun y => l.arrays - y.regs current) hbody hx + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hI + have hend : y.regs current = l.arrays := by + simp only [Cond.holds_lt, hyr.arrays] at ht + omega + refine ⟨y, ?_, ?_, hend, ?_, hyr, hykeys, hycounts, hyframe⟩ + · simpa only [hidx, Nat.sub_zero, Nat.mul_comm] using hy + · intro j hj + exact hyout j (by omega) + · simpa only [hend, Layout.endpoint] using hyheap + +/-- Complete array/descriptor construction, including all native constant and layout setup. -/ +theorem exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hkreg : x.regs Scan.keys = 1 + 2 * l.n) + (hcreg : x.regs Scan.counts = 1 + 3 * l.n) (hlenreg : x.regs len = rs.length) + (hd : x.regs descriptors = l.descriptors) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w program x y (l.arrays * (43 * rs.length + 32) + 21) ∧ + Output l U rs y.mem ∧ Registers l U rs.length y ∧ y.regs heap = l.endpoint ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + obtain ⟨a, ha, har, hai, hah, ham⟩ := setup_exec l rs.length x hU hn hΛ hkreg hcreg + hlenreg hd hfit + obtain ⟨y, hy, hout, _, hyh, hyr, hykeys, hycounts, hyframe⟩ := loop_exec l rs a har hai hah + hlen (by simpa only [ham] using hk) (by simpa only [ham] using hc) hkeys hfit + exact ⟨y, (ha.seq hy).mono (by omega), hout, hyr, hyh, hykeys, hycounts, + by simpa only [ham] using hyframe⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean new file mode 100644 index 000000000..0d6dc1669 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean @@ -0,0 +1,240 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds + +/-! +# Exact native frontend from initialized signed input + +The header is the unsigned length; payload cell one starts the canonical signed encoding. +The parameter program computes the magnitude and explicit radix bit count. The radix program +preserves full multiplicities; the complete descriptor program enumerates the existing inputs. + +## Main results + +Initialized-input execution uses a positive logarithmic physical width and fixed code per exponent. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +theorem parameterRegisters_injective : Function.Injective parameterRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [parameterRegisters] at this + omega + +theorem radixRegisters_injective : Function.Injective radixRegisters := by + intro i j h + exact Fin.ext (congrArg (fun z : Fin 128 => z.val) h) + +theorem arrayRegisters_injective : Function.Injective arrayRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [arrayRegisters] at this + omega + +/-- Parameter outputs remain available to the parent traversal without recomputation. -/ +theorem construct_preserves_parameters (i : Fin 24) : + construct.Preserves (parameterRegisters i) := by + fin_cases i <;> decide + +private theorem cost_sum_bound (p r A n k : ℕ) (hk : k ≤ n) : + p + (4 + (r + (9 + (A * (43 * k + 32) + 21 + 3)))) ≤ + p + r + A * (43 * n + 32) + 38 - 1 := by + have := Nat.mul_le_mul_left A (show 43 * k + 32 ≤ 43 * n + 32 by omega) + omega + +/-- Complete initialized-input native construction, with no supplied algorithm or fit advice. -/ +theorem exec_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (y : Data 128) (rs : List (ℕ × ℕ)), + ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y (cost κ input.length - 1) ∧ + Output (layout κ input.length) ((input.length + 2) ^ κ) rs y.mem ∧ + y.regs descriptorBase = (layout κ input.length).descriptors ∧ + y.regs descriptorCount = (layout κ input.length).count ∧ + y.regs heapEnd = (layout κ input.length).endpoint ∧ + (rs.map Prod.fst).Nodup ∧ (rs.map Prod.fst).Pairwise (· < ·) ∧ + (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) ∧ + (∀ p ∈ rs, 0 < p.2 ∧ (input.map Signed.encode).count p.1 = p.2) ∧ + Block.HoldsList (1 + 2 * input.length) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * input.length) (rs.map Prod.snd) y.mem ∧ + ChanHe.Parameters.Output input.length ((input.length + 2) ^ κ) + (y.comap parameterRegisters) ∧ + Modifies (Block.mk 1 ((layout κ input.length).endpoint - 1)).footprint + (Data.init (input.map Signed.encode) : Data 128).mem y.mem := by + let n := input.length + let U := (n + 2) ^ κ + let l := layout κ n + let x : Data 128 := Data.init (input.map Signed.encode) + have hparam : ChanHe.Parameters.width κ n ≤ w := by + change ChanHe.Parameters.width κ input.length ≤ w + dsimp only [width] at hw + omega + have hp := ChanHe.Parameters.budget_fits hparam + have hfront := array_budget_fits hw + have hN : Fits w (1 + 4 * n) := hp.of_le (by + dsimp only [ChanHe.Parameters.Math.budget]; omega) + have h1 : Fits w 1 := hN.of_le (by omega) + have h2 : Fits w 2 := hp.of_le (by dsimp only [ChanHe.Parameters.Math.budget]; omega) + have hB : Fits w l.digits := hp.of_le (by + have hl := Nat.log_le_self 2 (4 * U) + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ ChanHe.Parameters.Math.budget n U + dsimp only [Cslib.Algorithms.ThreeSum.labelBits, ChanHe.Parameters.Math.budget] + rw [show 2 * (2 * U) = 4 * U by omega] + omega) + have hU : 1 ≤ U := Nat.one_le_pow _ _ (by omega) + obtain ⟨p, hep, hop, hmp⟩ := ChanHe.Parameters.exec κ (x.comap parameterRegisters) + (by simpa only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] using hp) + simp only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] at hep hop + let a := p.extend parameterRegisters x + have ha := hep.rename parameterRegisters_injective + have hpr (i : Fin 24) : a.regs (parameterRegisters i) = p.regs i := by + exact congrArg (fun z : Data 24 => z.regs i) + (Data.comap_extend parameterRegisters_injective p x) + have han : a.regs (parameterRegisters ChanHe.Parameters.len) = n := + (hpr _).trans hop.len + have haU : a.regs (parameterRegisters ChanHe.Parameters.magnitude) = U := + (hpr _).trans hop.magnitude + have haB : a.regs (parameterRegisters ChanHe.Parameters.digits) = l.digits := + (hpr _).trans hop.digits + have ham : a.mem = x.mem := hmp + let b := (((a.set (radixRegisters RadixSort.len) n).set (radixRegisters RadixSort.src) 1).set + (radixRegisters RadixSort.dst) (n + 1)).set (radixRegisters RadixSort.bits) l.digits + have hb : ExecWithin w radixSetup a b 4 := by + have hn1 := hN.of_le (show n + 1 ≤ 1 + 4 * n by omega) + ram_eval [radixSetup, b, han, haB, h1, hB, hn1] + have hmem : Block.HoldsList 1 (input.map Signed.encode) (b.comap radixRegisters).mem := by + intro i + change a.mem (1 + i.val) = (input.map Signed.encode)[i.val] + rw [ham] + dsimp only [x] + rw [Nat.add_comm, Data.init_mem_succ] + simp only [List.getD, List.getElem?_eq_getElem i.isLt, Option.getD_some] + obtain ⟨q, rs, heq, hqout, hqkeys, hqcounts, hnodup, horder, hset, hcounts, hframe⟩ := + RadixSort.deduplicate_exec (input.map Signed.encode) (b.comap radixRegisters) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]) + (by simp +decide [b, Data.comap_regs, radixRegisters]) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]; omega) + (by simp +decide [b, Data.comap_regs, radixRegisters]) hmem + (by + intro u hu + obtain ⟨z, hz, rfl⟩ := List.mem_map.mp hu + exact key_lt_two_pow hU (hvalues z hz)) + (input_fits input hw hvalues).2 (by simpa only [List.length_map] using hN) hB h2 + have hrlen : rs.length ≤ n := by + have ht := hnodup.length_le_of_subset (fun k hk => (hset k).mp hk) + simpa only [List.length_map] using ht + let c := q.extend radixRegisters b + have hradix := heq.rename radixRegisters_injective + have hcout : c.regs (radixRegisters Deduplicate.out) = rs.length := by + have he := congrArg (fun z : Data 16 => z.regs Deduplicate.out) + (Data.comap_extend radixRegisters_injective q b) + exact he.trans hqout + have hhigh (i : Fin 24) : c.regs (parameterRegisters i) = a.regs (parameterRegisters i) := by + have hnot : ¬∃ j, radixRegisters j = parameterRegisters i := by + rintro ⟨j, he⟩ + have := congrArg Fin.val he + dsimp only [radixRegisters, parameterRegisters] at this + omega + simp only [c, Data.extend, Function.extend_apply' _ _ _ hnot] + have hne (j : Fin 16) : parameterRegisters i ≠ radixRegisters j := by + intro he; exact hnot ⟨j, he.symm⟩ + simp only [b, Data.set_regs, Function.update_of_ne (hne _)] + have hcn := (hhigh _).trans han + have hcU := (hhigh _).trans haU + have hcB := (hhigh _).trans haB + let d := ((((((((c.set (arrayRegisters Scan.len) rs.length).set + (arrayRegisters Select.magnitude) U).set (arrayRegisters Arrays.inputLen) n).set + (arrayRegisters Arrays.digits) l.digits).set (arrayRegisters Select.one) 1).set + (arrayRegisters Scan.keys) (n + n)).set (arrayRegisters Scan.keys) (n + n + 1)).set + (arrayRegisters Scan.counts) (n + n + 1 + n)).set + (arrayRegisters Arrays.descriptors) (n + n + 1 + n + n) + have hd : ExecWithin w arraySetup c d 9 := by + have f0 := hN.of_le (show n + n ≤ 1 + 4 * n by omega) + have f1 := hN.of_le (show n + n + 1 ≤ 1 + 4 * n by omega) + have f2 := hN.of_le (show n + n + 1 + n ≤ 1 + 4 * n by omega) + have f3 := hN.of_le (show n + n + 1 + n + n ≤ 1 + 4 * n by omega) + ram_eval [arraySetup, d, hcn, hcU, hcB, hcout, h1, f0, f1, f2, f3] + have hrawkeys : ∀ v ∈ rs, v.1 ≤ 2 * U + 1 ∧ v.1 / 2 ≤ U := by + intro v hv + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hset v.1).mp (List.mem_map.mpr ⟨v, hv, rfl⟩)) + rw [← he] + exact ⟨(Math.key_bounds (hvalues z hz)).1, + by + have hz' := hvalues z hz + rw [Signed.encode_div_two] + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by + simpa only [Int.natCast_natAbs] using hz')⟩ + obtain ⟨r, her, hor, hregs, hrheap, hrkeys, hrcounts, hrframe⟩ := Arrays.exec l rs + (d.comap arrayRegisters) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, Layout.descriptors, l, layout]; omega) + hrlen (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqkeys) + (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqcounts) hrawkeys hfront + let e := r.extend arrayRegisters d + have harray := her.rename arrayRegisters_injective + have herg (i : Fin 48) : e.regs (arrayRegisters i) = r.regs i := + congrArg (fun z : Data 48 => z.regs i) (Data.comap_extend arrayRegisters_injective r d) + let y := ((e.set descriptorBase l.descriptors).set descriptorCount l.count).set heapEnd l.endpoint + have hy : ExecWithin w finish e y 3 := by + ram_eval [finish, y, herg Arrays.descriptors, herg Arrays.triples, herg Arrays.heapEnd, + hregs.descriptors, hregs.triples, hregs.heapEnd] + have hall := ha.seq (hb.seq (hradix.seq (hd.seq (harray.seq hy)))) + refine ⟨y, rs, ?_, hor, ?_, ?_, ?_, hnodup, horder, hset, hcounts, hrkeys, hrcounts, ?_, ?_⟩ + · apply hall.mono + simpa only [cost, List.length_map, n, U, l, layout] using cost_sum_bound + (ChanHe.Parameters.time κ n U) (RadixSort.deduplicateTime n l.digits) + l.arrays n rs.length hrlen + · rfl + · rfl + · rfl + · have hconstruct := hb.seq (hradix.seq (hd.seq (harray.seq hy))) + have hprf (i : Fin 24) : y.regs (parameterRegisters i) = p.regs i := + (hconstruct.regs_eq (construct_preserves_parameters i)).trans (hpr i) + -- Only register equalities are needed: the parameter output does not constrain memory. + exact ⟨(hprf _).trans hop.len, (hprf _).trans hop.magnitude, + (hprf _).trans hop.digits, (hprf _).trans hop.height, (hprf _).trans hop.fuel, + (hprf _).trans hop.want, (hprf _).trans hop.primeBound, + (hprf _).trans hop.packedLen, (hprf _).trans hop.padding⟩ + · intro baddr hbaddr + have hbfront : baddr ∉ (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hdesc : 1 ≤ l.descriptors := by dsimp [Layout.descriptors]; omega + omega + have hbradix : baddr ∉ (Block.mk 1 (4 * n)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hbe : 1 + 4 * n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors, l, layout] + omega + omega + exact (hrframe baddr hbfront).trans ((hframe baddr + (by simpa only [List.length_map, n] using hbradix)).trans (congrFun ham baddr)) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean new file mode 100644 index 000000000..349ba2ea3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean @@ -0,0 +1,358 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Native frontend descriptor layout + +Descriptors have six words, three consecutive `(length,pointer)` pairs. Arrays occupy +disjoint blocks of capacity `n+1`; unused capacity is outside the compact enumeration. +The first four `n`-word blocks remain reserved for radix sorting and full multiplicities. + +## Main definitions + +* `Layout`: concrete descriptor and payload addresses. +* `array`: the compact list represented by one array slot. +* `Output`: exact descriptor words and payload readback, including empty arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Concrete layout at input length `n` and runtime label-bit budget `Λ`. -/ +structure Layout where + /-- Input length. -/ + n : ℕ + /-- Number of label bits. -/ + digits : ℕ + +namespace Layout + +/-- Number of bit-split triples. -/ +def splits (l : Layout) : ℕ := 2 * l.digits ^ 2 +/-- Total descriptor count, including the two multiplicity triples. -/ +def count (l : Layout) : ℕ := l.splits + 2 +/-- Total compact array count. -/ +def arrays (l : Layout) : ℕ := 3 * l.count +/-- Capacity allows singleton zero even when the original input is empty. -/ +def capacity (l : Layout) : ℕ := l.n + 1 +/-- Descriptor table follows all four radix buffers. -/ +def descriptors (l : Layout) : ℕ := 1 + 4 * l.n +/-- Payload blocks follow the complete descriptor table. -/ +def payloads (l : Layout) : ℕ := l.descriptors + 2 * l.arrays +/-- Pointer for array slot `j`. -/ +def pointer (l : Layout) (j : ℕ) : ℕ := l.payloads + l.capacity * j +/-- One-past-end address of all allocated blocks. -/ +def endpoint (l : Layout) : ℕ := l.pointer l.arrays + +/-- Payload pointers advance by the full reserved capacity. -/ +theorem pointer_succ (l : Layout) (j : ℕ) : l.pointer (j + 1) = l.pointer j + l.capacity := by + simp only [pointer, Nat.mul_add, Nat.mul_one, Nat.add_assoc] + +/-- Payload blocks occur in allocation order. -/ +theorem pointer_mono (l : Layout) {i j : ℕ} (h : i ≤ j) : l.pointer i ≤ l.pointer j := + Nat.add_le_add_left (Nat.mul_le_mul_left _ h) _ + +/-- Every descriptor word precedes every payload block. -/ +theorem descriptor_before (l : Layout) {j : ℕ} (hj : j < l.arrays) (i : ℕ) : + l.descriptors + 2 * j + 2 ≤ l.pointer i := by + dsimp only [pointer, payloads] + omega + +end Layout + +namespace Math + +/-- Selection mode for a physical array slot. -/ +def mode (Λ j : ℕ) : ℕ := + let t := j / 3 + let s := j % 3 + if t < 2 * Λ ^ 2 then s + else if t = 2 * Λ ^ 2 then if s = 0 then 3 else if s = 1 then 6 else 5 + else if s = 0 then 4 else 6 + +/-- Bit coordinates of a physical slot in the split part of the table. -/ +def beta (Λ j : ℕ) : ℕ := (j / 3 / 2) / Λ +@[inherit_doc beta] def beta' (Λ j : ℕ) : ℕ := (j / 3 / 2) % Λ +@[inherit_doc beta] def digit (j : ℕ) : ℕ := j / 3 % 2 + +/-- Compact encoded list for one physical array slot. -/ +def array (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : List ℕ := + if mode Λ j = 6 then [0] + else select U (beta Λ j) (beta' Λ j) (digit j) (mode Λ j) rs + +/-- The two bit coordinates are irrelevant in the three nonsplitting scan modes. -/ +theorem select_extra (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) (hm : 3 ≤ mode) : + select U β β' v mode rs = select U 0 0 0 mode rs := by + unfold select + congr 2 + funext p + unfold keep flag + split <;> first | rfl | omega + +/-- Physical array slots within a split triple follow A, B₀, B₁ order. -/ +theorem array_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t s : ℕ) + (ht : t < 2 * Λ ^ 2) (hs : s < 3) : + array U Λ rs (3 * t + s) = select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) s rs := by + have hd : (3 * t + s) / 3 = t := by omega + have hm : (3 * t + s) % 3 = s := by omega + have hn : s ≠ 6 := by omega + simp [array, mode, hd, hm, ht, hn, beta, beta', digit] + +/-- Read the three array specifications belonging to a descriptor. -/ +def root (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) : List ℕ × List ℕ × List ℕ := + (array U Λ rs (3 * t), array U Λ rs (3 * t + 1), array U Λ rs (3 * t + 2)) + +/-- A descriptor in the split prefix has exactly the requested three compact selections. -/ +theorem root_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) (ht : t < 2 * Λ ^ 2) : + root U Λ rs t = + (select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 0 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 1 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 2 rs) := by + have h0 := array_split U Λ rs t 0 ht (by decide) + simp only [Nat.add_zero] at h0 + simp only [root, h0, array_split U Λ rs t 1 ht (by decide), + array_split U Λ rs t 2 ht (by decide)] + +/-- The two final descriptors retain their singleton zeros and exact multiplicity scans. -/ +theorem root_extras (U Λ : ℕ) (rs : List (ℕ × ℕ)) : + root U Λ rs (2 * Λ ^ 2) = + (select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs) ∧ + root U Λ rs (2 * Λ ^ 2 + 1) = (select U 0 0 0 4 rs, [0], [0]) := by + constructor + all_goals simp [root, array, mode, select_extra, Nat.add_div, Nat.add_mod, + show ∀ n, ¬n + 1 < n from fun _ => by omega] + +/-- Every array fits in its `n+1` capacity, including empty-input singleton zeros. -/ +theorem length_array_le (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : + (array U Λ rs j).length ≤ rs.length + 1 := by + dsimp only [array] + split_ifs + · simp + · exact (length_select_le _ _ _ _ _ _).trans (by omega) + +/-- Every descriptor represents an injective enumeration. -/ +theorem array_nodup (U Λ : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) (j : ℕ) : (array U Λ rs j).Nodup := by + dsimp only [array] + split_ifs + · simp + · exact select_nodup _ _ _ _ _ h + +/-- Each output word is canonical zero, an original input key, or the key of twice an input. -/ +theorem array_values {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + u = 0 ∨ ∃ z ∈ input, u = Signed.encode z ∨ u = Signed.encode (2 * z) := by + unfold array at hu + split_ifs at hu + · exact .inl (by simpa only [List.mem_singleton] using hu) + · unfold select at hu + obtain ⟨p, hp, rfl⟩ := List.mem_map.mp hu + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr + ⟨p, (List.mem_filter.mp hp).1, rfl⟩)) + refine .inr ⟨z, hz, ?_⟩ + rw [← he] + unfold value + split_ifs + · exact .inr (twice_encode z) + · exact .inl rfl + +/-- Every compact output word is a canonical signed encoding. -/ +theorem array_canonical {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : Signed.encode (Signed.decode u) = u := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, _, rfl | rfl⟩ + · rfl + all_goals rw [Signed.decode_encode] + +/-- Injectivity holds for the represented integer array, as well as its native keys. -/ +theorem array_decoded_nodup {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (h : (rs.map Prod.fst).Nodup) (U Λ j : ℕ) : + ((array U Λ rs j).map Signed.decode).Nodup := by + apply List.Nodup.map_on _ (array_nodup U Λ h j) + intro u hu v hv he + rw [← array_canonical hkeys U Λ j u hu, ← array_canonical hkeys U Λ j v hv, he] + +/-- Every decoded payload integer has the exact reduction's common magnitude bound `2U`. -/ +theorem array_magnitude {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + |Signed.decode u| ≤ (2 * U : ℕ) := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, hz, rfl | rfl⟩ + · simp [Signed.decode] + · rw [Signed.decode_encode] + have := hvalues z hz + omega + · rw [Signed.decode_encode] + have := abs_le.mp (hvalues z hz) + apply abs_le.mpr + constructor <;> omega + +/-- The compact root lists before their native layout is assigned. -/ +def roots (U : ℕ) (rs : List (ℕ × ℕ)) : List (List ℕ × List ℕ × List ℕ) := + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ((List.range Λ).flatMap fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) ++ + [(select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs), + (select U 0 0 0 4 rs, [0], [0])] + +/-- Decode a compact triple to the finite sets used by the existing reduction. -/ +def decodeRoot (T : List ℕ × List ℕ × List ℕ) : Finset ℤ × Finset ℤ × Finset ℤ := + ((T.1.map Signed.decode).toFinset, (T.2.1.map Signed.decode).toFinset, + (T.2.2.map Signed.decode).toFinset) + +/-- Exact list equality with `ThreeSum.inputs`, preserving root order and every empty array. -/ +theorem roots_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + (roots U rs).map decodeRoot = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + simp only [roots, Cslib.Algorithms.ThreeSum.inputs, List.map_append, List.map_flatMap, + decodeRoot, select_A hkeys hcounts hvalues, + show ∀ β β' v, ((select U β β' v.toNat 1 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v false + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v false, + show ∀ β β' v, ((select U β β' v.toNat 2 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v true + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v true, + List.map_cons, List.map_nil, List.toFinset_cons, List.toFinset_nil, + Signed.decode, Nat.zero_mod, ↓reduceIte, Nat.zero_div, Int.natCast_zero, + Finset.insert_empty, select_twice hkeys hcounts, select_original hkeys hcounts, + select_zero hkeys hcounts] + +private theorem map_range_mul {α : Type*} (a b : ℕ) (f : ℕ → α) : + (List.range (a * b)).map f = + (List.range a).flatMap (fun i => (List.range b).map fun j => f (i * b + j)) := by + induction a with + | zero => simp + | succ a ih => + rw [Nat.succ_mul, List.range_add, List.map_append, ih, List.range_succ, + List.flatMap_append, List.flatMap_singleton] + simp [List.map_map, Function.comp_def, Nat.add_comm] + +/-- Physical descriptor indices have exactly the nested order of the finite root list. -/ +theorem roots_indexed (U : ℕ) (rs : List (ℕ × ℕ)) : + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = roots U rs := by + dsimp only + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + have hΛ : 0 < Λ := by dsimp [Λ, Cslib.Algorithms.ThreeSum.labelBits]; omega + have hlen : 2 * Λ ^ 2 = Λ * (Λ * 2) := by ring + change (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = _ + rw [List.range_add, List.map_append] + have hprefix : (List.range (2 * Λ ^ 2)).map (root U Λ rs) = + (List.range Λ).flatMap (fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) := by + rw [hlen, map_range_mul] + apply List.flatMap_congr + intro β hβ + rw [map_range_mul] + apply List.flatMap_congr + intro β' hβ' + have hb : β < Λ := List.mem_range.mp hβ + have hb' : β' < Λ := List.mem_range.mp hβ' + have hdiv : (β * Λ + β') / Λ = β := by + rw [Nat.add_comm, Nat.add_mul_div_right _ _ hΛ, Nat.div_eq_of_lt hb', Nat.zero_add] + have hmod : (β * Λ + β') % Λ = β' := Nat.mul_add_mod_of_lt hb' + have hlt0 : β * (Λ * 2) + (β' * 2 + 0) < 2 * Λ ^ 2 := by nlinarith + have hlt1 : β * (Λ * 2) + (β' * 2 + 1) < 2 * Λ ^ 2 := by nlinarith + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + root_split _ _ _ _ hlt0, root_split _ _ _ _ hlt1] + have hmul : β * (Λ * 2) = 2 * (β * Λ) := by ring + have hd0 : (β * (Λ * 2) + (β' * 2 + 0)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hd1 : (β * (Λ * 2) + (β' * 2 + 1)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hm0 : (β * (Λ * 2) + (β' * 2 + 0)) % 2 = 0 := by rw [hmul]; omega + have hm1 : (β * (Λ * 2) + (β' * 2 + 1)) % 2 = 1 := by rw [hmul]; omega + simp only [hd0, hd1, hm0, hm1, hdiv, hmod, Bool.toNat_false, Bool.toNat_true] + rw [hprefix] + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + Nat.add_zero, (root_extras U Λ rs).1, + (root_extras U Λ rs).2] + rfl + +end Math + +/-- Exact descriptor and compact payload readback. Unused capacity is unrestricted. -/ +def Output (l : Layout) (U : ℕ) (rs : List (ℕ × ℕ)) (mem : ℕ → ℕ) : Prop := + ∀ j < l.arrays, + mem (l.descriptors + 2 * j) = (Math.array U l.digits rs j).length ∧ + mem (l.descriptors + 2 * j + 1) = l.pointer j ∧ + Block.HoldsList (l.pointer j) (Math.array U l.digits rs j) mem + +/-- Follow one `(length,pointer)` pair in native memory. This is output interpretation, +not an instruction or an assumed computation by the frontend. -/ +def readArray (mem : ℕ → ℕ) (a : ℕ) : List ℕ := + (List.range (mem a)).map fun i => mem (mem (a + 1) + i) + +/-- Follow all six-word descriptors in order and decode their represented sets. -/ +def readRoots (l : Layout) (mem : ℕ → ℕ) : List (Finset ℤ × Finset ℤ × Finset ℤ) := + (List.range l.count).map fun t => + let a := l.descriptors + 6 * t + Math.decodeRoot (readArray mem a, readArray mem (a + 2), readArray mem (a + 4)) + +/-- Exact slot readback includes zero-length arrays and ignores unused capacity. -/ +theorem Output.read_array {l : Layout} {U : ℕ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output l U rs mem) {j : ℕ} (hj : j < l.arrays) : + readArray mem (l.descriptors + 2 * j) = Math.array U l.digits rs j := by + obtain ⟨hl, hp, hm⟩ := h j hj + unfold readArray + rw [hl, hp] + apply List.ext_getElem + · simp + · intro i hi hi' + simpa only [List.getElem_map, List.getElem_range] using hm.get i hi' + +/-- The descriptor table reads back as exactly the existing finite frontend list, in order. -/ +theorem Output.readback {n U : ℕ} {input : List ℤ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U rs mem) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + readRoots ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ mem = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + let l : Layout := ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + have he : readRoots l mem = + ((List.range l.count).map (Math.root U l.digits rs)).map Math.decodeRoot := by + simp only [readRoots, List.map_map, Function.comp_def] + apply List.map_congr_left + intro t ht + have hti : t < l.count := List.mem_range.mp ht + have hs (s : ℕ) (hs : s < 3) : + readArray mem (l.descriptors + 6 * t + 2 * s) = Math.array U l.digits rs (3 * t + s) := by + have hj : 3 * t + s < l.arrays := by dsimp only [Layout.arrays]; omega + have ha : l.descriptors + 6 * t + 2 * s = l.descriptors + 2 * (3 * t + s) := by omega + rw [ha] + exact h.read_array hj + have h0 := hs 0 (by decide) + have h1 := hs 1 (by decide) + have h2 := hs 2 (by decide) + simpa only [Math.root, Nat.mul_zero, Nat.add_zero, Nat.mul_one] using + congrArg Math.decodeRoot (congrArg₂ Prod.mk h0 (congrArg₂ Prod.mk h1 h2)) + rw [he] + have hi := Math.roots_indexed U rs + change (List.range l.count).map (Math.root U l.digits rs) = Math.roots U rs at hi + rw [hi] + exact Math.roots_decoded hkeys hcounts hvalues + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean new file mode 100644 index 000000000..9e704e6bd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean @@ -0,0 +1,282 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ThreeSets +public import Complexity.Computability.WordRAM.Signed.Encoding +public import Complexity.Computability.WordRAM.ListArray +public import Mathlib.Data.List.Nodup + +/-! +# Finite specifications for the native distinct-position frontend + +The mathematical sets are the existing `ThreeSum.splitA`, `ThreeSum.splitB`, +`ThreeSum.twiceSet` and `ThreeSum.zeroSet`. Native keys remain in unsigned encoding order. +The specifications here describe compact subsequences of the radix key/count arrays. + +## Main definitions + +* `label`: compute the shifted label from a canonical signed key. +* `twice`: the canonical key of twice the represented integer. +* `select`: compact selections from full key/count pairs. + +## Main results + +* `label_encode`: native label arithmetic agrees with the existing mathematical label. +* `twice_encode`: doubling has the required positive sign. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM +namespace Math + +/-- Shift a sign-and-magnitude key into the nonnegative label interval. -/ +def label (U u : ℕ) : ℕ := if u % 2 = 0 then 2 * U + u / 2 else 2 * U - u / 2 + +/-- The canonical key for twice the integer represented by a canonical key. -/ +def twice (u : ℕ) : ℕ := 2 * u - u % 2 + +/-- A mode selects A, B₀, B₁, repeated nonzero values, repeated zero, or all values. +Mode six is reserved for the separately written constant singleton zero. -/ +def flag (a b v mode : ℕ) (p : ℕ × ℕ) : Bool := + match mode with + | 0 => decide (a = v) + | 1 => decide (a ≠ v ∧ b = 0) + | 2 => decide (a ≠ v ∧ b = 1) + | 3 => decide (p.1 ≠ 0 ∧ 2 ≤ p.2) + | 4 => decide (p.1 = 0 ∧ 3 ≤ p.2) + | 5 => true + | _ => false + +/-- Selection from the two native label digits. -/ +def keep (U β β' v mode : ℕ) (p : ℕ × ℕ) : Bool := + flag ((label U p.1 >>> β) % 2) ((label U p.1 >>> β') % 2) v mode p + +/-- Key transformation after selection; only the repeated-nonzero mode doubles values. -/ +def value (mode u : ℕ) : ℕ := if mode = 3 then twice u else u + +/-- Compact output of one native scan. Multiplicities are never truncated. -/ +def select (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : List ℕ := + ((rs.filter (keep U β β' v mode)).map fun p => value mode p.1) + +/-- A single scan writes at most one cell per key/count pair. -/ +theorem length_select_le (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : + (select U β β' v mode rs).length ≤ rs.length := by + simpa only [select, List.length_map] using List.length_filter_le (keep U β β' v mode) rs + +/-- Native label arithmetic, including the absence of subtraction underflow. -/ +theorem label_encode {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + label U (Signed.encode z) = Cslib.Algorithms.ThreeSum.lab (2 * U) z := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + by_cases hneg : z < 0 + · have habs : z = -(z.natAbs : ℤ) := by + rw [Int.ofNat_natAbs_of_nonpos (by omega : z ≤ 0)] + omega + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, one_ne_zero, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + · have habs := Int.eq_natAbs_of_nonneg (by omega : 0 ≤ z) + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + +/-- The transient unsigned double is corrected by its low sign bit. -/ +theorem twice_encode (z : ℤ) : twice (Signed.encode z) = Signed.encode (2 * z) := by + have hs : (2 * z < 0) ↔ z < 0 := by omega + rw [twice, Signed.encode_mod_two] + simp only [Signed.encode, Int.natAbs_mul, show (2 : ℤ).natAbs = 2 from rfl, hs] + split_ifs <;> omega + +/-- Label arithmetic and the transient double stay within a common explicit bound. -/ +theorem key_bounds {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z ≤ 2 * U + 1 ∧ label U (Signed.encode z) ≤ 3 * U ∧ + Signed.encode z / 2 ≤ 2 * U ∧ 2 * Signed.encode z ≤ 4 * U + 2 := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + simp only [label, Signed.encode] + split_ifs <;> omega + +/-- The unsigned transformation is injective, even before restricting to canonical keys. -/ +theorem twice_injective : Function.Injective twice := by + intro a b h + have ha := Nat.mod_lt a (by decide : 0 < 2) + have hb := Nat.mod_lt b (by decide : 0 < 2) + dsimp only [twice] at h + omega + +/-- Every output value transformation preserves injectivity. -/ +theorem value_injective (mode : ℕ) : Function.Injective (value mode) := by + intro a b h + by_cases hm : mode = 3 + · exact twice_injective (by simpa only [value, hm, ↓reduceIte] using h) + · simpa only [value, hm, ↓reduceIte] using h + +/-- Every compact scan enumerates its output without repetitions. -/ +theorem select_nodup (U β β' v mode : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) : (select U β β' v mode rs).Nodup := by + have hk : ((rs.filter (keep U β β' v mode)).map Prod.fst).Nodup := + h.sublist ((List.filter_sublist).map _) + simpa only [select, List.map_map, Function.comp_def] using + hk.map (value_injective mode) + +/-- The supplied radix contracts identify every run with its exact input multiplicity. -/ +theorem mem_runs {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (p : ℕ × ℕ) : + p ∈ rs ↔ ∃ z ∈ input, p = (Signed.encode z, input.count z) := by + constructor + · intro hp + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr ⟨p, hp, rfl⟩)) + have hc := hcounts p hp + rw [← he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + exact ⟨z, hz, Prod.ext he.symm hc.symm⟩ + · rintro ⟨z, hz, rfl⟩ + obtain ⟨p, hp, he⟩ := List.mem_map.mp ((hkeys (Signed.encode z)).mpr + (List.mem_map.mpr ⟨z, hz, rfl⟩)) + have hc := hcounts p hp + rw [he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + have hp' : p = (Signed.encode z, input.count z) := Prod.ext he hc.symm + exact hp' ▸ hp + +/-- A compact native selection represents precisely its finite filtered image. -/ +theorem select_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v mode : ℕ) : + ((select U β β' v mode rs).map Signed.decode).toFinset = + (input.toFinset.filter fun z => keep U β β' v mode + (Signed.encode z, input.count z) = true).image + (fun z => Signed.decode (value mode (Signed.encode z))) := by + ext z + simp only [List.mem_toFinset, List.mem_map, select, List.mem_filter, Finset.mem_image, + Finset.mem_filter, mem_runs hkeys hcounts] + constructor + · rintro ⟨u, ⟨p, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + exact ⟨a, ⟨ha, hkeep⟩, rfl⟩ + · rintro ⟨a, ⟨ha, hkeep⟩, rfl⟩ + exact ⟨_, ⟨_, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + +/-- List multiplicity is exactly the number of original input positions. -/ +theorem count_positions (input : List ℤ) (z : ℤ) : + (Finset.univ.filter fun i : Fin input.length => input[i] = z).card = input.count z := + Fin.card_filter_univ_eq_vector_get_eq_count z ⟨input, rfl⟩ + +/-- Original input values are precisely the image of its positions. -/ +theorem input_values (input : List ℤ) : + Finset.univ.image (fun i : Fin input.length => input[i]) = input.toFinset := by + ext z + simp only [Finset.mem_image, Finset.mem_univ, true_and, List.mem_toFinset] + constructor + · rintro ⟨i, rfl⟩ + exact List.getElem_mem i.isLt + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp hz + exact ⟨⟨i, hi⟩, he⟩ + +/-- Native division/shift digits agree with the existing Boolean bit operation. -/ +theorem digit_eq (u β : ℕ) (v : Bool) : (u >>> β) % 2 = v.toNat ↔ u.testBit β = v := by + rw [Nat.shiftRight_eq_div_pow, ← Nat.toNat_testBit] + cases u.testBit β <;> cases v <;> decide + +/-- Zero has exactly one canonical key. -/ +theorem encode_eq_zero (z : ℤ) : Signed.encode z = 0 ↔ z = 0 := by + change Signed.encode z = Signed.encode 0 ↔ z = 0 + exact Signed.encode_injective.eq_iff + +/-- Exact bridge from the repeated-nonzero native scan to the existing `twiceSet`. -/ +theorem select_twice {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 3 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.twiceSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, ne_eq, encode_eq_zero, value, ↓reduceIte, + twice_encode, Signed.decode_encode, Cslib.Algorithms.ThreeSum.twiceSet, + input_values, count_positions] + +/-- The original-set scan preserves every distinct input value. -/ +theorem select_original {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 5 rs).map Signed.decode).toFinset = + Finset.univ.image (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts, input_values] + simp [keep, flag, value] + +/-- The zero scan uses the full original multiplicity and threshold three. -/ +theorem select_zero {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 4 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.zeroSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, encode_eq_zero, value, Nat.reduceEqDiff, + ↓reduceIte, Signed.decode_encode, Finset.image_id', Cslib.Algorithms.ThreeSum.zeroSet, + count_positions] + by_cases hc : 3 ≤ input.count 0 + · have hm : (0 : ℤ) ∈ input := List.count_pos_iff.mp (by omega) + ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.mem_singleton] + constructor + · exact fun h => h.2.1 + · rintro rfl + exact ⟨hm, rfl, hc⟩ + · ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.notMem_empty, + iff_false, not_and] + intro _ hz + subst z + exact hc + +/-- The A scan represents the exact existing bit split. -/ +theorem select_A {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v : Bool) : + ((select U β β' v.toNat 0 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitA (2 * U) β v + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + simp only [value, Nat.reduceEqDiff, ↓reduceIte, Signed.decode_encode, Finset.image_id'] + apply Finset.filter_congr + intro z hz + simp only [List.mem_toFinset] at hz + simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz), digit_eq] + +/-- Both B scans represent the exact existing two-bit split, including `β=β'`. -/ +theorem select_B {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v w : Bool) : + ((select U β β' v.toNat (if w then 2 else 1) rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v w + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + cases w + all_goals simp only [Bool.false_eq_true, ↓reduceIte, value, Nat.reduceEqDiff, + Signed.decode_encode, Finset.image_id'] + all_goals apply Finset.filter_congr + all_goals intro z hz + all_goals simp only [List.mem_toFinset] at hz + all_goals simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz)] + all_goals simp only [ne_eq, digit_eq _ _ v] + all_goals cases v <;> cases (Cslib.Algorithms.ThreeSum.lab (2 * U) z).testBit β <;> + simp only [Bool.false_eq_true, Bool.true_eq_false, not_false_eq_true, not_true_eq_false, + true_and, false_and, Bool.not_true, Bool.not_false] + all_goals first | exact digit_eq _ _ false | exact digit_eq _ _ true + +end Math +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean new file mode 100644 index 000000000..8463c2976 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Scan +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.RadixSort.Deduplicate + +/-! +# Initialized-input native distinct-position frontend + +The ABI has 128 registers. Radix uses 0–15; frontend locals use 16–63; the existing parameter +program uses 64–87. Registers 88–90 return descriptor base, descriptor count and heap end. +Each descriptor has six words `(length, pointer)` for its three compact injective arrays. +Each array has capacity `n+1`, including the singleton zeros required when `n=0`. + +## Main definitions + +* `Arrays.program`: native enumeration and descriptor construction. +* `program`: parameters, explicit-bit radix deduplication, and all frontend arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Arrays + +open Select Scan + +/-- Original input length and label-bit budget. -/ +abbrev inputLen : Fin 48 := 27 +@[inherit_doc inputLen] abbrev digits : Fin 48 := 28 +/-- Number of split triples, total triple count and total array count. -/ +abbrev splits : Fin 48 := 29 +@[inherit_doc splits] abbrev triples : Fin 48 := 30 +@[inherit_doc splits] abbrev arrays : Fin 48 := 31 +/-- Capacity per compact array. -/ +abbrev capacity : Fin 48 := 32 +/-- Next payload address. -/ +abbrev heap : Fin 48 := 33 +/-- Descriptor table base. -/ +abbrev descriptors : Fin 48 := 34 +/-- Current array index, current triple and slot. -/ +abbrev current : Fin 48 := 35 +@[inherit_doc current] abbrev triple : Fin 48 := 36 +@[inherit_doc current] abbrev slot : Fin 48 := 37 +/-- Arithmetic temporary and final heap endpoint. -/ +abbrev temp : Fin 48 := 38 +@[inherit_doc temp] abbrev heapEnd : Fin 48 := 39 + +/-- Write the singleton-zero array, including on empty input. -/ +def singleton : Code .standard 48 := ram[ *dst ← zero; out ← one ] + +/-- Compute the exact array selection mode for the two multiplicity triples. -/ +def extra : Code .standard 48 := ram[ + if triple = splits then [ + if slot = zero then [ mode ← three; ⟪Scan.program⟫ ] else [ + if slot = one then [ ⟪singleton⟫ ] else [ mode ← five; ⟪Scan.program⟫ ] ] + ] else [ + if slot = zero then [ mode ← four; ⟪Scan.program⟫ ] else [ ⟪singleton⟫ ] ] ] + +/-- Decode `β`, then `β'`, then `[false,true]`, and scan the indicated one of three arrays. -/ +def splitSetup : Code .standard 48 := ram[ + beta ← triple / two; beta' ← beta % digits; beta ← beta / digits; + digit ← triple % two; mode ← slot ] + +/-- Split setup followed by the complete compacting scan. -/ +def split : Code .standard 48 := .seq splitSetup Scan.program + +/-- Native choice between the split prefix and the two multiplicity triples. -/ +def pick : Code .standard 48 := .branch (.lt triple splits) split extra + +/-- Write a compact array's two descriptor words and advance the allocation cursor. -/ +def writeDescriptor : Code .standard 48 := ram[ + addr ← current + current; addr ← addr + descriptors; *addr ← out; + addr ← addr + one; *addr ← dst; + heap ← heap + capacity; current ← current + one ] + +/-- One complete array and its two descriptor words. -/ +def body : Code .standard 48 := + .seq (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) + (.seq pick writeDescriptor) + +/-- Initialize every constant, layout and cursor, then enumerate every array, including empties. -/ +def setup : Code .standard 48 := ram[ + zero ← #0; one ← #1; two ← #2; three ← #3; four ← #4; five ← #5; + beta ← #0; beta' ← #0; digit ← #0; + capacity ← inputLen + one; + splits ← digits * digits; splits ← splits * two; + triples ← splits + two; arrays ← triples * three; + temp ← arrays * two; heap ← descriptors + temp; + temp ← arrays * capacity; heapEnd ← heap + temp; + current ← #0 ] + +/-- Setup and all array iterations. -/ +def program : Code .standard 48 := .seq setup (.loop (.lt current arrays) body) + +end Arrays + +/-- Embed the 24-register parameter program above both native kernels. -/ +def parameterRegisters (i : Fin 24) : Fin 128 := ⟨64 + i.val, by omega⟩ +/-- Embed the 16-register radix program in the low registers. -/ +def radixRegisters (i : Fin 16) : Fin 128 := ⟨i.val, by omega⟩ +/-- Embed the 48-register frontend program between radix and parameters. -/ +def arrayRegisters (i : Fin 48) : Fin 128 := ⟨16 + i.val, by omega⟩ + +/-- Final descriptor base, descriptor count and one-past-end payload address. -/ +abbrev descriptorBase : Fin 128 := 88 +@[inherit_doc descriptorBase] abbrev descriptorCount : Fin 128 := 89 +@[inherit_doc descriptorBase] abbrev heapEnd : Fin 128 := 90 + +/-- Setup for the lower-level sorter with the magnitude-derived bit bound, never physical width. -/ +def radixSetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let B := parameterRegisters ChanHe.Parameters.digits + let radixLen := radixRegisters RadixSort.len + let radixSrc := radixRegisters RadixSort.src + let radixDst := radixRegisters RadixSort.dst + let radixBits := radixRegisters RadixSort.bits + ram[ + radixLen ← n; radixSrc ← #1; radixDst ← radixLen + radixSrc; radixBits ← B ] + +/-- Transfer actual parameters and radix outputs into the concrete frontend ABI. -/ +def arraySetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let U := parameterRegisters ChanHe.Parameters.magnitude + let B := parameterRegisters ChanHe.Parameters.digits + let distinct := radixRegisters Deduplicate.out + let len := arrayRegisters Scan.len + let mag := arrayRegisters Select.magnitude + let inputLen := arrayRegisters Arrays.inputLen + let digits := arrayRegisters Arrays.digits + let keys := arrayRegisters Scan.keys + let counts := arrayRegisters Scan.counts + let descriptors := arrayRegisters Arrays.descriptors + let one := arrayRegisters Select.one + ram[ + len ← distinct; mag ← U; inputLen ← n; digits ← B; one ← #1; + keys ← n + n; keys ← keys + one; + counts ← keys + n; descriptors ← counts + n ] + +/-- Return metadata while preserving parameter outputs and full radix key/count arrays. -/ +def finish : Code .standard 128 := + let base := arrayRegisters Arrays.descriptors + let count := arrayRegisters Arrays.triples + let endpoint := arrayRegisters Arrays.heapEnd + ram[ descriptorBase ← base; descriptorCount ← count; heapEnd ← endpoint ] + +/-- Native frontend after the existing complete parameter program. -/ +def construct : Code .standard 128 := + .seq radixSetup (.seq (RadixSort.deduplicateCode.rename radixRegisters) + (.seq arraySetup (.seq (Arrays.program.rename arrayRegisters) finish))) + +/-- One finite native program for each fixed polynomial magnitude exponent. -/ +def program (κ : ℕ) : Code .standard 128 := + .seq ((ChanHe.Parameters.program κ).rename parameterRegisters) construct + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean new file mode 100644 index 000000000..086209b83 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Select + +/-! +# Compact native frontend scans + +Each pass reads both canonical keys and their full multiplicities. It appends only selected +keys to a disjoint destination. The singleton-zero arrays are written separately, so empty +input needs no exceptional semantics. + +## Main definitions + +* `Scan.program`: charged compact selection from the radix output. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan + +open Cslib.WordRAM Select + +/-- Key pointer, count pointer and number of distinct keys. -/ +abbrev keys : Fin 48 := 20 +@[inherit_doc keys] abbrev counts : Fin 48 := 21 +@[inherit_doc keys] abbrev len : Fin 48 := 22 +/-- Output pointer, input cursor and output length. -/ +abbrev dst : Fin 48 := 23 +@[inherit_doc dst] abbrev idx : Fin 48 := 24 +@[inherit_doc dst] abbrev out : Fin 48 := 25 +/-- Address scratch. -/ +abbrev addr : Fin 48 := 26 + +/-- Load one full key/count pair, compute its selection, optionally append, and advance. -/ +def body : Code .standard 48 := + .seq (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) + (.seq Select.program (ram[ + if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ])) + +/-- Reset both cursors and scan all distinct keys. -/ +def program : Code .standard 48 := + .seq (ram[ idx ← #0; out ← #0 ]) (.loop (.lt idx len) body) + +/-- One charged compacting iteration. -/ +theorem step {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hsmall : Fits w 5) + (hkaddr : Fits w (x.regs keys + x.regs idx)) + (hcaddr : Fits w (x.regs counts + x.regs idx)) + (hdaddr : Fits w (x.regs dst + x.regs out)) + (hi : Fits w (x.regs idx + 1)) (ho : Fits w (x.regs out + 1)) + (hkey : Fits w (x.mem (x.regs keys + x.regs idx))) + (habs : x.mem (x.regs keys + x.regs idx) / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.mem (x.regs keys + x.regs idx))) : + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + ∃ y, ExecWithin w body x y 41 ∧ + y.regs idx = x.regs idx + 1 ∧ y.regs out = x.regs out + k.toNat ∧ + y.mem = (if k then Function.update x.mem (x.regs dst + x.regs out) + (Math.value (x.regs mode) p.1) else x.mem) := by + dsimp only + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let a := (((x.set addr (x.regs keys + x.regs idx)).set key p.1).set + addr (x.regs counts + x.regs idx)).set count p.2 + have ha : ExecWithin w + (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) x a 4 := by + ram_eval [a, p, hkaddr, hcaddr, hkey] + obtain ⟨u, hu, hut, huk, hum⟩ := Select.exec a + (by simpa +decide [a] using hz) (by simpa +decide [a] using h1) + (by simpa +decide [a] using h2) (by simpa +decide [a] using h3) + (by simpa +decide [a] using h4) (by simpa +decide [a] using h5) + hkey (by simpa +decide [a, p] using habs) (by simpa +decide [a] using hfit) + hdouble hsmall + have hui : u.regs idx = x.regs idx := by + simpa +decide [a] using hu.regs_eq (i := idx) (by decide) + have huo : u.regs out = x.regs out := by + simpa +decide [a] using hu.regs_eq (i := out) (by decide) + have hud : u.regs dst = x.regs dst := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hu1 : u.regs one = 1 := by + simpa +decide [a] using (hu.regs_eq (i := one) (by decide)).trans + (show a.regs one = 1 by simpa +decide [a] using h1) + simp +decide only [a, Data.set_regs, Function.update_apply, ↓reduceIte] at hut huk hum + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + have hv : Fits w (Math.value (x.regs mode) p.1) := by + dsimp only [Math.value, Math.twice] + split_ifs + · exact hdouble.of_le (Nat.sub_le _ _) + · exact hkey + have hdo : Fits w (u.regs dst + u.regs out) := by simpa only [hud, huo] using hdaddr + have hii : Fits w (u.regs idx + 1) := by simpa only [hui] using hi + have hoo : Fits w (u.regs out + 1) := by simpa only [huo] using ho + have hvu : Fits w (u.regs key) := by rw [huk]; exact hv + by_cases he : k = true + · have ht : u.regs take = 1 := by rw [hut]; change k.toNat = 1; simp [he] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?posY 6 := by + ram_eval [ht, hu1, hdo, hii, hoo, hvu] + refine ⟨?posY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he] + · simp +decide [Data.set_mem, Data.write_mem, hud, huo, huk, hum, k, p, he] + · have he' : k = false := Bool.eq_false_iff.mpr he + have ht : u.regs take = 0 := by rw [hut]; change k.toNat = 0; simp [he'] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?negY 6 := by + ram_eval [ht, hu1, hii] + refine ⟨?negY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he'] + · simpa only [k, p, he', Bool.false_eq_true, ↓reduceIte, Data.set_mem] using hum + +/-- Complete compact scan, with arbitrary old output/scratch memory and an exact memory frame. +The source key/count blocks precede the output; their contents and all caller registers that +the body does not write are retained. -/ +theorem exec {w : ℕ} (rs : List (ℕ × ℕ)) (x : Data 48) + (hn : x.regs len = rs.length) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hk : Block.HoldsList (x.regs keys) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (x.regs counts) (rs.map Prod.snd) x.mem) + (hkey : ∀ p ∈ rs, Fits w p.1) + (habs : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude) + (hdouble : ∀ p ∈ rs, Fits w (2 * p.1)) + (hfit : Fits w (4 * x.regs magnitude + 2)) (hsmall : Fits w 5) + (hka : Fits w (x.regs keys + rs.length)) + (hca : Fits w (x.regs counts + rs.length)) + (hda : Fits w (x.regs dst + rs.length + 1)) + (hkd : x.regs keys + rs.length ≤ x.regs dst) + (hcd : x.regs counts + rs.length ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) + (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, body.Preserves j → y.regs j = x.regs j) := by + let selected (i : ℕ) := Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (rs.take i) + have hselected (i : ℕ) : (selected i).length ≤ min i rs.length := + (Math.length_select_le _ _ _ _ _ _).trans_eq List.length_take + let I (y : Data 48) : Prop := + (∀ j, body.Preserves j → y.regs j = x.regs j) ∧ + y.regs idx ≤ rs.length ∧ y.regs out = (selected (y.regs idx)).length ∧ + Block.HoldsList (x.regs keys) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (x.regs counts) (rs.map Prod.snd) y.mem ∧ + Block.HoldsList (x.regs dst) (selected (y.regs idx)) y.mem ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 41 ∧ I z ∧ + rs.length - z.regs idx < rs.length - y.regs idx := by + obtain ⟨hr, hle, hout, hkeys, hcounts, hacc, hframe⟩ := hy + have hyl := (hr len (by decide)).trans hn + have hys := hr keys (by decide) + have hyc := hr counts (by decide) + have hyd := hr dst (by decide) + have hyU := hr magnitude (by decide) + have hyb := hr beta (by decide) + have hyb' := hr beta' (by decide) + have hyv := hr digit (by decide) + have hym := hr mode (by decide) + have hi : y.regs idx < rs.length := by simpa only [Cond.holds_lt, hyl] using ht + have hgetk : y.mem (y.regs keys + y.regs idx) = rs[y.regs idx].1 := by + rw [hys] + exact (hkeys.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hgetc : y.mem (y.regs counts + y.regs idx) = rs[y.regs idx].2 := by + rw [hyc] + exact (hcounts.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hu : y.regs out ≤ y.regs idx := by + rw [hout]; exact (hselected _).trans (min_le_left _ _) + obtain ⟨z, he, hzi, hzo, hzm⟩ := step y + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) hsmall + (hka.of_le (by rw [hys]; omega)) (hca.of_le (by rw [hyc]; omega)) + (hda.of_le (by rw [hyd]; omega)) (hka.of_le (by omega)) (hda.of_le (by omega)) + (by rw [hgetk]; exact hkey _ (List.getElem_mem hi)) + (by rw [hgetk, hyU]; exact habs _ (List.getElem_mem hi)) + (by simpa only [hyU] using hfit) + (by rw [hgetk]; exact hdouble _ (List.getElem_mem hi)) + dsimp only at hzo hzm + rw [hyU, hyb, hyb', hyv, hym, hgetk, hgetc] at hzo hzm + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs[y.regs idx] + change z.regs out = y.regs out + k.toNat at hzo + change z.mem = (if k then Function.update y.mem (y.regs dst + y.regs out) + (Math.value (x.regs mode) rs[y.regs idx].1) else y.mem) at hzm + have hsuc : selected (y.regs idx + 1) = selected (y.regs idx) ++ + (if k then [Math.value (x.regs mode) rs[y.regs idx].1] else []) := by + dsimp only [selected] + rw [List.take_succ_eq_append_getElem hi] + simp only [Math.select, List.filter_append, List.map_append, List.filter_cons, + List.filter_nil] + split_ifs <;> rfl + have hf (a : ℕ) (hne : a ≠ x.regs dst + y.regs out) : z.mem a = y.mem a := by + rw [hzm, hyd] + split_ifs <;> simp only [Function.update_of_ne hne] + refine ⟨z, he, ?_, by omega⟩ + refine ⟨?_, by omega, ?_, ?_, ?_, ?_, ?_⟩ + · intro j hj + exact (he.regs_eq hj).trans (hr j hj) + · rw [hzi, hsuc, List.length_append, hzo, hout] + cases k <;> simp + · apply hkeys.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · apply hcounts.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · rw [hzi, hsuc] + by_cases htake : k = true + · rw [ite_eq_left htake] + have hm : z.mem = Function.update y.mem (x.regs dst + (selected (y.regs idx)).length) + (Math.value (x.regs mode) rs[y.regs idx].1) := by + simpa only [htake, ↓reduceIte, hyd, hout] using hzm + rw [hm] + exact hacc.append_one _ + · have hfalse : k = false := Bool.eq_false_iff.mpr htake + rw [ite_eq_right htake, List.append_nil] + have hm : z.mem = y.mem := by simpa only [hfalse, Bool.false_eq_true, ↓reduceIte] + using hzm + simpa only [hm] using hacc + · intro a hnot + have hne : a ≠ x.regs dst + y.regs out := by + intro ha + apply hnot + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + rs.length + omega + exact (hf a hne).trans (hframe a hnot) + let x₀ := (x.set idx 0).set out 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, hk, hc, ?_, Modifies.refl _ _⟩ + · intro j hj + have hji : j ≠ idx := by rintro rfl; exact (by decide : ¬body.Preserves idx) hj + have hjo : j ≠ out := by rintro rfl; exact (by decide : ¬body.Preserves out) hj + simp only [x₀, Data.set_regs, Function.update_of_ne hji, Function.update_of_ne hjo] + · simp +decide [x₀] + · simp +decide [x₀, selected, Math.select] + · simpa +decide [x₀, selected, Math.select] using Block.holdsList_nil (x.regs dst) x.mem + obtain ⟨y, he, hy, ht⟩ := ExecWithin.loop_invariant I + (fun y => rs.length - y.regs idx) hbody hx₀ + obtain ⟨hr, hle, hout, _, _, hacc, hframe⟩ := hy + have hend : y.regs idx = rs.length := by + have hyl := (hr len (by decide)).trans hn + simp only [Cond.holds_lt, hyl] at ht + omega + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; out ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + refine ⟨y, ?_, ?_, ?_, hframe, hr⟩ + · convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, ↓reduceIte, Nat.sub_zero] + omega + · simpa only [hend, selected, List.take_length] using hacc + · simpa only [hend, selected, List.take_length] using hout + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean new file mode 100644 index 000000000..7eb8c2d17 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean @@ -0,0 +1,250 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +public import Complexity.Computability.WordRAM.Notation + +/-! +# Native selection of canonical signed keys + +All arithmetic is unsigned. The sign bit and magnitude are decoded explicitly, the label +is formed by addition or subtraction, and its two digits are computed with native shifts and +remainders. The repeated-value case doubles the key and subtracts its original sign bit. + +## Main definitions + +* `Select.program`: concrete selection and transformation instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Select + +/-- Magnitude bound, followed by two bit positions, selected digit and scan mode. -/ +abbrev magnitude : Fin 48 := 0 +@[inherit_doc magnitude] abbrev beta : Fin 48 := 1 +@[inherit_doc magnitude] abbrev beta' : Fin 48 := 2 +@[inherit_doc magnitude] abbrev digit : Fin 48 := 3 +@[inherit_doc magnitude] abbrev mode : Fin 48 := 4 +/-- Input key; replaced by its selected output value. -/ +abbrev key : Fin 48 := 5 +/-- Full input multiplicity, read without truncation. -/ +abbrev count : Fin 48 := 6 +/-- Small constants installed by the scan. -/ +abbrev zero : Fin 48 := 7 +@[inherit_doc zero] abbrev one : Fin 48 := 8 +@[inherit_doc zero] abbrev two : Fin 48 := 9 +@[inherit_doc zero] abbrev three : Fin 48 := 10 +@[inherit_doc zero] abbrev four : Fin 48 := 11 +@[inherit_doc zero] abbrev five : Fin 48 := 12 +/-- Scratch registers for decoding and label digits. -/ +abbrev sign : Fin 48 := 13 +@[inherit_doc sign] abbrev label : Fin 48 := 14 +@[inherit_doc sign] abbrev bitA : Fin 48 := 15 +@[inherit_doc sign] abbrev bitB : Fin 48 := 16 +/-- Selection flag, either zero or one. -/ +abbrev take : Fin 48 := 17 +/-- Magnitude temporary. -/ +abbrev absolute : Fin 48 := 18 + +/-- Explicit native sign/magnitude decoding, shifted label and binary digit computation. -/ +def labels : Code .standard 48 := ram[ + sign ← key % two; absolute ← key / two; label ← magnitude + magnitude; + if sign = zero then [ label ← label + absolute ] else [ label ← label - absolute ]; + bitA ← label >>> beta; bitA ← bitA % two; + bitB ← label >>> beta'; bitB ← bitB % two ] + +/-- A no-op used by unsuccessful selections. -/ +def skip : Code .standard 48 := ram[ take ← take ] + +/-- Select one of the six finite scan modes and perform the repeated-value transformation. -/ +def choose : Code .standard 48 := ram[ + take ← #0; + if mode = zero then [ + if bitA = digit then [ take ← one ] else [ ⟪skip⟫ ] + ] else [ if mode = one then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = zero then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = two then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = one then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = three then [ + if key = zero then [ ⟪skip⟫ ] else [ + if count < two then [ ⟪skip⟫ ] else [ take ← one ] ]; + key ← key + key; key ← key - sign + ] else [ if mode = four then [ + if key = zero then [ + if count < three then [ ⟪skip⟫ ] else [ take ← one ] + ] else [ ⟪skip⟫ ] + ] else [ if mode = five then [ take ← one ] else [ ⟪skip⟫ ] ] ] ] ] ] ] + +/-- Every decoding, arithmetic, branch and selection instruction is part of this code. -/ +def program : Code .standard 48 := .seq labels choose + +/-- This scalar program never reads the physical word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- Registers below the decoding scratch are preserved by label calculation. -/ +theorem labels_preserves (j : Fin 48) (hj : j.val ≤ 12) : labels.Preserves j := by + have hne (k : Fin 48) (hk : 13 ≤ k.val) : (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [labels, Code.Preserves, Op.dest] + exact ⟨hne sign (by decide), hne absolute (by decide), hne label (by decide), + ⟨hne label (by decide), hne label (by decide)⟩, hne bitA (by decide), + hne bitA (by decide), hne bitB (by decide), hne bitB (by decide)⟩ + +/-- Selection preserves all caller parameters, constants and scan registers. -/ +theorem preserves (j : Fin 48) (hj : (j.val ≤ 12 ∧ j.val ≠ 5) ∨ 19 ≤ j.val) : + program.Preserves j := by + have hne (k : Fin 48) (hk : k.val = 5 ∨ 13 ≤ k.val ∧ k.val ≤ 18) : + (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [program, labels, choose, skip, Code.Preserves, Op.bin, Op.dest] + repeat' constructor + all_goals exact hne _ (by decide) + +/-- Decoding and both binary digits cost at most eleven native instructions. -/ +theorem labels_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h2 : x.regs two = 2) + (hkey : Fits w (x.regs key)) + (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) : + ∃ y, ExecWithin w labels x y 11 ∧ + y.regs sign = x.regs key % 2 ∧ + y.regs label = Math.label (x.regs magnitude) (x.regs key) ∧ + y.regs bitA = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta) % 2 ∧ + y.regs bitB = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta') % 2 ∧ + y.mem = x.mem := by + have fm := hkey.of_le (Nat.div_le_self _ 2) + have fs := hfit.of_le (show x.regs key % 2 ≤ 4 * x.regs magnitude + 2 by omega) + have fd := hfit.of_le (show x.regs magnitude + x.regs magnitude ≤ + 4 * x.regs magnitude + 2 by omega) + have fa := hfit.of_le (show x.regs magnitude + x.regs magnitude + x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + have fb := hfit.of_le (show x.regs magnitude + x.regs magnitude - x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + by_cases he : x.regs key % 2 = 0 + · have fsa := fa.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fa.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fa, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + · have fsa := fb.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fb.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fb, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + +/-- Selection from already computed digits, with the full repeated-value count. -/ +theorem choose_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hs : x.regs sign = x.regs key % 2) + (hfit : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w choose x y 20 ∧ + y.regs take = (Math.flag (x.regs bitA) (x.regs bitB) (x.regs digit) + (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + have f1 := hsmall.of_le (show 1 ≤ 5 by decide) + have fd : Fits w (x.regs key + x.regs key) := by simpa only [two_mul] using hfit + have fs : Fits w (x.regs key + x.regs key - x.regs key % 2) := fd.of_le (Nat.sub_le _ _) + rcases (show x.regs mode = 0 ∨ x.regs mode = 1 ∨ x.regs mode = 2 ∨ + x.regs mode = 3 ∨ x.regs mode = 4 ∨ x.regs mode = 5 ∨ 6 ≤ x.regs mode by omega) with + hm | hm | hm | hm | hm | hm | hm + · by_cases ha : x.regs bitA = x.regs digit + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 0 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 1 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases hk : x.regs key = 0 + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hs, fd, fs] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice] + · by_cases hc : x.regs count < 2 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, hs, + fd, fs, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice, hc, + show (2 ≤ x.regs count) ↔ ¬x.regs count < 2 from by omega, two_mul] + · by_cases hk : x.regs key = 0 + · by_cases hc : x.regs count < 3 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, hc, + show (3 ≤ x.regs count) ↔ ¬x.regs count < 3 from by omega] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, f1] + all_goals simp +decide [Data.set_regs, hm, Math.value, Math.flag] + · have hn0 : x.regs mode ≠ 0 := by omega + have hn1 : x.regs mode ≠ 1 := by omega + have hn2 : x.regs mode ≠ 2 := by omega + have hn3 : x.regs mode ≠ 3 := by omega + have hn4 : x.regs mode ≠ 4 := by omega + have hn5 : x.regs mode ≠ 5 := by omega + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hn0, hn1, hn2, hn3, hn4, hn5] + all_goals simp +decide [Data.set_regs, Math.value, Math.flag, hn3] + +/-- Complete native scalar selection. Its cost includes decoding, both label bits and doubling. -/ +theorem exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hkey : Fits w (x.regs key)) (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w program x y 31 ∧ + y.regs take = (Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + obtain ⟨u, hu, hus, _, hua, hub, hum⟩ := labels_exec x hz h2 hkey habs hfit + have hr (j : Fin 48) (hj : j.val ≤ 12) : u.regs j = x.regs j := + hu.regs_eq (labels_preserves j hj) + obtain ⟨y, hy, hyt, hyk, hym⟩ := choose_exec u + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) + (by simpa only [hr key (by decide)] using hus) + (by simpa only [hr key (by decide)] using hdouble) hsmall + refine ⟨y, hu.seq hy, ?_, ?_, hym.trans hum⟩ + · simpa only [Math.keep, hr mode (by decide), hr digit (by decide), hr key (by decide), + hr count (by decide), hua, hub] using hyt + · simpa only [hr mode (by decide), hr key (by decide)] using hyk + +end Select +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean new file mode 100644 index 000000000..02d4d8a19 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds + +/-! +# Uniform logarithmic-square native frontend cost + +The bound includes the complete parameter program, explicit-bit radix sorting, full +multiplicities, every compact scan, all descriptor/payload setup and writes, and the final halt. +It is uniform over all admitted sufficiently wide words, including empty input. + +## Main results + +* `cost_bound`: an explicit `Oκ((n+1) clog₂(n+2)²)` instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +/-- Explicit uniform coefficient; deliberately coarse and independent of physical word width. -/ +def costCoefficient (κ : ℕ) : ℕ := 2000 * (20 * κ + 121) ^ 2 + 400 * κ + 4000 + +/-- Concrete derivation from the actual charged instruction formula to logarithmic-square cost. -/ +theorem cost_bound (κ n : ℕ) : + cost κ n ≤ costCoefficient κ * (n + 1) * Nat.clog 2 (n + 2) ^ 2 := by + let L := Nat.clog 2 (n + 2) + let K := 20 * κ + 121 + let Λ := (layout κ n).digits + let A := (layout κ n).arrays + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hK : 1 ≤ K := by dsimp [K]; omega + have hΛ : Λ ≤ K * L := digits_bound κ n + have hKL : 1 ≤ K * L := by nlinarith + have hKLsq : K * L ≤ (K * L) ^ 2 := by nlinarith + have hAs : A ≤ 12 * (K * L) ^ 2 := by + have hs := Nat.pow_le_pow_left hΛ 2 + dsimp only [A, Layout.arrays, Layout.count, Layout.splits, Λ] + nlinarith + have hRad : RadixSort.deduplicateTime n Λ ≤ (37 * Λ + 43) * (n + 1) := by + dsimp only [RadixSort.deduplicateTime] + nlinarith + have hRcoef : 37 * Λ + 43 ≤ 80 * (K * L) ^ 2 := by nlinarith + have hR := hRad.trans (Nat.mul_le_mul_right (n + 1) hRcoef) + have hA := Nat.mul_le_mul hAs (show 43 * n + 32 ≤ 75 * (n + 1) by omega) + have hParams := ChanHe.Parameters.time_bound κ n + have hsqrt := Nat.sqrt_le_self n + have hLsq : L ≤ L ^ 2 := by nlinarith + have hLN : L ≤ (n + 1) * L ^ 2 := by nlinarith + have hN : n + 1 ≤ (n + 1) * L ^ 2 := by nlinarith + have hC := Nat.mul_le_mul_left (400 * κ + 3000) hLN + change ChanHe.Parameters.time κ n ((n + 2) ^ κ) + RadixSort.deduplicateTime n Λ + + A * (43 * n + 32) + 38 ≤ _ + dsimp only [costCoefficient, K, L] at * + nlinarith + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Computability/WordRAM/GhostLoop.lean b/Complexity/Computability/WordRAM/GhostLoop.lean new file mode 100644 index 000000000..8f3b8e7d3 --- /dev/null +++ b/Complexity/Computability/WordRAM/GhostLoop.lean @@ -0,0 +1,59 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Structured + +/-! +# Native loop invariants with explicit proof state + +An invariant may relate the native data to additional mathematical proof state, such as the +represented worklist and emitted output. The decreasing measure then applies directly to that +proof state. It need not be reconstructed by an additional machine program or a choice of +representation from the native data alone. + +## Main results + +* `ExecWithin.loop_invariant_state`: the native loop rule with explicit proof state and a natural + decreasing measure, including all loop-control instructions in the bound. + +This is an original extension of the existing native invariant/measure rule. The body premise +is actual `ExecWithin` execution of the given native code, not a source execution or time oracle. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- Native loop rule carrying explicit mathematical proof state. The body must execute the +actual native code, preserve the relation and strictly decrease the proof-state measure. -/ +theorem ExecWithin.loop_invariant_state {B : Basis} {r w T : ℕ} {σ : Type*} + (I : σ → Data r → Prop) (μ : σ → ℕ) {cond : Cond r} {body : Code B r} + (hbody : ∀ s x, I s x → cond.Holds x → + ∃ s' y, ExecWithin w body x y T ∧ I s' y ∧ μ s' < μ s) + {s : σ} {x : Data r} (hx : I s x) : + ∃ s' y, ExecWithin w (.loop cond body) x y (μ s * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + have aux : ∀ n, ∀ s x, μ s ≤ n → I s x → + ∃ s' y, ExecWithin w (.loop cond body) x y (n * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + intro n + induction n using Nat.strong_induction_on with + | h n ih => + intro s x hμ hI + by_cases hc : cond.Holds x + · obtain ⟨s₁, y, hy, hIy, hdec⟩ := hbody s x hI hc + obtain ⟨s₂, z, hz, hIz, hexit⟩ := ih (μ s₁) (by omega) s₁ y le_rfl hIy + refine ⟨s₂, z, (ExecWithin.loop_iter hc hy hz).mono ?_, hIz, hexit⟩ + have hm := Nat.mul_le_mul_right (T + 2) (show μ s₁ + 1 ≤ n by omega) + rw [Nat.add_mul, Nat.one_mul] at hm + omega + · exact ⟨s, x, (ExecWithin.loop_exit hc).mono (by omega), hI, hc⟩ + exact aux (μ s) s x le_rfl hx + +end Cslib.WordRAM diff --git a/ComplexityTests/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean new file mode 100644 index 000000000..bd68b60be --- /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/NativeChanHeTraversal.lean b/ComplexityTests/NativeChanHeTraversal.lean new file mode 100644 index 000000000..32e6ea740 --- /dev/null +++ b/ComplexityTests/NativeChanHeTraversal.lean @@ -0,0 +1,256 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalExec +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +private def fixture : Data 128 where + regs i := if i = registers Stack.ptr then 20 else if i = registers Stack.len then 0 + else if 64 ≤ i.val ∧ i.val < 71 then i.val - 60 else 79 + mem _ := 79 + +private def roundTrip : Code .standard 128 := .seq pushFrame popFrame + +-- A complete native record round trip restores all seven words, keeps low kernel registers, +-- and preserves memory on both sides of the reserved stack. Old stack contents are nonzero. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 8 16 roundTrip fixture).map (fun (y, _) => + (List.ofFn (fun i : Fin 7 => y.regs (slot i)), + y.regs 0, y.regs (registers Stack.len), y.mem 19, y.mem 27)) = + some ([4, 5, 6, 7, 8, 9, 10], 79, 0, 79, 79)) + +-- Address arithmetic really runs in the admitted physical width. +/-- info: true -/ +#guard_msgs in +#eval (Code.eval 1 16 pushFrame fixture).isNone + +private theorem fixture_rep : Stack.Rep 8 20 7 [] (fixture.comap registers) := by + refine ⟨?_, ?_, ?_, by decide, fun i => Fin.elim0 i⟩ + · constructor + · intro i + change Fits 8 (fixture.regs (registers i)) + dsimp only [fixture] + split + · decide + · split + · decide + · split + · change (registers i).val - 60 < 256 + have := (registers i).isLt + omega + · decide + · intro a + change Fits 8 79 + decide + · decide + · decide + +-- The native transfer contracts have a fully admitted client, including old nonzero memory. +example : ∃ y, Exec 8 roundTrip fixture y 163 ∧ + Stack.Rep 8 20 7 [] (y.comap registers) ∧ + (∀ i, y.regs (slot i) = fixture.regs (slot i)) := by + have hwords (i : Fin 7) : Fits 8 (fixture.regs (slot i)) := by + dsimp only [fixture] + split + · decide + · split + · decide + · split + · change (slot i).val - 60 < 256 + have := (slot i).isLt + omega + · decide + obtain ⟨z, hz, hrz, _, _⟩ := pushFrame_exec fixture [] (by decide) (by decide) + fixture_rep hwords (by decide) + obtain ⟨y, hy, hry, hslots, _, _⟩ := popFrame_exec z [] + (fun i => fixture.regs (slot i)) (by decide) (by decide) hrz + exact ⟨y, hz.seq hy, hry, hslots⟩ + +-- Pending entries include zero-fuel calls; capacity bounds are neither empty nor universal. +private def root : Math.Task := ⟨1, fun _ => {0}⟩ +example : Math.Bounded 1 [root] ∧ ¬Math.Bounded 0 [root] ∧ Math.work 1 = 4 := by + norm_num [Math.Bounded, root, Math.work] + +-- A zero-fuel call produces no node even with three nonempty sets. +example : (Math.step {2} 1 ⟨0, fun _ => {0}⟩ []).1 = [] ∧ + (Math.step {2} 1 ⟨0, fun _ => {0}⟩ []).2 = none := by + decide + +private def treeInput (f n : ℕ) (z : ℤ) : Data 128 where + regs i := match i.val with + | 64 => f + | 65 => 1 + | 66 => n + | 67 => 2 + | 68 => n + | 69 => 3 + | 70 => n + | 71 => 2 + | 72 => 1 + | 73 => 1 + | 74 => 8 + | 75 => 10 + | 76 => 1 + | 77 => 100 + | 78 => 60 + | 79 => 0 + | 80 => 20 + | 81 => 21 + | _ => 79 + mem a := if a = 1 ∨ a = 2 ∨ a = 3 then Signed.encode z + else if a = 10 ∨ a = 11 then 0 else if a = 12 then 1 else 79 + +private def observeTree (f n : ℕ) (z : ℤ) := + (Code.eval 16 32 oneRoot (treeInput f n z)).map fun (y, _) => + (y.regs outputCount, y.regs heap, y.mem 60, y.regs (registers Stack.len), + (List.range 32).map (fun i => y.mem (106 + i))) + +private def packedSingleton (z : ℤ) : List ℕ := + let ν : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{z}, {z}, {z}, 4⟩ + (List.range 32).map fun i => Signed.encode (ν.oneArray 1 i) + +-- Zero fuel and empty sets are actually popped and skipped without calling either node kernel. +/-- info: true -/ +#guard_msgs in +#eval decide ((observeTree 0 1 0).map (fun r => (r.1, r.2.1, r.2.2.1)) = some (0, 100, 79)) + +/-- info: true -/ +#guard_msgs in +#eval decide ((observeTree 1 0 0).map (fun r => (r.1, r.2.1, r.2.2.1)) = some (0, 100, 79)) + +-- Actual full controller: both prime searches, all three node counts, all packed stores, +-- three native child pushes, and the three zero-fuel pops. Every packed cell is checked. +/-- info: true -/ +#guard_msgs in +#eval decide (observeTree 1 1 0 = some (1, 145, 106, 0, packedSingleton 0)) + +-- The same complete traversal executes negative signed input and negated-Z placement. +/-- info: true -/ +#guard_msgs in +#eval decide (observeTree 1 1 (-1) = some (1, 145, 106, 0, packedSingleton (-1))) + +private def branchingInput : Data 128 where + regs i := if i = slot 2 then 3 else if i = slot 3 then 4 else if i = slot 5 then 5 + else if i = digits then 4 else if i = magnitude then 4 + else if i = stackCapacity then 35 else (treeInput 2 1 0).regs i + mem a := match a with + | 1 => Signed.encode 0 + | 2 => Signed.encode 4 + | 3 => Signed.encode 1 + | 4 => Signed.encode 0 + | 5 => Signed.encode (-1) + | _ => (treeInput 2 1 0).mem a + +private def expectedBranching : List (List ℕ) := + let ν₀ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4, 1}, {0}, {-1}, 4⟩ + let ν₁ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4}, {0}, {-1}, 4⟩ + [ν₀, ν₁].map fun ν => (List.range 32).map fun i => Signed.encode (ν.oneArray 4 i) + +-- Only the first column has heavy elements. The child changes that descriptor and keeps the +-- other two, then its zero-fuel children and both empty siblings are popped without outputs. +-- The two packed arrays differ, so this also checks their preorder and exact readback. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 16 64 oneRoot branchingInput).map (fun (y, _) => + (y.regs outputCount, y.regs heap, y.regs (registers Stack.len), + [y.mem 60, y.mem 61], [108, 156].map fun p => (List.range 32).map fun i => y.mem (p + i))) = + some (2, 196, 0, [108, 156], expectedBranching)) + +private theorem treeInput_zero_bounded : (treeInput 0 1 0).Bounded 8 := by + constructor + · intro i + dsimp only [treeInput] + split <;> decide + · intro a + dsimp only [treeInput] + split + · decide + · split + · decide + · split <;> decide + +-- All premises of the complete-controller theorem are discharged in a concrete native state. +example : ∃ y, Exec 8 oneRoot (treeInput 0 1 0) y 173 ∧ + y.regs outputCount = 0 ∧ y.regs heap = 100 := by + obtain ⟨y, hy, _, _, hc, hh, _⟩ := oneRoot_zero (capacity := 7) (treeInput 0 1 0) + treeInput_zero_bounded (by decide) (by decide) (by decide) rfl + exact ⟨y, hy, hc, hh⟩ + +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 8 16 oneRoot (treeInput 0 1 0)).map Prod.snd = some 173) + +private def admittedTree : Data 128 where + regs i := if i = zero then 0 else if i = one then 1 else (treeInput 1 1 0).regs i + mem := (treeInput 1 1 0).mem + +private def admittedEntry : Entry := ⟨1, ![1, 2, 3], fun _ => [0]⟩ + +private def admittedArena : Arena where + n := 1 + m := 2 + Λ := 1 + U := 1 + N := 8 + fuel := 1 + primes := 10 + stack := 20 + capacity := 21 + table := 60 + outputCapacity := 4 + floor := 100 + ceiling := 280 + retained := admittedTree.mem + +private theorem admittedTree_bounded : admittedTree.Bounded 66 := by + constructor + · intro i + dsimp only [admittedTree] + split + · decide + · split + · decide + · dsimp only [treeInput] + split <;> decide + · intro p + dsimp only [admittedTree, treeInput] + split + · decide + · split + · decide + · split <;> decide + +-- All premises of the complete live root theorem hold in one concrete native allocation. +-- This includes both searches, real histograms/packing, depth, width and space reservations. +private theorem admittedRoot : RootInput admittedArena 64 admittedEntry [] admittedTree := by + refine ⟨admittedTree_bounded, by unfold Arena.Controls; decide, + by unfold Entry.Loaded; decide, by unfold Entry.Stored Block.Realizes; decide, + ?_, ?_, by unfold Entry.Around; decide, + by decide, rfl, by decide, by decide, rfl, by decide, storedNodes_nil _ _ _ _, + fun i => Fin.elim0 i, fun _ _ => rfl⟩ + · refine ⟨by decide, ?_⟩ + refine ⟨by decide, by decide, by decide, ?_, ?_, ?_⟩ <;> + simp [Cslib.Algorithms.ThreeSum.Bdd, admittedEntry] + · intro q hq + change q ≤ 2 at hq + interval_cases q <;> decide + +example : ∃ (ns : List (ℕ × Cslib.Algorithms.ThreeSum.ChanHe.Node)) (y : Data 128), + ExecWithin 66 oneRoot admittedTree y (rootTime 1 1 2 8) ∧ + ns.map Prod.snd = admittedEntry.task.output (Nat.primesLE 2) 1 ∧ + StoredNodes 1 8 60 ns y.mem ∧ y.regs outputCount = ns.length := by + obtain ⟨ns, y, he, hi, hout, _, _⟩ := root_exec admittedEntry [] admittedTree + (by unfold Arena.Separated; decide) (by unfold Arena.Ready; decide) admittedRoot + exact ⟨ns, y, he, by simpa only [List.map_nil, List.nil_append, admittedArena] using hout, + hi.output, hi.count⟩ + +-- The common envelope is a real restriction; width 1 cannot admit this live allocation. +example : ¬admittedArena.Ready 1 := by unfold Arena.Ready; decide diff --git a/ComplexityTests/WordRAMChanHeModulus.lean b/ComplexityTests/WordRAMChanHeModulus.lean new file mode 100644 index 000000000..655224e24 --- /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 000000000..dc16eb04c --- /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 000000000..cbdd66497 --- /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 000000000..43c6cb6a4 --- /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/ComplexityTests/WordRAMThreeSumFrontend.lean b/ComplexityTests/WordRAMThreeSumFrontend.lean new file mode 100644 index 000000000..a11266ed8 --- /dev/null +++ b/ComplexityTests/WordRAMThreeSumFrontend.lean @@ -0,0 +1,90 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +meta import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program + +/-! +# Executed initialized-input frontend boundary cases + +These checks execute the complete structured native program, including parameters and radix. +The observation follows each concrete descriptor and decodes its compact payload to a set. +-/ + +set_option linter.privateModule false + +open Cslib.WordRAM Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +private meta def observe (κ w : ℕ) (input : List ℤ) : + Option (List (Finset ℤ × Finset ℤ × Finset ℤ) × ℕ) := + ((program κ).eval w 512 (Data.init (input.map Signed.encode))).map fun (x, time) => + let array (a : ℕ) := ((List.range (x.mem a)).map fun i => + Signed.decode (x.mem (x.mem (a + 1) + i))).toFinset + ((List.range (x.regs descriptorCount)).map fun i => + let a := x.regs descriptorBase + 6 * i + (array a, array (a + 2), array (a + 4)), time) + +private meta def exact (κ : ℕ) (input : List ℤ) : Bool := + (observe κ 64 input).map (fun p => p.1 == Cslib.Algorithms.ThreeSum.inputs + ((input.length + 2) ^ κ) (fun i : Fin input.length => input[i])) == some true + +-- Empty arrays coexist with all constant singleton-zero arrays; exponent zero is admitted. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [] + +-- One and two positions are kept exactly; signed endpoints remain canonical. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [-1] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [-4, 4] + +-- Nonzero multiplicity one versus two; twiceSet uses positive 2*a. +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, -2] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, 1, -2] + +-- Zero multiplicity two versus three, and full multiplicity above three. +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0] + +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0, 0] + +/-- info: true -/ +#guard_msgs in +#eval exact 0 [0, 0, 0, 0, -1, -1, 1] + +-- Actual distinct-position yes/no inputs, including three distinct values and equal bit positions. +/-- info: true -/ +#guard_msgs in +#eval exact 1 [-3, 1, 2] + +/-- info: true -/ +#guard_msgs in +#eval exact 1 [1, 2, 3] + +-- Two successful physical widths give identical output and charged time. +-- Both are below the exported sufficient threshold of 1692 for this input. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 1 64 [-3, 1, 2] = observe 1 512 [-3, 1, 2]) + +-- Invalid width faults during genuine setup/layout arithmetic. +/-- info: true -/ +#guard_msgs in +#eval decide (observe 0 3 [] = none) diff --git a/ComplexityTests/WordRAMThreeSumFrontendContracts.lean b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean new file mode 100644 index 000000000..08cf0b89d --- /dev/null +++ b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean @@ -0,0 +1,106 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +import Complexity.Algorithms.WordRAM.ThreeSum.Frontend + +/-! +# Fully admissible clients of the complete native frontend + +The execution observations are in `WordRAMThreeSumFrontend`. These clients consume proved +native contracts, discharge all global promises, and check integer injectivity and signed-width +composition. They do not claim interpreter execution. +-/ + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +-- The empty case still has twenty triples and sixty array slots at κ=0. +example : width 0 0 = 484 ∧ (layout 0 0).count = 20 ∧ (layout 0 0).arrays = 60 := by + decide +kernel + +example : ∃ (y : Data 128) (steps : ℕ), + Executes 4096 ((program 0).program descriptorCount) (State.init []) 20 steps ∧ + Runs 4096 ((program 0).program descriptorCount) (State.init []) + ⟨(program 0).size, y⟩ (steps - 1) ∧ + steps ≤ cost 0 0 ∧ readRoots (layout 0 0) y.mem = + Cslib.Algorithms.ThreeSum.inputs 1 (fun i : Fin 0 => ([] : List ℤ)[i]) := by + obtain ⟨y, steps, he, hr, ht, ho, _, _, _, _⟩ := runs_input (κ := 0) (w := 4096) [] (by decide) + (by simp) + exact ⟨y, steps, he, hr, ht, ho⟩ + +-- The signed endpoints satisfy both the input promise and the physical/signed width offset. +example : ∃ steps, + Executes 4096 ((program 1).program descriptorCount) + (State.init ([(-4 : ℤ), 4].map Signed.encode)) (layout 1 2).count steps ∧ + steps ≤ cost 1 2 := by + have hv : ∀ z ∈ ([(-4 : ℤ), 4] : List ℤ), |z| ≤ ((2 + 2) ^ 1 : ℕ) := by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false] at hz + rcases hz with rfl | rfl <;> decide + obtain ⟨y, steps, he, _, ht, _, _, _, _, _⟩ := + runs_input (κ := 1) (w := 4096) [(-4 : ℤ), 4] (by decide) hv + exact ⟨steps, he, ht⟩ + +-- A generic caller obtains actual native slots, injective integer arrays and full multiplicities. +example {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hv : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ y rs, ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y + (cost κ input.length - 1) ∧ + Output (layout κ input.length) ((input.length + 2) ^ κ) rs y.mem ∧ + (∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) ∧ + (∀ j < (layout κ input.length).arrays, + ((readArray y.mem ((layout κ input.length).descriptors + 2 * j)).map Signed.decode).Nodup ∧ + ∀ u ∈ readArray y.mem ((layout κ input.length).descriptors + 2 * j), + Cslib.GenericRAM.Signed.Fits (w - 2) (Signed.decode u)) := by + obtain ⟨y, rs, he, ho, _, _, _, hn, _, hk, hc, _, _, _, _⟩ := exec_input input hw hv + refine ⟨y, rs, he, ho, fun p hp => (hc p hp).2, ?_⟩ + intro j hj + rw [ho.read_array hj] + refine ⟨Math.array_decoded_nodup hk hn _ _ j, ?_⟩ + intro u hu + exact signed_output_fits hw (Math.array_magnitude hk hv _ j u hu) + +-- A polynomial allocation bound includes descriptor addresses as well as all output payloads. +example (κ n : ℕ) : (layout κ n).endpoint ≤ (n + 2) ^ (80 * κ + 480) := by + exact (Arrays.layout_bounds (layout κ n) ((n + 2) ^ κ)).2.2.2.2.2.2.2.2.1 |>.trans + (array_budget_polynomial κ n) + +-- The original distinct-position problem admits equal values, but never reuses a position. +example : Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => + ([1, 1, -2] : List ℤ)[i]) := by + unfold Cslib.Algorithms.ThreeSum.HasSolution + decide +kernel + +example : ¬Cslib.Algorithms.ThreeSum.HasSolution (fun i : Fin 3 => + ([1, 2, 3] : List ℤ)[i]) := by + unfold Cslib.Algorithms.ThreeSum.HasSolution + decide +kernel + +-- The two width promises are explicit; a physical width of three is not globally admissible. +example : ¬width 0 0 ≤ 3 ∧ + Cslib.GenericRAM.Signed.Fits (4096 - 2) (-8) := by + constructor + · decide + · exact signed_output_fits (κ := 1) (n := 2) (by decide) (by decide) + +-- Complete array construction also accepts arbitrary old output and scratch memory. +example (mem : ℕ → ℕ) : + let x : Data 48 := ⟨(fun i => match i.val with + | 0 | 20 | 21 | 34 => 1 + | 28 => 3 + | _ => 0), mem⟩ + ∃ y, ExecWithin 16 Arrays.program x y (60 * 32 + 21) ∧ + Output ⟨0, 3⟩ 1 [] y.mem ∧ + Modifies (Block.mk 1 180).footprint mem y.mem := by + dsimp only + obtain ⟨y, he, ho, _, _, _, _, hf⟩ := Arrays.exec (w := 16) (U := 1) ⟨0, 3⟩ [] + ⟨(fun i => match i.val with | 0 | 20 | 21 | 34 => 1 | 28 => 3 | _ => 0), mem⟩ + rfl rfl rfl rfl rfl rfl rfl (by decide) (Block.holdsList_nil _ _) (Block.holdsList_nil _ _) + (by simp) (by decide) + exact ⟨y, he, ho, hf⟩ diff --git a/ROADMAP.md b/ROADMAP.md index 5fc6cb5a6..0b28c7dea 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -407,8 +407,17 @@ each statement against the numbered result. Organize modules by subject, not cha communication and interactive classes, with amplification, containments and complete problems. +[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 +[#1463]: https://github.com/cslib-community/complexitylib/issues/1463 +[#1481]: https://github.com/cslib-community/complexitylib/issues/1481 +[#1482]: https://github.com/cslib-community/complexitylib/issues/1482 + ## Machines, RAM and data structures +- **Native distinct-position frontend** ([#1482]): initialized signed input produces exact + compact root descriptors, with derived logarithmic width and uniform + `Oκ((n+1) log²(n+2))` charged cost. Full preprocessing still needs heavy-extraction and + packed-node integration, recursive traversal, packed output and global width/time composition. - **Efficient universal simulation** ([#800]): realize the buffered Hennie–Stearns backend with time `O_M(t log(t + 2))`, together with a source-space-sensitive storage bound. The current interpreter has a stronger polynomial certificate and a streaming input lookup; @@ -429,6 +438,16 @@ 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, + collision-tested minimum-search and complete two-prime modulus programs have explicit local + fit/frame/cost contracts. The complete native distinct-position frontend ([#1482]) starts from + initialized signed input and produces exact compact root descriptors with derived logarithmic + width and uniform `Oκ((n+1) log²(n+2))` charged cost. The complete native one-node component + ([#1481]) computes all residues/histograms, three compact heavy arrays and exact packed output + in linear time. Native bounded recursive traversal ([#1512]) executes those components with an + explicit stack, exact preorder output, polynomial local fit/frame contracts and charged + ternary-tree bounds. Initialized-input forest integration and global preprocessing width/time + composition remain. - **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 +463,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,6 +617,7 @@ 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 [#1524]: https://github.com/cslib-community/complexitylib/issues/1524 diff --git a/UPSTREAM.md b/UPSTREAM.md index b009e0bce..2fcbe7ea5 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -177,6 +177,15 @@ 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 are composed by a fixed 128-register bounded traversal with an explicit seven-word + stack frame and exact preorder packed output. The traversal consumes a root descriptor and + runtime fuel; its polynomial local envelope derives kernel fits and charges every stack, + descriptor and address operation. It does not invoke a Convolution-3SUM solver. Composition + from initialized input through all frontend roots, global logarithmic width and subquadratic + preprocessing bounds remain in #1446. These components do not affect the curated headlines. `Algorithms/ThreeSum/ThreeSets.lean` and `ArrayPacking.lean` provide the generic distinct-position frontend and three-array packing. `Algorithms/ThreeSum/ChanHe/` now adapts the core mathematical