diff --git a/Complexity.lean b/Complexity.lean index 4d8216846c..5e76bbf984 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -61,6 +61,8 @@ 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.ShiftedSizes public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes public import Complexity.Algorithms.ThreeSum.Convolution public import Complexity.Algorithms.ThreeSum.Defs @@ -146,17 +148,86 @@ 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.Preprocessing +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingBounds +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingEntries +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingExecute +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingFinish +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingIntermediates +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLayout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoad +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingPrimes +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingReserve +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSnapshot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingStep +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingWidth public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Query public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Search +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.Frame +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Input +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +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 @@ -1092,6 +1163,7 @@ public import Complexity.Computability.GenericRAM.Native.MeasurementFold public import Complexity.Computability.GenericRAM.Native.Nat public import Complexity.Computability.GenericRAM.Native.SubsetPairFold public import Complexity.Computability.GenericRAM.Signed +public import Complexity.Computability.GenericRAM.Signed.Fits public import Complexity.Computability.GenericRAM.Simulation.Finite public import Complexity.Computability.GenericRAM.Simulation.Run public import Complexity.Computability.GenericRAM.Simulation.Turing @@ -1725,6 +1797,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 @@ -1758,6 +1831,7 @@ public import Complexity.Computability.WordRAM.Signed.Arithmetic public import Complexity.Computability.WordRAM.Signed.Comparison public import Complexity.Computability.WordRAM.Signed.Compile public import Complexity.Computability.WordRAM.Signed.Encoding +public import Complexity.Computability.WordRAM.Signed.Fits public import Complexity.Computability.WordRAM.Signed.Input public import Complexity.Computability.WordRAM.Signed.Operations public import Complexity.Computability.WordRAM.Signed.State @@ -1765,6 +1839,7 @@ public import Complexity.Computability.WordRAM.Structured public import Complexity.Computability.WordRAM.Subroutine public import Complexity.Computability.WordRAM.Time public import Complexity.Computability.WordRAM.Trace +public import Complexity.Computability.WordRAM.Width public import Complexity.Crypto.Computational.Basic public import Complexity.Crypto.Computational.CollisionResistant public import Complexity.Crypto.Computational.Commitment diff --git a/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean new file mode 100644 index 0000000000..e8e22d0e9e --- /dev/null +++ b/Complexity/Algorithms/ThreeSum/ChanHe/SelectorBounds.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Defs + +/-! +# Size bounds for the Chan–He selectors, including their default + +Bounding the modulus does not require either filter to succeed: a failed filter returns one. +These bounds therefore apply to native execution with arbitrary nonnegative collision thresholds, +including empty input sets. They do not assert the collision-shrinking property of a successful +search. + +## Main results + +* `minOrOne_filter_bounds`: a positive bounded candidate set keeps its filtered selector bounded. +* `modulus_bounds_of_candidates`: both selections, including defaults, have product in `[1,m²]`. + +The bounds are original elementary consequences of the existing finite selectors. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ThreeSum.ChanHe + +open Finset + +/-- Filtering positive candidates bounded by `m` keeps the default-one selector in `[1,m]`. +The candidate set and the filtered set may both be empty. -/ +theorem minOrOne_filter_bounds {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (A : ℕ → Prop) [DecidablePred A] : + 1 ≤ minOrOne {q ∈ Q | A q} ∧ minOrOne {q ∈ Q | A q} ≤ m := by + let S := {q ∈ Q | A q} + by_cases h : S.Nonempty + · have hmem : S.min' h ∈ Q := (mem_filter.mp (min'_mem S h)).1 + change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_left h] + exact ⟨hpos _ hmem, hle _ hmem⟩ + · change 1 ≤ minOrOne S ∧ minOrOne S ≤ m + rw [minOrOne, dite_eq_right h] + exact ⟨le_rfl, hm⟩ + +/-- Positive bounded candidates bound the modulus regardless of whether either filter succeeds. +This is a size bound only; no magnitude or successful-search premise is needed. -/ +theorem modulus_bounds_of_candidates {Q : Finset ℕ} {m : ℕ} (hm : 1 ≤ m) + (hpos : ∀ q ∈ Q, 1 ≤ q) (hle : ∀ q ∈ Q, q ≤ m) + (Λ : ℕ) (S₁ S₂ S₃ : Finset ℤ) : + 1 ≤ modulus Q Λ S₁ S₂ S₃ ∧ modulus Q Λ S₁ S₂ S₃ ≤ m ^ 2 := by + have hp : 1 ≤ firstP Q Λ S₁ S₂ S₃ ∧ firstP Q Λ S₁ S₂ S₃ ≤ m := + minOrOne_filter_bounds hm hpos hle _ + have hq : 1 ≤ secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ∧ + secondP Q Λ S₁ S₂ S₃ (firstP Q Λ S₁ S₂ S₃) ≤ m := + minOrOne_filter_bounds hm hpos hle _ + exact ⟨Nat.mul_le_mul hp.1 hq.1, + (Nat.mul_le_mul hp.2 hq.2).trans_eq (pow_two m).symm⟩ + +end Cslib.Algorithms.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean b/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean new file mode 100644 index 0000000000..adb73ffd09 --- /dev/null +++ b/Complexity/Algorithms/ThreeSum/ChanHe/ShiftedSizes.lean @@ -0,0 +1,74 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Sizes + +/-! +# Size bounds for the initialized-input magnitude convention + +Native parameter computation uses `(n+2)^κ`, including at lengths zero and one. The existing +finite instances retain their actual input-length parameter `n`; only their quantitative upper +bounds are compared with the existing estimates at `n+2`. + +## Main results + +* `mPar_mono_input`: candidate bounds are monotone in input length. +* `mPar_shifted_pow_le`: square-root times squared-logarithm bound for the actual native modulus. +* `length_instances_shifted_pow_le`: degree-seven logarithmic instance count at every length. +* `packed_length_shifted_pow_le`: linear times fourth-logarithm common packed length. + +These elementary parameter bridges are original and reuse the existing finite Chan–He bounds. +They do not change the finite instances or replace the actual input length by a padded input. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.ThreeSum.ChanHe + +/-- Increasing the length parameter increases the candidate window without changing magnitude. -/ +theorem mPar_mono_input {n n' : ℕ} (hn : n ≤ n') (U : ℕ) : mPar n U ≤ mPar n' U := by + have hs := Nat.sqrt_le_sqrt hn + unfold mPar wPar + gcongr + +/-- The actual native parameter keeps length `n` and uses shifted polynomial magnitude. -/ +theorem mPar_shifted_pow_le (n κ : ℕ) : + mPar n (2 * (n + 2) ^ κ) ≤ + (5 * κ + 16) * (10 * κ + 40) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 2 := + (mPar_mono_input (show n ≤ n + 2 by omega) _).trans (mPar_pow_le (n + 2) κ) + +/-- The unchanged actual finite instance list has a logarithmic count even at lengths zero/one. -/ +theorem length_instances_shifted_pow_le (n κ : ℕ) (x : Fin n → ℤ) : + (instances n ((n + 2) ^ κ) x).length ≤ numBound κ (n + 2) := by + have hlog : 1 ≤ Nat.log 2 (n + 2) := Nat.log_pos (by decide) (by omega) + have hΛ : labelBits (2 * (n + 2) ^ κ) ≤ (κ + 2) * (Nat.log 2 (n + 2) + 1) := + (labelBits_pow_le (n + 2) κ).trans (by rw [add_mul]; omega) + have hone : 1 ≤ ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2 := + Nat.one_le_pow _ _ (by positivity) + have hn : Nat.log 2 n ≤ Nat.log 2 (n + 2) := Nat.log_mono_right (by omega) + calc (instances n ((n + 2) ^ κ) x).length + ≤ (2 * labelBits (2 * (n + 2) ^ κ) ^ 2 + 2) * (2 * Nat.log 2 n + 2) ^ 5 := + length_instances_le .. + _ ≤ (2 * ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2 + + 2 * ((κ + 2) * (Nat.log 2 (n + 2) + 1)) ^ 2) * + (2 * Nat.log 2 (n + 2) + 2) ^ 5 := by gcongr; omega + _ = numBound κ (n + 2) := by unfold numBound; ring + +/-- The unchanged common packed length is bounded by the old estimate evaluated at `n+2`. -/ +theorem packed_length_shifted_pow_le (κ n : ℕ) : + 8 * mPar n (2 * (n + 2) ^ κ) ^ 2 ≤ + 32 * ((5 * κ + 16) * (10 * κ + 40)) ^ 2 * (n + 2) * + (Nat.log 2 (n + 2) + 1) ^ 4 := by + calc 8 * mPar n (2 * (n + 2) ^ κ) ^ 2 ≤ lenOf κ (n + 2) := + Nat.mul_le_mul_left 8 (Nat.pow_le_pow_left + (mPar_mono_input (show n ≤ n + 2 by omega) _) 2) + _ ≤ _ := lenOf_le κ (n + 2) (by omega) + +end Cslib.Algorithms.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean new file mode 100644 index 0000000000..aedff6aaea --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/BucketCounts.lean @@ -0,0 +1,171 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Collisions + +/-! +# Histogram buckets for the native Chan–He node construction + +These bridges identify position counts with the existing set-based buckets. Injectivity is +essential: two positions containing the same integer do not make that integer heavy. +The proofs are original; no source machine semantics is used. + +## Main definitions + +* `residueKey`: the unsigned Euclidean key already computed by `Count.program`. + +## Main results + +* `count_eq_card_bucket`: histogram counts equal bucket cardinalities. +* `mem_heavy_iff_count`: the native count test recognizes the existing heavy subset. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +open Finset Cslib.Algorithms.ThreeSum.ChanHe + +/-- The ordinary unsigned remainder of an integer modulo a positive natural modulus. -/ +def residueKey (M : ℕ) (z : ℤ) : ℕ := (z % (M : ℤ)).toNat + +/-- Interpreting a key as an integer recovers its Euclidean remainder. -/ +theorem residueKey_cast {M : ℕ} (hM : 0 < M) (z : ℤ) : + (residueKey M z : ℤ) = z % (M : ℤ) := + Int.toNat_of_nonneg (Int.emod_nonneg _ (by omega)) + +/-- Every key is a valid histogram address offset. -/ +theorem residueKey_lt {M : ℕ} (hM : 0 < M) (z : ℤ) : residueKey M z < M := by + have := residueKey_cast hM z + have := Int.emod_lt_of_pos z (show (0 : ℤ) < M by omega) + omega + +/-- Filtering positions before taking their image gives exactly the existing bucket. -/ +theorem bucket_image (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) (b : ℕ) : + bucket ((range n).image values) M b = + ({i ∈ range n | residueKey M (values i) = b}).image values := by + ext z + simp only [bucket, mem_filter, mem_image] + constructor + · rintro ⟨⟨i, hi, rfl⟩, hb⟩ + exact ⟨i, ⟨hi, by rw [residueKey, hb]; simp⟩, rfl⟩ + · rintro ⟨i, ⟨hi, hb⟩, rfl⟩ + exact ⟨⟨i, hi, rfl⟩, by rw [← residueKey_cast hM, hb]⟩ + +/-- Injective input enumeration makes the native histogram a set-cardinality computation. -/ +theorem count_eq_card_bucket (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) (b : ℕ) : + Histogram.count (fun i => residueKey M (values i)) n b = + (bucket ((range n).image values) M b).card := by + rw [bucket_image values n hM b, card_image_of_injOn (hinj.mono (by + intro i hi + exact (mem_filter.mp hi).1))] + rfl + +/-- A member is heavy precisely when its residue bucket has at least two elements. -/ +theorem mem_heavy_iff_bucket {S : Finset ℤ} {M : ℕ} {z : ℤ} (hz : z ∈ S) : + z ∈ heavy S M ↔ 1 < (bucket S M (z % (M : ℤ))).card := by + have hzB : z ∈ bucket S M (z % (M : ℤ)) := mem_filter.mpr ⟨hz, rfl⟩ + rw [heavy, mem_filter, and_iff_right hz] + constructor + · rintro ⟨y, hy, hne, hd⟩ + have hyB : y ∈ bucket S M (z % (M : ℤ)) := + mem_filter.mpr ⟨hy, ((natCast_dvd_sub_iff_emod_eq M z y).mp hd).symm⟩ + have hsub : {z, y} ⊆ bucket S M (z % (M : ℤ)) := by + intro a ha + rcases mem_insert.mp ha with rfl | ha + · exact hzB + · exact (mem_singleton.mp ha) ▸ hyB + have := card_le_card hsub + have hc : 2 ≤ (bucket S M (z % (M : ℤ))).card := by simpa [hne.symm] using this + omega + · intro hc + have hnot : ¬bucket S M (z % (M : ℤ)) ⊆ {z} := by + intro h + have := card_le_card h + simp only [card_singleton] at this + omega + obtain ⟨y, hy, hne⟩ := not_subset.mp hnot + obtain ⟨hyS, hyR⟩ := mem_filter.mp hy + exact ⟨y, hyS, by simpa only [mem_singleton] using hne, + (natCast_dvd_sub_iff_emod_eq M z y).mpr hyR.symm⟩ + +/-- The count test in the native scan selects exactly `ChanHe.heavy`. -/ +theorem mem_heavy_iff_count (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) {i : ℕ} (hi : i < n) : + values i ∈ heavy ((range n).image values) M ↔ + 1 < Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + rw [count_eq_card_bucket values n hM hinj, residueKey_cast hM] + exact mem_heavy_iff_bucket (mem_image.mpr ⟨i, mem_range.mpr hi, rfl⟩) + +/-- An input position contributes at least one to its own bucket. -/ +theorem count_residue_pos (values : ℕ → ℤ) (n M : ℕ) {i : ℕ} (hi : i < n) : + 0 < Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + exact card_pos.mpr ⟨i, mem_filter.mpr ⟨mem_range.mpr hi, rfl⟩⟩ + +/-- A singleton histogram bucket gives the exact set and hence its unique light value. -/ +theorem bucket_eq_singleton_of_count (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(range n) : Set ℕ)) {i : ℕ} (hi : i < n) + (hc : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1) : + bucket ((range n).image values) M (residueKey M (values i)) = {values i} := by + have hm : values i ∈ bucket ((range n).image values) M (residueKey M (values i)) := + mem_filter.mpr ⟨mem_image.mpr ⟨i, mem_range.mpr hi, rfl⟩, + (residueKey_cast hM _).symm⟩ + have hcard := (count_eq_card_bucket values n hM hinj (residueKey M (values i))).symm.trans hc + obtain ⟨z, hz⟩ := card_eq_one.mp hcard + rw [hz, mem_singleton] at hm + exact hz.trans (congrArg singleton hm.symm) + +/-- No two distinct positions can be light in the same bucket, even before injectivity is used. -/ +theorem eq_of_count_one (values : ℕ → ℤ) (n M : ℕ) {i j : ℕ} + (hi : i < n) (hj : j < n) + (hc : Histogram.count (fun k => residueKey M (values k)) n (residueKey M (values i)) = 1) + (he : residueKey M (values i) = residueKey M (values j)) : i = j := by + have hi' : i ∈ {k ∈ range n | residueKey M (values k) = residueKey M (values i)} := + mem_filter.mpr ⟨mem_range.mpr hi, rfl⟩ + have hj' : j ∈ {k ∈ range n | residueKey M (values k) = residueKey M (values i)} := + mem_filter.mpr ⟨mem_range.mpr hj, he.symm⟩ + obtain ⟨k, hk⟩ := card_eq_one.mp hc + rw [hk, mem_singleton] at hi' hj' + exact hi'.trans hj'.symm + +/-- Residue equality is unchanged by simultaneous negation. -/ +theorem residueKey_neg_eq_iff {M : ℕ} (hM : 0 < M) (a b : ℤ) : + residueKey M (-a) = residueKey M (-b) ↔ residueKey M a = residueKey M b := by + rw [← Int.natCast_inj, residueKey_cast hM, residueKey_cast hM, + ← Int.natCast_inj, residueKey_cast hM, residueKey_cast hM] + change -a ≡ -b [ZMOD (M : ℤ)] ↔ a ≡ b [ZMOD (M : ℤ)] + exact Int.neg_modEq_neg + +/-- A histogram built on original residues also classifies the negated third set exactly. -/ +theorem count_neg_residue (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) (i : ℕ) : + Histogram.count (fun j => residueKey M (-values j)) n (residueKey M (-values i)) = + Histogram.count (fun j => residueKey M (values j)) n (residueKey M (values i)) := by + simp only [Histogram.count, residueKey_neg_eq_iff hM] + +/-- Every bucket at an index beyond the modulus is empty. -/ +theorem bucket_eq_empty_of_le (S : Finset ℤ) {M i : ℕ} (hM : 0 < M) (hi : M ≤ i) : + bucket S M i = ∅ := by + apply eq_empty_iff_forall_notMem.mpr + intro z hz + have he := (mem_filter.mp hz).2 + have := Int.emod_lt_of_pos z (show (0 : ℤ) < M by omega) + omega + +/-- The first two light arrays equal their prescribed padding beyond the modulus. -/ +theorem arrXY_eq_pad_of_le (S : Finset ℤ) {M i : ℕ} (hM : 0 < M) (hi : M ≤ i) (U : ℕ) : + arrXY S M U i = pad U := by + simp [arrXY, arr, bucket_eq_empty_of_le S hM hi] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean index 5a8a697325..427dfe83ae 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Count.lean @@ -28,6 +28,7 @@ For injective input arrays its counter is exactly the existing Chan–He `coll`. * `Count.exec`: execution in at most `34n + 5M + 18` native instructions. * `Count.exec_coll`: the counter equals the existing mathematical collision count. +* `Count.memory_before`: cells below the two workspace blocks retain their contents. ## References @@ -102,6 +103,12 @@ def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst : ℕ) : Histogram.memory (Residues.memory mem values M scratch n) (fun i => (values i % (M : ℤ)).toNat) dst M n +/-- Neither the residue array nor the histogram modifies an earlier memory cell. -/ +theorem memory_before (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst a : ℕ) + (hs : a < scratch) (hd : a < dst) : memory mem values n M scratch dst a = mem a := by + simp [memory, Histogram.memory, Residues.memory, show ¬(dst ≤ a ∧ a < dst + M) by omega, + show ¬(scratch ≤ a ∧ a < scratch + n) by omega] + /-- Complete native collision counting, including residue computation, clearing the histogram and register setup. Input, scratch and histogram blocks occur in that order and are disjoint. The final halt is excluded. Fit hypotheses are local; a caller must derive them from its diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean index de574e2935..a9b32a3402 100644 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountCall.lean @@ -7,7 +7,7 @@ Authors: Erik Demaine module -- upstream: candidate -public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count import Mathlib.Tactic.FinCases /-! diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean deleted file mode 100644 index ba7f1a1e05..0000000000 --- a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountFrame.lean +++ /dev/null @@ -1,35 +0,0 @@ -/- -Copyright (c) 2026 Erik Demaine. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Authors: Erik Demaine --/ - -module --- upstream: candidate - -public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Count - -/-! -# Collision-counting register and memory frames - -Prime-search clients reuse the same fixed counting program on successive candidates. -Its input parameters and all memory below its scratch blocks are preserved. - -## Main results - -* `Count.memory_before`: cells below the two workspace blocks retain their contents. --/ - -@[expose] public section - -namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count - -open Cslib.WordRAM - -/-- Neither the residue array nor the histogram modifies an earlier memory cell. -/ -theorem memory_before (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M scratch dst a : ℕ) - (hs : a < scratch) (hd : a < dst) : memory mem values n M scratch dst a = mem a := by - simp [memory, Histogram.memory, Residues.memory, show ¬(dst ≤ a ∧ a < dst + M) by omega, - show ¬(scratch ≤ a ∧ a < scratch + n) by omega] - -end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Count diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean new file mode 100644 index 0000000000..2fe25904c2 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlace.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountCall +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceExec +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PlaceMemory + +/-! +# A complete native counting and placement call + +This routine actually computes the residues and histogram before extracting a heavy array and +placing the corresponding packed column. The histogram is not a premise of the execution +contract. It also writes the compact output count to its caller-supplied descriptor cell. + +## Main definitions + +* `CountPlace.program`: modulus encoding, complete `CountCall.program 2`, runtime argument + delivery, complete `Place.program`, and the count store. + +## References + +This native composition is original and reuses the existing Chan–He mathematical definitions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Input length (the selected third argument of the existing counting call). -/ +abbrev len : Fin 64 := 32 +/-- Signed input base. -/ +abbrev src : Fin 64 := 33 +/-- Unsigned residue workspace base. -/ +abbrev scratch : Fin 64 := 34 +/-- Histogram workspace base. -/ +abbrev hist : Fin 64 := 35 +/-- Compact heavy output base. -/ +abbrev heavy : Fin 64 := 37 +/-- Packed node array base. -/ +abbrev packed : Fin 64 := 38 +/-- Positive modulus, decoded unsigned. -/ +abbrev modulus : Fin 64 := 40 +/-- Encoded positive tag. -/ +abbrev tag : Fin 64 := 41 +/-- Packed column. -/ +abbrev column : Fin 64 := 42 +/-- Distance between the two stores. -/ +abbrev distance : Fin 64 := 43 +/-- Zero for X/Y, one for Z. -/ +abbrev negative : Fin 64 := 44 +/-- Cell in which the exact heavy count is stored. -/ +abbrev descriptor : Fin 64 := 45 + +/-- Fixed embedding of the placement workspace below all caller parameters. -/ +def registers (i : Fin 32) : Fin 64 := ⟨i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j he + apply Fin.ext + exact congrArg (fun k : Fin 64 => k.val) he + +@[inherit_doc Place.src] +abbrev psrc : Fin 64 := registers Place.src +@[inherit_doc Place.keys] +abbrev pkeys : Fin 64 := registers Place.keys +@[inherit_doc Place.len] +abbrev plen : Fin 64 := registers Place.len +@[inherit_doc Place.hist] +abbrev phist : Fin 64 := registers Place.hist +@[inherit_doc Place.heavy] +abbrev pheavy : Fin 64 := registers Place.heavy +@[inherit_doc Place.packed] +abbrev ppacked : Fin 64 := registers Place.packed +@[inherit_doc Place.modulus] +abbrev pmodulus : Fin 64 := registers Place.modulus +@[inherit_doc Place.tag] +abbrev ptag : Fin 64 := registers Place.tag +@[inherit_doc Place.column] +abbrev pcolumn : Fin 64 := registers Place.column +@[inherit_doc Place.distance] +abbrev pdistance : Fin 64 := registers Place.distance +@[inherit_doc Place.negative] +abbrev pnegative : Fin 64 := registers Place.negative +@[inherit_doc Place.count] +abbrev pcount : Fin 64 := registers Place.count + +/-- All eleven runtime argument copies are real instructions. -/ +def prepare : Code .standard 64 := ram[ + psrc ← src; pkeys ← scratch; plen ← len; phist ← hist; pheavy ← heavy; + ppacked ← packed; pmodulus ← modulus; ptag ← tag; pcolumn ← column; + pdistance ← distance; pnegative ← negative ] + +/-- Complete fixed native routine: no host histogram, heavy filter or output constructor. -/ +def program : Code .standard 64 := + .seq (.op (.bin .add CountCall.modulus modulus modulus)) + (.seq (CountCall.program 2) (.seq prepare + (.seq (Place.program.rename registers) (.op (.store descriptor pcount))))) + +/-- Only the counting/placement workspace, the encoded modulus, and the discarded collision +counter can change. All caller registers at least 46 are included in this frame. -/ +theorem preserves (i : Fin 64) (hi : 32 ≤ i.val) (hm : i ≠ CountCall.modulus) + (hc : i ≠ CountCall.result 2) : program.Preserves i := by + have h : 32 ≤ i.val ∧ i ≠ CountCall.modulus ∧ i ≠ CountCall.result 2 := ⟨hi, hm, hc⟩ + fin_cases i <;> norm_num [CountCall.modulus, CountCall.result] at h <;> decide + +/-- Polynomial envelope for addresses, counters, encoded magnitudes and every scan intermediate. -/ +def budget (x : Data 64) (M N U t : ℕ) : ℕ := + x.regs src + x.regs scratch + x.regs hist + x.regs heavy + x.regs packed + + x.regs len * x.regs len + 2 * x.regs len + M + 4 * N + 2 * t + 2 * U + 64 + +/-- Exact memory, including actual residue/histogram construction and the descriptor write. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) : ℕ → ℕ := + Function.update + (Place.memory (Count.memory mem values n M scratch hist) values n M out dst col dist t neg n) + descriptor (Place.heavyList values n M n).length + +/-- Complete native count/extract/place execution, charging modulus encoding, all subroutine +argument/result copies and the final count write. The caller supplies actual signed input words; +no histogram or light/heavy-output contract is assumed. -/ +theorem exec {w M N U t : ℕ} (x : Data 64) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hmod : GenericRAM.Signed.Fits w (M : ℤ)) + (hmreg : x.regs modulus = M) (hN : 2 * M ≤ N) (ht : U ≤ t) + (htag : x.regs tag = 2 * t) (hneg : x.regs negative = if neg then 1 else 0) + (hc : x.regs column < 4) (hd : x.regs distance ≤ 4 * M) + (hbdd : ∀ i < x.regs len, (values i).natAbs ≤ U) + (hsigned : ∀ i < x.regs len, GenericRAM.Signed.Fits w (values i)) + (hfit : Fits (w + 2) (budget x M N U t)) + (hsh : x.regs src + x.regs len ≤ x.regs heavy) + (hhp : x.regs heavy + x.regs len ≤ x.regs packed) + (hpk : x.regs packed + 4 * N ≤ x.regs scratch) + (hkh : x.regs scratch + x.regs len ≤ x.regs hist) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) + (hinj : Set.InjOn values (↑(Finset.range (x.regs len)) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) program x y (66 * x.regs len + 5 * M + 45) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t (x.regs scratch) (x.regs hist) + (x.regs descriptor) neg ∧ + ∀ i, 32 ≤ i.val → i ≠ CountCall.modulus → i ≠ CountCall.result 2 → + y.regs i = x.regs i := by + let z := x.set CountCall.modulus (encode (M : ℤ)) + have hemod : encode (M : ℤ) = M + M := by simp [encode, two_mul] + have hz : ExecWithin (w + 2) (.op (.bin .add CountCall.modulus modulus modulus)) x z 1 := by + have hf : Fits (w + 2) (x.regs modulus + x.regs modulus) := by + rw [hmreg, ← hemod] + exact (encode_fits hmod).mono (by omega) + simpa only [BinOp.eval, hmreg, ← hemod] using + (ExecWithin.bin (d := CountCall.modulus) (f := .add) (i := modulus) (j := modulus) hf) + have zmem : z.mem = x.mem := rfl + have zreg (i : Fin 64) (hi : i ≠ CountCall.modulus) : z.regs i = x.regs i := by + simp only [z, Data.set_regs, Function.update_of_ne hi] + have fs : Fits (w + 2) (x.regs src + x.regs len) := hfit.of_le (by + dsimp only [budget]; omega) + have fk : Fits (w + 2) (x.regs scratch + x.regs len) := hfit.of_le (by + dsimp only [budget]; omega) + have fh : Fits (w + 2) (x.regs hist + M) := hfit.of_le (by dsimp only [budget]; omega) + have fn : Fits (w + 2) (x.regs len * x.regs len + x.regs len + 1) := + hfit.of_le (by dsimp only [budget]; omega) + have zlen : z.regs (CountCall.length 2) = x.regs len := zreg _ (by decide) + have zsrc : z.regs (CountCall.source 2) = x.regs src := zreg _ (by decide) + have zscratch : z.regs CountCall.scratch = x.regs scratch := zreg _ (by decide) + have zhist : z.regs CountCall.histogram = x.regs hist := zreg _ (by decide) + obtain ⟨a, ha, ham, _, har⟩ := CountCall.exec 2 z values hM hmod + (by simp [z]) (by simpa only [zlen] using hsigned) + (by simpa only [zsrc, zlen] using fs) (by simpa only [zscratch, zlen] using fk) + (by simpa only [zhist] using fh) (by simpa only [zlen] using fn) + (by rw [zsrc, zlen, zscratch]; omega) + (by simpa only [zscratch, zlen, zhist] using hkh) + (by simpa only [zsrc, zlen, zmem] using hinput) + (by simpa only [zlen] using hinj) + have ar (i : Fin 64) (hi : 32 ≤ i.val) (hm : i ≠ CountCall.modulus) + (hc : i ≠ CountCall.result 2) : a.regs i = x.regs i := + (har i (by omega) hc).trans (zreg i hm) + have am : a.mem = Count.memory x.mem values (x.regs len) M + (x.regs scratch) (x.regs hist) := by + simpa only [zlen, zscratch, zhist, zmem] using ham + let q := ((((((((((a.set psrc (x.regs src)).set pkeys (x.regs scratch)).set plen + (x.regs len)).set phist (x.regs hist)).set pheavy (x.regs heavy)).set ppacked + (x.regs packed)).set pmodulus (x.regs modulus)).set ptag (x.regs tag)).set pcolumn + (x.regs column)).set pdistance (x.regs distance)).set pnegative (x.regs negative) + have hp : ExecWithin (w + 2) prepare a q 11 := by + have rsrc := ar src (by decide) (by decide) (by decide) + have rlen := ar len (by decide) (by decide) (by decide) + have rk := ar scratch (by decide) (by decide) (by decide) + have rh := ar hist (by decide) (by decide) (by decide) + have ro := ar heavy (by decide) (by decide) (by decide) + have rp := ar packed (by decide) (by decide) (by decide) + have rm := ar modulus (by decide) (by decide) (by decide) + have rt := ar tag (by decide) (by decide) (by decide) + have rc := ar column (by decide) (by decide) (by decide) + have rd := ar distance (by decide) (by decide) (by decide) + have rn := ar negative (by decide) (by decide) (by decide) + ram_eval [prepare, q, rsrc, rlen, rk, rh, ro, rp, rm, rt, rc, rd, rn] + have qs : (q.comap registers).regs Place.src = x.regs src := by + simp +decide [q, registers, Place.src] + have qk : (q.comap registers).regs Place.keys = x.regs scratch := by + simp +decide [q, registers, Place.keys] + have qn : (q.comap registers).regs Place.len = x.regs len := by + simp +decide [q, registers, Place.len] + have qh : (q.comap registers).regs Place.hist = x.regs hist := by + simp +decide [q, registers, Place.hist] + have qo : (q.comap registers).regs Place.heavy = x.regs heavy := by + simp +decide [q, registers, Place.heavy] + have qp : (q.comap registers).regs Place.packed = x.regs packed := by + simp +decide [q, registers, Place.packed] + have qm : (q.comap registers).regs Place.modulus = x.regs modulus := by + simp +decide [q, registers, Place.modulus] + have qt : (q.comap registers).regs Place.tag = x.regs tag := by + simp +decide [q, registers, Place.tag] + have qc : (q.comap registers).regs Place.column = x.regs column := by + simp +decide [q, registers, Place.column] + have qd : (q.comap registers).regs Place.distance = x.regs distance := by + simp +decide [q, registers, Place.distance] + have qg : (q.comap registers).regs Place.negative = x.regs negative := by + simp +decide [q, registers, Place.negative] + have qmem : q.mem = a.mem := rfl + have qfit : Fits (w + 2) (Place.budget (q.comap registers) M N U t) := by + rw [Place.budget, qs, qk, qh, qo, qp, qn] + exact hfit.of_le (by dsimp only [budget]; omega) + obtain ⟨b, hb, hbm, hbc, _⟩ := Place.exec (q.comap registers) values neg hM hN + (by simpa only [qc] using hc) (by simpa only [qd] using hd) ht + (qm.trans hmreg) (qt.trans htag) (qg.trans hneg) + (by simpa only [qn] using hbdd) qfit + (by simpa only [qs, qn, qo] using hsh) (by simpa only [qo, qn, qp] using hhp) + (by simpa only [qp, qk] using hpk) (by simpa only [qk, qn, qh] using hkh) + (by + intro i hi + rw [Data.comap_mem, qmem, am, qs] + have hi' : i < x.regs len := by simpa only [qn] using hi + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hinput i hi') + (by + intro i hi + have hi' : i < x.regs len := by simpa only [qn] using hi + rw [Data.comap_mem, qmem, am, qk, Count.memory, Histogram.memory, + ite_eq_right (by omega)] + simp [Residues.memory, show i < x.regs len by simpa only [qn] using hi, residueKey]) + (by + intro r hr + rw [Data.comap_mem, qmem, am, qh, Count.memory, Histogram.memory_read _ _ _ _ _ _ hr] + rfl) + let y := b.extend registers q + have hy : ExecWithin (w + 2) (Place.program.rename registers) q y + (32 * x.regs len + 8) := by simpa only [qn] using hb.rename registers_injective + have yc : y.regs pcount = (Place.heavyList values (x.regs len) M (x.regs len)).length := by + change Function.extend registers b.regs q.regs (registers Place.count) = _ + rw [registers_injective.extend_apply, hbc, qn] + have yd : y.regs descriptor = x.regs descriptor := by + change Function.extend registers b.regs q.regs descriptor = _ + rw [Function.extend_apply' _ _ _ (by + rintro ⟨i, hi⟩ + have := congrArg Fin.val hi + dsimp only [registers, descriptor] at this + omega)] + have hq : q.regs descriptor = a.regs descriptor := by simp +decide [q, descriptor] + exact hq.trans (ar descriptor (by decide) (by decide) (by decide)) + have ha' : ExecWithin (w + 2) (CountCall.program 2) z a + (34 * x.regs len + 5 * M + 24) := by simpa only [zlen] using ha + have he := hz.seq (ha'.seq + (hp.seq (hy.seq (ExecWithin.store (a := descriptor) (s := pcount))))) + refine ⟨y.write (y.regs descriptor) (y.regs pcount), he.mono (by omega), ?_, ?_⟩ + · simp only [Data.write_mem, yd, yc] + change Function.update b.mem (x.regs descriptor) _ = _ + rw [hbm, Data.comap_mem, qmem, am, qn, qo, qp, qc, qd] + rfl + · intro i hi hm hc + exact ExecWithin.regs_eq he (preserves i hi hm hc) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean new file mode 100644 index 0000000000..24f7f33dca --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/CountPlaceMemory.lean @@ -0,0 +1,110 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace + +/-! +# Memory frames and output readback for complete counting/placement calls + +## Main results + +* `CountPlace.memory_outside`: only the specified data, descriptor and workspace cells change. +* `CountPlace.memory_heavy`: exact compact heavy data. +* `CountPlace.memory_arrXY`, `CountPlace.memory_arrZ`: exact packed light columns. + +## References + +These composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace + +open Cslib.WordRAM.Signed + +/-- No write escapes the four explicit blocks and descriptor cell. -/ +theorem memory_outside (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor N : ℕ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : col < 4) (hd : dist ≤ 4 * M) + (a : ℕ) (ho : ¬(out ≤ a ∧ a < out + n)) (hp : ¬(dst ≤ a ∧ a < dst + 4 * N)) + (hk : ¬(scratch ≤ a ∧ a < scratch + n)) (hh : ¬(hist ≤ a ∧ a < hist + M)) + (he : a ≠ descriptor) : + memory mem values n M out dst col dist t scratch hist descriptor neg a = mem a := by + rw [memory, Function.update_of_ne he, + Place.memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd le_rfl ho hp, + Count.memory, Histogram.memory_outside _ _ _ _ _ _ hh] + simp only [Residues.memory, hk, ↓reduceIte] + +/-- The count store always writes the compact output length. -/ +theorem memory_descriptor (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) : + memory mem values n M out dst col dist t scratch hist descriptor neg descriptor = + (Place.heavyList values n M n).length := by + exact Function.update_self .. + +/-- The compact output contains the original signed encodings in input order. -/ +theorem memory_heavy (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor : ℕ) (neg : Bool) + (hdisj : out + n ≤ dst) (he : descriptor < out) + {j : ℕ} (hj : j < (Place.heavyList values n M n).length) : + memory mem values n M out dst col dist t scratch hist descriptor neg (out + j) = + encode ((Place.heavyList values n M n)[j]) := by + rw [memory, Function.update_of_ne (by omega)] + exact Place.memory_heavy _ _ _ _ _ _ _ _ _ _ hdisj le_rfl hj + +/-- The counting workspace and descriptor do not affect other packed columns. -/ +theorem memory_other_column (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col dist t scratch hist descriptor N : ℕ) (neg : Bool) + (hdisj : out + n ≤ dst) (hc : col < 4) (hd : dist = 0 ∨ dist = 4 * M) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + {u : ℕ} (hu : u < 4 * N) (hcol : u % 4 ≠ col) : + memory mem values n M out dst col dist t scratch hist descriptor neg (dst + u) = + mem (dst + u) := by + rw [memory, Function.update_of_ne (by omega), + Place.memory_other_column _ _ _ _ _ _ _ _ _ _ hdisj hc hd u hcol, + Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + +/-- A complete counting/placement call writes the exact first or second light column. -/ +theorem memory_arrXY (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col t U scratch hist descriptor N : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) + {i : ℕ} (hi : 4 * i + col < 4 * N) + (hpad : mem (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col 0 t scratch hist descriptor false + (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range n).image values) M U i + (t : ℤ)) := by + rw [memory, Function.update_of_ne (by omega)] + apply Place.memory_arrXY _ _ _ _ _ _ _ _ _ hM hdisj hinj i + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hpad + +/-- A complete counting/placement call writes the exact duplicated and negated third column. -/ +theorem memory_arrZ (mem : ℕ → ℕ) (values : ℕ → ℤ) + (n M out dst col t U scratch hist descriptor N : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hk : dst + 4 * N ≤ scratch) (hh : scratch + n ≤ hist) (he : descriptor < dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) + {i : ℕ} (hi : 4 * i + col < 4 * N) + (hpad : mem (dst + 4 * i + col) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col (4 * M) t scratch hist descriptor true + (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range n).image values) M U i + (t : ℤ)) := by + rw [memory, Function.update_of_ne (by omega)] + apply Place.memory_arrZ _ _ _ _ _ _ _ _ _ hM hdisj hinj i + rw [Count.memory_before _ _ _ _ _ _ _ (by omega) (by omega)] + exact hpad + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlace diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Modulus.lean new file mode 100644 index 0000000000..2040216292 --- /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 0000000000..0eeb3337ac --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeExec.lean @@ -0,0 +1,108 @@ +/- +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 +public import Complexity.Computability.WordRAM.Signed.Fits + +/-! +# 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 : ℤ) := hwidth'.of_natAbs_le + (by simpa only [Int.natAbs_natCast] using + (show L.M ≤ L.budget by have := L.bounds.2.2; omega)) + have hu : L.U ≤ L.budget := by have := L.bounds.2.2; omega + have hsigned (j : Fin 3) (i : ℕ) (hi : i < L.n j) : + GenericRAM.Signed.Fits w (values j i) := + hwidth'.of_natAbs_le ((hbdd j i hi).trans hu) + let R (y : Data 64) : Prop := Parameters x y ∧ Prepared L y ∧ + ∀ a < L.base, y.mem a = x.mem a + obtain ⟨a, ha, hap, har, ham⟩ := exec_initialize x hfit + have hRa : R a := by + refine ⟨hap, har, ?_⟩ + intro b hb + rw [ham] + have hp : L.base ≤ L.packed := by dsimp only [Layout.packed]; omega + have hnot : ¬(L.packed ≤ b ∧ b < L.packed + 4 * L.N) := by omega + change paddingMemory L x.mem b = x.mem b + simp only [paddingMemory, Padding.memory, hnot, ↓reduceIte] + have hcall (j : Fin 3) (y : Data 64) (hy : R y) : + ∃ z, ExecWithin (w + 2) (round j) y z (66 * L.n j + 5 * L.M + 65) ∧ + R z ∧ z.mem = roundMemory L values j y.mem := by + obtain ⟨hp, hr, hm⟩ := hy + obtain ⟨z, hz, hzm, _, hzp, hzr⟩ := exec_round x y values j hp hr hfit hM' hN' hmod + (hsrc j) (hbdd j) (hsigned j) + (by + intro i hi + have hi' : i < L.n j := hi + have hsi := hsrc j + have hsi' : x.regs (source j) + L.n j ≤ L.base := hsi + rw [hm _ (by omega)] + exact hinput j i hi) + (hinj j) + refine ⟨z, hz, ⟨hzp, hzr, ?_⟩, hzm⟩ + intro b hb + rw [hzm, roundMemory_before L values j y.mem hM' hN' + (lt_of_lt_of_le hb (L.base_le_descriptor j)), hm b hb] + obtain ⟨b, hb, hRb, hbm⟩ := hcall 0 a hRa + obtain ⟨c, hc, hRc, hcm⟩ := hcall 1 b hRb + obtain ⟨d, hd, hRd, hdm⟩ := hcall 2 c hRc + refine ⟨d, (ha.seq (hb.seq (hc.seq hd))).mono (by dsimp only [L, Layout.total]; omega), + ?_, hRd.1⟩ + rw [hdm, hcm, hbm, ham] + rfl + +/-- The complete charged bound is uniformly linear in input lengths, modulus and output length. -/ +theorem time_le_linear (L : Layout) : + 66 * L.total + 15 * L.M + 12 * L.N + 228 ≤ 256 * (L.total + L.M + L.N + 1) := by + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean new file mode 100644 index 0000000000..ddce7d18d0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeInitialize.lean @@ -0,0 +1,82 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeMemory + +/-! +# Complete native initialization of a Chan–He node + +## Main results + +* `NodePacking.exec_initialize`: exact runtime layout, encoded constants and all `4N` padding + writes, charged within `12N+33` native instructions. + +## References + +This native composition proof is original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +private theorem paddingRegisters_injective : Function.Injective paddingRegisters := by + intro i j he + apply Fin.ext + exact congrArg (fun k : Fin 64 => k.val) he + +private theorem padding_extend_frame (a : Data 10) (x : Data 64) : + Frame x (a.extend paddingRegisters x) := by + intro i hi hne + change Function.extend paddingRegisters a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + have := congrArg Fin.val hj + dsimp only [paddingRegisters] at this + omega + +/-- All setup and padding are real instructions, starting from arbitrary old output memory. -/ +theorem exec_initialize {w : ℕ} (x : Data 64) (hfit : Fits w (layout x).budget) : + ∃ y, ExecWithin w initializeCode x y (12 * (layout x).N + 33) ∧ Parameters x y ∧ + Prepared (layout x) y ∧ y.mem = paddingMemory (layout x) x.mem := by + let L := layout x + have hfit' : Fits w L.budget := hfit + obtain ⟨a, ha, har, ham, hap, hak, hah, ha1, ha4, ha3⟩ := exec_layout x hfit + have haU : a.regs magnitude = L.U := har magnitude (by decide) + have haN : a.regs groups = L.N := har groups (by decide) + obtain ⟨b, hb, hbr, hbm, hready, hpad⟩ := exec_constants L a hfit' haU haN hak hah hap ha1 ha4 ha3 + have hargs : Parameters x b := fun i hi => (hbr i hi).trans (har i hi) + obtain ⟨hbd, hbn, hbp₀, hbp₁, hbp₂, hbp₃⟩ := hpad + have hpd : (b.comap paddingRegisters).regs Padding.dst = L.packed := hbd + have hpn : (b.comap paddingRegisters).regs Padding.len = L.N := hbn + have hf4 : Fits w 4 := hfit'.of_le (by have := L.bounds.2.2; omega) + have hpend : Fits w ((b.comap paddingRegisters).regs Padding.dst + + 4 * (b.comap paddingRegisters).regs Padding.len) := by + rw [hpd, hpn] + exact (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + obtain ⟨c, hc, hcm, _⟩ := Padding.exec (b.comap paddingRegisters) hf4 hpend + let y := c.extend paddingRegisters b + have hy : ExecWithin w (Padding.program.rename paddingRegisters) b y (12 * L.N + 5) := + by simpa only [hpn] using hc.rename paddingRegisters_injective + have hframe : Frame b y := padding_extend_frame c b + refine ⟨y, (ha.seq (hb.seq hy)).mono (by dsimp only [L] at *; omega), hargs.of_frame hframe, + hready.of_frame hframe, ?_⟩ + change c.mem = _ + rw [hcm, Data.comap_mem, hbm, ham, hpd, hpn] + change Padding.memory x.mem L.packed (b.regs pad0) (b.regs pad1) + (b.regs pad2) (b.regs pad3) L.N = _ + rw [hbp₀, hbp₁, hbp₂, hbp₃] + rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean new file mode 100644 index 0000000000..11a3fb3e7f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeLayout.lean @@ -0,0 +1,162 @@ +/- +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 + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean new file mode 100644 index 0000000000..4e6e4be17e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeMemory.lean @@ -0,0 +1,314 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeLayout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.CountPlaceMemory + +/-! +# Exact output memory of complete native Chan–He node construction + +## Main definitions + +* `NodePacking.paddingMemory`, `roundMemory`, `memory`: exact instruction-level write specs. +* `NodePacking.node`: the existing mathematical node for the three supplied enumerations. + +## References + +These memory composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM.Signed + +/-- Every output cell is initialized, even when the three input arrays are empty. -/ +def paddingMemory (L : Layout) (mem : ℕ → ℕ) : ℕ → ℕ := + Padding.memory mem L.packed (120 * L.U + 80) (16 * L.U + 10) + (40 * L.U + 26) (44 * L.U + 30) L.N + +/-- Exact complete count/place call for the selected supplied input array. -/ +def roundMemory (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) (mem : ℕ → ℕ) : ℕ → ℕ := + CountPlace.memory mem (values j) (L.n j) L.M (L.heavy j) L.packed (j.val + 1) + (if j = 2 then 4 * L.M else 0) (L.tag j) L.scratch L.hist (L.descriptor j) + (decide (j = 2)) + +/-- Exact memory after padding and all three complete native calls. -/ +def memory (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) : ℕ → ℕ := + roundMemory L values 2 (roundMemory L values 1 (roundMemory L values 0 (paddingMemory L mem))) + +/-- The existing mathematical Chan–He node, with no alternative 3SUM semantics. -/ +def node (L : Layout) (values : Fin 3 → ℕ → ℤ) : + Cslib.Algorithms.ThreeSum.ChanHe.Node := + ⟨(Finset.range (L.n 0)).image (values 0), (Finset.range (L.n 1)).image (values 1), + (Finset.range (L.n 2)).image (values 2), L.M⟩ + +/-- The native unsigned pattern encodes exactly the signed padding and tags of `oneArray`. -/ +theorem padding_values (L : Layout) : + 120 * L.U + 80 = encode (10 * (L.gap : ℤ)) ∧ + 16 * L.U + 10 = encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.gap) ∧ + 40 * L.U + 26 = encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * L.gap) ∧ + 44 * L.U + 30 = encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap) := by + have h₀ : 10 * (L.gap : ℤ) = ((60 * L.U + 40 : ℕ) : ℤ) := by + dsimp only [Layout.gap] + push_cast + ring + have h₁ : Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + (L.gap : ℤ) = + ((8 * L.U + 5 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + have h₂ : Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * (L.gap : ℤ) = + ((20 * L.U + 13 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + have h₃ : -Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * (L.gap : ℤ) = + ((22 * L.U + 15 : ℕ) : ℤ) := by + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.pad, Layout.gap] + push_cast + ring + rw [h₀, h₁, h₂, h₃] + simp only [encode, Int.natAbs_natCast, Int.natCast_nonneg, not_lt_of_ge, + ↓reduceIte, Nat.add_zero] + omega + +/-- Reading initialized packed memory at any fitting index. -/ +theorem paddingMemory_read (L : Layout) (mem : ℕ → ℕ) {u : ℕ} (hu : u < 4 * L.N) : + paddingMemory L mem (L.packed + u) = + Padding.pattern (120 * L.U + 80) (16 * L.U + 10) (40 * L.U + 26) (44 * L.U + 30) u := by + simp [paddingMemory, Padding.memory, hu] + +/-- Later rounds preserve every earlier count/capacity block. -/ +theorem roundMemory_before (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) + {a : ℕ} (ha : a < L.descriptor j) : roundMemory L values j mem a = mem a := by + have ho := L.descriptor_lt_packed j + apply CountPlace.memory_outside _ _ _ _ _ _ _ _ _ _ _ _ L.N _ hM hN + (by omega) (by split_ifs <;> omega) + · dsimp only [Layout.heavy]; omega + · omega + · dsimp only [Layout.scratch]; omega + · dsimp only [Layout.hist, Layout.scratch]; omega + · omega + +/-- Every complete call preserves every other packed column, including column zero. -/ +theorem roundMemory_other (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) {u : ℕ} (hu : u < 4 * L.N) (hc : u % 4 ≠ j.val + 1) : + roundMemory L values j mem (L.packed + u) = mem (L.packed + u) := by + have hn := L.n_le_total j + apply CountPlace.memory_other_column _ _ _ _ _ _ _ _ _ _ _ _ L.N _ + (L.heavy_end_le_packed j) (by omega) + (by by_cases hj : j = 2 <;> simp [hj]) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed j) hu hc + +/-- The first two complete rounds produce the existing mathematical light array. -/ +theorem roundMemory_arrXY (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hj : j ≠ 2) (hM : 0 < L.M) + (hinj : Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) + (hpad : mem (L.packed + 4 * i + (j.val + 1)) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.tag j)) : + roundMemory L values j mem (L.packed + 4 * i + (j.val + 1)) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range (L.n j)).image (values j)) L.M L.U i + L.tag j) := by + have hn := L.n_le_total j + simpa only [roundMemory, hj, ↓reduceIte, decide_false] using + CountPlace.memory_arrXY mem (values j) (L.n j) L.M (L.heavy j) L.packed (j.val + 1) + (L.tag j) L.U L.scratch L.hist (L.descriptor j) L.N hM (L.heavy_end_le_packed j) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed j) hinj + (by omega) hpad + +/-- The third complete round produces the existing duplicated, negated mathematical array. -/ +theorem roundMemory_arrZ (L : Layout) (values : Fin 3 → ℕ → ℤ) + (mem : ℕ → ℕ) (hM : 0 < L.M) + (hinj : Set.InjOn (values 2) (↑(Finset.range (L.n 2)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) + (hpad : mem (L.packed + 4 * i + 3) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap)) : + roundMemory L values 2 mem (L.packed + 4 * i + 3) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range (L.n 2)).image (values 2)) L.M L.U i + 4 * L.gap) := by + have hn := L.n_le_total 2 + simpa +decide only [roundMemory, Layout.tag, Nat.reduceAdd, ↓reduceIte, + decide_true, Fin.val_two, Nat.cast_mul, Nat.cast_ofNat] using + CountPlace.memory_arrZ mem (values 2) (L.n 2) L.M (L.heavy 2) L.packed 3 + (4 * L.gap) L.U L.scratch L.hist (L.descriptor 2) L.N hM (L.heavy_end_le_packed 2) + le_rfl (Nat.add_le_add_left hn L.scratch) (L.descriptor_lt_packed 2) hinj + (by omega) hpad + +/-- All four packed columns have exactly the existing node's required values. -/ +theorem memory_cells (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {i : ℕ} (hi : i < L.N) : + memory L values mem (L.packed + 4 * i) = encode (10 * (L.gap : ℤ)) ∧ + memory L values mem (L.packed + 4 * i + 1) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrXY (node L values).S₁ L.M L.U i + L.gap) ∧ + memory L values mem (L.packed + 4 * i + 2) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrXY (node L values).S₂ L.M L.U i + 3 * L.gap) ∧ + memory L values mem (L.packed + 4 * i + 3) = encode + (Cslib.Algorithms.ThreeSum.ChanHe.arrZ (node L values).S₃ L.M L.U i + 4 * L.gap) := by + have hp (c : ℕ) (hc : c < 4) : + paddingMemory L mem (L.packed + (4 * i + c)) = + Padding.pattern (120 * L.U + 80) (16 * L.U + 10) + (40 * L.U + 26) (44 * L.U + 30) (4 * i + c) := + paddingMemory_read L mem (by omega) + have hp₀ : paddingMemory L mem (L.packed + 4 * i) = encode (10 * (L.gap : ℤ)) := by + have he := hp 0 (by decide) + simpa [Padding.pattern, (padding_values L).1] using he + have hp₁ : paddingMemory L mem (L.packed + 4 * i + 1) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + L.gap) := by + have he := hp 1 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.1] using he + have hp₂ : paddingMemory L mem (L.packed + 4 * i + 2) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 3 * L.gap) := by + have he := hp 2 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.2.1] using he + have hp₃ : paddingMemory L mem (L.packed + 4 * i + 3) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad L.U + 4 * L.gap) := by + have he := hp 3 (by decide) + simpa [Padding.pattern, Nat.add_assoc, (padding_values L).2.2.2] using he + have other (j : Fin 3) (μ : ℕ → ℕ) (c : ℕ) (hc : c < 4) (hne : c ≠ j.val + 1) : + roundMemory L values j μ (L.packed + 4 * i + c) = μ (L.packed + 4 * i + c) := by + simpa only [Nat.add_assoc] using roundMemory_other L values j μ + (u := 4 * i + c) (by omega) (by omega) + refine ⟨?_, ?_, ?_, ?_⟩ + · change roundMemory L values 2 _ _ = _ + rw [show L.packed + 4 * i = L.packed + 4 * i + 0 by omega, + other 2 _ 0 (by decide) (by decide), other 1 _ 0 (by decide) (by decide), + other 0 _ 0 (by decide) (by decide)] + exact hp₀ + · change roundMemory L values 2 _ _ = _ + rw [other 2 _ 1 (by decide) (by decide), other 1 _ 1 (by decide) (by decide)] + simpa +decide [Layout.tag, node] using roundMemory_arrXY L values 0 (paddingMemory L mem) + (by decide) hM (hinj 0) hi (by simpa [Layout.tag] using hp₁) + · change roundMemory L values 2 _ _ = _ + rw [other 2 _ 2 (by decide) (by decide)] + simpa +decide [Layout.tag, node, Nat.cast_mul] using roundMemory_arrXY L values 1 + (roundMemory L values 0 (paddingMemory L mem)) (by decide) hM (hinj 1) hi (by + change roundMemory L values 0 (paddingMemory L mem) (L.packed + 4 * i + 2) = _ + rw [other 0 _ 2 (by decide) (by decide)] + simpa [Layout.tag, Nat.cast_mul] using hp₂) + · change roundMemory L values 2 _ _ = _ + apply roundMemory_arrZ L values _ hM (hinj 2) hi + rw [other 1 _ 3 (by decide) (by decide), other 0 _ 3 (by decide) (by decide)] + exact hp₃ + +/-- Every one of the `4N` native output cells equals the existing mathematical `Node.oneArray`. +This includes column-zero sentinels, empty and heavy buckets, and the entire padded tail. -/ +theorem memory_oneArray (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hinj : ∀ j, Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ)) + {u : ℕ} (hu : u < 4 * L.N) : + memory L values mem (L.packed + u) = encode ((node L values).oneArray L.U u) := by + obtain ⟨h₀, h₁, h₂, h₃⟩ := memory_cells L values mem hM hinj (i := u / 4) (by omega) + have hgap : 3 * (((2 * L.U + 1 : ℕ) : ℤ)) + 1 = (L.gap : ℤ) := by + dsimp only [Layout.gap] + push_cast + ring + by_cases h1 : u % 4 = 1 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 1 by omega, h₁] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h1, ↓reduceIte, hgap, node] + by_cases h2 : u % 4 = 2 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 2 by omega, h₂] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h2, ↓reduceIte, hgap, node, Nat.reduceEqDiff] + by_cases h3 : u % 4 = 3 + · rw [show L.packed + u = L.packed + 4 * (u / 4) + 3 by omega, h₃] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h3, ↓reduceIte, hgap, node, Nat.reduceEqDiff] + · rw [show L.packed + u = L.packed + 4 * (u / 4) by omega, h₀] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.oneArray, + Cslib.Algorithms.ThreeSum.oneArray, h1, h2, h3, ↓reduceIte, hgap] + +/-- Every count cell and every used heavy cell survives subsequent rounds. -/ +theorem memory_heavy (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) (j : Fin 3) : + memory L values mem (L.descriptor j) = (Place.heavyList (values j) (L.n j) L.M (L.n j)).length ∧ + ∀ k (hk : k < (Place.heavyList (values j) (L.n j) L.M (L.n j)).length), + memory L values mem (L.heavy j + k) = + encode ((Place.heavyList (values j) (L.n j) L.M (L.n j))[k]) := by + have hround (j : Fin 3) (μ : ℕ → ℕ) : + roundMemory L values j μ (L.descriptor j) = + (Place.heavyList (values j) (L.n j) L.M (L.n j)).length ∧ + ∀ k (hk : k < (Place.heavyList (values j) (L.n j) L.M (L.n j)).length), + roundMemory L values j μ (L.heavy j + k) = + encode ((Place.heavyList (values j) (L.n j) L.M (L.n j))[k]) := by + constructor + · exact CountPlace.memory_descriptor .. + · intro k hk + exact CountPlace.memory_heavy _ _ _ _ _ _ _ _ _ _ _ _ _ + (L.heavy_end_le_packed j) (by dsimp only [Layout.heavy]; omega) hk + have hnext := L.next_descriptor + have hdesc01 : L.descriptor 0 < L.descriptor 1 := by + dsimp only [Layout.heavy] at hnext + omega + have hdesc12 : L.descriptor 1 < L.descriptor 2 := by + dsimp only [Layout.heavy] at hnext + omega + fin_cases j <;> simp only [Fin.mk_zero, Fin.mk_one] + · obtain ⟨hc, hd⟩ := hround 0 (paddingMemory L mem) + constructor + · change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega), + roundMemory_before L values 1 _ hM hN (by omega)] + exact hc + · intro k hk + have hlen := Place.length_heavyList_le (values 0) (L.n 0) L.M (L.n 0) + change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega), + roundMemory_before L values 1 _ hM hN (by omega)] + exact hd k hk + · obtain ⟨hc, hd⟩ := hround 1 (roundMemory L values 0 (paddingMemory L mem)) + constructor + · change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega)] + exact hc + · intro k hk + have hlen := Place.length_heavyList_le (values 1) (L.n 1) L.M (L.n 1) + change roundMemory L values 2 _ _ = _ + rw [roundMemory_before L values 2 _ hM hN (by omega)] + exact hd k hk + · exact hround 2 (roundMemory L values 1 (roundMemory L values 0 (paddingMemory L mem))) + +/-- Each complete round preserves all memory outside the concrete allocation. -/ +theorem roundMemory_outside (L : Layout) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (mem : ℕ → ℕ) (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) + {a : ℕ} (ha : a < L.base ∨ L.endpoint ≤ a) : roundMemory L values j mem a = mem a := by + have ho := L.heavy_end_le_packed j + have hb := L.base_le_descriptor j + have hd := L.descriptor_lt_packed j + have hn := L.n_le_total j + have hp : L.packed + 4 * L.N ≤ L.endpoint := by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega + have hk : L.scratch + L.n j ≤ L.hist := Nat.add_le_add_left hn _ + have hh : L.hist + L.M = L.endpoint := rfl + have hs : L.scratch = L.packed + 4 * L.N := rfl + have hout : L.heavy j = L.descriptor j + 1 := rfl + apply CountPlace.memory_outside _ _ _ _ _ _ _ _ _ _ _ _ L.N _ hM hN + (by omega) (by split_ifs <;> omega) + all_goals rcases ha with ha | ha <;> omega + +/-- Inputs, caller memory before the allocation, and memory beyond its endpoint are preserved. -/ +theorem memory_outside (L : Layout) (values : Fin 3 → ℕ → ℤ) (mem : ℕ → ℕ) + (hM : 0 < L.M) (hN : 2 * L.M ≤ L.N) {a : ℕ} (ha : a < L.base ∨ L.endpoint ≤ a) : + memory L values mem a = mem a := by + rw [memory, roundMemory_outside L values 2 _ hM hN ha, + roundMemory_outside L values 1 _ hM hN ha, roundMemory_outside L values 0 _ hM hN ha] + have hp : L.packed + 4 * L.N ≤ L.endpoint := by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega + have hb : L.base ≤ L.packed := by dsimp only [Layout.packed]; omega + have hnot : ¬(L.packed ≤ a ∧ a < L.packed + 4 * L.N) := by + rcases ha with ha | ha <;> omega + simp only [paddingMemory, Padding.memory, hnot, ↓reduceIte] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean new file mode 100644 index 0000000000..65255a32cd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeProgram.lean @@ -0,0 +1,168 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeLayout + +/-! +# Complete fixed native program for one Chan–He node + +The source comparator is [NodeArray.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/ChanHe/NodeArray.lean) at +`anthropics/formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The layout, native code and proofs are original. The source's `Light` machine is not imported. + +## Main definitions + +* `NodePacking.program`: computes the layout and tags, pads every output cell, then performs + three actual residue/histogram/extraction/placement calls, including all three count stores. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- The three supplied signed input bases. -/ +def source (j : Fin 3) : Fin 64 := ⟨46 + 2 * j.val, by omega⟩ +/-- The three supplied input lengths. -/ +def length (j : Fin 3) : Fin 64 := ⟨47 + 2 * j.val, by omega⟩ +/-- Fresh allocation base after all input arrays. -/ +abbrev base : Fin 64 := 52 +/-- Common number of four-cell groups. -/ +abbrev groups : Fin 64 := 53 +/-- Node input magnitude bound. -/ +abbrev magnitude : Fin 64 := 54 +/-- Positive modulus. -/ +abbrev modulus : Fin 64 := 55 +/-- Computed residue workspace base. -/ +abbrev scratch : Fin 64 := 56 +/-- Computed histogram workspace base. -/ +abbrev histogram : Fin 64 := 57 +/-- Computed packed output base. -/ +abbrev packed : Fin 64 := 58 +/-- Twice the gap `G=6U+4`, ready for sign-and-magnitude tagging. -/ +abbrev gap : Fin 64 := 59 +/-- Twice the padding magnitude `W=2U+1`. -/ +abbrev padding : Fin 64 := 60 +/-- Setup scratch. -/ +abbrev temporary : Fin 64 := 61 +/-- Constant one, retained across the complete calls. -/ +abbrev one : Fin 64 := 62 +/-- Constant four, retained across the complete calls. -/ +abbrev four : Fin 64 := 63 + +/-- The mathematical layout determined by the runtime parameters. -/ +def layout (x : Data 64) : Layout := + ⟨x.regs base, fun j => x.regs (length j), x.regs groups, x.regs modulus, x.regs magnitude⟩ + +/-- Padding workspace embedded below all persistent node parameters. -/ +def paddingRegisters (i : Fin 10) : Fin 64 := ⟨i.val, by omega⟩ + +@[inherit_doc Padding.dst] +abbrev pdst : Fin 64 := paddingRegisters Padding.dst +@[inherit_doc Padding.len] +abbrev plen : Fin 64 := paddingRegisters Padding.len +@[inherit_doc Padding.pad0] +abbrev pad0 : Fin 64 := paddingRegisters Padding.pad0 +@[inherit_doc Padding.pad1] +abbrev pad1 : Fin 64 := paddingRegisters Padding.pad1 +@[inherit_doc Padding.pad2] +abbrev pad2 : Fin 64 := paddingRegisters Padding.pad2 +@[inherit_doc Padding.pad3] +abbrev pad3 : Fin 64 := paddingRegisters Padding.pad3 + +/-- Compute every permanent and scratch block address in twelve native instructions. -/ +def layoutCode : Code .standard 64 := + let n₀ := length 0 + let n₁ := length 1 + let n₂ := length 2 + ram[ one ← #1; four ← #4; temporary ← #3; + packed ← base + temporary; packed ← packed + n₀; packed ← packed + n₁; + packed ← packed + n₂; scratch ← four * groups; scratch ← packed + scratch; + histogram ← scratch + n₀; histogram ← histogram + n₁; histogram ← histogram + n₂ ] + +/-- Compute the exact positive encoded padding pattern, including all signed-negation/tag work. +The third padding word uses subtraction because its integer contribution is `-W`. -/ +def constantsCode : Code .standard 64 := ram[ + padding ← magnitude + magnitude; padding ← padding + one; padding ← padding + padding; + gap ← temporary * padding; temporary ← #2; gap ← gap + temporary; + pad0 ← #10; pad0 ← pad0 * gap; pad1 ← padding + gap; + temporary ← #3; pad2 ← temporary * gap; pad2 ← pad2 + padding; + pad3 ← four * gap; pad3 ← pad3 - padding; pdst ← packed; plen ← groups ] + +/-- Compute the selected heavy-array descriptor address. The choice is one of three fixed +pieces of code, never a program whose size depends on an input length. -/ +def descriptorCode (j : Fin 3) : Code .standard 64 := + let d := CountPlace.descriptor + let n₀ := length 0 + let n₁ := length 1 + if j = 0 then ram[ d ← base ] else if j = 1 then ram[ d ← base + one; d ← d + n₀ ] + else ram[ d ← base + one; d ← d + n₀; d ← d + one; d ← d + n₁ ] + +/-- Deliver the exact encoded tag for one fixed column. -/ +def tagCode (j : Fin 3) : Code .standard 64 := + let t := CountPlace.tag + if j = 0 then ram[ t ← gap ] else if j = 1 then ram[ temporary ← #3; t ← temporary * gap ] + else ram[ t ← four * gap ] + +/-- Deliver the duplicated-Z distance; the other columns perform two coincident stores. -/ +def distanceCode (j : Fin 3) : Code .standard 64 := + let d := CountPlace.distance + if j = 2 then ram[ d ← four * modulus ] else ram[ d ← #0 ] + +/-- Every runtime input and output parameter is explicitly delivered to the complete call. -/ +def prepare (j : Fin 3) : Code .standard 64 := + let n := length j + let s := source j + let l := CountPlace.len + let a := CountPlace.src + let k := CountPlace.scratch + let h := CountPlace.hist + let d := CountPlace.descriptor + let o := CountPlace.heavy + let p := CountPlace.packed + let m := CountPlace.modulus + let c := CountPlace.column + let neg := CountPlace.negative + .seq (ram[ l ← n; a ← s; k ← scratch; h ← histogram ]) + (.seq (descriptorCode j) (.seq (ram[ o ← d + one; p ← packed; m ← modulus ]) + (.seq (tagCode j) (.seq (.op (.const c (j.val + 1))) + (.seq (distanceCode j) (.op (.const neg (if j = 2 then 1 else 0)))))))) + +/-- Complete one-set native round with charged setup and a written heavy count. -/ +def round (j : Fin 3) : Code .standard 64 := .seq (prepare j) CountPlace.program + +/-- Complete charged address/tag/padding initialization. -/ +def initializeCode : Code .standard 64 := + .seq layoutCode (.seq constantsCode (Padding.program.rename paddingRegisters)) + +/-- Complete one-node preprocessing. This code is independent of `n,w,M,N,U`. -/ +def program : Code .standard 64 := + .seq initializeCode (.seq (round 0) (.seq (round 1) (round 2))) + +/-- The original three input bases/lengths and base, length, magnitude and modulus parameters +are preserved by the complete program. -/ +theorem preserves_parameters (i : Fin 64) (hi : 46 ≤ i.val ∧ i.val ≤ 55) : + program.Preserves i := by + fin_cases i <;> norm_num at hi <;> decide + +/-- The assembled code length has a fixed bound, independent of every runtime parameter. -/ +theorem size_program_le : program.size ≤ 4096 := by decide + +/-- The complete program is deterministic and never reads the word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- No instruction samples randomness or invokes an oracle. -/ +theorem isRandFree : program.IsRandFree := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean new file mode 100644 index 0000000000..15ec928703 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeRound.lean @@ -0,0 +1,178 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeInitialize + +/-! +# Complete charged native rounds of a Chan–He node + +## Main definitions + +* `NodePacking.CallArgs`: exact runtime delivery to the existing complete counting/placement code. + +## References + +These native setup and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- Exact runtime parameters delivered to one complete counting/placement call. -/ +structure CallArgs (L : Layout) (s : Fin 3 → ℕ) (j : Fin 3) (x : Data 64) : Prop where + /-- Actual input length. -/ + len : x.regs CountPlace.len = L.n j + /-- Actual signed input base. -/ + src : x.regs CountPlace.src = s j + /-- Computed residue workspace. -/ + scratch : x.regs CountPlace.scratch = L.scratch + /-- Computed histogram workspace. -/ + hist : x.regs CountPlace.hist = L.hist + /-- Computed compact heavy data base. -/ + heavy : x.regs CountPlace.heavy = L.heavy j + /-- Computed packed array base. -/ + packed : x.regs CountPlace.packed = L.packed + /-- Actual positive modulus. -/ + modulus : x.regs CountPlace.modulus = L.M + /-- Exact encoded positive tag. -/ + tag : x.regs CountPlace.tag = 2 * L.tag j + /-- Packed column. -/ + column : x.regs CountPlace.column = j.val + 1 + /-- Duplicate-store offset. -/ + distance : x.regs CountPlace.distance = if j = 2 then 4 * L.M else 0 + /-- Sign multiplier flag. -/ + negative : x.regs CountPlace.negative = if j = 2 then 1 else 0 + /-- Computed descriptor/count cell. -/ + descriptor : x.regs CountPlace.descriptor = L.descriptor j + +/-- Runtime argument delivery keeps all persistent caller registers. -/ +theorem prepare_preserves (j : Fin 3) (i : Fin 64) (hi : 46 ≤ i.val) (hne : i ≠ temporary) : + (prepare j).Preserves i := by + have h : 46 ≤ i.val ∧ i ≠ temporary := ⟨hi, hne⟩ + fin_cases j <;> fin_cases i <;> norm_num [temporary] at h <;> decide + +/-- Every complete native round retains the caller arguments and prepared layout/constants. -/ +theorem round_preserves (j : Fin 3) (i : Fin 64) (hi : 46 ≤ i.val) (hne : i ≠ temporary) : + (round j).Preserves i := by + change (prepare j).Preserves i ∧ CountPlace.program.Preserves i + exact ⟨prepare_preserves j i hi hne, CountPlace.preserves i (by omega) + (by intro he; subst i; norm_num [CountCall.modulus] at hi) + (by intro he; subst i; norm_num [CountCall.result] at hi)⟩ + +/-- All count/place arguments are computed from actual retained caller values in at most +twenty instructions. In particular no output pointer or encoded tag is supplied for free. -/ +theorem exec_prepare {w : ℕ} (x y : Data 64) (j : Fin 3) + (hparams : Parameters x y) (hready : Prepared (layout x) y) + (hfit : Fits w (layout x).budget) (hN : 2 * (layout x).M ≤ (layout x).N) : + ∃ q, ExecWithin w (prepare j) y q 20 ∧ + CallArgs (layout x) (fun j => x.regs (source j)) j q ∧ q.mem = y.mem := by + let L := layout x + have hfit' : Fits w L.budget := hfit + have hN' : 2 * L.M ≤ L.N := hN + obtain ⟨hk, hh, hp, hg, hpad, h1, h4⟩ := hready + have hl (k : Fin 3) : y.regs (length k) = L.n k := + hparams _ (by dsimp only [length]; omega) + have hs (k : Fin 3) : y.regs (source k) = x.regs (source k) := + hparams _ (by dsimp only [source]; omega) + have hb : y.regs base = L.base := hparams _ (by decide) + have hm : y.regs modulus = L.M := hparams _ (by decide) + have f (v : ℕ) (hv : v ≤ L.packed) : Fits w v := + (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + have fc (v : ℕ) (hv : v ≤ 16) : Fits w v := hfit'.of_le (by have := L.bounds.2.2; omega) + have fm (v : ℕ) (hv : v ≤ 120 * L.U + 80) : Fits w v := + (hfit'.of_le L.bounds.2.1).of_le hv + have f0 : Fits w 0 := fits_zero _ + have f1 : Fits w 1 := fc _ (by decide) + have f2 : Fits w 2 := fc _ (by decide) + have f3 : Fits w 3 := fc _ (by decide) + have fb₀ : Fits w (L.base + 1) := f _ (by dsimp only [Layout.packed]; omega) + have fb₁ : Fits w (L.base + 1 + L.n 0) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fb₂ : Fits w (L.base + 1 + L.n 0 + 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fb₃ : Fits w (L.base + 1 + L.n 0 + 1 + L.n 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have fo₁ : Fits w (L.base + 1 + L.n 0 + 1) := fb₂ + have fo₂ : Fits w (L.base + 1 + L.n 0 + 1 + L.n 1 + 1) := f _ (by + dsimp only [Layout.packed, Layout.total]; omega) + have ft₁ : Fits w (3 * (2 * L.gap)) := fm _ (by dsimp only [Layout.gap]; omega) + have ft₂ : Fits w (4 * (2 * L.gap)) := fm _ (by dsimp only [Layout.gap]; omega) + have fd : Fits w (4 * L.M) := (hfit'.of_le L.bounds.1).of_le (by + dsimp only [Layout.endpoint, Layout.hist, Layout.scratch] + omega) + dsimp only [L] at * + fin_cases j <;> simp only [Fin.mk_zero, Fin.mk_one] + all_goals + refine ⟨?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [prepare, descriptorCode, tagCode, distanceCode, Fin.val_zero, Fin.val_one, + hk, hh, hp, hg, h1, h4, hl, hs, hb, hm, f0, f1, f2, f3, + fb₀, fb₁, fb₂, fb₃, fo₁, fo₂, ft₁, ft₂, fd] + · constructor <;> + simp +decide [CountPlace.len, CountPlace.src, CountPlace.scratch, + CountPlace.hist, CountPlace.heavy, CountPlace.packed, CountPlace.modulus, + CountPlace.tag, CountPlace.column, CountPlace.distance, CountPlace.negative, + CountPlace.descriptor, Layout.heavy, Layout.descriptor, Layout.tag, layout, + source, length] <;> omega + · rfl + +/-- A complete native round, with all actual counting calls discharged and all pointer, +tag, duplicate-store and count-descriptor work charged. -/ +theorem exec_round {w : ℕ} (x y : Data 64) (values : Fin 3 → ℕ → ℤ) (j : Fin 3) + (hparams : Parameters x y) (hready : Prepared (layout x) y) + (hfit : Fits (w + 2) (layout x).budget) + (hM : 0 < (layout x).M) (hN : 2 * (layout x).M ≤ (layout x).N) + (hmod : GenericRAM.Signed.Fits w ((layout x).M : ℤ)) + (hsrc : x.regs (source j) + (layout x).n j ≤ (layout x).base) + (hbdd : ∀ i < (layout x).n j, (values j i).natAbs ≤ (layout x).U) + (hsigned : ∀ i < (layout x).n j, GenericRAM.Signed.Fits w (values j i)) + (hinput : ∀ i < (layout x).n j, y.mem (x.regs (source j) + i) = + Signed.encode (values j i)) + (hinj : Set.InjOn (values j) (↑(Finset.range ((layout x).n j)) : Set ℕ)) : + ∃ z, ExecWithin (w + 2) (round j) y z (66 * (layout x).n j + 5 * (layout x).M + 65) ∧ + z.mem = roundMemory (layout x) values j y.mem ∧ Frame y z ∧ + Parameters x z ∧ Prepared (layout x) z := by + let L := layout x + have hsrc' : x.regs (source j) + L.n j ≤ L.base := hsrc + obtain ⟨q, hp, hq, hqm⟩ := exec_prepare x y j hparams hready hfit hN + have hf : Fits (w + 2) (CountPlace.budget q L.M L.N L.U (L.tag j)) := by + rw [CountPlace.budget, hq.src, hq.scratch, hq.hist, hq.heavy, hq.packed, hq.len] + exact hfit.of_le (L.count_budget_le j _ _ (by omega) (L.tag_bounds j).2) + have hn := L.n_le_total j + obtain ⟨z, hz, hzm, _⟩ := CountPlace.exec q (values j) (decide (j = 2)) hM hmod + hq.modulus hN (L.tag_bounds j).1 hq.tag + (by by_cases hj : j = 2 <;> simpa [hj] using hq.negative) + (by rw [hq.column]; omega) (by rw [hq.distance]; split_ifs <;> omega) + (by simpa only [hq.len] using hbdd) (by simpa only [hq.len] using hsigned) hf + (by + rw [hq.src, hq.len, hq.heavy] + have hd : (layout x).base ≤ (layout x).descriptor j := L.base_le_descriptor j + dsimp only [Layout.heavy] + omega) + (by rw [hq.heavy, hq.len, hq.packed]; exact L.heavy_end_le_packed j) + (by rw [hq.packed, hq.scratch]; rfl) + (by rw [hq.scratch, hq.len, hq.hist]; exact Nat.add_le_add_left hn _) + (by simpa only [hq.len, hq.src, hqm] using hinput) + (by simpa only [hq.len] using hinj) + have he : ExecWithin (w + 2) (round j) y z (66 * L.n j + 5 * L.M + 65) := by + have hz' : ExecWithin (w + 2) CountPlace.program q z (66 * L.n j + 5 * L.M + 45) := + by simpa only [hq.len] using hz + exact (hp.seq hz').mono (by omega) + have hframe : Frame y z := fun i hi hne => ExecWithin.regs_eq he (round_preserves j i hi hne) + refine ⟨z, he, ?_, hframe, hparams.of_frame hframe, hready.of_frame hframe⟩ + rw [hzm, hqm, hq.len, hq.heavy, hq.packed, hq.column, hq.distance, + hq.scratch, hq.hist, hq.descriptor] + rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean new file mode 100644 index 0000000000..444119ae48 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSetup.lean @@ -0,0 +1,197 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeProgram + +/-! +# Charged runtime layout and tag setup for one Chan–He node + +## Main definitions + +* `NodePacking.Prepared`: exact computed layout, encoded tags and fixed constants. + +## Main results + +* `NodePacking.exec_layout`: all address calculations are native instructions. + +## References + +These native setup proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking + +open Cslib.WordRAM + +/-- The immutable caller arguments survive every phase of the node program. -/ +def Parameters (x y : Data 64) : Prop := + ∀ i, 46 ≤ i.val ∧ i.val ≤ 55 → y.regs i = x.regs i + +/-- Runtime arguments, computed bases, encoded tags and constants retained by every round. +Register 61 is the sole setup scratch register in this range. -/ +def Frame (x y : Data 64) : Prop := + ∀ i, 46 ≤ i.val → i ≠ temporary → y.regs i = x.regs i + +/-- The computed runtime layout and encoded tag constants. -/ +def Prepared (L : Layout) (y : Data 64) : Prop := + y.regs scratch = L.scratch ∧ y.regs histogram = L.hist ∧ y.regs packed = L.packed ∧ + y.regs gap = 2 * L.gap ∧ y.regs padding = 4 * L.U + 2 ∧ + y.regs one = 1 ∧ y.regs four = 4 + +/-- A caller frame transports the prepared address/tag interface. -/ +theorem Prepared.of_frame {L : Layout} {x y : Data 64} (hx : Prepared L x) (hr : Frame x y) : + Prepared L y := by + obtain ⟨hk, hh, hp, hg, hpad, h1, h4⟩ := hx + exact ⟨(hr scratch (by decide) (by decide)).trans hk, + (hr histogram (by decide) (by decide)).trans hh, + (hr packed (by decide) (by decide)).trans hp, + (hr gap (by decide) (by decide)).trans hg, + (hr padding (by decide) (by decide)).trans hpad, + (hr one (by decide) (by decide)).trans h1, (hr four (by decide) (by decide)).trans h4⟩ + +/-- A caller frame transports the immutable input/size interface. -/ +theorem Parameters.of_frame {x y z : Data 64} (hy : Parameters x y) (hr : Frame y z) : + Parameters x z := by + intro i hi + have hne : i ≠ temporary := by intro h; subst i; norm_num [temporary] at hi + exact (hr i hi.1 hne).trans (hy i hi) + +/-- Exact native arguments for writing the four-column padding pattern. -/ +def PaddingReady (L : Layout) (y : Data 64) : Prop := + y.regs pdst = L.packed ∧ y.regs plen = L.N ∧ + y.regs pad0 = 120 * L.U + 80 ∧ y.regs pad1 = 16 * L.U + 10 ∧ + y.regs pad2 = 40 * L.U + 26 ∧ y.regs pad3 = 44 * L.U + 30 + +/-- Compute the disjoint addresses from the supplied runtime lengths and fresh base. -/ +theorem exec_layout {w : ℕ} (x : Data 64) (hfit : Fits w (layout x).budget) : + ∃ y, ExecWithin w layoutCode x y 12 ∧ Parameters x y ∧ y.mem = x.mem ∧ + y.regs packed = (layout x).packed ∧ y.regs scratch = (layout x).scratch ∧ + y.regs histogram = (layout x).hist ∧ y.regs one = 1 ∧ y.regs four = 4 ∧ + y.regs temporary = 3 := by + let L := layout x + have hfit' : Fits w L.budget := hfit + have fb := hfit'.of_le L.bounds.1 + have f (v : ℕ) (hv : v ≤ L.endpoint) : Fits w v := fb.of_le hv + have fc (v : ℕ) (hv : v ≤ 16) : Fits w v := + hfit'.of_le (by have := L.bounds.2.2; omega) + have f1 : Fits w 1 := fc _ (by decide) + have f3 : Fits w 3 := fc _ (by decide) + have f4 : Fits w 4 := fc _ (by decide) + have fp₀ : Fits w (x.regs base + 3) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp₁ : Fits w (x.regs base + 3 + x.regs (length 0)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp₂ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fp : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fn : Fits w (4 * x.regs groups) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fk : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh₀ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh₁ : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0) + x.regs (length 1)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + have fh : Fits w (x.regs base + 3 + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2) + 4 * x.regs groups + x.regs (length 0) + x.regs (length 1) + + x.regs (length 2)) := + f _ (by dsimp only [L, layout, Layout.endpoint, Layout.hist, Layout.scratch, + Layout.packed, Layout.total]; omega) + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [layoutCode, f1, f3, f4, fp₀, fp₁, fp₂, fp, fn, fk, fh₀, fh₁, fh] + · intro i hi + fin_cases i <;> norm_num at hi <;> rfl + · rfl + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.packed, Layout.total] + omega + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.scratch, Layout.packed, Layout.total] + omega + · simp +decide only [Data.set_regs, Function.update_apply, packed, scratch, histogram, + one, four, temporary, ↓reduceIte] + dsimp only [layout, Layout.hist, Layout.scratch, Layout.packed, Layout.total] + omega + · rfl + · rfl + · rfl + +/-- Compute encoded tags and signed padding through native arithmetic in sixteen instructions. +The single envelope supplies every intermediate fit, including the third-column subtraction. -/ +theorem exec_constants {w : ℕ} (L : Layout) (x : Data 64) + (hfit : Fits w L.budget) (hU : x.regs magnitude = L.U) (hN : x.regs groups = L.N) + (hk : x.regs scratch = L.scratch) (hh : x.regs histogram = L.hist) + (hp : x.regs packed = L.packed) (h1 : x.regs one = 1) (h4 : x.regs four = 4) + (h3 : x.regs temporary = 3) : + ∃ y, ExecWithin w constantsCode x y 16 ∧ Parameters x y ∧ y.mem = x.mem ∧ + Prepared L y ∧ PaddingReady L y := by + have f (v : ℕ) (hv : v ≤ 120 * L.U + 80) : Fits w v := + (hfit.of_le L.bounds.2.1).of_le hv + have f₂ : Fits w 2 := f _ (by omega) + have f₃ : Fits w 3 := f _ (by omega) + have f₁₀ : Fits w 10 := f _ (by omega) + have fw₀ : Fits w (L.U + L.U) := f _ (by omega) + have fw₁ : Fits w (L.U + L.U + 1) := f _ (by omega) + have fw : Fits w (L.U + L.U + 1 + (L.U + L.U + 1)) := f _ (by omega) + have fg₀ : Fits w (3 * (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + have fg : Fits w (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) := f _ (by omega) + have fp₀ : Fits w (10 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₁ : Fits w (L.U + L.U + 1 + (L.U + L.U + 1) + + (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₂₀ : Fits w (3 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₂ : Fits w (3 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) + + (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + have fp₃₀ : Fits w (4 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2)) := f _ (by omega) + have fp₃ : Fits w (4 * (3 * (L.U + L.U + 1 + (L.U + L.U + 1)) + 2) - + (L.U + L.U + 1 + (L.U + L.U + 1))) := f _ (by omega) + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [constantsCode, hU, hN, hp, h1, h4, h3, f₂, f₃, f₁₀, + fw₀, fw₁, fw, fg₀, fg, fp₀, fp₁, fp₂₀, fp₂, fp₃₀, fp₃] + · intro i hi + fin_cases i <;> norm_num at hi <;> rfl + · rfl + · refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact hk + · exact hh + · exact hp + · simp +decide [gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3, Layout.gap] + omega + · simp +decide [gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3] + omega + · exact h1 + · exact h4 + · simp +decide [PaddingReady, gap, padding, temporary, pdst, plen, pad0, pad1, pad2, pad3, + paddingRegisters, Padding.dst, Padding.len, Padding.pad0, Padding.pad1, + Padding.pad2, Padding.pad3] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean new file mode 100644 index 0000000000..dcf81eeb18 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/NodeSpec.lean @@ -0,0 +1,113 @@ +/- +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.Output.packed_square`: exact native readback at the common `8m²` length. +* `NodePacking.abs_oneArray_le`: the node magnitude bound is `60U+40`. + +These programs and composition proofs are original. The mathematical comparator is the +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 + +/-- 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]; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.NodePacking diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean new file mode 100644 index 0000000000..ad4c2c0e4c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Padding.lean @@ -0,0 +1,178 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation +import Mathlib.Tactic.NormNum + +/-! +# Initializing the four columns of a Chan–He node + +## Main definitions + +* `Padding.program`: a fixed loop writing a four-word pattern to every group. + +## Main results + +* `Padding.exec`: exact memory and a charged `12N+5` instruction bound. + +## References + +This native program and its execution proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Padding + +open Cslib.WordRAM + +/-- Packed output base. -/ +abbrev dst : Fin 10 := 0 +/-- Number of four-cell groups. -/ +abbrev len : Fin 10 := 1 +/-- Padding word for column zero. -/ +abbrev pad0 : Fin 10 := 2 +/-- Padding word for column one. -/ +abbrev pad1 : Fin 10 := 3 +/-- Padding word for column two. -/ +abbrev pad2 : Fin 10 := 4 +/-- Padding word for column three. -/ +abbrev pad3 : Fin 10 := 5 +/-- Group cursor. -/ +abbrev idx : Fin 10 := 6 +/-- Temporary address. -/ +abbrev addr : Fin 10 := 7 +/-- Constant one. -/ +abbrev one : Fin 10 := 8 +/-- Constant four. -/ +abbrev four : Fin 10 := 9 + +/-- A concrete four-word pattern, addressed by its residue modulo four. -/ +def pattern (p₀ p₁ p₂ p₃ u : ℕ) : ℕ := + if u % 4 = 0 then p₀ else if u % 4 = 1 then p₁ else if u % 4 = 2 then p₂ else p₃ + +/-- Initial memory with the first `k` groups overwritten, including all padding. -/ +def memory (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ k : ℕ) : ℕ → ℕ := fun a => + if dst ≤ a ∧ a < dst + 4 * k then pattern p₀ p₁ p₂ p₃ (a - dst) else mem a + +@[simp] theorem memory_zero (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ : ℕ) : + memory mem dst p₀ p₁ p₂ p₃ 0 = mem := by + funext a + simp [memory] + +/-- Four distinct stores make one complete group. -/ +theorem memory_succ (mem : ℕ → ℕ) (dst p₀ p₁ p₂ p₃ k : ℕ) : + memory mem dst p₀ p₁ p₂ p₃ (k + 1) = + Function.update (Function.update (Function.update (Function.update + (memory mem dst p₀ p₁ p₂ p₃ k) (dst + 4 * k) p₀) + (dst + 4 * k + 1) p₁) (dst + 4 * k + 2) p₂) (dst + 4 * k + 3) p₃ := by + funext a + by_cases h₀ : a = dst + 4 * k + · subst a; simp [memory, pattern] + by_cases h₁ : a = dst + 4 * k + 1 + · subst a + simp [memory, pattern, show dst + 4 * k + 1 - dst = 4 * k + 1 by omega, + show dst ≤ dst + 4 * k + 1 by omega, + show dst + 4 * k + 1 < dst + 4 * (k + 1) by omega] + by_cases h₂ : a = dst + 4 * k + 2 + · subst a + simp [memory, pattern, show dst + 4 * k + 2 - dst = 4 * k + 2 by omega, + show dst ≤ dst + 4 * k + 2 by omega, + show dst + 4 * k + 2 < dst + 4 * (k + 1) by omega] + by_cases h₃ : a = dst + 4 * k + 3 + · subst a + simp [memory, pattern, show dst + 4 * k + 3 - dst = 4 * k + 3 by omega, + show dst ≤ dst + 4 * k + 3 by omega, + show dst + 4 * k + 3 < dst + 4 * (k + 1) by omega] + simp only [Function.update_of_ne h₃, Function.update_of_ne h₂, + Function.update_of_ne h₁, Function.update_of_ne h₀, memory] + have he : (dst ≤ a ∧ a < dst + 4 * (k + 1)) ↔ (dst ≤ a ∧ a < dst + 4 * k) := by omega + simp only [he] + +/-- Every address, store and increment of a four-word group is charged. -/ +def body : Code .standard 10 := ram[ + addr ← four * idx; addr ← dst + addr; *addr ← pad0; + addr ← addr + one; *addr ← pad1; addr ← addr + one; *addr ← pad2; + addr ← addr + one; *addr ← pad3; idx ← idx + one ] + +/-- Runtime pattern values and length, with fixed code and registers. -/ +def program : Code .standard 10 := + .seq (ram[ idx ← #0; one ← #1; four ← #4 ]) (.loop (.lt idx len) body) + +/-- Initialize exactly `4N` cells from arbitrary old memory. -/ +theorem exec {w : ℕ} (x : Data 10) (h4 : Fits w 4) + (hfit : Fits w (x.regs dst + 4 * x.regs len)) : + ∃ y, ExecWithin w program x y (12 * x.regs len + 5) ∧ + y.mem = memory x.mem (x.regs dst) (x.regs pad0) (x.regs pad1) + (x.regs pad2) (x.regs pad3) (x.regs len) ∧ + ∀ i, i.val < 6 → y.regs i = x.regs i := by + let I (y : Data 10) : Prop := + (∀ i, i.val < 6 → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs four = 4 ∧ + y.regs idx ≤ x.regs len ∧ y.mem = memory x.mem (x.regs dst) + (x.regs pad0) (x.regs pad1) (x.regs pad2) (x.regs pad3) (y.regs idx) + have hbody (y : Data 10) (hy : I y) (hcond : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 10 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, h1, hfour, hi, hm⟩ := hy + have hd := hr dst (by decide) + have hl := hr len (by decide) + have hp₀ := hr pad0 (by decide) + have hp₁ := hr pad1 (by decide) + have hp₂ := hr pad2 (by decide) + have hp₃ := hr pad3 (by decide) + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hcond + have fa : Fits w (4 * y.regs idx) := hfit.of_le (by omega) + have fb : Fits w (x.regs dst + 4 * y.regs idx) := hfit.of_le (by omega) + have fc : Fits w (x.regs dst + 4 * y.regs idx + 1) := hfit.of_le (by omega) + have fd : Fits w (x.regs dst + 4 * y.regs idx + 1 + 1) := hfit.of_le (by omega) + have fe : Fits w (x.regs dst + 4 * y.regs idx + 1 + 1 + 1) := hfit.of_le (by omega) + have fi : Fits w (y.regs idx + 1) := hfit.of_le (by omega) + refine ⟨?z, ?run, ?inv, ?decrease⟩ + case run => ram_eval [body, h1, hfour, hd, hp₀, hp₁, hp₂, hp₃, fa, fb, fc, fd, fe, fi] + case inv => + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have ha : i ≠ addr := by intro h; subst i; norm_num [addr] at hi + have hn : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simpa only [Data.set_regs, Data.write_regs, Function.update_of_ne ha, + Function.update_of_ne hn] using hr i hi + · simpa +decide [one, addr, idx] using h1 + · simpa +decide [four, addr, idx] using hfour + · simpa only [Data.set_regs, Function.update_self, h1] using Nat.succ_le_of_lt hlt + · simp only [Data.set_mem, Data.write_mem] + rw [hm, ← memory_succ] + rfl + case decrease => simp only [Data.set_regs, Function.update_self]; omega + let x₀ := ((x.set idx 0).set one 1).set four 4 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hi₁ : i ≠ one := by intro h; subst i; norm_num [one] at hi + have hi₄ : i ≠ four := by intro h; subst i; norm_num [four] at hi + have hin : i ≠ idx := by intro h; subst i; norm_num [idx] at hi + simp only [x₀, Data.set_regs, Function.update_of_ne hi₁, + Function.update_of_ne hi₄, Function.update_of_ne hin] + all_goals simp +decide [x₀, one, four, idx] + obtain ⟨y, he, hy, hstop⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, h1, hfour, hi, hm⟩ := hy + have hl := hr len (by decide) + have hend : y.regs idx = x.regs len := by + simp only [Cond.holds_lt, hl] at hstop + omega + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; one ← #1; four ← #4 ]) x x₀ 3 := by + ram_eval [x₀, h4, h4.of_le (by decide : 1 ≤ 4)] + refine ⟨y, ?_, by simpa only [hend] using hm, hr⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, idx, one, four, Data.set_regs, Function.update_apply, + ↓reduceIte, Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Padding diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean new file mode 100644 index 0000000000..5fc95cc148 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Place.lean @@ -0,0 +1,221 @@ +/- +Copyright (c) 2026 Anthropic, PBC, Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Anthropic, PBC, Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.BucketCounts +public import Complexity.Computability.WordRAM.Signed.Arithmetic +import Mathlib.Tactic.FinCases + +/-! +# Native heavy extraction and light placement + +The input, unsigned residue array and histogram are supplied by the actual `Count.program`. +One scan copies heavy integers, in input order, to a compact output. Each light integer is +tagged and placed directly in the packed node array. The third pass negates its integer and +its residue, and writes both copies required by `arrZ`. + +The mathematical comparator is Anthropic's [Buckets.lean](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem21a/ChanHe/Buckets.lean) +and `NodeArray.lean` at `formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. +The native code, encoding arithmetic and execution proofs here are original. No `Light` +execution theorem or source-machine simulation is assumed. + +## Main definitions + +* `Place.program`: a fixed scan with runtime bases, length, modulus, tag and column. +* `Place.heavyList`: compact heavy elements in input order. +* `Place.memory`: exact writes of a prefix of the scan, including both third-column copies. + +## References + +* [T. M. Chan and Q. He, *Reducing 3SUM to Convolution-3SUM*][ChanHe2020] +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Input array base. -/ +abbrev src : Fin 32 := 9 +/-- Unsigned residue array base. -/ +abbrev keys : Fin 32 := 10 +/-- Input length. -/ +abbrev len : Fin 32 := 11 +/-- Histogram base. -/ +abbrev hist : Fin 32 := 12 +/-- Compact heavy output base. -/ +abbrev heavy : Fin 32 := 13 +/-- Packed node output base. -/ +abbrev packed : Fin 32 := 14 +/-- Positive modulus, decoded unsigned. -/ +abbrev modulus : Fin 32 := 15 +/-- Twice the positive tag added to a light integer. -/ +abbrev tag : Fin 32 := 16 +/-- Column in a four-cell group. -/ +abbrev column : Fin 32 := 17 +/-- Distance to the second write: zero for X/Y, `4M` for Z. -/ +abbrev distance : Fin 32 := 18 +/-- Zero for X/Y, one for Z. -/ +abbrev negative : Fin 32 := 19 +/-- Input cursor. -/ +abbrev idx : Fin 32 := 20 +/-- Number of heavy integers already written. -/ +abbrev count : Fin 32 := 21 +/-- Temporary address. -/ +abbrev addr : Fin 32 := 22 +/-- Current remainder. -/ +abbrev key : Fin 32 := 23 +/-- Current value and its tagged encoding. -/ +abbrev value : Fin 32 := 24 +/-- Twice the current magnitude. -/ +abbrev magnitude : Fin 32 := 25 +/-- Current input sign. -/ +abbrev sign : Fin 32 := 26 +/-- Constant one. -/ +abbrev one : Fin 32 := 27 +/-- Constant two. -/ +abbrev two : Fin 32 := 28 +/-- Constant four. -/ +abbrev four : Fin 32 := 29 +/-- Constant zero. -/ +abbrev zero : Fin 32 := 30 +/-- Current histogram count. -/ +abbrev bucketSize : Fin 32 := 31 + +/-- Negating a Euclidean residue needs only a subtraction and a remainder. -/ +def targetKey (M : ℕ) (neg : Bool) (z : ℤ) : ℕ := + if neg then (M - residueKey M z) % M else residueKey M z + +/-- Integer contribution before the positive tag. -/ +def signedValue (neg : Bool) (z : ℤ) : ℤ := if neg then -z else z + +/-- The target index is exactly the residue of the signed contribution. -/ +theorem targetKey_eq {M : ℕ} (hM : 0 < M) (neg : Bool) (z : ℤ) : + targetKey M neg z = residueKey M (signedValue neg z) := by + cases neg + · rfl + · have hr := residueKey_lt hM z + apply Int.ofNat_inj.mp + change (((M - residueKey M z) % M : ℕ) : ℤ) = (residueKey M (-z) : ℤ) + rw [Int.natCast_emod, Nat.cast_sub (Nat.le_of_lt hr), residueKey_cast hM, + residueKey_cast hM] + rw [Int.sub_emod_emod, Int.sub_emod_left] + +/-- A prefix of the compact output; membership is tested against the full histogram. -/ +def heavyList (values : ℕ → ℤ) (n M : ℕ) : ℕ → List ℤ + | 0 => [] + | k + 1 => heavyList values n M k ++ + if 1 < Histogram.count (fun i => residueKey M (values i)) n (residueKey M (values k)) + then [values k] else [] + +/-- A compact prefix has at most as many cells as the corresponding input prefix. -/ +theorem length_heavyList_le (values : ℕ → ℤ) (n M k : ℕ) : + (heavyList values n M k).length ≤ k := by + induction k with + | zero => rfl + | succ k ih => + simp only [heavyList, List.length_append] + split_ifs <;> simp only [List.length_singleton, List.length_nil] <;> omega + +/-- The compact array is the input subsequence selected by the histogram test. -/ +theorem heavyList_eq_filter (values : ℕ → ℤ) (n M k : ℕ) : + heavyList values n M k = ((List.range k).filter fun i => + decide (1 < Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)))).map values := by + induction k with + | zero => rfl + | succ k ih => + rw [heavyList, List.range_succ, List.filter_append, List.map_append, ih] + split_ifs <;> simp_all + +/-- The native compact output has exactly the existing heavy subset as its underlying set. -/ +theorem heavyList_toFinset (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).toFinset = + Cslib.Algorithms.ThreeSum.ChanHe.heavy ((Finset.range n).image values) M := by + ext z + rw [heavyList_eq_filter] + simp only [List.mem_toFinset, List.mem_map, List.mem_filter, List.mem_range, decide_eq_true_eq] + constructor + · rintro ⟨i, ⟨hi, hc⟩, rfl⟩ + exact (mem_heavy_iff_count values n hM hinj hi).mpr hc + · intro hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp + ((Finset.mem_filter.mp hz).1) + exact ⟨i, ⟨Finset.mem_range.mp hi, + (mem_heavy_iff_count values n hM hinj (Finset.mem_range.mp hi)).mp hz⟩, rfl⟩ + +/-- The exact per-input write, either to the next compact heavy cell or to packed light cells. -/ +def write (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (k : ℕ) : ℕ → ℕ := + if 1 < Histogram.count (fun i => residueKey M (values i)) n (residueKey M (values k)) then + Function.update mem (out + (heavyList values n M k).length) (encode (values k)) + else + let a := dst + 4 * targetKey M neg (values k) + col + let v := encode (signedValue neg (values k) + (t : ℤ)) + Function.update (Function.update mem a v) (a + dist) v + +/-- All writes of the first `k` iterations, framed by arbitrary initial memory. -/ +def memory (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) : ℕ → ℕ → ℕ + | 0 => mem + | k + 1 => write (memory mem values n M out dst col dist t neg k) + values n M out dst col dist t neg k + +/-- Copy a heavy integer without changing its signed encoding. -/ +def heavyCode : Code .standard 32 := ram[ + addr ← heavy + count; *addr ← value; count ← count + one ] + +/-- Tag a light integer using its magnitude and sign, then write its required cells. -/ +def lightCode : Code .standard 32 := ram[ + if negative = zero then [ key ← key ] + else [ key ← modulus - key; key ← key % modulus ]; + magnitude ← value / two; magnitude ← magnitude + magnitude; sign ← value % two; + if sign = negative then [ value ← tag + magnitude ] else [ value ← tag - magnitude ]; + addr ← four * key; addr ← packed + addr; addr ← addr + column; *addr ← value; + addr ← addr + distance; *addr ← value ] + +/-- Every iteration reads the original integer, its unsigned key and its exact bucket size. -/ +def body : Code .standard 32 := ram[ + addr ← keys + idx; key ← *addr; addr ← hist + key; bucketSize ← *addr; + addr ← src + idx; value ← *addr; + if one < bucketSize then [ ⟪heavyCode⟫ ] else [ ⟪lightCode⟫ ]; + idx ← idx + one ] + +/-- The same finite code handles every input length, width, modulus and output length. -/ +def program : Code .standard 32 := + .seq (ram[ idx ← #0; count ← #0; one ← #1; two ← #2; four ← #4; zero ← #0 ]) + (.loop (.lt idx len) body) + +/-- All runtime parameters survive the scan. -/ +theorem preserves_parameters (i : Fin 32) (hi : 9 ≤ i.val ∧ i.val ≤ 19) : + program.Preserves i := by + fin_cases i <;> norm_num at hi <;> decide + +/-- A scan can only change its heavy capacity block and its packed output block. -/ +theorem memory_outside (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t N : ℕ) + (neg : Bool) (hM : 0 < M) (hN : 2 * M ≤ N) (hc : col < 4) (hd : dist ≤ 4 * M) + {k : ℕ} (hk : k ≤ n) {a : ℕ} + (ha : ¬(out ≤ a ∧ a < out + n)) (hb : ¬(dst ≤ a ∧ a < dst + 4 * N)) : + memory mem values n M out dst col dist t neg k a = mem a := by + induction k with + | zero => rfl + | succ k ih => + have hh := length_heavyList_le values n M k + have hr : targetKey M neg (values k) < M := by + rw [targetKey_eq hM] + exact residueKey_lt hM _ + have hn₁ : a ≠ out + (heavyList values n M k).length := by omega + have hn₂ : a ≠ dst + 4 * targetKey M neg (values k) + col := by omega + have hn₃ : a ≠ dst + 4 * targetKey M neg (values k) + col + dist := by omega + simp only [memory, write] + split_ifs <;> simp only [Function.update_of_ne hn₁, Function.update_of_ne hn₂, + Function.update_of_ne hn₃] <;> exact ih (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean new file mode 100644 index 0000000000..d985795a0b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceExec.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +/-! +# Charged execution of the native Chan–He placement scan + +## Main results + +* `Place.tagged_encode`: the unsigned sign/magnitude calculation is the exact tagged integer. + +## References + +The encoding calculation and native proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The encoded tagged value can be computed without a signed primitive. The positive tag +dominates the integer magnitude, so unsigned subtraction cannot truncate a negative result. -/ +theorem tagged_encode (neg : Bool) (z : ℤ) (t : ℕ) (ht : z.natAbs ≤ t) : + encode (signedValue neg z + (t : ℤ)) = + if encode z % 2 = (if neg then 1 else 0) then + 2 * t + (encode z / 2 + encode z / 2) + else 2 * t - (encode z / 2 + encode z / 2) := by + cases neg <;> by_cases hz : z < 0 + · have he : (z.natAbs : ℤ) = -z := Int.ofNat_natAbs_of_nonpos (by omega) + have hnon : 0 ≤ z + (t : ℤ) := by omega + simp only [signedValue, Bool.false_eq_true, ↓reduceIte, encode_mod_two, hz, + encode_div_two, Nat.reduceEqDiff] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = z := (Int.eq_natAbs_of_nonneg (by omega)).symm + have hnon : 0 ≤ z + (t : ℤ) := by omega + simp only [signedValue, Bool.false_eq_true, ↓reduceIte, encode_mod_two, hz, + encode_div_two] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = -z := Int.ofNat_natAbs_of_nonpos (by omega) + have hnon : 0 ≤ -z + (t : ℤ) := by omega + simp only [signedValue, ↓reduceIte, encode_mod_two, hz, encode_div_two] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + · have he : (z.natAbs : ℤ) = z := (Int.eq_natAbs_of_nonneg (by omega)).symm + have hnon : 0 ≤ -z + (t : ℤ) := by omega + simp only [signedValue, ↓reduceIte, encode_mod_two, hz, encode_div_two, Nat.reduceEqDiff] + unfold encode + rw [ite_eq_right (by omega)] + have hm := Int.natAbs_of_nonneg hnon + omega + +/-- A polynomial envelope for every value and address produced by the placement scan. +The caller supplies a fit for this sum; no fit of an uncomputed output is assumed. -/ +def budget (x : Data 32) (M N U t : ℕ) : ℕ := + x.regs src + x.regs keys + x.regs hist + x.regs heavy + x.regs packed + x.regs len + + M + 4 * N + 2 * t + 2 * U + 16 + + +/-- One complete scan round, including classification, sign/tag work, both light stores, +heavy count maintenance and the input cursor increment. -/ +theorem exec_body {w M N U t : ℕ} (x : Data 32) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : x.regs column < 4) + (hd : x.regs distance ≤ 4 * M) (ht : U ≤ t) + (hmod : x.regs modulus = M) (htag : x.regs tag = 2 * t) + (hneg : x.regs negative = if neg then 1 else 0) + (h1 : x.regs one = 1) (h2 : x.regs two = 2) (h4 : x.regs four = 4) + (h0 : x.regs zero = 0) (hi : x.regs idx < x.regs len) + (hcount : x.regs count = (heavyList values (x.regs len) M (x.regs idx)).length) + (hbdd : (values (x.regs idx)).natAbs ≤ U) + (hfit : Fits w (budget x M N U t)) + (hinput : x.mem (x.regs src + x.regs idx) = encode (values (x.regs idx))) + (hkey : x.mem (x.regs keys + x.regs idx) = residueKey M (values (x.regs idx))) + (hhist : x.mem (x.regs hist + residueKey M (values (x.regs idx))) = + Histogram.count (fun j => residueKey M (values j)) (x.regs len) + (residueKey M (values (x.regs idx)))) : + ∃ y, ExecWithin w body x y 30 ∧ + y.mem = write x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (x.regs idx) ∧ + y.regs count = (heavyList values (x.regs len) M (x.regs idx + 1)).length ∧ + y.regs idx = x.regs idx + 1 ∧ + (∀ i, (9 ≤ i.val ∧ i.val ≤ 19) ∨ i = one ∨ i = two ∨ i = four ∨ i = zero → + y.regs i = x.regs i) := by + let k := x.regs idx + let n := x.regs len + let r := residueKey M (values k) + let c := Histogram.count (fun j => residueKey M (values j)) n r + have hr : r < M := residueKey_lt hM _ + have hr' : targetKey M neg (values k) < M := by + rw [targetKey_eq hM] + exact residueKey_lt hM _ + have f (v : ℕ) (hv : v ≤ budget x M N U t) : Fits w v := hfit.of_le hv + have fone : Fits w 1 := f _ (by dsimp only [budget]; omega) + have fzero : Fits w 0 := fits_zero _ + have fs : Fits w (x.regs src + k) := f _ (by dsimp only [budget, k]; omega) + have fk : Fits w (x.regs keys + k) := f _ (by dsimp only [budget, k]; omega) + have fh : Fits w (x.regs hist + r) := f _ (by dsimp only [budget]; omega) + have fo : Fits w (x.regs heavy + x.regs count) := f _ (by + have hl := length_heavyList_le values n M k + dsimp only [n, k] at hl + dsimp only [budget] + omega) + have fcount : Fits w (x.regs count + 1) := f _ (by + have hl := length_heavyList_le values n M k + dsimp only [n, k] at hl + dsimp only [budget] + omega) + have fi : Fits w (k + 1) := f _ (by dsimp only [budget, k]; omega) + have fm : Fits w (M - r) := f _ (by dsimp only [budget]; omega) + have fr : Fits w ((M - r) % M) := f _ (by + have := Nat.mod_lt (M - r) hM + dsimp only [budget] + omega) + have fmag : Fits w (values k).natAbs := f _ (by dsimp only [budget, k]; omega) + have fmag2 : Fits w ((values k).natAbs + (values k).natAbs) := + f _ (by dsimp only [budget, k]; omega) + have fsign : Fits w (encode (values k) % 2) := f _ (by + have := Nat.mod_lt (encode (values k)) (by decide : 0 < 2) + dsimp only [budget] + omega) + have fadd : Fits w (2 * t + ((values k).natAbs + (values k).natAbs)) := + f _ (by dsimp only [budget, k]; omega) + have fsub : Fits w (2 * t - ((values k).natAbs + (values k).natAbs)) := + f _ (by dsimp only [budget]; omega) + have fa : Fits w (4 * targetKey M neg (values k)) := + f _ (by dsimp only [budget]; omega) + have fb : Fits w (x.regs packed + 4 * targetKey M neg (values k)) := + f _ (by dsimp only [budget]; omega) + have fc : Fits w (x.regs packed + 4 * targetKey M neg (values k) + x.regs column) := + f _ (by dsimp only [budget]; omega) + have fd : Fits w (x.regs packed + 4 * targetKey M neg (values k) + x.regs column + + x.regs distance) := f _ (by dsimp only [budget]; omega) + have hv := tagged_encode neg (values k) t (hbdd.trans ht) + have hcp : 0 < c := count_residue_pos values n M (show k < n from hi) + by_cases hh : 1 < c + · refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [body, heavyCode, src, keys, idx, hist, addr, key, bucketSize, value, one, + heavy, count, Fin.mk.injEq, h1, hinput, hkey, hhist, hh, + fs, fk, fh, fo, fcount, fi, k, n, r, c] + · simp [write, hh, k, n, r, c, hcount] + · simp +decide [heavyList, hh, k, n, r, c, hcount, count, idx] + · simp +decide [idx] + · intro i hi + fin_cases i <;> norm_num [one, two, four, zero] at hi <;> rfl + · have hc1 : c = 1 := by omega + cases neg <;> by_cases hs : values k < 0 + all_goals + simp only [targetKey, Bool.false_eq_true, ↓reduceIte] at fa fb fc fd + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ + rotate_left + · ram_eval [body, lightCode, src, keys, idx, hist, addr, key, bucketSize, value, + one, two, four, zero, modulus, negative, tag, magnitude, sign, packed, column, + distance, Fin.mk.injEq, h1, h2, h4, h0, hmod, htag, hneg, hinput, hkey, hhist, + hh, hc1, hs, encode_div_two, encode_mod_two, fs, fk, fh, fi, fm, fr, fmag, + fmag2, fsign, fadd, fsub, fone, fzero, fa, fb, fc, fd, targetKey, k, n, r, c] + · simp only [write, show ¬1 < Histogram.count (fun i => residueKey M (values i)) + (x.regs len) (residueKey M (values (x.regs idx))) from hh, ↓reduceIte] + simp only [Data.set_mem, Data.write_mem] + simp +decide [targetKey, k, hs, encode_div_two, encode_mod_two, hv] + · simp +decide [heavyList, hh, k, n, r, c, hcount, count, idx] + · simp +decide [idx] + · intro i hi + fin_cases i <;> norm_num [one, two, four, zero] at hi <;> rfl + + +/-- Complete native placement in at most `32n+8` instructions, excluding halt. All input, +residue and histogram reads are proved from the memory frame, rather than assumed anew at +iterations. The heavy block precedes the packed block, which precedes the counting workspace. -/ +theorem exec {w M N U t : ℕ} (x : Data 32) (values : ℕ → ℤ) (neg : Bool) + (hM : 0 < M) (hN : 2 * M ≤ N) (hc : x.regs column < 4) + (hd : x.regs distance ≤ 4 * M) (ht : U ≤ t) + (hmod : x.regs modulus = M) (htag : x.regs tag = 2 * t) + (hneg : x.regs negative = if neg then 1 else 0) + (hbdd : ∀ i < x.regs len, (values i).natAbs ≤ U) + (hfit : Fits w (budget x M N U t)) + (hsh : x.regs src + x.regs len ≤ x.regs heavy) + (hhp : x.regs heavy + x.regs len ≤ x.regs packed) + (hpk : x.regs packed + 4 * N ≤ x.regs keys) + (hkh : x.regs keys + x.regs len ≤ x.regs hist) + (hinput : ∀ i < x.regs len, x.mem (x.regs src + i) = encode (values i)) + (hkey : ∀ i < x.regs len, x.mem (x.regs keys + i) = residueKey M (values i)) + (hhist : ∀ r < M, x.mem (x.regs hist + r) = + Histogram.count (fun j => residueKey M (values j)) (x.regs len) r) : + ∃ y, ExecWithin w program x y (32 * x.regs len + 8) ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (x.regs len) ∧ + y.regs count = (heavyList values (x.regs len) M (x.regs len)).length ∧ + ∀ i, 9 ≤ i.val ∧ i.val ≤ 19 → y.regs i = x.regs i := by + let P (i : Fin 32) : Prop := 9 ≤ i.val ∧ i.val ≤ 19 + let I (y : Data 32) : Prop := + (∀ i, P i → y.regs i = x.regs i) ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs four = 4 ∧ y.regs zero = 0 ∧ y.regs idx ≤ x.regs len ∧ + y.regs count = (heavyList values (x.regs len) M (y.regs idx)).length ∧ + y.mem = memory x.mem values (x.regs len) M (x.regs heavy) (x.regs packed) + (x.regs column) (x.regs distance) t neg (y.regs idx) + have hbody (y : Data 32) (hy : I y) (hcond : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 30 ∧ I z ∧ + x.regs len - z.regs idx < x.regs len - y.regs idx := by + obtain ⟨hr, h1, h2, h4, h0, hi, hcount, hm⟩ := hy + have hs := hr src (by decide) + have hk := hr keys (by decide) + have hl := hr len (by decide) + have hh := hr hist (by decide) + have ho := hr heavy (by decide) + have hp := hr packed (by decide) + have hcol := hr column (by decide) + have hdist := hr distance (by decide) + have hmod' := (hr modulus (by decide)).trans hmod + have htag' := (hr tag (by decide)).trans htag + have hneg' := (hr negative (by decide)).trans hneg + have hlt : y.regs idx < x.regs len := by simpa only [Cond.holds_lt, hl] using hcond + have hbefore (a : ℕ) (ha : a < x.regs heavy) : y.mem a = x.mem a := by + rw [hm] + apply memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd hi <;> omega + have hafter (a : ℕ) (ha : x.regs keys ≤ a) : y.mem a = x.mem a := by + rw [hm] + apply memory_outside _ _ _ _ _ _ _ _ _ N neg hM hN hc hd hi <;> omega + obtain ⟨z, he, hzm, hzc, hzi, hzr⟩ := exec_body y values neg hM hN + (by simpa only [hcol] using hc) (by simpa only [hdist] using hd) ht hmod' htag' hneg' + h1 h2 h4 h0 (by simpa only [hl] using hlt) (by simpa only [hl] using hcount) + (hbdd _ hlt) + (by simpa only [budget, hs, hk, hh, ho, hp, hl] using hfit) + (by rw [hs, hbefore _ (by omega)]; exact hinput _ hlt) + (by rw [hk, hafter _ (by omega)]; exact hkey _ hlt) + (by rw [hh, hl, hafter _ (by omega)]; exact hhist _ (residueKey_lt hM _)) + refine ⟨z, he, ?_, by omega⟩ + refine ⟨fun i hi => (hzr i (Or.inl hi)).trans (hr i hi), + (hzr one (by simp)).trans h1, + (hzr two (by simp)).trans h2, (hzr four (by simp)).trans h4, + (hzr zero (by simp)).trans h0, by omega, + by simpa only [hl, hzi] using hzc, ?_⟩ + rw [hzm, hl, ho, hp, hcol, hdist, hm, hzi] + rfl + let x₀ := (((((x.set idx 0).set count 0).set one 1).set two 2).set four 4).set zero 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + fin_cases i <;> norm_num [P] at hi <;> rfl + all_goals simp +decide [x₀, idx, count, one, two, four, zero, heavyList, memory] + obtain ⟨y, he, hy, hstop⟩ := ExecWithin.loop_invariant I + (fun y => x.regs len - y.regs idx) hbody hx₀ + obtain ⟨hr, h1, h2, h4, h0, hi, hcount, hm⟩ := hy + have hend : y.regs idx = x.regs len := by + have hl := hr len (by decide) + simp only [Cond.holds_lt, hl] at hstop + omega + have f4 : Fits w 4 := hfit.of_le (by dsimp only [budget]; omega) + have f2 : Fits w 2 := f4.of_le (by decide) + have f1 : Fits w 1 := f4.of_le (by decide) + have hinit : ExecWithin (B := .standard) w (ram[ + idx ← #0; count ← #0; one ← #1; two ← #2; four ← #4; zero ← #0 ]) x x₀ 6 := by + ram_eval [x₀, f1, f2, f4] + refine ⟨y, ?_, by simpa only [hend] using hm, by simpa only [hend] using hcount, hr⟩ + convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, idx, count, one, two, four, zero, Data.set_regs, + Function.update_apply, ↓reduceIte, Nat.sub_zero] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean new file mode 100644 index 0000000000..9753467df9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PlaceMemory.lean @@ -0,0 +1,308 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +/-! +# Meaning of the native Chan–He placement writes + +## Main results + +* `Place.heavyList_nodup`: compact output retains injective enumeration. +* `Place.memory_heavy`: each compact cell contains its original signed integer. + +## References + +These memory proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place + +open Cslib.WordRAM.Signed + +/-- Selecting a subsequence preserves injective input enumeration. -/ +theorem heavyList_nodup (values : ℕ → ℤ) (n M : ℕ) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).Nodup := by + rw [heavyList_eq_filter] + apply List.Nodup.map_on + · intro i hi j hj he + exact hinj (Finset.mem_range.mpr (List.mem_range.mp (List.mem_filter.mp hi).1)) + (Finset.mem_range.mpr (List.mem_range.mp (List.mem_filter.mp hj).1)) he + · exact List.nodup_range.filter _ + +/-- The number returned by the scan is the exact cardinality of the existing heavy subset. -/ +theorem length_heavyList (values : ℕ → ℤ) (n : ℕ) {M : ℕ} (hM : 0 < M) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) : + (heavyList values n M n).length = + (Cslib.Algorithms.ThreeSum.ChanHe.heavy ((Finset.range n).image values) M).card := by + rw [← heavyList_toFinset values n hM hinj] + exact (List.toFinset_card_of_nodup (heavyList_nodup values n M hinj)).symm + +/-- Every compact cell is written once, without padding or a tag. Unused capacity retains +its arbitrary old contents. -/ +theorem memory_heavy (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) {k : ℕ} (hk : k ≤ n) + {j : ℕ} (hj : j < (heavyList values n M k).length) : + memory mem values n M out dst col dist t neg k (out + j) = + encode ((heavyList values n M k)[j]) := by + induction k generalizing j with + | zero => simp only [heavyList, List.length_nil] at hj; omega + | succ k ih => + let L := heavyList values n M k + have hl : L.length ≤ k := length_heavyList_le values n M k + have hlen : (heavyList values n M k).length = L.length := rfl + by_cases hh : 1 < Histogram.count (fun i => residueKey M (values i)) n + (residueKey M (values k)) + · have hlist : heavyList values n M (k + 1) = L ++ [values k] := by + simp only [heavyList, hh, ↓reduceIte, L] + simp only [hlist] at hj ⊢ + simp only [memory, write, hh, ↓reduceIte] + by_cases hj' : j < L.length + · rw [Function.update_of_ne (by omega), ih (by omega) hj'] + rw [List.getElem_append_left hj'] + · have he : j = L.length := by + simp only [List.length_append, List.length_singleton] at hj + omega + subst j + rw [Function.update_self] + simp + · have hlist : heavyList values n M (k + 1) = L := by + simp only [heavyList, hh, ↓reduceIte, List.append_nil, L] + simp only [hlist] at hj ⊢ + simp only [memory, write, hh, ↓reduceIte] + rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact ih (by omega) hj + +/-- The signed target keys identify exactly the same buckets as the original keys. -/ +theorem targetKey_eq_iff {M : ℕ} (hM : 0 < M) (neg : Bool) (a b : ℤ) : + targetKey M neg a = targetKey M neg b ↔ residueKey M a = residueKey M b := by + rw [targetKey_eq hM, targetKey_eq hM] + cases neg + · rfl + · exact residueKey_neg_eq_iff hM a b + +/-- The two copies cannot alias another bucket's cells. -/ +theorem targetKey_eq_of_address_eq {M dst col dist r s : ℕ} (hr : r < M) (hs : s < M) + (hd : dist = 0 ∨ dist = 4 * M) {a : ℕ} + (ha : a = dst + 4 * r + col ∨ a = dst + 4 * r + col + dist) + (hb : a = dst + 4 * s + col ∨ a = dst + 4 * s + col + dist) : r = s := by + rcases hd with hd | hd <;> rcases ha with ha | ha <;> rcases hb with hb | hb <;> omega + +/-- A light entry is written to all of its required cells, and no later input can overwrite it. +The count-one hypothesis itself proves uniqueness of the position. -/ +theorem memory_light (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hM : 0 < M) (hd : dist = 0 ∨ dist = 4 * M) (hdisj : out + n ≤ dst) + {i k a : ℕ} (hk : k ≤ n) (hi : i < k) + (hlight : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1) + (ha : a = dst + 4 * targetKey M neg (values i) + col ∨ + a = dst + 4 * targetKey M neg (values i) + col + dist) : + memory mem values n M out dst col dist t neg k a = + encode (signedValue neg (values i) + (t : ℤ)) := by + have ha' : dst ≤ a := by rcases ha with ha | ha <;> omega + induction k with + | zero => omega + | succ k ih => + have hl := length_heavyList_le values n M k + simp only [memory, write] + by_cases hh : 1 < Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values k)) + · rw [ite_eq_left hh, Function.update_of_ne (by omega)] + apply ih (by omega) (by + by_contra h + have : i = k := by omega + subst i + omega) + · rw [ite_eq_right hh] + by_cases he : i = k + · subst i + rcases ha with rfl | rfl <;> simp [Function.update_apply] + · have hne (b : ℕ) + (hb : b = dst + 4 * targetKey M neg (values k) + col ∨ + b = dst + 4 * targetKey M neg (values k) + col + dist) : a ≠ b := by + intro hab + have heq := targetKey_eq_of_address_eq + (by rw [targetKey_eq hM]; exact residueKey_lt hM _) + (by rw [targetKey_eq hM]; exact residueKey_lt hM _) hd ha (hab ▸ hb) + have hr := (targetKey_eq_iff hM neg (values i) (values k)).mp heq + exact he (eq_of_count_one values n M (by omega) (by omega) hlight hr) + rw [Function.update_of_ne (hne _ (Or.inr rfl)), + Function.update_of_ne (hne _ (Or.inl rfl))] + exact ih (by omega) (by omega) + +/-- A packed cell to which no light input is assigned retains its initialized padding. -/ +theorem memory_unwritten (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) {k a : ℕ} (hk : k ≤ n) (ha : dst ≤ a) + (hnot : ∀ i < k, Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values i)) = 1 → + a ≠ dst + 4 * targetKey M neg (values i) + col ∧ + a ≠ dst + 4 * targetKey M neg (values i) + col + dist) : + memory mem values n M out dst col dist t neg k a = mem a := by + induction k with + | zero => rfl + | succ k ih => + have hl := length_heavyList_le values n M k + simp only [memory, write] + split_ifs with hh + · rw [Function.update_of_ne (by omega)] + exact ih (by omega) (fun i hi => hnot i (by omega)) + · have hc : Histogram.count (fun j => residueKey M (values j)) n + (residueKey M (values k)) = 1 := by + have := count_residue_pos values n M (show k < n by omega) + omega + obtain ⟨hne₁, hne₂⟩ := hnot k (by omega) hc + rw [Function.update_of_ne hne₂, Function.update_of_ne hne₁] + exact ih (by omega) (fun i hi => hnot i (by omega)) + +/-- The first or second packed column is exactly the existing `arrXY`, including all padding +beyond the modulus. No initial contents of the output block are required except its pattern. -/ +theorem memory_arrXY (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col t U : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) (i : ℕ) + (hpad : mem (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col 0 t false n (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrXY + ((Finset.range n).image values) M U i + (t : ℤ)) := by + let S := (Finset.range n).image values + by_cases hc : (Cslib.Algorithms.ThreeSum.ChanHe.bucket S M i).card = 1 + · obtain ⟨z, hz⟩ := Finset.card_eq_one.mp hc + have hmem : z ∈ Cslib.Algorithms.ThreeSum.ChanHe.bucket S M i := by + rw [hz] + exact Finset.mem_singleton_self _ + obtain ⟨hS, hres⟩ := Finset.mem_filter.mp hmem + obtain ⟨j, hj, rfl⟩ := Finset.mem_image.mp hS + have hkey : residueKey M (values j) = i := by + simp only [residueKey, hres, Int.toNat_natCast] + have hj' := Finset.mem_range.mp hj + have hlight : Histogram.count (fun k => residueKey M (values k)) n + (residueKey M (values j)) = 1 := by + rw [hkey, count_eq_card_bucket values n hM hinj] + exact hc + have he := memory_light mem values n M out dst col 0 t false hM (Or.inl rfl) + hdisj le_rfl hj' hlight (a := dst + 4 * i + col) + (Or.inl (by simp only [targetKey, Bool.false_eq_true, ↓reduceIte, hkey])) + rw [he] + dsimp only [S] at hc hz + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrXY, + Cslib.Algorithms.ThreeSum.ChanHe.arr, ↓reduceIte, hz, Finset.sum_singleton, + Finset.card_singleton, + signedValue, Bool.false_eq_true] + · have he := memory_unwritten mem values n M out dst col 0 t false hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hne : dst + 4 * i + col ≠ dst + 4 * targetKey M false (values j) + col := by + intro heq + have hkey : residueKey M (values j) = i := by + change dst + 4 * i + col = dst + 4 * residueKey M (values j) + col at heq + omega + apply hc + rw [← count_eq_card_bucket values n hM hinj, ← hkey] + exact hlight + exact ⟨hne, by simpa only [Nat.add_zero] using hne⟩) + rw [he, hpad] + dsimp only [S] at hc + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrXY, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hc, ↓reduceIte] + +/-- Below `2M`, a residue occurs at exactly its first and second copies. -/ +theorem mod_eq_iff_copies {M i r : ℕ} (hi : i < 2 * M) (hr : r < M) : + i % M = r ↔ i = r ∨ i = r + M := by + by_cases h : i < M + · rw [Nat.mod_eq_of_lt h] + omega + · rw [Nat.mod_eq_sub_mod (by omega : M ≤ i), Nat.mod_eq_of_lt (by omega : i - M < M)] + omega + +/-- The duplicated, negated third column is exactly the existing `arrZ`. -/ +theorem memory_arrZ (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col t U : ℕ) + (hM : 0 < M) (hdisj : out + n ≤ dst) + (hinj : Set.InjOn values (↑(Finset.range n) : Set ℕ)) (i : ℕ) + (hpad : mem (dst + 4 * i + col) = + encode (-Cslib.Algorithms.ThreeSum.ChanHe.pad U + (t : ℤ))) : + memory mem values n M out dst col (4 * M) t true n (dst + 4 * i + col) = + encode (Cslib.Algorithms.ThreeSum.ChanHe.arrZ + ((Finset.range n).image values) M U i + (t : ℤ)) := by + let vals := fun j => -values j + let T := (Finset.range n).image vals + have hT : ((Finset.range n).image values).image (fun z : ℤ => -z) = T := by + rw [Finset.image_image] + rfl + have hinj' : Set.InjOn vals (↑(Finset.range n) : Set ℕ) := by + intro j hj k hk he + exact hinj hj hk (neg_injective he) + have hkey (j : ℕ) : targetKey M true (values j) = residueKey M (vals j) := + targetKey_eq hM true _ + have hcount (j : ℕ) : + Histogram.count (fun k => residueKey M (vals k)) n (residueKey M (vals j)) = + Histogram.count (fun k => residueKey M (values k)) n (residueKey M (values j)) := + count_neg_residue values n hM j + by_cases hi : i < 2 * M + · by_cases hc : (Cslib.Algorithms.ThreeSum.ChanHe.bucket T M ((i % M : ℕ) : ℤ)).card = 1 + · obtain ⟨z, hz⟩ := Finset.card_eq_one.mp hc + have hmem : z ∈ Cslib.Algorithms.ThreeSum.ChanHe.bucket T M ((i % M : ℕ) : ℤ) := by + rw [hz] + exact Finset.mem_singleton_self _ + obtain ⟨hS, hres⟩ := Finset.mem_filter.mp hmem + obtain ⟨j, hj, rfl⟩ := Finset.mem_image.mp hS + have hr : residueKey M (vals j) = i % M := by + simp only [residueKey, hres, Int.toNat_natCast] + have hj' := Finset.mem_range.mp hj + have hlight : Histogram.count (fun k => residueKey M (values k)) n + (residueKey M (values j)) = 1 := by + rw [← hcount, hr, count_eq_card_bucket vals n hM hinj'] + exact hc + have hcopies := (mod_eq_iff_copies hi (residueKey_lt hM (vals j))).mp hr.symm + have he := memory_light mem values n M out dst col (4 * M) t true hM (Or.inr rfl) + hdisj le_rfl hj' hlight (a := dst + 4 * i + col) (by + rw [hkey] + rcases hcopies with hc | hc + · exact Or.inl (by omega) + · exact Or.inr (by omega)) + rw [he] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte, hT, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hz, Finset.sum_singleton, + Finset.card_singleton, signedValue] + rfl + · have he := memory_unwritten mem values n M out dst col (4 * M) t true hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hn : i % M ≠ targetKey M true (values j) := by + intro heq + apply hc + rw [← count_eq_card_bucket vals n hM hinj', heq, hkey, hcount] + exact hlight + have hr := residueKey_lt hM (vals j) + have hcopies := (mod_eq_iff_copies hi hr).not.mp (by simpa only [hkey] using hn) + rw [hkey] + constructor <;> intro heq <;> apply hcopies <;> omega) + rw [he, hpad] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte, hT, + Cslib.Algorithms.ThreeSum.ChanHe.arr, hc] + · have he := memory_unwritten mem values n M out dst col (4 * M) t true hdisj + (k := n) (a := dst + 4 * i + col) le_rfl (by omega) (by + intro j hj hlight + have hr : targetKey M true (values j) < M := by rw [hkey]; exact residueKey_lt hM _ + constructor <;> omega) + rw [he, hpad] + simp only [Cslib.Algorithms.ThreeSum.ChanHe.arrZ, hi, ↓reduceIte] + +/-- Placement changes only its own packed column. -/ +theorem memory_other_column (mem : ℕ → ℕ) (values : ℕ → ℤ) (n M out dst col dist t : ℕ) + (neg : Bool) (hdisj : out + n ≤ dst) (hc : col < 4) + (hd : dist = 0 ∨ dist = 4 * M) (u : ℕ) (hu : u % 4 ≠ col) : + memory mem values n M out dst col dist t neg n (dst + u) = mem (dst + u) := by + apply memory_unwritten _ _ _ _ _ _ _ _ _ _ hdisj le_rfl (by omega) + intro j hj hlight + rcases hd with hd | hd <;> constructor <;> intro he <;> apply hu <;> omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Place diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean new file mode 100644 index 0000000000..5a63b8f2a8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean @@ -0,0 +1,41 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingTime +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingIntermediates + +/-! +# Complete native Chan–He preprocessing + +One finite 128-register program per fixed exponent computes every parameter, sorts and counts +the initialized signed input, constructs the exact distinct-position frontend, builds its prime +bitmap, executes both modulus searches and every heavy/light recursion, and returns a table of +exact packed convolution instances. Counts, pointers and the common length are unsigned; packed +payloads use the existing signed encoding. Runtime code does not read physical width. + +## Main definitions + +This umbrella re-exports these definitions and results from its imported modules. + +* `program`, `flatProgram`: structured and flat native code from `PreprocessingProgram`. +* `width`: the positive threshold `(1300κ+7865) clog₂(n+2)` from `PreprocessingWidth` derives fits. +* `cost`: all native instructions, including the final halt, from `PreprocessingInput`. + +## Main results + +* `runs_input`: complete `State.init` execution and exact existing `ChanHe.instances` readback. +* `cost_bound`: at most `Kκ (n+2)(sqrt(n+2)+1)(log₂(n+2)+1)^13` charged instructions. +* `intermediates_polynomial`, `space_polynomial`: every prefix at every admitted width uses only + polynomial words and a polynomial initial address range. + +The initialized-input controller, layout, native execution and resource composition are original. +The reused finite Chan–He mathematics credits Anthropic, PBC and [ChanHe2020]. No solver or source +machine simulation is assumed. This preprocessing endpoint leaves the Exact Triangle solver and +complete subquadratic 3SUM composition, including the final 1.9992 exponent, separate. +-/ diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean new file mode 100644 index 0000000000..b00a063e0c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingBounds.lean @@ -0,0 +1,150 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLayout + +/-! +# Polynomial global preprocessing reservations + +Every frontend allocation, explicit-stack reservation and root allocation is bounded by a fixed +power of the existing parameter budget. This derives local kernel fits from logarithmic width. + +## Main results + +* `arena_envelope_le`: the complete global signed envelope has polynomial magnitude. + +These coarse resource estimates are original. Charged subquadratic time is a separate bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The complete global envelope is a fixed power of the already bounded native parameter budget. -/ +theorem arena_envelope_le (κ n : ℕ) (retained : ℕ → ℕ) : + let a := arena κ n retained + Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N ≤ + Parameters.Math.budget n ((n + 2) ^ κ) ^ 52 := by + let U := (n + 2) ^ κ + let P := Parameters.Math.budget n U + let l := Frontend.layout κ n + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let N := 2 * m ^ 2 + have hP : 64 ≤ P := by dsimp only [P, Parameters.Math.budget]; omega + have hP1 : 1 ≤ P := by omega + have hsmall : 8 * U + 4 * n + 64 ≤ P := by + dsimp only [P, Parameters.Math.budget] + omega + have hms : 8 * (m + 1) ^ 2 ≤ P := by + dsimp only [P, Parameters.Math.budget, m] + omega + have hm : m ≤ P := by nlinarith + have hmp : m + 1 ≤ P := by nlinarith + have hm2 : m ^ 2 ≤ P := by nlinarith + have hN : N ≤ P := by dsimp only [N]; nlinarith + have hn : n + 1 ≤ P := by omega + have hU : 2 * U ≤ P := by omega + have hΛ : l.digits ≤ P := by + have hl := Nat.log_le_self 2 (4 * U) + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ P + dsimp only [Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + omega + have hheight : Cslib.Algorithms.ThreeSum.ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + have hlog := Nat.log_le_self 2 n + have hF : F ≤ P := by + dsimp only [F, Cslib.Algorithms.ThreeSum.ChanHe.fuel] + omega + have hwork : Traversal.Math.work F ≤ P ^ 6 := by + have hw := Traversal.work_fuel_le n + change Traversal.Math.work F ≤ 3 * (2 * Nat.log 2 n + 2) ^ 5 at hw + calc Traversal.Math.work F ≤ 3 * P ^ 5 := + hw.trans (Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by omega) 5)) + _ ≤ P * P ^ 5 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 6 := by ring + have hpower : 3 ^ (F + 1) ≤ P ^ 7 := by + have he := Traversal.Math.work_eq F + have hpow : 1 ≤ P ^ 6 := Nat.one_le_pow _ _ hP1 + calc 3 ^ (F + 1) ≤ 3 * P ^ 6 := by omega + _ ≤ P * P ^ 6 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 7 := by ring + have hD : l.count ≤ P ^ 3 := by + have hsq := Nat.pow_le_pow_left hΛ 2 + have hpos : 1 ≤ P ^ 2 := Nat.one_le_pow _ _ hP1 + calc + l.count ≤ 4 * P ^ 2 := by + dsimp only [Frontend.Layout.count, Frontend.Layout.splits] + omega + _ ≤ P * P ^ 2 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 3 := by ring + have hfront : l.endpoint ≤ P ^ 4 := by + have hb := Frontend.array_budget_le n U + change Frontend.Arrays.budget l U ≤ P ^ 4 at hb + apply le_trans ?_ hb + dsimp only [Frontend.Arrays.budget] + omega + have hdelta : Traversal.allocationSize (n + 1) m N ≤ P ^ 2 := by + dsimp only [Traversal.allocationSize] + nlinarith + have powmono {i j : ℕ} (hij : i ≤ j) : P ^ i ≤ P ^ j := + Nat.pow_le_pow_right hP1 hij + have hstack : l.endpoint + m + 1 ≤ P ^ 5 := by + have hp := powmono (show 1 ≤ 4 by decide) + simp only [pow_one] at hp + calc l.endpoint + m + 1 ≤ 2 * P ^ 4 := by omega + _ ≤ P * P ^ 4 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 5 := by ring + have hcap : 7 * (2 * F + 1) ≤ P ^ 2 := by nlinarith + have htable : l.endpoint + m + 1 + 7 * (2 * F + 1) ≤ P ^ 6 := by + have hp := powmono (show 2 ≤ 5 by decide) + calc _ ≤ 2 * P ^ 5 := by omega + _ ≤ P * P ^ 5 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 6 := by ring + have houtcap : l.count * 3 ^ (F + 1) ≤ P ^ 10 := by + calc _ ≤ P ^ 3 * P ^ 7 := Nat.mul_le_mul hD hpower + _ = P ^ 10 := by ring + have hfloor : (arena κ n retained).floor ≤ P ^ 11 := by + have hp := powmono (show 6 ≤ 10 by decide) + change l.endpoint + m + 1 + 7 * (2 * F + 1) + l.count * 3 ^ (F + 1) ≤ _ + calc _ ≤ 2 * P ^ 10 := by omega + _ ≤ P * P ^ 10 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 11 := by ring + have halloc : l.count * (Traversal.Math.work F * + Traversal.allocationSize (n + 1) m N) ≤ P ^ 11 := by + calc _ ≤ P ^ 3 * (P ^ 6 * P ^ 2) := + Nat.mul_le_mul hD (Nat.mul_le_mul hwork hdelta) + _ = P ^ 11 := by ring + have hceil : (arena κ n retained).ceiling ≤ P ^ 12 := by + change (arena κ n retained).floor + l.count * + (Traversal.Math.work F * Traversal.allocationSize (n + 1) m N) ≤ _ + calc _ ≤ 2 * P ^ 11 := by omega + _ ≤ P * P ^ 11 := Nat.mul_le_mul_right _ (by omega) + _ = P ^ 12 := by ring + have hsum : (arena κ n retained).ceiling + (n + 1) + m ^ 2 + l.digits + m + + 2 * U + N + 100 ≤ 2 * P ^ 12 := by + have hpow : P ^ 2 ≤ P ^ 12 := powmono (by decide) + have hsmall' : (n + 1) + m ^ 2 + l.digits + m + 2 * U + N + 100 ≤ P ^ 2 := by + nlinarith + omega + change 65536 * ((arena κ n retained).ceiling + (n + 1) + m ^ 2 + l.digits + m + + 2 * U + N + 100) ^ 4 ≤ P ^ 52 + calc _ ≤ 65536 * (2 * P ^ 12) ^ 4 := + Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hsum 4) + _ = 1048576 * P ^ 48 := by ring + _ ≤ P ^ 4 * P ^ 48 := Nat.mul_le_mul_right _ (by + have := Nat.pow_le_pow_left hP 4 + norm_num at this + omega) + _ = P ^ 52 := by ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean new file mode 100644 index 0000000000..9614ec7ffb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingEntries.lean @@ -0,0 +1,136 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers + +/-! +# Exact native frontend entries for Chan–He traversal + +The semantic entries use the actual frontend payload bases and decoded compact lists. Native +readback follows from the frontend's canonical encoding theorem. Their output is exactly the +existing finite Chan–He node list; no new 3SUM predicate or padded input is introduced. + +## Main definitions + +* `entry`, `entries`: semantic records of the actual six-word frontend descriptors. + +## Main results + +* `entry_stored`: exact signed-encoded payload readback. +* `entry_valid`, `entry_ends`: admissibility and containment in the concrete frontend allocation. +* `entries_output`: exact equality with the existing finite node list, in order. + +These native representation bridges are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- One actual frontend descriptor, with the shared runtime fuel and concrete payload bases. -/ +def entry (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) : Traversal.Entry := + ⟨f, fun j => l.pointer (3 * t + j.val), + fun j => (Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode⟩ + +/-- All actual frontend roots in their existing enumeration order. -/ +def entries (l : Frontend.Layout) (U : ℕ) (rs : List (ℕ × ℕ)) : List Traversal.Entry := + (List.range l.count).map (entry l U (Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) rs) + +/-- Each concrete root column is a valid physical frontend slot. -/ +theorem entry_slot {l : Frontend.Layout} {t : ℕ} (ht : t < l.count) (j : Fin 3) : + 3 * t + j.val < l.arrays := by + have := j.isLt + dsimp only [Frontend.Layout.arrays] + omega + +/-- A root column occupies at most its actual `n+1`-word payload capacity. -/ +theorem entry_length_le (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) + (hcard : rs.length ≤ l.n) (t : ℕ) (j : Fin 3) : + ((entry l U f rs t).data j).length ≤ l.n + 1 := by + simpa only [entry, List.length_map] using + (Frontend.Math.length_array_le U l.digits rs (3 * t + j.val)).trans (by omega) + +/-- The actual native frontend payload is exactly the signed encoding expected by traversal. -/ +theorem entry_stored {input : List ℤ} {rs : List (ℕ × ℕ)} {l : Frontend.Layout} + {U f t : ℕ} {mem : ℕ → ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (h : Frontend.Output l U rs mem) (ht : t < l.count) : + (entry l U f rs t).Stored mem := by + intro j i + have hj := entry_slot ht j + have hi : i.val < (Frontend.Math.array U l.digits rs (3 * t + j.val)).length := by + simpa only [entry, List.length_map] using i.isLt + have hm := (h _ hj).2.2.get i.val hi + change mem (l.pointer (3 * t + j.val) + i.val) = + Signed.encode (((Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode)[i.val]) + simp only [List.getElem_map] + exact hm.trans (Frontend.Math.array_canonical hkeys U l.digits _ _ + (List.getElem_mem hi)).symm + +/-- Frontend roots satisfy the existing admissibility notion, including singleton zero at n=0. -/ +theorem entry_valid {input : List ℤ} {rs : List (ℕ × ℕ)} (l : Frontend.Layout) (U f t : ℕ) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + (entry l U f rs t).Valid (l.n + 1) (2 * U) := by + have hn (j : Fin 3) : ((entry l U f rs t).data j).Nodup := + Frontend.Math.array_decoded_nodup hkeys hnd U l.digits (3 * t + j.val) + have hc (j : Fin 3) : ((entry l U f rs t).data j).toFinset.card ≤ l.n + 1 := by + rw [List.toFinset_card_of_nodup (hn j)] + exact entry_length_le l U f rs hcard t j + have hb (j : Fin 3) : Cslib.Algorithms.ThreeSum.Bdd (2 * U) + ((entry l U f rs t).data j).toFinset := by + intro z hz + change z ∈ ((Frontend.Math.array U l.digits rs (3 * t + j.val)).map Signed.decode).toFinset + at hz + obtain ⟨u, hu, rfl⟩ := List.mem_map.mp (List.mem_toFinset.mp hz) + exact Frontend.Math.array_magnitude hkeys hvalues l.digits _ u hu + exact ⟨hn, hc 0, hc 1, hc 2, hb 0, hb 1, hb 2⟩ + +/-- Every actual root payload ends inside the frontend's concrete allocation. -/ +theorem entry_ends (l : Frontend.Layout) (U f : ℕ) (rs : List (ℕ × ℕ)) + (hcard : rs.length ≤ l.n) {t : ℕ} (ht : t < l.count) (j : Fin 3) : + (entry l U f rs t).source j + ((entry l U f rs t).data j).length ≤ l.endpoint := by + have hj := entry_slot ht j + have hn := entry_length_le l U f rs hcard t j + calc (entry l U f rs t).source j + ((entry l U f rs t).data j).length + ≤ l.pointer (3 * t + j.val) + l.capacity := Nat.add_le_add_left hn _ + _ = l.pointer (3 * t + j.val + 1) := (l.pointer_succ _).symm + _ ≤ l.pointer l.arrays := l.pointer_mono (by omega) + +/-- Traversing these exact entries produces precisely the unchanged finite Chan–He node list. -/ +theorem entries_output {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + let l : Frontend.Layout := ⟨input.length, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + Traversal.Math.output + (Nat.primesLE (Cslib.Algorithms.ThreeSum.ChanHe.mPar input.length (2 * U))) l.digits + ((entries l U rs).map Traversal.Entry.task) = + Cslib.Algorithms.ThreeSum.ChanHe.allNodes input.length U + (fun i : Fin input.length => input[i]) := by + let l : Frontend.Layout := ⟨input.length, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + have hr := Frontend.Math.roots_indexed U rs + have hd := Frontend.Math.roots_decoded hkeys hcounts hvalues + change ((List.range l.count).map (Frontend.Math.root U l.digits rs)) = + Frontend.Math.roots U rs at hr + calc Traversal.Math.output _ _ ((entries l U rs).map Traversal.Entry.task) + = (((List.range l.count).map (Frontend.Math.root U l.digits rs)).map + Frontend.Math.decodeRoot).flatMap (fun T => + Cslib.Algorithms.ThreeSum.ChanHe.reduction input.length (2 * U) T.1 T.2.1 T.2.2) := by + simp only [Traversal.Math.output, entries, List.flatMap_map, List.map_map, + Function.comp_def] + rfl + _ = _ := by rw [hr, hd]; rfl + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean new file mode 100644 index 0000000000..3b053270ee --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingExecute.lean @@ -0,0 +1,129 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoop +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingPrimes +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingFinish +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingWidth + +/-! +# Composing every computed native preprocessing root + +Starting from the complete frontend's actual output, the native program computes its shared +prime table and reservations, then executes every actual descriptor and recursion tree. + +## Main results + +* `afterFrontend_exec`: the complete native suffix has exact finite node output and charged cost. + +The native composition proofs are original. No runtime algorithm or fit advice is supplied. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- The complete actual suffix consumes only computed frontend output and a global width. -/ +theorem afterFrontend_exec {κ w : ℕ} (input : List ℤ) (rs : List (ℕ × ℕ)) (x : Data 128) + (hw : width κ input.length ≤ w + 2) (hx : x.Bounded (w + 2)) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + (hfront : Frontend.Output (Frontend.layout κ input.length) + ((input.length + 2) ^ κ) rs x.mem) + (hparams : Parameters.Output input.length ((input.length + 2) ^ κ) + (x.comap Frontend.parameterRegisters)) + (hbase : x.regs Frontend.descriptorBase = (Frontend.layout κ input.length).descriptors) + (hcount : x.regs Frontend.descriptorCount = (Frontend.layout κ input.length).count) + (hend : x.regs Frontend.heapEnd = (Frontend.layout κ input.length).endpoint) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ input.length) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let l := Frontend.layout κ n + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) afterFrontend x y + ((m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + l.count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17)) ∧ + out.map Prod.snd = Traversal.Math.output (Nat.primesLE m) l.digits + ((entries l U rs).map Traversal.Entry.task) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n x.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n x.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 := by + let n := input.length + let l := Frontend.layout κ n + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + have hfits := setup_fits hw x.mem + obtain ⟨p, hp, ps, pm⟩ := snapshot_exec x hparams hbase hcount hend hfits.2.2.2 + obtain ⟨q, hq, qs, qm, qcount, qbitmap, qprefix⟩ := primes_exec p ps hfits.2.1 + obtain ⟨r, hr, rsaved, rcontrols, rcount, rpower, rremaining, rscalar, rm⟩ := + setupTraversal_exec q qs qcount (setup_fits hw q.mem).1 + obtain ⟨b, hb, bs, bcontrols, bcount, bheap, _, _, bm⟩ := + finish_exec r rsaved rcontrols rcount rpower rremaining rscalar + (setup_fits hw r.mem).2.2.1 + ((setup_fits hw r.mem).1.of_le (by omega)) + let a := arena κ n q.mem + have bmem : b.mem = q.mem := bm.trans rm + have bcontrol : a.Controls b := by simpa only [rm] using bcontrols + have bh : b.regs Traversal.heap = a.floor := by simpa only [rm] using bheap + have bfront : Frontend.Output l U rs b.mem := by + rw [bmem] + apply hfront.frame hcard + intro v hv + exact (qprefix v hv).trans (congrFun pm v) + have bbounded := hb.bounded (hr.bounded (hq.bounded (hp.bounded hx))) + have hi : (ForestCursor.mk 0 []).Invariant a w l U rs b := by + constructor + · exact bbounded + · exact bcontrol + · exact bfront + · exact bs.base + · exact bs.count + · exact bs.index + · exact bs.fuel + · exact bs.stride + · exact bs.one + · exact Nat.zero_le _ + · intro v hv + rw [bmem] + exact qbitmap v hv + · exact bh.ge + · rw [bh] + exact le_rfl + · exact bcount + · simp only [List.length_nil, Nat.sub_zero, Nat.zero_add] + exact Nat.mul_le_mul_left l.count (work_le_power F) + · exact Traversal.storedNodes_nil _ _ _ _ + · intro i + exact Fin.elim0 i + · simp only [List.map_nil, List.drop_zero, List.nil_append] + · intro v _ + exact congrFun bmem v + have hready : a.Ready w := by + have hh := arena_ready hw q.mem + simpa only [Nat.add_sub_cancel] using hh + obtain ⟨s, y, he, hy, _, hout⟩ := forest_exec (arena_separated κ n q.mem) hready hi + rfl rfl rfl (arena_frontend_before κ n q.mem) hkeys hnd hcard hvalues + refine ⟨s.nodes, y, ?_, hout, hy.output, hy.count, hy.controls.2.2.2.2.2.2.1, ?_⟩ + · have htotal := hp.within.seq (hq.seq (hr.within.seq (hb.seq he))) + exact htotal.mono (by + dsimp only [n, l, U, m, F, a, arena] at * + simp only [Nat.sub_zero] at * + omega) + · have hpersist : forest.Preserves packedLength := body_preserves _ (by decide) + (by decide) (by decide) + exact (he.regs_eq hpersist).trans bs.length + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean new file mode 100644 index 0000000000..d0ee490e66 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingFinish.lean @@ -0,0 +1,81 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSetup +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingReserve + +/-! +# Completing the native forest reservations + +The actual power loop computes the per-root tree reservation. Three further instructions multiply +by the root count and place the initial heap above the complete pointer table. + +## Main results + +* `finish_exec`: execute reservation and layout completion with exact native heap/table metadata. + +The native composition and memory-frame proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Compute all root reservations and the actual first fresh heap address by native code. -/ +theorem finish_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hc : (arena κ n x.mem).Controls x) (hcount : x.regs Traversal.outputCount = 0) + (hp : x.regs power = 1) + (hr : x.regs remaining = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1) + (hscalar : x.regs scalar = 3) + (hpow : Fits w (3 ^ (Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1))) + (hfloor : Fits w (arena κ n x.mem).floor) : + ∃ y, ExecWithin w (.seq reserve finishLayout) x y + (4 * (Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1) + 5) ∧ + Saved κ n y ∧ (arena κ n x.mem).Controls y ∧ + y.regs Traversal.outputCount = 0 ∧ y.regs Traversal.heap = (arena κ n x.mem).floor ∧ + y.regs outputCapacity = (arena κ n x.mem).outputCapacity ∧ + y.regs heapFloor = (arena κ n x.mem).floor ∧ y.mem = x.mem := by + let a := arena κ n x.mem + obtain ⟨p, he, hpower, _, hmem, hregs⟩ := reserve_exec x hp hr hs.zero hs.one hscalar hpow + have ps : Saved κ n p := hs.congr (fun i hi hj => hregs i + (by intro hh; subst i; norm_num [power] at hj) + (by intro hh; subst i; norm_num [remaining] at hj)) + have pc : a.Controls p := hc.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact hregs i (by intro hh; subst i; norm_num [power] at hi) + (by intro hh; subst i; norm_num [remaining] at hi)) + have pcount : p.regs Traversal.outputCount = 0 := + (hregs _ (by decide) (by decide)).trans hcount + let y := p |>.set outputCapacity a.outputCapacity |>.set Traversal.heap a.floor + |>.set heapFloor a.floor + have hcap : Fits w a.outputCapacity := hfloor.of_le (by dsimp only [a, arena]; omega) + have hout := pc.2.2.2.2.2.2.1 + dsimp only [a, arena] at hcap hfloor hout + have hz : Exec w finishLayout p y 3 := by + ram_eval [finishLayout, y, a, arena, ps.count, hpower, hout, hcap, hfloor, + outputCapacity, roots, power, Traversal.outputs, Traversal.heap, heapFloor] + have ys : Saved κ n y := ps.congr (fun i hi hj => hz.within.regs_eq (by + have hh : ∀ i : Fin 128, 109 ≤ i.val → (i.val ≤ 120 ∨ i.val = 126) → + finishLayout.Preserves i := by decide + exact hh i hi hj)) + have yc : a.Controls y := pc.congr (by + intro i hi + apply hz.within.regs_eq + have hh : ∀ i : Fin 128, i.val ∈ [71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 83] → + finishLayout.Preserves i := by decide + exact hh i hi) + refine ⟨y, (he.seq hz.within).mono (by omega), ys, yc, ?_, ?_, ?_, ?_, hmem⟩ + · simpa +decide [y, outputCapacity, Traversal.heap, heapFloor, + Traversal.outputCount] using pcount + all_goals simp +decide [y, a, outputCapacity, Traversal.heap, heapFloor] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean new file mode 100644 index 0000000000..88c214c766 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInput.lean @@ -0,0 +1,107 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingExecute + +/-! +# Complete native Chan–He preprocessing from initialized input + +Every parameter, sorted multiplicity table, frontend descriptor, prime bit, heavy list and packed +output is computed by one finite native program for the fixed magnitude exponent. The output +list is exactly the existing finite instances, including negative and small-input cases. + +## Main definitions + +* `cost`: charged complete-program bound, including the final halt. + +## Main results + +* `exec_input`: complete actual native execution and exact finite packed output. +* `runs_input`: native halting execution and the same actual pre-halt memory witness. + +These initialized-input composition proofs are original; existing finite reduction mathematics, +native kernels and native compilation APIs are reused without an algorithm or operation oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- All actual native instructions, including the complete initialized frontend and final halt. -/ +def cost (κ n : ℕ) : ℕ := + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + Frontend.cost κ n + (m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) + +/-- Complete actual native preprocessing, uniformly over every admitted sufficiently wide input. -/ +theorem exec_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y (cost κ n - 1) ∧ + out.map (fun p => p.2.oneArray (2 * U)) = + Cslib.Algorithms.ThreeSum.ChanHe.instances n U (fun i : Fin n => input[i]) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n y.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n y.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 := by + obtain ⟨x, rs, hf, ho, hbase, hcount, hend, hnd, _, hkeys, hcounts, _, _, hp, _⟩ := + Frontend.exec_input input ((frontend_width_le κ input.length).trans hw) hvalues + have hcard : rs.length ≤ input.length := by + have ht := hnd.length_le_of_subset (fun k hk => (hkeys k).mp hk) + simpa only [List.length_map] using ht + have hb : x.Bounded w := hf.bounded (Data.Bounded.init (input_fits input hw hvalues)) + have hw2 : w - 2 + 2 = w := by have := (three_le_width κ input.length).trans hw; omega + obtain ⟨out, y, hs, hout, hstored, hlen, htable, hpacked⟩ := + afterFrontend_exec (w := w - 2) input rs x (by simpa only [hw2] using hw) + (by simpa only [hw2] using hb) hvalues ho hp hbase hcount hend hkeys hnd hcard + rw [hw2] at hs + have hnodes := entries_output hkeys (fun p hp => (hcounts p hp).2) hvalues + have heq : out.map Prod.snd = Cslib.Algorithms.ThreeSum.ChanHe.allNodes input.length + ((input.length + 2) ^ κ) (fun i : Fin input.length => input[i]) := hout.trans hnodes + refine ⟨out, y, (hf.seq hs).mono ?_, ?_, hstored, hlen, htable, hpacked⟩ + · have hpos : 0 < Frontend.cost κ input.length := by dsimp only [Frontend.cost]; omega + dsimp only [cost] + omega + · rw [Cslib.Algorithms.ThreeSum.ChanHe.instances, ← heq, List.map_map] + rfl + +/-- The native flat program halts, and its actual pre-halt state retains the exact output. -/ +theorem runs_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + let n := input.length + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes w (flatProgram κ) (State.init (input.map Signed.encode)) out.length steps ∧ + Runs w (flatProgram κ) (State.init (input.map Signed.encode)) + ⟨(program κ).size, y⟩ (steps - 1) ∧ steps ≤ cost κ n ∧ + out.map (fun p => p.2.oneArray (2 * U)) = + Cslib.Algorithms.ThreeSum.ChanHe.instances n U (fun i : Fin n => input[i]) ∧ + Traversal.StoredNodes (2 * U) (2 * m ^ 2) (arena κ n y.mem).table out y.mem ∧ + y.regs Traversal.outputCount = out.length ∧ + y.regs Traversal.outputs = (arena κ n y.mem).table ∧ + y.regs packedLength = 8 * m ^ 2 ∧ y.Bounded w := by + obtain ⟨out, y, ⟨t, ht, he⟩, hout, hstored, hlen, htable, hpacked⟩ := + exec_input input hw hvalues + have hpos : 0 < cost κ input.length := by dsimp only [cost]; omega + refine ⟨out, y, t + 1, ?_, ?_, by omega, hout, hstored, hlen, htable, hpacked, ?_⟩ + · simpa only [flatProgram, hlen, State.init] using he.executes Traversal.outputCount + · simpa only [flatProgram, State.init, Nat.zero_add, Nat.add_sub_cancel] using + he.runs (pc := 0) (p := flatProgram κ) (Program.Contains.self _).append_left + · exact he.bounded (Data.Bounded.init (input_fits input hw hvalues)) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean new file mode 100644 index 0000000000..b6610a1063 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingIntermediates.lean @@ -0,0 +1,86 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInput +public import Complexity.Computability.WordRAM.Width +import Mathlib.Data.List.GetD + +/-! +# Polynomial native preprocessing intermediates at every admitted width + +The program never reads physical width. Its complete execution at the derived minimum width +therefore identifies every prefix at every larger admitted width. Every actual register and +memory word, including address and arithmetic temporaries, has polynomial magnitude throughout. + +## Main results + +* `two_pow_width_le`: the minimum-width word universe has polynomial magnitude. +* `intermediates_polynomial`: uniform polynomial bounds on every actual run-prefix word. +* `space_polynomial`: every actual run prefix has zero memory beyond the same polynomial range. + +These native representation bounds are original and use the generic width-prefix API. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The derived minimum-width word universe is bounded by a fixed polynomial. -/ +theorem two_pow_width_le (κ n : ℕ) : + 2 ^ width κ n ≤ (n + 2) ^ (2720 * κ + 16456) := by + have hw : width κ n ≤ 17 * Frontend.width κ n := by + dsimp only [width, Frontend.width] + omega + calc 2 ^ width κ n ≤ 2 ^ (17 * Frontend.width κ n) := Nat.pow_le_pow_right (by decide) hw + _ = (2 ^ Frontend.width κ n) ^ 17 := by rw [← pow_mul, Nat.mul_comm] + _ ≤ ((n + 2) ^ (160 * κ + 968)) ^ 17 := + Nat.pow_le_pow_left (Frontend.two_pow_width_le κ n) 17 + _ = _ := by + rw [← pow_mul] + exact congrArg (fun e : ℕ => (n + 2) ^ e) (by omega) + +/-- Every register, temporary and memory word has polynomial magnitude at every admitted width. -/ +theorem intermediates_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps : ℕ} + (hr : Runs w (flatProgram κ) (State.init (input.map Signed.encode)) s steps) : + (∀ i, s.data.regs i < (input.length + 2) ^ (2720 * κ + 16456)) ∧ + ∀ a, s.data.mem a < (input.length + 2) ^ (2720 * κ + 16456) := by + obtain ⟨_, _, _, he, _, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := Runs.prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) Traversal.outputCount) hw hr he + have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) + have hpoly := two_pow_width_le κ input.length + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +/-- Every prefix uses a polynomial initial interval of memory, even at wider physical widths. -/ +theorem space_polynomial {κ w : ℕ} (input : List ℤ) + (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) + {s : State 128} {steps a : ℕ} + (hr : Runs w (flatProgram κ) (State.init (input.map Signed.encode)) s steps) + (ha : (input.length + 2) ^ (2720 * κ + 16456) ≤ a) : s.data.mem a = 0 := by + obtain ⟨_, _, _, he, _, _, _, _, _, _, _, _⟩ := runs_input input le_rfl hvalues + have hsmall := Runs.prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) Traversal.outputCount) hw hr he + have hi := input_fits input (κ := κ) le_rfl hvalues + have hword : 2 ^ width κ input.length ≤ a := (two_pow_width_le κ input.length).trans ha + rw [hsmall.mem_above (Data.Bounded.init hi) hword] + have hn : input.length < 2 ^ width κ input.length := by + simpa only [Fits, List.length_map] using hi.1 + have hpos : a ≠ 0 := by omega + obtain ⟨k, rfl⟩ := Nat.exists_eq_succ_of_ne_zero hpos + change (Data.init (input.map Signed.encode) : Data 128).mem (k + 1) = 0 + rw [Data.init_mem_succ] + exact List.getD_eq_default _ _ (by simp only [List.length_map]; omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean new file mode 100644 index 0000000000..efdeeb8e1d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingInvariant.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLoad +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Frame + +/-! +# Shared resources across all native frontend roots + +The outer loop reserves heap and pointer cells for every remaining root. A particular root gets +only its own remaining allocation, so its execution theorem cannot consume another root's budget. + +## Main definitions + +* `rootArena`: the existing traversal arena restricted to one root's actual remaining budget. +* `ForestCursor`, `ForestCursor.Invariant`: exact root index, output and global resource relation. + +## Main results + +* `rootArena_ready`: the global signed envelope derives each dynamic root envelope. + +These native composition invariants are original; the program remains the existing native code. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Restrict a root call to its own heap and pointer-table allowance. All addresses stay fixed. -/ +def rootArena (a : Traversal.Arena) (x : Data 128) (ns : List (ℕ × Node)) : Traversal.Arena := + { a with + ceiling := x.regs Traversal.heap + + Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N + outputCapacity := ns.length + Traversal.Math.work a.fuel } + +/-- Monotonicity of the existing polynomial envelope derives every root's actual local fit. -/ +theorem rootArena_ready {a : Traversal.Arena} {w : ℕ} {x : Data 128} + {ns : List (ℕ × Node)} (hw : a.Ready w) + (hh : (rootArena a x ns).ceiling ≤ a.ceiling) : (rootArena a x ns).Ready w := by + refine ⟨hw.1, hw.2.1, ?_⟩ + apply GenericRAM.Signed.Fits.of_natAbs_le hw.2.2 + simp only [Int.natAbs_natCast] + dsimp only [Traversal.envelope] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by dsimp only [rootArena] at *; omega) 4) + +/-- Mathematical proof state of the native outer descriptor loop. -/ +structure ForestCursor where + /-- Number of root descriptors already executed. -/ + index : ℕ + /-- Actual native pointer and existing-node pairs already emitted. -/ + nodes : List (ℕ × Node) + +/-- Exact frontend output, native controller registers and all remaining-root reservations. -/ +structure ForestCursor.Invariant (a : Traversal.Arena) (w : ℕ) (l : Frontend.Layout) + (U : ℕ) (rs : List (ℕ × ℕ)) (s : ForestCursor) (x : Data 128) : Prop where + /-- Every actual word fits. -/ + bounded : x.Bounded (w + 2) + /-- The traversal constants remain actual registers. -/ + controls : a.Controls x + /-- Every frontend descriptor and compact array is still available. -/ + frontend : Frontend.Output l U rs x.mem + /-- Actual descriptor base and root count. -/ + descriptors : x.regs descriptors = l.descriptors + roots : x.regs roots = l.count + /-- Actual root index and shared fuel. -/ + current : x.regs current = s.index + levels : x.regs levels = a.fuel + /-- Native descriptor stride and outer increment constant. -/ + stride : x.regs stride = 6 + one : x.regs one = 1 + /-- The actual index never passes the last descriptor. -/ + index_le : s.index ≤ l.count + /-- Every candidate prime still has its actual bitmap bit. -/ + bitmap : a.Bitmap x.mem + /-- The heap avoids all shared reservations. -/ + floor : a.floor ≤ x.regs Traversal.heap + /-- All unprocessed roots retain their complete allocation budgets. -/ + heapBudget : x.regs Traversal.heap + (l.count - s.index) * + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) ≤ a.ceiling + /-- The native count agrees with the emitted list. -/ + count : x.regs Traversal.outputCount = s.nodes.length + /-- All unprocessed roots retain their complete pointer-table budgets. -/ + tableBudget : s.nodes.length + (l.count - s.index) * Traversal.Math.work a.fuel ≤ + a.outputCapacity + /-- Exact native pointer-table and packed-cell readback. -/ + output : Traversal.StoredNodes a.U a.N a.table s.nodes x.mem + /-- Every earlier packed array is safely below the current fresh allocation. -/ + outputBounds : ∀ i : Fin s.nodes.length, + a.floor ≤ s.nodes[i.val].1 ∧ s.nodes[i.val].1 + 4 * a.N ≤ x.regs Traversal.heap + /-- The emitted prefix and untouched roots give exactly the original finite forest. -/ + exactOutput : s.nodes.map Prod.snd ++ + Traversal.Math.output (Nat.primesLE a.m) a.Λ + (((entries l U rs).drop s.index).map Traversal.Entry.task) = + Traversal.Math.output (Nat.primesLE a.m) a.Λ + ((entries l U rs).map Traversal.Entry.task) + /-- Original memory below the shared stack is retained throughout the forest. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- A live outer iteration has room for the one root it is about to execute. -/ +theorem ForestCursor.Invariant.root_budgets {a : Traversal.Arena} {w U : ℕ} + {l : Frontend.Layout} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (h : s.Invariant a w l U rs x) + (hc : (Cond.lt Preprocessing.current Preprocessing.roots).Holds x) : + (rootArena a x s.nodes).ceiling ≤ a.ceiling ∧ + (rootArena a x s.nodes).outputCapacity ≤ a.outputCapacity := by + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + have hm : 1 ≤ l.count - s.index := by omega + have hh := Nat.mul_le_mul_left + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) hm + have ht := Nat.mul_le_mul_left (Traversal.Math.work a.fuel) hm + rw [Nat.mul_one, Nat.mul_comm _ (l.count - s.index)] at hh ht + constructor <;> dsimp only [rootArena] + · have := h.heapBudget + omega + · have := h.tableBudget + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean new file mode 100644 index 0000000000..bd557ecb6b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLayout.lean @@ -0,0 +1,73 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCost + +/-! +# Concrete global layout of native Chan–He preprocessing + +The prime bitmap follows the frontend. A seven-word-per-entry stack and a pointer table precede +all heavy and packed arrays. Reservations use the actual input length and actual computed fuel. + +## Main definitions + +* `arena`: complete finite memory layout, including every root's allocation reservation. + +## Main results + +* `arena_separated`: concrete stack, bitmap, table and heap reservations are disjoint. + +The layout and resource estimates are original. They do not change the existing finite instances. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- All memory regions computed by the actual native forest controller. -/ +def arena (κ n : ℕ) (retained : ℕ → ℕ) : Traversal.Arena := + let l := Frontend.layout κ n + let U := (n + 2) ^ κ + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * U) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let N := 2 * m ^ 2 + let stack := l.endpoint + m + 1 + let cap := 7 * (2 * F + 1) + let table := stack + cap + let outcap := l.count * 3 ^ (F + 1) + let floor := table + outcap + { n := n + 1, m := m, Λ := l.digits, U := 2 * U, N := N, fuel := F, + primes := l.endpoint, stack := stack, capacity := cap, table := table, + outputCapacity := outcap, floor := floor, + ceiling := floor + l.count * + (Traversal.Math.work F * Traversal.allocationSize (n + 1) m N), + retained := retained } + +/-- The actual reservations put the bitmap, bounded stack, pointer table and arrays in order. -/ +theorem arena_separated (κ n : ℕ) (retained : ℕ → ℕ) : (arena κ n retained).Separated := by + constructor + · dsimp only [arena] + omega + · exact ⟨le_rfl, le_rfl, le_rfl⟩ + +/-- Every actual frontend payload precedes the reserved shared stack. -/ +theorem arena_frontend_before (κ n : ℕ) (retained : ℕ → ℕ) : + (Frontend.layout κ n).endpoint ≤ (arena κ n retained).stack := by + dsimp only [arena] + omega + +/-- Full ternary work never exceeds the native controller's computed power reservation. -/ +theorem work_le_power (f : ℕ) : Traversal.Math.work f ≤ 3 ^ (f + 1) := by + have := Traversal.Math.work_eq f + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean new file mode 100644 index 0000000000..ba409ecb43 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoad.lean @@ -0,0 +1,85 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingEntries + +/-! +# Native frontend descriptor loads + +The controller executes all six memory reads and the fuel copy. The semantic entry is obtained +from the existing frontend output relation, including empty compact arrays. + +## Main results + +* `loadRoot_exec`: exact native descriptor loading, charged execution and memory/register frame. + +These representation and native execution proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- A descriptor load changes only the seven current-record slots and its address scratch. -/ +theorem loadRoot_preserves : ∀ i : Fin 128, + (i.val < 64 ∨ 71 ≤ i.val) → i ≠ address → loadRoot.Preserves i := by decide + +/-- Execute all descriptor arithmetic, six native memory reads and the runtime fuel copy. -/ +theorem loadRoot_exec {w U f t : ℕ} {l : Frontend.Layout} {rs : List (ℕ × ℕ)} + (x : Data 128) (hx : x.Bounded w) (ht : t < l.count) + (hc : x.regs current = t) (hd : x.regs descriptors = l.descriptors) + (hf : x.regs levels = f) (hs : x.regs stride = 6) (ho : x.regs one = 1) + (hfit : Fits w (l.descriptors + 6 * t + 5)) + (houtput : Frontend.Output l U rs x.mem) : + ∃ y, Exec w loadRoot x y 14 ∧ (entry l U f rs t).Loaded y ∧ y.mem = x.mem ∧ + ∀ i : Fin 128, (i.val < 64 ∨ 71 ≤ i.val) → i ≠ address → y.regs i = x.regs i := by + let a := l.descriptors + 6 * t + let y := x |>.set address (t * 6) |>.set address a |>.set (Traversal.slot 0) f + |>.set (Traversal.slot 2) (x.mem a) |>.set address (a + 1) + |>.set (Traversal.slot 1) (x.mem (a + 1)) |>.set address (a + 2) + |>.set (Traversal.slot 4) (x.mem (a + 2)) |>.set address (a + 3) + |>.set (Traversal.slot 3) (x.mem (a + 3)) |>.set address (a + 4) + |>.set (Traversal.slot 6) (x.mem (a + 4)) |>.set address (a + 5) + |>.set (Traversal.slot 5) (x.mem (a + 5)) + have ha (k : ℕ) (hk : k ≤ 5) : Fits w (a + k) := hfit.of_le (by dsimp only [a]; omega) + have ha' (k : ℕ) (hk : k ≤ 5) : Fits w (l.descriptors + (t * 6 + k)) := + hfit.of_le (by omega) + have hbase : Fits w (l.descriptors + t * 6) := by + simpa only [Nat.add_zero] using ha' 0 (by decide) + have hmul : Fits w (t * 6) := hfit.of_le (by omega) + have hF : Fits w f := hf ▸ hx.1 levels + have he : Exec w loadRoot x y 14 := by + ram_eval [loadRoot, y, a, hc, hd, hf, hs, ho, hmul, ha 0 (by decide), + ha 1 (by decide), ha 2 (by decide), ha 3 (by decide), ha 4 (by decide), + ha 5 (by decide), hF, hx.2, current, descriptors, levels, stride, one, address, + Traversal.slot, Nat.mul_comm, Nat.add_assoc, hbase, + ha' 1 (by decide), ha' 2 (by decide), ha' 3 (by decide), ha' 4 (by decide), + ha' 5 (by decide), Fin.val_zero, Fin.val_one, Fin.val_two] + refine ⟨y, he, ?_, rfl, fun i hi hj => he.within.regs_eq (loadRoot_preserves i hi hj)⟩ + have hread (j : Fin 3) := houtput (3 * t + j.val) (entry_slot ht j) + have h₀ := hread 0 + have h₁ := hread 1 + have h₂ := hread 2 + have ha₀ : l.descriptors + 2 * (3 * t + 0) = a := by dsimp only [a]; omega + have ha₁ : l.descriptors + 2 * (3 * t + 1) = a + 2 := by dsimp only [a]; omega + have ha₂ : l.descriptors + 2 * (3 * t + 2) = a + 4 := by dsimp only [a]; omega + simp only [Fin.val_zero, Fin.val_one, Fin.val_two, ha₀, ha₁, ha₂] at h₀ h₁ h₂ + refine ⟨by simp +decide [y, entry, Traversal.slot, address], ?_, ?_⟩ + · intro j + fin_cases j <;> simpa +decide [y, entry, Traversal.arraySource, Traversal.slot, + address, Nat.add_assoc] using (by first | exact h₀.2.1 | exact h₁.2.1 | exact h₂.2.1) + · intro j + fin_cases j <;> simpa +decide [y, entry, Traversal.arrayLength, Traversal.slot, + address, List.length_map, Nat.add_assoc] using + (by first | exact h₀.1 | exact h₁.1 | exact h₂.1) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean new file mode 100644 index 0000000000..f72667ba26 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingLoop.lean @@ -0,0 +1,159 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingStep + +/-! +# Complete native frontend-root loop + +Every iteration loads the next actual descriptor, executes its complete bounded recursion and +increments the native index. Remaining-root reservations pay for every later iteration. + +## Main results + +* `forest_body_exec`: actual root execution preserves the global resource and output invariant. +* `forest_exec`: the entire actual outer loop emits exactly the finite frontend forest. + +These composition proofs are original and use the existing charged native loop rule. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +private theorem entries_drop_cons {l : Frontend.Layout} {U : ℕ} {rs : List (ℕ × ℕ)} + {i : ℕ} (hi : i < l.count) : + (entries l U rs).drop i = + entry l U (Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) rs i :: + (entries l U rs).drop (i + 1) := by + have hlen : i < (entries l U rs).length := by + simpa only [entries, List.length_map, List.length_range] using hi + rw [List.drop_eq_getElem_cons hlen] + simp only [entries, List.getElem_map, List.getElem_range] + +/-- An actual root execution leaves enough heap and table capacity for all later roots. -/ +theorem forest_body_exec {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hc : (Cond.lt current roots).Holds x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hf : a.fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + ∃ (s' : ForestCursor) (y : Data 128), + ExecWithin (w + 2) body x y (Traversal.rootTime a.fuel a.n a.m a.N + 15) ∧ + s'.Invariant a w l U rs y ∧ l.count - s'.index < l.count - s.index := by + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + obtain ⟨out, y, he, hy, hout, hprefix, hregs⟩ := + execute_next_root ha hw h hc hn hU hfront hkeys hnd hcard hvalues + have yi : y.regs current = s.index := (hregs _ (by decide) (by decide)).trans h.current + have yo : y.regs one = 1 := (hregs _ (by decide) (by decide)).trans h.one + have hfit : Fits (w + 2) (s.index + 1) := (h.bounded.1 roots).of_le (by + rw [h.roots] + omega) + let z := y.set current (s.index + 1) + have hz : Exec (B := .standard) (w + 2) (.op (.bin .add current current one)) y z 1 := by + ram_eval [z, yi, yo, hfit] + have zreg (i : Fin 128) (hi : i ≠ current) : z.regs i = y.regs i := by + simp only [z, Data.set_regs, Function.update_of_ne hi] + have zouter (i : Fin 128) (hi : 109 ≤ i.val) (hc : i ≠ current) (ha : i ≠ address) : + z.regs i = x.regs i := (zreg i hc).trans (hregs i hi ha) + let s' : ForestCursor := ⟨s.index + 1, out⟩ + refine ⟨s', z, ?_, ?_, by dsimp only [s']; omega⟩ + · obtain ⟨t, ht, he⟩ := he + cases he with + | seq hload hroot => + exact ⟨_, by omega, hload.seq (hroot.seq hz)⟩ + · constructor + · exact hz.bounded hy.bounded + · exact hy.controls.congr (by + intro i hi + apply zreg + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + intro he + subst i + norm_num [current] at hi) + · exact h.frontend.frame hcard (fun p hp => hprefix p (hp.trans_le hfront)) + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.descriptors + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.roots + · rfl + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.levels + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.stride + · exact (zouter _ (by decide) (by decide) (by decide)).trans h.one + · dsimp only [s']; omega + · exact hy.bitmap + · simpa only [rootArena, zreg Traversal.heap (by decide)] using hy.floor + · have hh : y.regs Traversal.heap ≤ x.regs Traversal.heap + + Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N := by + simpa only [rootArena, Traversal.Cursor.work, List.map_nil, + Traversal.Math.potential, List.sum_nil, Nat.zero_mul, Nat.add_zero] using hy.heapBudget + have hg := h.heapBudget + have hrem : l.count - s.index = (l.count - (s.index + 1)) + 1 := by omega + rw [hrem, Nat.add_mul, Nat.one_mul] at hg + change z.regs Traversal.heap + (l.count - (s.index + 1)) * _ ≤ _ + rw [zreg _ (by decide)] + omega + · exact (zreg _ (by decide)).trans hy.count + · have ht : out.length ≤ s.nodes.length + Traversal.Math.work a.fuel := by + simpa only [rootArena, Traversal.Cursor.work, List.map_nil, + Traversal.Math.potential, List.sum_nil, Nat.add_zero] using hy.tableBudget + have hg := h.tableBudget + have hrem : l.count - s.index = (l.count - (s.index + 1)) + 1 := by omega + rw [hrem, Nat.add_mul, Nat.one_mul] at hg + change out.length + (l.count - (s.index + 1)) * _ ≤ _ + omega + · exact hy.output + · simpa only [s', rootArena, zreg Traversal.heap (by decide)] using hy.outputBounds + · change out.map Prod.snd ++ _ = _ + rw [hout, List.append_assoc] + have hm := h.exactOutput + rw [entries_drop_cons hi, List.map_cons, Traversal.Math.output, + List.flatMap_cons] at hm + rw [hf] + exact hm + · intro p hp + exact (hprefix p hp).trans (h.protectedMemory p hp) + +/-- Execute every native frontend root with exact finite output and all loop control charged. -/ +theorem forest_exec {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hf : a.fuel = Cslib.Algorithms.ThreeSum.ChanHe.fuel l.n) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + ∃ (s' : ForestCursor) (y : Data 128), + ExecWithin (w + 2) forest x y + ((l.count - s.index) * (Traversal.rootTime a.fuel a.n a.m a.N + 17) + 2) ∧ + s'.Invariant a w l U rs y ∧ s'.index = l.count ∧ + s'.nodes.map Prod.snd = Traversal.Math.output (Nat.primesLE a.m) a.Λ + ((entries l U rs).map Traversal.Entry.task) := by + obtain ⟨s', y, he, hy, hc⟩ := ExecWithin.loop_invariant_state + (fun (s : ForestCursor) y => s.Invariant a w l U rs y) + (fun s => l.count - s.index) + (fun s x hi hc => forest_body_exec ha hw hi hc hn hU hf hfront hkeys hnd hcard hvalues) h + have hend : s'.index = l.count := by + have hge : l.count ≤ s'.index := by + simpa only [Cond.holds_lt, hy.current, hy.roots, not_lt] using hc + exact hy.index_le.antisymm hge + refine ⟨s', y, he, hy, hend, ?_⟩ + have hlen : (entries l U rs).length = l.count := by + simp only [entries, List.length_map, List.length_range] + simpa only [hend, ← hlen, List.drop_length, List.map_nil, Traversal.Math.output, + List.flatMap_nil, List.append_nil] using hy.exactOutput + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean new file mode 100644 index 0000000000..1b170b66b9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingPrimes.lean @@ -0,0 +1,79 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSnapshot + +/-! +# Computing the shared native prime bitmap + +The forest copies its computed candidate bound and fresh frontend endpoint into the existing +native trial-division program. Its actual bitmap and count are retained for every recursion root. + +## Main results + +* `primes_exec`: execute argument delivery and the complete table builder, preserving the frontend. + +This native composition proof is original and reuses the existing primality computation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Compute the actual shared bitmap from the already computed frontend and numeric parameters. -/ +theorem primes_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hfit : Fits w ((Frontend.layout κ n).endpoint + + Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + 4)) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let base := (Frontend.layout κ n).endpoint + ∃ y, ExecWithin w (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) x y + (2 + ((m + 1) * (7 * m + 15) + 8)) ∧ + Saved κ n y ∧ y.mem = PrimeTable.memory x.mem base (m + 1) ∧ + y.regs (primeRegisters PrimeTable.count) = (Nat.primesLE m).card ∧ + (∀ q ≤ m, y.mem (base + q) = if q.Prime then 1 else 0) ∧ + ∀ p < base, y.mem p = x.mem p := by + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let base := (Frontend.layout κ n).endpoint + let p := (x.set (primeRegisters PrimeTable.limit) m).set (primeRegisters PrimeTable.base) base + have hp : Exec w preparePrimes x p 2 := by + ram_eval [preparePrimes, p, hs.limit, hs.endpoint, m, base] + have hpi : Function.Injective primeRegisters := by + intro i j hij + exact Fin.ext (congrArg (fun k : Fin 128 => k.val) hij) + obtain ⟨z, hz, hmem, hcount, _, _⟩ := PrimeTable.exec (p.comap primeRegisters) + (hfit.of_le (by change m + 3 ≤ base + m + 4; omega)) + (hfit.of_le (by change base + (m + 1) ≤ base + m + 4; omega)) + let y := z.extend primeRegisters p + have he : ExecWithin w (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) x y + (2 + ((m + 1) * (7 * m + 15) + 8)) := by + simpa +decide [y, p, Data.comap, primeRegisters, PrimeTable.limit, m] using + hp.within.seq (hz.rename hpi) + have hmemory : y.mem = PrimeTable.memory x.mem base (m + 1) := by + simpa +decide [y, p, Data.extend, Data.comap, primeRegisters, PrimeTable.limit, + PrimeTable.base] using hmem + refine ⟨y, he, hs.congr (fun i hi _ => he.regs_eq ?_), hmemory, ?_, ?_, ?_⟩ + · have hh : ∀ i : Fin 128, 109 ≤ i.val → + (Code.seq preparePrimes (PrimeTable.program.rename primeRegisters)).Preserves i := by decide + exact hh i hi + · have hc := congrArg (fun d : Data 15 => d.regs PrimeTable.count) + (Data.comap_extend hpi z p) + exact hc.trans (by simpa +decide [p, Data.comap, primeRegisters, PrimeTable.limit] using hcount) + · intro q hq + change y.mem (base + q) = if q.Prime then 1 else 0 + change q ≤ m at hq + rw [hmemory] + simp only [PrimeTable.memory, show base ≤ base + q ∧ base + q < base + (m + 1) by omega, + and_self, ↓reduceIte, Nat.add_sub_cancel_left] + · intro q hq + rw [hmemory] + simp only [PrimeTable.memory, show ¬(base ≤ q ∧ q < base + (m + 1)) by omega, ↓reduceIte] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean new file mode 100644 index 0000000000..96ea089e48 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingProgram.lean @@ -0,0 +1,190 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalRoot +public import Complexity.Algorithms.WordRAM.PrimeTable + +/-! +# Native initialized-input Chan–He preprocessing program + +The complete finite instruction program first executes the native frontend and parameter +computation. It builds the prime bitmap by actual trial division, computes concrete stack and +output-table reservations, and loads and traverses every actual six-word frontend descriptor. +The ternary reservation is computed by a native multiplication loop, not supplied by a caller. + +This module defines the executable program. `PreprocessingInput`, `PreprocessingWidth` and +`PreprocessingTime` prove initialized-input execution, derived width and charged global bounds. + +## Main definitions + +* `program`: one finite 128-register program per fixed magnitude exponent. +* `flatProgram`: the complete native program with its final count-returning halt. +* `loadRoot`, `forest`: actual descriptor loads and the outer native root loop. + +## Main results + +* `not_readsWidth`: runtime physical width never controls the program's computation. +* `size_program`: code size depends only on the fixed exponent. + +The code and layout computations are original. Output metadata is unsigned; payload words use +the existing signed encoding. No solver, collision, primality or operation-count oracle is used. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Saved frontend descriptor base. -/ +abbrev descriptors : Fin 128 := 109 +/-- Saved number of roots. -/ +abbrev roots : Fin 128 := 110 +/-- Index of the next root. -/ +abbrev current : Fin 128 := 111 +/-- The computed recursion fuel shared by all roots. -/ +abbrev levels : Fin 128 := 112 +/-- The computed original-input magnitude. -/ +abbrev inputMagnitude : Fin 128 := 113 +/-- The computed candidate-prime bound. -/ +abbrev candidateLimit : Fin 128 := 114 +/-- The computed label-bit parameter. -/ +abbrev labels : Fin 128 := 115 +/-- Common packed length in words, retained as unsigned output metadata. -/ +abbrev packedLength : Fin 128 := 116 +/-- One-past-end frontend address; the shared prime bitmap begins here. -/ +abbrev firstFree : Fin 128 := 117 +/-- Six words per frontend root descriptor. -/ +abbrev stride : Fin 128 := 118 +/-- Outer-controller constant one. -/ +abbrev one : Fin 128 := 119 +/-- Outer-controller constant zero. -/ +abbrev zero : Fin 128 := 120 +/-- Descriptor-loading address temporary. -/ +abbrev address : Fin 128 := 121 +/-- Computed ternary-tree reservation. -/ +abbrev power : Fin 128 := 122 +/-- Remaining reservation multiplications. -/ +abbrev remaining : Fin 128 := 123 +/-- Reserved number of unsigned output pointers. -/ +abbrev outputCapacity : Fin 128 := 124 +/-- Setup constant, first seven and then three. -/ +abbrev scalar : Fin 128 := 125 +/-- The constant four converts packed words to groups. -/ +abbrev four : Fin 128 := 126 +/-- Initial heap frontier, above the complete stack and pointer-table reservations. -/ +abbrev heapFloor : Fin 128 := 127 + +/-- The complete 15-register prime builder runs in the low registers. -/ +def primeRegisters (i : Fin 15) : Fin 128 := ⟨i.val, by omega⟩ + +/-- Save every frontend/parameter output that the reused traversal registers will overwrite. -/ +def snapshot : Code .standard 128 := + let frontBase := Frontend.descriptorBase + let frontCount := Frontend.descriptorCount + let frontEnd := Frontend.heapEnd + let f := Frontend.parameterRegisters Parameters.fuel + let U := Frontend.parameterRegisters Parameters.magnitude + let m := Frontend.parameterRegisters Parameters.primeBound + let Λ := Frontend.parameterRegisters Parameters.digits + let N := Frontend.parameterRegisters Parameters.packedLen + ram[ descriptors ← frontBase; roots ← frontCount; levels ← f; inputMagnitude ← U; + candidateLimit ← m; labels ← Λ; packedLength ← N; firstFree ← frontEnd; + current ← #0; stride ← #6; one ← #1; zero ← #0; four ← #4 ] + +/-- Deliver the actual native prime-table inputs. -/ +def preparePrimes : Code .standard 128 := + let m := primeRegisters PrimeTable.limit + let b := primeRegisters PrimeTable.base + ram[ m ← candidateLimit; b ← firstFree ] + +/-- Compute the traversal ABI and concrete stack reservation, retaining the outer metadata. -/ +def setupTraversal : Code .standard 128 := + let m := Traversal.limit + let Λ := Traversal.digits + let U := Traversal.magnitude + let N := Traversal.groups + let bitmap := Traversal.bitmap + let card := Traversal.primeCount + let nativeCard := primeRegisters PrimeTable.count + let stack := Traversal.stackBase + let capacity := Traversal.stackCapacity + let out := Traversal.outputs + let count := Traversal.outputCount + let z := Traversal.zero + let unit := Traversal.one + ram[ m ← candidateLimit; Λ ← labels; U ← inputMagnitude + inputMagnitude; + N ← packedLength / four; bitmap ← firstFree; card ← nativeCard; + stack ← firstFree + candidateLimit; stack ← stack + one; + capacity ← levels + levels; capacity ← capacity + one; scalar ← #7; + capacity ← capacity * scalar; out ← stack + capacity; count ← #0; z ← #0; unit ← #1; + power ← #1; remaining ← levels + one; scalar ← #3 ] + +/-- One actual multiplication and decrement per ternary-reservation level. -/ +def powerBody : Code .standard 128 := ram[ + power ← power * scalar; remaining ← remaining - one ] + +/-- Compute `3^(fuel+1)` by a finite native loop. -/ +def reserve : Code .standard 128 := .loop (.lt zero remaining) powerBody + +/-- Reserve all output pointers before the initial fresh heap frontier. -/ +def finishLayout : Code .standard 128 := + let out := Traversal.outputs + let heap := Traversal.heap + ram[ outputCapacity ← roots * power; heap ← out + outputCapacity; heapFloor ← heap ] + +/-- Load all six descriptor words in their actual length/pointer order, with the shared fuel. -/ +def loadRoot : Code .standard 128 := + let f := Traversal.slot 0 + let p₀ := Traversal.slot 1 + let n₀ := Traversal.slot 2 + let p₁ := Traversal.slot 3 + let n₁ := Traversal.slot 4 + let p₂ := Traversal.slot 5 + let n₂ := Traversal.slot 6 + ram[ address ← current * stride; address ← descriptors + address; f ← levels; + n₀ ← *address; address ← address + one; p₀ ← *address; address ← address + one; + n₁ ← *address; address ← address + one; p₁ ← *address; address ← address + one; + n₂ ← *address; address ← address + one; p₂ ← *address ] + +/-- Traverse the actually loaded root, then advance the actual descriptor index. -/ +def body : Code .standard 128 := + .seq loadRoot (.seq Traversal.oneRoot (.op (.bin .add current current one))) + +/-- Enumerate every frontend root, including the exact empty and multiplicity boundary roots. -/ +def forest : Code .standard 128 := .loop (.lt current roots) body + +/-- Complete native preprocessing after the native initialized-input frontend. -/ +def afterFrontend : Code .standard 128 := + .seq snapshot (.seq (.seq preparePrimes (PrimeTable.program.rename primeRegisters)) + (.seq setupTraversal (.seq (.seq reserve finishLayout) forest))) + +/-- One finite native program per fixed magnitude exponent, independent of input length/width. -/ +def program (κ : ℕ) : Code .standard 128 := .seq (Frontend.program κ) afterFrontend + +/-- Complete flat native program; it returns the unsigned output count. -/ +def flatProgram (κ : ℕ) : Program .standard 128 := (program κ).program Traversal.outputCount + +private theorem afterFrontend_not_readsWidth : ¬afterFrontend.ReadsWidth := by decide + +/-- Physical width never determines radix passes, prime candidates, recursion fuel or code. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := + not_or.mpr ⟨Frontend.not_readsWidth κ, afterFrontend_not_readsWidth⟩ + +/-- The finite controller adds a fixed code suffix to the exponent-dependent frontend. -/ +theorem size_program (κ : ℕ) : + (program κ).size = κ + 71 + Frontend.construct.size + afterFrontend.size := by + rw [program, Code.size, Frontend.size_program] + +/-- Only the current index and address scratch among registers 109–127 change during a root. -/ +theorem body_preserves : ∀ i : Fin 128, + 109 ≤ i.val → i ≠ current → i ≠ address → body.Preserves i := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean new file mode 100644 index 0000000000..47822f2afb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingReserve.lean @@ -0,0 +1,88 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram + +/-! +# Executing the native ternary reservation + +The forest controller computes its output reservation by actual multiplications and a native +countdown. The loop has no supplied exponentiation instruction or resource oracle. + +## Main results + +* `reserve_exec`: exact native power, loop count, memory frame and register frame. + +The controller proof and its charged bound are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- The power loop changes only its accumulator and countdown. -/ +theorem reserve_preserves : ∀ i : Fin 128, i ≠ power → i ≠ remaining → reserve.Preserves i := + by decide + +/-- Actual multiplication and subtraction compute the complete ternary-tree reservation. -/ +theorem reserve_exec {w F : ℕ} (x : Data 128) + (hp : x.regs power = 1) (hr : x.regs remaining = F + 1) + (hz : x.regs zero = 0) (ho : x.regs one = 1) (hs : x.regs scalar = 3) + (hfit : Fits w (3 ^ (F + 1))) : + ∃ y, ExecWithin w reserve x y (4 * (F + 1) + 2) ∧ + y.regs power = 3 ^ (F + 1) ∧ y.regs remaining = 0 ∧ y.mem = x.mem ∧ + ∀ i : Fin 128, i ≠ power → i ≠ remaining → y.regs i = x.regs i := by + let I (y : Data 128) : Prop := + y.regs remaining ≤ F + 1 ∧ y.regs power = 3 ^ (F + 1 - y.regs remaining) ∧ + y.regs zero = 0 ∧ y.regs one = 1 ∧ y.regs scalar = 3 ∧ y.mem = x.mem + have hbody (y : Data 128) (hy : I y) (hc : (Cond.lt zero remaining).Holds y) : + ∃ z, ExecWithin w powerBody y z 2 ∧ I z ∧ + z.regs remaining < y.regs remaining := by + rcases hy with ⟨hle, hpow, hzero, hone, hscalar, hmem⟩ + have hpos : 0 < y.regs remaining := by + simpa only [Cond.holds_lt, hzero] using hc + have hmul : Fits w (y.regs power * y.regs scalar) := by + rw [hpow, hscalar, ← pow_succ] + exact hfit.of_le (Nat.pow_le_pow_right (by decide) (by omega)) + have hsub : Fits w (y.regs remaining - 1) := hfit.of_le (by + have hp : F + 1 < 3 ^ (F + 1) := Nat.lt_pow_self (by decide) + omega) + let z := (y.set power (y.regs power * y.regs scalar)).set remaining + (y.regs remaining - 1) + have he : Exec w powerBody y z 2 := by + ram_eval [powerBody, z, hone, hmul, hsub, power, remaining, scalar, one] + refine ⟨z, he.within, ?_, ?_⟩ + · refine ⟨?_, ?_, ?_, ?_, ?_, hmem⟩ + · change y.regs remaining - 1 ≤ F + 1 + omega + · change y.regs power * y.regs scalar = 3 ^ (F + 1 - (y.regs remaining - 1)) + rw [hpow, hscalar, ← pow_succ] + congr 1 + omega + · simpa +decide [z, Data.set, zero, power, remaining] using hzero + · simpa +decide [z, Data.set, one, power, remaining] using hone + · simpa +decide [z, Data.set, scalar, power, remaining] using hscalar + · change y.regs remaining - 1 < y.regs remaining + omega + have hx : I x := by + refine ⟨by omega, ?_, hz, ho, hs, rfl⟩ + rw [hp, hr, Nat.sub_self, pow_zero] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => y.regs remaining) hbody hx + have hend : y.regs remaining = 0 := by + have hzero := hy.2.2.1 + simpa only [Cond.holds_lt, hzero, not_lt, Nat.le_zero] using hc + refine ⟨y, ?_, ?_, hend, hy.2.2.2.2.2, ?_⟩ + · simpa only [reserve, hr, Nat.reduceAdd, Nat.mul_comm] using he + · simpa only [hend, Nat.sub_zero] using hy.2.1 + · intro i hi hj + exact he.regs_eq (reserve_preserves i hi hj) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean new file mode 100644 index 0000000000..d258931715 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSetup.lean @@ -0,0 +1,99 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingSnapshot +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingLayout + +/-! +# Executing the native shared traversal layout + +The controller copies computed parameters, doubles the actual magnitude, divides the common +packed length into four-cell groups and computes the explicit stack reservation by instructions. + +## Main results + +* `setupTraversal_exec`: actual nineteen-instruction layout setup and exact native controls. + +The native layout and composition proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Execute all shared traversal arguments, constants and explicit-stack reservations. -/ +theorem setupTraversal_exec {κ n w : ℕ} (x : Data 128) (hs : Saved κ n x) + (hc : x.regs (primeRegisters PrimeTable.count) = + (Nat.primesLE (Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ))).card) + (hfit : Fits w ((arena κ n x.mem).floor + (arena κ n x.mem).U + + (arena κ n x.mem).N + (arena κ n x.mem).fuel + 16)) : + ∃ y, Exec w setupTraversal x y 19 ∧ Saved κ n y ∧ + (arena κ n x.mem).Controls y ∧ y.regs Traversal.outputCount = 0 ∧ + y.regs power = 1 ∧ + y.regs remaining = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + 1 ∧ + y.regs scalar = 3 ∧ y.mem = x.mem := by + let a := arena κ n x.mem + let F := a.fuel + let y := x |>.set Traversal.limit a.m |>.set Traversal.digits a.Λ + |>.set Traversal.magnitude a.U |>.set Traversal.groups a.N + |>.set Traversal.bitmap a.primes |>.set Traversal.primeCount (Nat.primesLE a.m).card + |>.set Traversal.stackBase (a.primes + a.m) + |>.set Traversal.stackBase a.stack + |>.set Traversal.stackCapacity (F + F) + |>.set Traversal.stackCapacity (F + F + 1) |>.set scalar 7 + |>.set Traversal.stackCapacity a.capacity |>.set Traversal.outputs a.table + |>.set Traversal.outputCount 0 |>.set Traversal.zero 0 |>.set Traversal.one 1 + |>.set power 1 |>.set remaining (F + 1) |>.set scalar 3 + have hfloor : a.primes + a.m + 1 + 7 * (2 * F + 1) ≤ a.floor := by + dsimp only [a, F, arena] + omega + have fit (v : ℕ) (hv : v ≤ a.floor + a.U + a.N + F + 16) : Fits w v := hfit.of_le hv + have hU : Fits w (2 * (n + 2) ^ κ) := fit _ (by change a.U ≤ _; omega) + have hN : Fits w (2 * a.m ^ 2) := fit _ (by change a.N ≤ _; omega) + have hstack₀ : Fits w (a.primes + a.m) := fit _ (by omega) + have hstack : Fits w a.stack := fit _ (by change a.primes + a.m + 1 ≤ _; omega) + have hF₀ : Fits w (F + F) := fit _ (by omega) + have hF₁ : Fits w (F + F + 1) := fit _ (by omega) + have hcap : Fits w a.capacity := fit _ (by change 7 * (2 * F + 1) ≤ _; omega) + have htable : Fits w a.table := fit _ (by change a.primes + a.m + 1 + 7 * (2 * F + 1) ≤ _; omega) + have hnext : Fits w (F + 1) := fit _ (by omega) + have h1 : Fits w 1 := fit _ (by omega) + have h3 : Fits w 3 := fit _ (by omega) + have h7 : Fits w 7 := fit _ (by omega) + have hdiv : 8 * a.m ^ 2 / 4 = 2 * a.m ^ 2 := by omega + simp only [a, F, arena, ← two_mul, Nat.mul_comm, Nat.add_assoc] + at hU hN hstack₀ hstack hF₀ hF₁ hcap htable hnext hdiv + dsimp only [primeRegisters, PrimeTable.count] at hc + have he : Exec w setupTraversal x y 19 := by + ram_eval [setupTraversal, y, a, F, arena, hs.limit, hs.digits, hs.magnitude, + hs.length, hs.endpoint, hs.fuel, hs.one, hs.four, hc, hdiv, hU, hN, + hstack₀, hstack, hF₀, hF₁, hcap, htable, hnext, h1, h3, h7, + Traversal.limit, Traversal.digits, Traversal.magnitude, Traversal.groups, + Traversal.bitmap, Traversal.primeCount, Traversal.stackBase, Traversal.stackCapacity, + Traversal.outputs, Traversal.outputCount, Traversal.zero, Traversal.one, + candidateLimit, labels, inputMagnitude, packedLength, four, firstFree, levels, + one, power, remaining, scalar, primeRegisters, PrimeTable.count, + ← two_mul, Nat.mul_comm, Nat.add_assoc] + refine ⟨y, he, hs.congr (fun i hi hj => he.within.regs_eq ?_), ?_, ?_, ?_, ?_, ?_, rfl⟩ + · have hh : ∀ i : Fin 128, 109 ≤ i.val → i ≠ power → i ≠ remaining → i ≠ scalar → + setupTraversal.Preserves i := by decide + exact hh i hi (by intro hh; subst i; norm_num [power] at hj) + (by intro hh; subst i; norm_num [remaining] at hj) + (by intro hh; subst i; norm_num [scalar] at hj) + · constructor <;> simp +decide [y, a, F, arena, Traversal.limit, Traversal.digits, + Traversal.magnitude, Traversal.groups, Traversal.bitmap, Traversal.primeCount, + Traversal.outputs, Traversal.stackBase, Traversal.stackCapacity, + Traversal.zero, Traversal.one, scalar, remaining, power] + all_goals simp +decide [y, F, a, arena, Traversal.outputCount, + Traversal.one, Traversal.zero, Traversal.outputs, Traversal.stackCapacity, + power, remaining, scalar] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean new file mode 100644 index 0000000000..426dd7e78c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingSnapshot.lean @@ -0,0 +1,104 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram + +/-! +# Saving the actual frontend and parameter outputs + +The outer controller retains every shared scalar in registers outside the complete root program's +write set. These are copies of computed frontend results, not inputs supplied by a caller. + +## Main definitions + +* `Saved`: exact native outer metadata after the snapshot instructions. + +## Main results + +* `snapshot_exec`: execute all thirteen scalar copies and constant initializations. + +The native ABI and composition proof are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Exact computed metadata retained outside the root program's register bank. -/ +structure Saved (κ n : ℕ) (x : Data 128) : Prop where + /-- Descriptor table and count produced by the frontend. -/ + base : x.regs descriptors = (Frontend.layout κ n).descriptors + count : x.regs roots = (Frontend.layout κ n).count + /-- Exact numeric parameter outputs. -/ + fuel : x.regs levels = Cslib.Algorithms.ThreeSum.ChanHe.fuel n + magnitude : x.regs inputMagnitude = (n + 2) ^ κ + limit : x.regs candidateLimit = Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + digits : x.regs labels = (Frontend.layout κ n).digits + length : x.regs packedLength = + 8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) ^ 2 + endpoint : x.regs firstFree = (Frontend.layout κ n).endpoint + /-- Actual index and small constants. -/ + index : x.regs current = 0 + stride : x.regs stride = 6 + one : x.regs one = 1 + zero : x.regs zero = 0 + four : x.regs four = 4 + +/-- Programs restricted to lower registers retain all computed outer metadata. -/ +theorem Saved.congr {κ n : ℕ} {x y : Data 128} (h : Saved κ n x) + (hr : ∀ i : Fin 128, 109 ≤ i.val → (i.val ≤ 120 ∨ i.val = 126) → + y.regs i = x.regs i) : Saved κ n y := by + exact ⟨(hr _ (by decide) (by decide)).trans h.base, + (hr _ (by decide) (by decide)).trans h.count, + (hr _ (by decide) (by decide)).trans h.fuel, + (hr _ (by decide) (by decide)).trans h.magnitude, + (hr _ (by decide) (by decide)).trans h.limit, + (hr _ (by decide) (by decide)).trans h.digits, + (hr _ (by decide) (by decide)).trans h.length, + (hr _ (by decide) (by decide)).trans h.endpoint, + (hr _ (by decide) (by decide)).trans h.index, + (hr _ (by decide) (by decide)).trans h.stride, + (hr _ (by decide) (by decide)).trans h.one, + (hr _ (by decide) (by decide)).trans h.zero, + (hr _ (by decide) (by decide)).trans h.four⟩ + +/-- Saving computed scalars and initializing the outer constants changes no memory. -/ +theorem snapshot_exec {κ n w : ℕ} (x : Data 128) + (hp : Parameters.Output n ((n + 2) ^ κ) (x.comap Frontend.parameterRegisters)) + (hb : x.regs Frontend.descriptorBase = (Frontend.layout κ n).descriptors) + (hc : x.regs Frontend.descriptorCount = (Frontend.layout κ n).count) + (he : x.regs Frontend.heapEnd = (Frontend.layout κ n).endpoint) + (h6 : Fits w 6) : ∃ y, Exec w snapshot x y 13 ∧ Saved κ n y ∧ y.mem = x.mem := by + let l := Frontend.layout κ n + let y := x |>.set descriptors l.descriptors |>.set roots l.count + |>.set levels (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) + |>.set inputMagnitude ((n + 2) ^ κ) + |>.set candidateLimit (Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ)) + |>.set labels l.digits + |>.set packedLength (8 * Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) ^ 2) + |>.set firstFree l.endpoint |>.set current 0 |>.set stride 6 + |>.set one 1 |>.set zero 0 |>.set four 4 + have hf := hp.fuel + have hU := hp.magnitude + have hm := hp.primeBound + have hΛ := hp.digits + have hN := hp.packedLen + simp only [Data.comap_regs] at hf hU hm hΛ hN + have h1 : Fits w 1 := h6.of_le (by decide) + have h4 : Fits w 4 := h6.of_le (by decide) + refine ⟨y, ?_, ?_, rfl⟩ + · ram_eval [snapshot, y, l, hb, hc, he, hf, hU, hm, hΛ, hN, h1, h4, h6, + Frontend.layout, descriptors, roots, levels, inputMagnitude, candidateLimit, + labels, packedLength, firstFree, current, stride, one, zero, four] + · constructor <;> simp +decide [y, l, descriptors, roots, levels, inputMagnitude, + candidateLimit, labels, packedLength, firstFree, current, stride, one, zero, four] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean new file mode 100644 index 0000000000..1791528800 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingStep.lean @@ -0,0 +1,117 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingInvariant + +/-! +# Executing the next actual frontend root + +The actual descriptor loads establish the complete root program's ABI. Global remaining-root +reservations derive its local arena, rather than supplying another assumed execution contract. + +## Main results + +* `execute_next_root`: actual loads followed by the complete native traversal, with exact output. + +The native composition and resource proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Execute the next actual descriptor and its full recursion within that root's own budget. -/ +theorem execute_next_root {a : Traversal.Arena} {w U : ℕ} {l : Frontend.Layout} + {input : List ℤ} {rs : List (ℕ × ℕ)} {s : ForestCursor} {x : Data 128} + (ha : a.Separated) (hw : a.Ready w) (h : s.Invariant a w l U rs x) + (hc : (Cond.lt current roots).Holds x) + (hn : a.n = l.n + 1) (hU : a.U = 2 * U) + (hfront : l.endpoint ≤ a.stack) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hnd : (rs.map Prod.fst).Nodup) (hcard : rs.length ≤ l.n) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + let e := entry l U a.fuel rs s.index + ∃ (out : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) (.seq loadRoot Traversal.oneRoot) x y + (14 + Traversal.rootTime a.fuel a.n a.m a.N) ∧ + (Traversal.Cursor.mk [] out).Invariant (rootArena a x s.nodes) w + (s.nodes.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) y ∧ + out.map Prod.snd = s.nodes.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ ∧ + (∀ p < a.stack, y.mem p = x.mem p) ∧ + ∀ i : Fin 128, 109 ≤ i.val → i ≠ address → y.regs i = x.regs i := by + let e := entry l U a.fuel rs s.index + let b := rootArena a x s.nodes + have hi : s.index < l.count := by + simpa only [Cond.holds_lt, h.current, h.roots] using hc + obtain ⟨hceil, htable⟩ := h.root_budgets hc + have hheap : x.regs Traversal.heap ≤ a.ceiling := by + have := h.heapBudget + omega + have hstack : a.stack ≤ x.regs Traversal.heap := by + have ht := ha.2.1 + have hg := ha.2.2.1 + have := h.floor + omega + have haddr : l.descriptors + 6 * s.index + 5 ≤ a.ceiling := by + have hd := l.descriptor_before (entry_slot hi (2 : Fin 3)) l.arrays + change l.descriptors + 2 * (3 * s.index + 2) + 2 ≤ l.endpoint at hd + omega + have hsum := Traversal.envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have hfit := (Signed.unsigned_fits_of_signed hw.2.2).of_le (haddr.trans (by omega)) + obtain ⟨p, hp, hloaded, hmem, hpr⟩ := loadRoot_exec x h.bounded hi h.current + h.descriptors h.levels h.stride h.one hfit h.frontend + have pheap : p.regs Traversal.heap = x.regs Traversal.heap := hpr _ (by decide) (by decide) + have pcount : p.regs Traversal.outputCount = s.nodes.length := + (hpr _ (by decide) (by decide)).trans h.count + have hb : b.Separated := by + refine ⟨ha.1, ha.2.1, ?_, ha.2.2.2⟩ + have hg := ha.2.2.1 + change a.table + (s.nodes.length + Traversal.Math.work a.fuel) ≤ a.floor + change s.nodes.length + Traversal.Math.work a.fuel ≤ a.outputCapacity at htable + omega + have hready : b.Ready w := rootArena_ready hw hceil + have hinput : Traversal.RootInput b w e s.nodes p := by + refine ⟨hp.bounded h.bounded, ?_, hloaded, ?_, ?_, ?_, ?_, ?_, rfl, ?_, ?_, + pcount, le_rfl, ?_, ?_, ?_⟩ + · exact h.controls.congr (fun i hi => hpr i (Or.inr (by + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + omega)) (by + intro he + subst i + norm_num [address] at hi)) + · rw [hmem] + exact entry_stored hkeys h.frontend hi + · dsimp only [b, rootArena] + rw [hn, hU] + exact entry_valid l U a.fuel s.index hkeys hnd hcard hvalues + · simpa only [b, rootArena, Traversal.Arena.Bitmap, hmem] using h.bitmap + · intro j + apply Or.inl + exact (entry_ends l U a.fuel rs hcard hi j).trans hfront + · intro j + rw [pheap] + exact (entry_ends l U a.fuel rs hcard hi j).trans (hfront.trans hstack) + · simpa only [b, rootArena, pheap] using h.floor + · dsimp only [b, rootArena, e, entry] + rw [pheap] + · simpa only [b, rootArena, hmem] using h.output + · simpa only [b, rootArena, pheap] using h.outputBounds + · simpa only [b, rootArena, hmem] using h.protectedMemory + obtain ⟨out, y, he, hfinal, hout, hprefix, hregs⟩ := + Traversal.root_exec e s.nodes p hb hready hinput + refine ⟨out, y, hp.within.seq he, hfinal, hout, ?_, ?_⟩ + · intro q hq + exact (hprefix q hq).trans (congrFun hmem q) + · intro i hi hj + exact (hregs i hi).trans (hpr i (Or.inr (by omega)) hj) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.lean new file mode 100644 index 0000000000..601b122db7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingTime.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.PreprocessingInput +public import Complexity.Algorithms.ThreeSum.ChanHe.ShiftedSizes + +/-! +# Subquadratic charged native preprocessing cost + +The bound counts complete initialized-input execution, trial division, all descriptor loads, +every recursion transition, native collision/heavy computation, array writes and the final halt. +Its polynomial factor is `(n+2)(sqrt(n+2)+1)` and its logarithm has fixed degree thirteen. + +## Main definitions + +* `rootCoefficient`, `costCoefficient`: explicit constants depending only on the fixed exponent. + +## Main results + +* `cost_bound`: the actual native instruction count is at most square-root times linear times + a fixed logarithmic power, uniformly over physical width. + +These charged estimates are original. The existing mathematical prime/instance bounds are reused. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- Uniform complete-root coefficient after substituting the actual candidate bound. -/ +def rootCoefficient (κ : ℕ) : ℕ := + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + 70 + 196608 * (C + 4 * C ^ 3) + +/-- Explicit whole-program coefficient, independent of input length, data and physical width. -/ +def costCoefficient (κ : ℕ) : ℕ := + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + Frontend.costCoefficient κ + 60 * C ^ 2 + 77 + + 4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17) + +private theorem parameter_scale_bounds (κ n : ℕ) : + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + 1 ≤ C * S * L ^ 2 ∧ + S ^ 2 ≤ 4 * B := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + constructor + · have hm := Cslib.Algorithms.ThreeSum.ChanHe.mPar_shifted_pow_le n κ + have hu : 1 ≤ S * L ^ 2 := by + have hp : 0 < S * L ^ 2 := by positivity + omega + calc _ ≤ ((5 * κ + 16) * (10 * κ + 40)) * S * L ^ 2 + S * L ^ 2 := + Nat.add_le_add hm hu + _ = C * S * L ^ 2 := by dsimp only [C]; ring + · have hs := Nat.sqrt_le B + have ht := Nat.sqrt_le_self B + change S ^ 2 ≤ 4 * B + dsimp only [S] + nlinarith only [hs, ht, hB] + +/-- The complete root cost at the actual magnitude-derived candidate window. -/ +theorem root_cost_bound (κ n : ℕ) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + Traversal.rootTime (Cslib.Algorithms.ThreeSum.ChanHe.fuel n) (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 11 := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have hC : 1 ≤ C := by dsimp only [C]; omega + have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) + obtain ⟨hmb, hsquare⟩ := parameter_scale_bounds κ n + change m + 1 ≤ C * S * L ^ 2 at hmb + change S ^ 2 ≤ 4 * B at hsquare + have hcube : S ^ 3 ≤ 4 * B * S := by + have hh := Nat.mul_le_mul_right S hsquare + nlinarith + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hroot : Traversal.rootTime F (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * B * S * L ^ 11 := by + have hr := Traversal.rootTime_fuel_square_le n (n + 1) m + have hlogs : 2 * Nat.log 2 n + 2 ≤ 2 * L := by dsimp only [L]; omega + calc Traversal.rootTime F (n + 1) m (2 * m ^ 2) + ≤ 70 + 6144 * (2 * L) ^ 5 * + (B * (C * S * L ^ 2) + (C * S * L ^ 2) ^ 3) := by + apply hr.trans + change 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + (B * (m + 1) + (m + 1) ^ 3) ≤ _ + gcongr + _ = 70 + 196608 * (C * B * S * L ^ 7 + C ^ 3 * S ^ 3 * L ^ 11) := by ring + _ ≤ 70 + 196608 * (C * B * S * L ^ 11 + C ^ 3 * (4 * B * S) * L ^ 11) := by + have hl : C * B * S * L ^ 7 ≤ C * B * S * L ^ 11 := + Nat.mul_le_mul_left _ (lp (show 7 ≤ 11 by decide)) + have hr : C ^ 3 * S ^ 3 * L ^ 11 ≤ C ^ 3 * (4 * B * S) * L ^ 11 := + Nat.mul_le_mul_right _ (Nat.mul_le_mul_left _ hcube) + exact Nat.add_le_add_left (Nat.mul_le_mul_left _ (Nat.add_le_add hl hr)) _ + _ ≤ rootCoefficient κ * B * S * L ^ 11 := by + have hu : 1 ≤ B * S * L ^ 11 := by + have hp : 0 < B * S * L ^ 11 := by positivity + omega + change _ ≤ (70 + 196608 * (C + 4 * C ^ 3)) * B * S * L ^ 11 + nlinarith only [hu] + exact hroot + +private theorem forest_time_bound (κ n : ℕ) : + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * + ((n + 2) * (Nat.sqrt (n + 2) + 1) * (Nat.log 2 (n + 2) + 1) ^ 13) := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let V := B * S * L ^ 13 + have hL : 1 ≤ L := by dsimp only [L]; omega + have hroot := root_cost_bound κ n + change Traversal.rootTime F (n + 1) m (2 * m ^ 2) ≤ + rootCoefficient κ * B * S * L ^ 11 at hroot + have hD : (Frontend.layout κ n).count ≤ 4 * (κ + 3) ^ 2 * L ^ 2 := by + have hb := Cslib.Algorithms.ThreeSum.ChanHe.labelBits_pow_le B κ + have hb' : (Frontend.layout κ n).digits ≤ (κ + 3) * L := by + exact hb.trans (by change κ * L + 3 ≤ (κ + 3) * L; nlinarith only [hL]) + have hh := Nat.pow_le_pow_left hb' 2 + have hu : 1 ≤ ((κ + 3) * L) ^ 2 := by + have hp : 0 < ((κ + 3) * L) ^ 2 := by positivity + omega + dsimp only [Frontend.Layout.count, Frontend.Layout.splits] + nlinarith only [hh, hu] + have hforest : (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V := by + have hu : 1 ≤ B * S * L ^ 11 := by + have hp : 0 < B * S * L ^ 11 := by positivity + omega + have hr : Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17 ≤ + (rootCoefficient κ + 17) * B * S * L ^ 11 := by nlinarith only [hroot, hu] + calc _ ≤ (4 * (κ + 3) ^ 2 * L ^ 2) * + ((rootCoefficient κ + 17) * B * S * L ^ 11) := Nat.mul_le_mul hD hr + _ = _ := by dsimp only [V]; ring + exact hforest + +private theorem prime_time_bound (B S C L m : ℕ) + (hB : 1 ≤ B) (hS : 1 ≤ S) (hL : 1 ≤ L) + (hmb : m + 1 ≤ C * S * L ^ 2) (hsquare : S ^ 2 ≤ 4 * B) : + (m + 1) * (7 * m + 15) + 8 ≤ + (60 * C ^ 2 + 8) * (B * S * L ^ 13) := by + let V := B * S * L ^ 13 + have hV : 1 ≤ V := by + have hp : 0 < V := by dsimp only [V]; positivity + omega + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hprime : (m + 1) * (7 * m + 15) + 8 ≤ (60 * C ^ 2 + 8) * V := by + have hm2 := Nat.pow_le_pow_left hmb 2 + have hs := Nat.mul_le_mul_right (C ^ 2 * L ^ 4) hsquare + have hsmall : B * L ^ 4 ≤ V := by + have hh := Nat.mul_le_mul_left B (lp (show 4 ≤ 13 by decide)) + calc B * L ^ 4 ≤ B * L ^ 13 := hh + _ ≤ B * S * L ^ 13 := Nat.mul_le_mul_right _ (by nlinarith only [hS]) + have hms : (m + 1) ^ 2 ≤ 4 * C ^ 2 * B * L ^ 4 := by nlinarith only [hm2, hs] + have hh := Nat.mul_le_mul_left (60 * C ^ 2) hsmall + nlinarith only [hms, hh, hV] + exact hprime + +private theorem frontend_time_bound (κ n : ℕ) : + Frontend.cost κ n ≤ Frontend.costCoefficient κ * + ((n + 2) * (Nat.sqrt (n + 2) + 1) * (Nat.log 2 (n + 2) + 1) ^ 13) := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let V := B * S * L ^ 13 + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hclog : Nat.clog 2 B ≤ L := + Nat.clog_le_of_le_pow (Nat.lt_pow_succ_log_self (by decide) B).le + have hfront : Frontend.cost κ n ≤ Frontend.costCoefficient κ * V := by + have hh := Frontend.cost_bound κ n + have hlogs := Nat.pow_le_pow_left hclog 2 + have hp := lp (show 2 ≤ 13 by decide) + have hnb : n + 1 ≤ B := by dsimp only [B]; omega + have hsmall : (n + 1) * Nat.clog 2 B ^ 2 ≤ V := by + calc (n + 1) * Nat.clog 2 B ^ 2 ≤ B * L ^ 13 := + Nat.mul_le_mul hnb (hlogs.trans hp) + _ ≤ B * S * L ^ 13 := Nat.mul_le_mul_right _ (by nlinarith only [hS]) + rw [Nat.mul_assoc] at hh + exact hh.trans (Nat.mul_le_mul_left (Frontend.costCoefficient κ) hsmall) + exact hfront + +/-- The actual native program has a genuinely subquadratic polynomial factor and fixed polylog. -/ +theorem cost_bound (κ n : ℕ) : + cost κ n ≤ costCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 13 := by + let B := n + 2 + let L := Nat.log 2 B + 1 + let S := Nat.sqrt B + 1 + let C := (5 * κ + 16) * (10 * κ + 40) + 1 + let m := Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * B ^ κ) + let F := Cslib.Algorithms.ThreeSum.ChanHe.fuel n + let V := B * S * L ^ 13 + have hB : 2 ≤ B := by dsimp only [B]; omega + have hL : 1 ≤ L := by dsimp only [L]; omega + have hS : 1 ≤ S := by dsimp only [S]; omega + have hC : 1 ≤ C := by dsimp only [C]; omega + have hlog : Nat.log 2 n ≤ Nat.log 2 B := Nat.log_mono_right (by dsimp only [B]; omega) + obtain ⟨hmb, hsquare⟩ := parameter_scale_bounds κ n + change m + 1 ≤ C * S * L ^ 2 at hmb + change S ^ 2 ≤ 4 * B at hsquare + have lp {i j : ℕ} (hij : i ≤ j) : L ^ i ≤ L ^ j := Nat.pow_le_pow_right hL hij + have hV : 1 ≤ V := by + have hp : 0 < V := by dsimp only [V]; positivity + omega + have hforest := forest_time_bound κ n + change (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V at hforest + have hprime : (m + 1) * (7 * m + 15) + 8 ≤ (60 * C ^ 2 + 8) * V := + prime_time_bound B S C L m (by omega) hS hL hmb hsquare + have hfront : Frontend.cost κ n ≤ Frontend.costCoefficient κ * V := frontend_time_bound κ n + have hheight : Cslib.Algorithms.ThreeSum.ChanHe.height n ≤ Nat.log 2 n + 2 := + Nat.clog_le_of_le_pow Nat.lt_two_pow_self.le + have hF : 4 * F + 45 ≤ 69 * V := by + have hLV : L ≤ V := by + have hh := lp (show 1 ≤ 13 by decide) + simp only [pow_one] at hh + calc L ≤ L ^ 13 := hh + _ ≤ B * S * L ^ 13 := Nat.le_mul_of_pos_left _ (by positivity) + have hsmall : F ≤ 6 * L := by + change 3 * Cslib.Algorithms.ThreeSum.ChanHe.height n - 2 ≤ _ + dsimp only [L] + omega + have hh : 4 * F + 45 ≤ 69 * L := by nlinarith only [hsmall, hL] + exact hh.trans (Nat.mul_le_mul_left 69 hLV) + change Frontend.cost κ n + (m + 1) * (7 * m + 15) + 8 + 4 * F + 45 + + (Frontend.layout κ n).count * (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) ≤ _ + rw [show costCoefficient κ * (n + 2) * (Nat.sqrt (n + 2) + 1) * + (Nat.log 2 (n + 2) + 1) ^ 13 = costCoefficient κ * V by dsimp only [V, B, S, L]; ring] + change _ ≤ (Frontend.costCoefficient κ + 60 * C ^ 2 + 77 + + 4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V + calc _ = Frontend.cost κ n + ((m + 1) * (7 * m + 15) + 8) + + (4 * F + 45) + (Frontend.layout κ n).count * + (Traversal.rootTime F (n + 1) m (2 * m ^ 2) + 17) := by omega + _ ≤ Frontend.costCoefficient κ * V + (60 * C ^ 2 + 8) * V + 69 * V + + (4 * (κ + 3) ^ 2 * (rootCoefficient κ + 17)) * V := + Nat.add_le_add (Nat.add_le_add (Nat.add_le_add hfront hprime) hF) hforest + _ = _ := by ring + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean new file mode 100644 index 0000000000..1c9b786b00 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/PreprocessingWidth.lean @@ -0,0 +1,139 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingBounds + +/-! +# Derived logarithmic width for native Chan–He preprocessing + +The positive physical-width threshold depends only on the fixed magnitude exponent and actual +input length. Its signed envelope covers every root, including all shared memory reservations. + +## Main definitions + +* `width`: positive physical width sufficient for the complete preprocessing envelope. + +## Main results + +* `arena_ready`: global logarithmic width derives the complete traversal fit contract. +* `input_fits`: the same width derives actual signed-encoded input fitting. + +These coarse native resource bounds are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +open Cslib.WordRAM + +/-- One positive logarithmic physical width for every native preprocessing component. -/ +def width (κ n : ℕ) : ℕ := 65 * Parameters.width κ n + +/-- The threshold has a positive exponent-dependent coefficient and no dependence on input data. -/ +theorem width_eq (κ n : ℕ) : + width κ n = (1300 * κ + 7865) * Nat.clog 2 (n + 2) := by + dsimp only [width, Parameters.width] + ring + +/-- Empty inputs and exponent zero also receive a strictly positive word width. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := Parameters.width_pos κ n + dsimp only [width] + omega + +/-- The signed compiler's two representation bits are always available. -/ +theorem three_le_width (κ n : ℕ) : 3 ≤ width κ n := by + have hL : 1 ≤ Nat.clog 2 (n + 2) := Nat.clog_pos (by decide) (by omega) + rw [width_eq] + nlinarith + +/-- The whole-frontend threshold is covered by the global preprocessing threshold. -/ +theorem frontend_width_le (κ n : ℕ) : Frontend.width κ n ≤ width κ n := by + dsimp only [Frontend.width, width] + omega + +/-- The global logarithmic width derives all local signed kernel fits and allocation bounds. -/ +theorem arena_ready {κ n w : ℕ} (hw : width κ n ≤ w) (retained : ℕ → ℕ) : + (arena κ n retained).Ready (w - 2) := by + let P := Parameters.Math.budget n ((n + 2) ^ κ) + let p := Parameters.width κ n + let a := arena κ n retained + have hL : 1 ≤ Nat.clog 2 (n + 2) := Nat.clog_pos (by decide) (by omega) + have hp : 3 ≤ p := by dsimp only [p, Parameters.width]; nlinarith + have hwide : 65 * p ≤ w := hw + have hP : P < 2 ^ p := Parameters.budget_fits le_rfl + have hpow : P ^ 52 < 2 ^ (52 * p) := by + have hh := Nat.pow_lt_pow_left hP (by decide : (52 : ℕ) ≠ 0) + simpa only [← pow_mul, Nat.mul_comm] using hh + have henv := arena_envelope_le κ n retained + change Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N ≤ P ^ 52 at henv + have hfit : Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N < 2 ^ (w - 3) := + (henv.trans_lt hpow).trans_le (Nat.pow_le_pow_right (by decide) (by omega)) + have hfitZ : ((Traversal.envelope a.ceiling a.n a.m a.Λ a.U a.N : ℕ) : ℤ) < + (2 : ℤ) ^ (w - 3) := by exact_mod_cast hfit + refine ⟨?_, le_rfl, ?_⟩ + · change 2 ≤ Cslib.Algorithms.ThreeSum.ChanHe.mPar n (2 * (n + 2) ^ κ) + dsimp only [Cslib.Algorithms.ThreeSum.ChanHe.mPar] + nlinarith + · refine ⟨by omega, ?_, ?_⟩ + all_goals rw [show w - 2 - 1 = w - 3 by omega] + all_goals omega + +/-- The actual native unsigned length header and every signed payload fit the derived width. -/ +theorem input_fits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := + Frontend.input_fits input ((frontend_width_le κ input.length).trans hw) hvalues + +/-- The same global width covers every scalar and address of the native layout controller. -/ +theorem setup_fits {κ n w : ℕ} (hw : width κ n ≤ w) (retained : ℕ → ℕ) : + let a := arena κ n retained + Fits w (a.floor + a.U + a.N + a.fuel + 16) ∧ + Fits w ((Frontend.layout κ n).endpoint + a.m + 4) ∧ + Fits w (3 ^ (a.fuel + 1)) ∧ Fits w 6 := by + let a := arena κ n retained + change Fits w (a.floor + a.U + a.N + a.fuel + 16) ∧ + Fits w ((Frontend.layout κ n).endpoint + a.m + 4) ∧ + Fits w (3 ^ (a.fuel + 1)) ∧ Fits w 6 + let S := a.ceiling + a.n + a.m ^ 2 + a.Λ + a.m + a.U + a.N + 100 + have hS : 1 ≤ S := by dsimp only [S]; omega + have hs : S ≤ S ^ 4 := Nat.le_self_pow (by decide) S + have hf : a.fuel ≤ a.floor := by dsimp only [a, arena]; omega + have hfloor : a.floor ≤ a.ceiling := by dsimp only [a, arena]; omega + have hfront : (Frontend.layout κ n).endpoint ≤ a.ceiling := by + dsimp only [a, arena] + omega + have hready := arena_ready hw retained + have hword := Signed.unsigned_fits_of_signed hready.2.2 + have hw3 : 3 ≤ w := (three_le_width κ n).trans hw + rw [show w - 2 + 2 = w by omega] at hword + have cover {v : ℕ} (hv : v ≤ 3 * S) : Fits w v := hword.of_le (by + change v ≤ 65536 * S ^ 4 + omega) + refine ⟨cover (by dsimp only [S]; omega), cover (by dsimp only [S]; omega), ?_, + cover (by dsimp only [S]; omega)⟩ + have hD : 2 ≤ (Frontend.layout κ n).count := by + dsimp only [Frontend.Layout.count] + omega + have hdelta : 3 ≤ Traversal.allocationSize a.n a.m a.N := by + dsimp only [Traversal.allocationSize] + omega + have hmul := Nat.mul_le_mul hD + (Nat.mul_le_mul_left (Traversal.Math.work a.fuel) hdelta) + have hwork := Traversal.Math.work_pos a.fuel + have he := Traversal.Math.work_eq a.fuel + have hceil : (Frontend.layout κ n).count * + (Traversal.Math.work a.fuel * Traversal.allocationSize a.n a.m a.N) ≤ a.ceiling := by + exact Nat.le_add_left _ _ + apply cover + dsimp only [S] + nlinarith + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalAdmitted.lean new file mode 100644 index 0000000000..bfa4e752bb --- /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 := GenericRAM.Signed.Fits.of_natAbs_le hw.2.2 hz + have hunsigned := Signed.unsigned_fits_of_signed hw.2.2 + have hlen (j : Fin 3) : x.regs (arrayLength j) ≤ a.n := by + rw [hl.2.2] + exact hv.length_le j + have hsource (j : Fin 3) : x.regs (arraySource j) + x.regs (arrayLength j) ≤ a.ceiling := by + rw [hl.2.1, hl.2.2] + exact (hsrc j).trans hheap + have hbitmapBase : x.regs bitmap ≤ a.ceiling := by + rw [hb] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega + obtain ⟨y, he, hem⟩ := emit_entry x e hl (by simpa only [hU] using hv) hs + (by rw [hm]; exact hw.1) + (by apply hbound; simp only [hm, Int.natAbs_natCast]; omega) + (hunsigned.of_le (modulus_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N hheap + hsource hlen hm.le hΛ.le + (by rw [hcard]; exact Nat.card_primesLE_le a.m) hbitmapBase)) + (by + intro M _ hM + apply hbound + simp only [Int.natAbs_natCast] + exact node_budget_le_envelope x a.ceiling a.n a.m a.Λ a.U a.N M hheap hlen + (by simpa only [hm] using hM) hU.le hN.le) + (by rw [hm, hN]; exact hw.2.1) + (by rw [hm, hcard]) + (by + rw [hb, hm] + have h₀ := ha.1 + have h₁ := ha.2.1 + have h₂ := ha.2.2.1 + omega) + (by simpa only [Arena.Bitmap, hm, hb] using hbitmap) + (by + intro j i hi + apply hbound + exact (hv.natAbs_le j i hi).trans (by omega)) + hsrc hone htable + exact ⟨y, by simpa only [hm, hN] using he, by simpa only [hm, hΛ] using hem⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean new file mode 100644 index 0000000000..6d4b27dbff --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCall.lean @@ -0,0 +1,271 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram +import Complexity.Algorithms.ThreeSum.ChanHe.SelectorBounds + +/-! +# Actual native argument delivery in the Chan–He traversal + +The current record and persistent parameters are delivered to the existing complete modulus +and node programs in registers 0–63. Every copy and histogram-base addition is a native +instruction. The record, controller parameters and native word-stack registers survive setup. + +## Main definitions + +* `modulusState`, `nodeState`: exact states after the two native argument blocks. + +## Main results + +* `prepareModulus_exec`: fourteen charged instructions, including all histogram-base additions. +* `prepareNode_exec`: ten charged native register copies. +* `modulusState_preserves`, `nodeState_preserves`: all registers above the kernel survive. + +These calling-convention proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The exact state after the fourteen-instruction modulus argument block. -/ +def modulusState (x : Data 128) : Data 128 := + (((((((((((((x.set (kernel (CountCall.source 0)) (x.regs (slot 1))).set + (kernel (CountCall.length 0)) (x.regs (slot 2))).set + (kernel (CountCall.source 1)) (x.regs (slot 3))).set + (kernel (CountCall.length 1)) (x.regs (slot 4))).set + (kernel (CountCall.source 2)) (x.regs (slot 5))).set + (kernel (CountCall.length 2)) (x.regs (slot 6))).set + (kernel CountCall.scratch) (x.regs heap)).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2))).set + (kernel CountCall.histogram) (x.regs heap + x.regs (slot 2) + x.regs (slot 4))).set + (kernel CountCall.histogram) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))).set + (kernel Search.limit) (x.regs limit)).set (kernel Search.primes) (x.regs bitmap)).set + (kernel CollisionTest.card) (x.regs primeCount)).set (kernel Modulus.digits) (x.regs digits) + +/-- The exact state after the ten-instruction node argument block. -/ +def nodeState (x : Data 128) : Data 128 := + (((((((((x.set (kernel (NodePacking.source 0)) (x.regs (slot 1))).set + (kernel (NodePacking.length 0)) (x.regs (slot 2))).set + (kernel (NodePacking.source 1)) (x.regs (slot 3))).set + (kernel (NodePacking.length 1)) (x.regs (slot 4))).set + (kernel (NodePacking.source 2)) (x.regs (slot 5))).set + (kernel (NodePacking.length 2)) (x.regs (slot 6))).set + (kernel NodePacking.base) (x.regs heap)).set + (kernel NodePacking.groups) (x.regs groups)).set + (kernel NodePacking.magnitude) (x.regs magnitude)).set + (kernel NodePacking.modulus) (x.regs selected) + +/-- The existing node layout instantiated by the traversal record and one computed modulus. -/ +def recordLayout (x : Data 128) (M : ℕ) : NodePacking.Layout := + ⟨x.regs heap, fun j => x.regs (arrayLength j), x.regs groups, M, x.regs magnitude⟩ + +/-- The delivered node ABI has exactly the current record's layout. -/ +theorem nodeState_layout (x : Data 128) : + NodePacking.layout ((nodeState x).comap kernel) = recordLayout x (x.regs selected) := by + unfold NodePacking.layout recordLayout + congr 1 + funext j + fin_cases j <;> rfl + +/-- The layout reads only persistent record/controller fields outside the selected-modulus word. -/ +theorem recordLayout_congr (x y : Data 128) (M : ℕ) + (h : ∀ i, 64 ≤ i.val → i ≠ selected → y.regs i = x.regs i) : + recordLayout y M = recordLayout x M := by + unfold recordLayout + congr 1 + · exact h heap (by decide) (by decide) + · funext j + fin_cases j <;> exact h _ (by decide) (by decide) + · exact h groups (by decide) (by decide) + · exact h magnitude (by decide) (by decide) + +/-- The final endpoint formula expressed entirely in the persistent traversal ABI. -/ +theorem recordLayout_endpoint (x : Data 128) (M : ℕ) : + (recordLayout x M).endpoint = x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + 4 * x.regs groups + M := by + rw [NodePacking.Layout.endpoint_eq] + rfl + +private theorem kernel_ne {i : Fin 128} (hi : 64 ≤ i.val) (j : Fin 64) : i ≠ kernel j := by + intro h + have := congrArg Fin.val h + dsimp [kernel] at this + omega + +private theorem set_kernel_regs (i : Fin 128) (hi : 64 ≤ i.val) + (x : Data 128) (j : Fin 64) (v : ℕ) : + (x.set (kernel j) v).regs i = x.regs i := Function.update_of_ne (kernel_ne hi j) .. + +/-- Modulus argument delivery preserves every traversal and stack register. -/ +theorem modulusState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (modulusState x).regs i = x.regs i := by + simp only [modulusState, set_kernel_regs i hi] + +/-- Node argument delivery preserves every traversal and stack register. -/ +theorem nodeState_preserves (x : Data 128) (i : Fin 128) (hi : 64 ≤ i.val) : + (nodeState x).regs i = x.regs i := by + simp only [nodeState, set_kernel_regs i hi] + +/-- Native modulus argument delivery charges the three histogram-base additions too. +A fit for their largest value derives all intermediate address fits. -/ +theorem prepareModulus_exec {w : ℕ} (x : Data 128) + (h : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6))) : + Exec w prepareModulus x (modulusState x) 14 := by + have h₀ : Fits w (x.regs heap + x.regs (slot 2)) := h.of_le (by omega) + have h₁ : Fits w (x.regs heap + x.regs (slot 2) + x.regs (slot 4)) := h.of_le (by omega) + dsimp only [heap, slot] at h₀ h₁ h + ram_eval [prepareModulus, modulusState, kernel, slot, CountCall.source, CountCall.length, + CountCall.scratch, CountCall.histogram, Search.limit, Search.primes, CollisionTest.card, + Modulus.digits, heap, limit, bitmap, primeCount, digits, h₀, h₁, h] + +/-- Complete node argument delivery is ten native copies; no address arithmetic is hidden. -/ +theorem prepareNode_exec {w : ℕ} (x : Data 128) : + Exec w prepareNode x (nodeState x) 10 := by + ram_eval [prepareNode, nodeState, kernel, slot, NodePacking.source, NodePacking.length, + NodePacking.base, NodePacking.groups, NodePacking.magnitude, NodePacking.modulus, + heap, groups, magnitude, selected] + +private theorem kernel_injective : Function.Injective kernel := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 128 => i.val) h) + +private theorem extend_kernel_regs (y : Data 64) (x : Data 128) (i : Fin 128) + (hi : 64 ≤ i.val) : (y.extend kernel x).regs i = x.regs i := by + change Function.extend kernel y.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact kernel_ne hi j hj.symm + +/-- Actual argument delivery and complete two-prime computation inside the traversal's fixed +register file. The mathematical filters may fail; their default-one selections are still bounded. +The bitmap/count are the outputs of the separate native prime-table routine. -/ +theorem callModulus_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < x.regs (arrayLength j) → GenericRAM.Signed.Fits w (values j i)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + ∃ y, ExecWithin (w + 2) (.seq prepareModulus (Modulus.program.rename kernel)) x y + (14 + Modulus.cost ((modulusState x).comap kernel)) ∧ + (∀ i, 64 ≤ i.val → y.regs i = x.regs i) ∧ + (∀ a < x.regs heap, y.mem a = x.mem a) ∧ + y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) ∧ + 1 ≤ y.regs (kernel Modulus.output) ∧ y.regs (kernel Modulus.output) ≤ x.regs limit ^ 2 := by + let a := modulusState x + let z := a.comap kernel + have hn (j : Fin 3) : z.regs (CountCall.length j) = x.regs (arrayLength j) := by + fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (CountCall.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hzm : z.regs Search.limit = x.regs limit := rfl + have hzd : z.regs Modulus.digits = x.regs digits := rfl + have hhist : Fits (w + 2) + (x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := by + change Fits (w + 2) (z.regs CountCall.histogram) + have hbz : Fits (w + 2) (Modulus.budget z) := hbudget + apply hbz.of_le + dsimp only [Modulus.budget] + omega + have ha := prepareModulus_exec x hhist + obtain ⟨b, hb, _, hprefix, _, hout⟩ := Modulus.exec z values hm hmod hbudget hcard hbefore + hbitmap + (by intro j i hi; exact hvalues j i ((hn j) ▸ hi)) + (by intro j; change z.regs (CountCall.source j) + z.regs (CountCall.length j) ≤ x.regs heap + rw [hs j, hn j]; exact hsrc j) + (by + intro j + change x.regs heap + z.regs (CountCall.length j) ≤ + x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + rw [hn j] + fin_cases j + · change x.regs heap + x.regs (slot 2) ≤ _ + omega + · change x.regs heap + x.regs (slot 4) ≤ _ + omega + · change x.regs heap + x.regs (slot 6) ≤ _ + omega) + (by intro j i hi; change x.mem (z.regs (CountCall.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) + (by intro j; simpa only [hn j] using hinj j) + let y := b.extend kernel a + have hout' : y.regs (kernel Modulus.output) = Cslib.Algorithms.ThreeSum.ChanHe.modulus + (Nat.primesLE (x.regs limit)) (x.regs digits) + ((Finset.range (x.regs (arrayLength 0))).image (values 0)) + ((Finset.range (x.regs (arrayLength 1))).image (values 1)) + ((Finset.range (x.regs (arrayLength 2))).image (values 2)) := by + change Function.extend kernel b.regs a.regs (kernel Modulus.output) = _ + rw [kernel_injective.extend_apply] + simpa only [hn, hzm, hzd] using hout + refine ⟨y, ha.within.seq (hb.rename kernel_injective), ?_, ?_, hout', ?_⟩ + · intro i hi + exact (extend_kernel_regs b a i hi).trans (modulusState_preserves x i hi) + · intro p hp + exact hprefix p hp + · rw [hout'] + exact Cslib.Algorithms.ThreeSum.ChanHe.modulus_bounds_of_candidates (by omega) + (fun q hq => (Nat.prime_of_mem_primesLE hq).two_le.trans' (by decide)) + (fun _ hq => Nat.le_of_mem_primesLE hq) _ _ _ _ + +/-- Ten actual argument copies followed by the complete native heavy/packed-node program. +The existing exact semantic output and the full allocation frame survive register renaming. -/ +theorem callNode_exec {w : ℕ} (x : Data 128) (values : Fin 3 → ℕ → ℤ) + (hM : 0 < x.regs selected) (hN : 2 * x.regs selected ≤ x.regs groups) + (hwidth : GenericRAM.Signed.Fits w + ((NodePacking.layout ((nodeState x).comap kernel)).budget : ℤ)) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ x.regs heap) + (hbdd : ∀ j i, i < x.regs (arrayLength j) → (values j i).natAbs ≤ x.regs magnitude) + (hinput : ∀ j i, i < x.regs (arrayLength j) → + x.mem (x.regs (arraySource j) + i) = Signed.encode (values j i)) + (hinj : ∀ j, Set.InjOn (values j) + (↑(Finset.range (x.regs (arrayLength j))) : Set ℕ)) : + let L := NodePacking.layout ((nodeState x).comap kernel) + ∃ y, ExecWithin (w + 2) (.seq prepareNode (NodePacking.program.rename kernel)) x y + (10 + 66 * L.total + 15 * L.M + 12 * L.N + 228) ∧ + NodePacking.Output L values x.mem y.mem ∧ + ∀ i, 64 ≤ i.val → y.regs i = x.regs i := by + let a := nodeState x + let z := a.comap kernel + let L := NodePacking.layout z + have hn (j : Fin 3) : L.n j = x.regs (arrayLength j) := by fin_cases j <;> rfl + have hs (j : Fin 3) : z.regs (NodePacking.source j) = x.regs (arraySource j) := by + fin_cases j <;> rfl + have hinj' (j : Fin 3) : Set.InjOn (values j) (↑(Finset.range (L.n j)) : Set ℕ) := by + simpa only [hn] using hinj j + obtain ⟨b, hb, hmem, _⟩ := NodePacking.exec z values hM hN hwidth + (by intro j; rw [hs j]; change x.regs (arraySource j) + L.n j ≤ x.regs heap + rw [hn j]; exact hsrc j) + (by intro j i hi; exact hbdd j i ((hn j) ▸ hi)) + (by intro j i hi; change x.mem (z.regs (NodePacking.source j) + i) = _ + rw [hs j]; exact hinput j i ((hn j) ▸ hi)) hinj' + refine ⟨b.extend kernel a, ?_, ?_, ?_⟩ + · simpa only [Nat.add_assoc] using + (prepareNode_exec (w := w + 2) x).within.seq (hb.rename kernel_injective) + · change NodePacking.Output L values z.mem b.mem + rw [hmem] + exact NodePacking.memory_output L values z.mem hM hN hinj' + · intro i hi + exact (extend_kernel_regs b a i hi).trans (nodeState_preserves x i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean new file mode 100644 index 0000000000..4773b8adfe --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalChildren.lean @@ -0,0 +1,166 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalProgram + +/-! +# Native preparation of the three Chan–He child records + +The third child is pushed first. Original descriptors are retained above the stack dispatcher +and restored before replacing another column. All descriptor moves and the fuel decrement are +native instructions; the same finite code is used at every recursion level. + +## Main definitions + +* `thirdState`, `secondState`, `firstState`: exact states after the native child-argument blocks. +* `childWords`: the seven actual words of one child record, in its pop order. + +## Main results + +* `prepareThird_exec`, `prepareSecond_exec`, `prepareFirst_exec`: charged native argument blocks. + +The native stack controller and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Save both descriptors needed for restoration and prepare the third child. -/ +def thirdState (x : Data 128) : Data 128 := + ((((((x.set (slot 0) (x.regs (slot 0) - 1)).set (original 0) (x.regs (slot 3))).set + (original 1) (x.regs (slot 4))).set (original 2) (x.regs (slot 5))).set + (original 3) (x.regs (slot 6))).set (slot 5) (x.regs (heavyBase 2))).set + (slot 6) (x.regs (heavyLength 2)) + +/-- Restore the third original column and prepare the second child. -/ +def secondState (x : Data 128) : Data 128 := + (((x.set (slot 5) (x.regs (original 2))).set (slot 6) (x.regs (original 3))).set + (slot 3) (x.regs (heavyBase 1))).set (slot 4) (x.regs (heavyLength 1)) + +/-- Restore the second original column and prepare the first child. -/ +def firstState (x : Data 128) : Data 128 := + (((x.set (slot 3) (x.regs (original 0))).set (slot 4) (x.regs (original 1))).set + (slot 1) (x.regs (heavyBase 0))).set (slot 2) (x.regs (heavyLength 0)) + +/-- Exact child-record serialization. Only the selected column is replaced by its heavy output. -/ +def childWords (x : Data 128) (j : Fin 3) : List ℕ := + [x.regs (slot 0) - 1, + if j = 0 then x.regs (heavyBase 0) else x.regs (slot 1), + if j = 0 then x.regs (heavyLength 0) else x.regs (slot 2), + if j = 1 then x.regs (heavyBase 1) else x.regs (slot 3), + if j = 1 then x.regs (heavyLength 1) else x.regs (slot 4), + if j = 2 then x.regs (heavyBase 2) else x.regs (slot 5), + if j = 2 then x.regs (heavyLength 2) else x.regs (slot 6)] + +/-- Every child occupies exactly one seven-word native record. -/ +theorem length_childWords (x : Data 128) (j : Fin 3) : (childWords x j).length = 7 := rfl + +/-- The fuel subtraction and all six descriptor copies cost seven native instructions. -/ +theorem prepareThird_exec {w : ℕ} (x : Data 128) (hx : x.Bounded w) (hone : x.regs one = 1) : + Exec w prepareThird x (thirdState x) 7 := by + have hfit : Fits w (x.regs (slot 0) - 1) := (hx.1 (slot 0)).of_le (Nat.sub_le _ _) + dsimp only [slot, one] at hfit hone + ram_eval [prepareThird, thirdState, slot, one, original, heavyBase, heavyLength, hone, hfit] + +/-- Restoring the third column and replacing the second costs four native copies. -/ +theorem prepareSecond_exec {w : ℕ} (x : Data 128) : + Exec w prepareSecond x (secondState x) 4 := by + ram_eval [prepareSecond, secondState, slot, original, heavyBase, heavyLength] + +/-- Restoring the second column and replacing the first costs four native copies. -/ +theorem prepareFirst_exec {w : ℕ} (x : Data 128) : + Exec w prepareFirst x (firstState x) 4 := by + ram_eval [prepareFirst, firstState, slot, original, heavyBase, heavyLength] + +private theorem thirdState_comap (x : Data 128) : + (thirdState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [thirdState, Data.comap, Data.set, slot, original, registers] + · rfl + +private theorem secondState_comap (x : Data 128) : + (secondState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [secondState, Data.comap, Data.set, slot, registers] + · rfl + +private theorem firstState_comap (x : Data 128) : + (firstState x).comap registers = x.comap registers := by + ext i + · fin_cases i <;> simp +decide [firstState, Data.comap, Data.set, slot, registers] + · rfl + +/-- Child pushes preserve every persistent controller parameter outside the record and stack. -/ +theorem pushChildren_preserves : ∀ i : Fin 128, + ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → pushChildren.Preserves i := by decide + +private def words (x : Data 128) : List ℕ := + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] + +/-- All three child records are pushed by native instructions in reverse order. The resulting +top-first stack contains first, second, third, followed by the original pending suffix. -/ +theorem pushChildren_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (hx : x.Bounded w) (hone : x.regs one = 1) (h2 : Fits w 2) + (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hcapacity : 21 + vs.length ≤ capacity) : + ∃ y, Exec w pushChildren x y 207 ∧ + Stack.Rep w base capacity (childWords x 0 ++ childWords x 1 ++ childWords x 2 ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ i, ((71 ≤ i.val ∧ i.val < 96) ∨ 109 ≤ i.val) → y.regs i = x.regs i := by + let a := thirdState x + have ha := prepareThird_exec x hx hone + have hra : Stack.Rep w base capacity vs (a.comap registers) := by + simpa only [a, thirdState_comap] using hr + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a vs h2 hcap hra + (fun i => (ha.bounded hx).1 (slot i)) (by omega) + have hwa : words a = childWords x 2 := by + simp +decide [words, a, thirdState, childWords, Data.set, slot, original, + heavyBase, heavyLength] + change Stack.Rep w base capacity (words a ++ vs) (b.comap registers) at hrb + rw [hwa] at hrb + let c := secondState b + have hc := prepareSecond_exec (w := w) b + have hrc : Stack.Rep w base capacity (childWords x 2 ++ vs) (c.comap registers) := by + simpa only [c, secondState_comap] using hrb + obtain ⟨d, hd, hrd, hfd, hpd⟩ := pushFrame_exec c (childWords x 2 ++ vs) h2 hcap hrc + (fun i => (hc.bounded (hb.bounded (ha.bounded hx))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwc : words c = childWords x 1 := by + simp +decide [words, c, secondState, hpb, Outside, a, thirdState, childWords, Data.set, + slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words c ++ (childWords x 2 ++ vs)) (d.comap registers) at hrd + rw [hwc] at hrd + let e := firstState d + have he := prepareFirst_exec (w := w) d + have hre : Stack.Rep w base capacity (childWords x 1 ++ (childWords x 2 ++ vs)) + (e.comap registers) := by + simpa only [e, firstState_comap] using hrd + obtain ⟨z, hz, hrz, hfz, _⟩ := pushFrame_exec e (childWords x 1 ++ (childWords x 2 ++ vs)) + h2 hcap hre + (fun i => (he.bounded (hd.bounded (hc.bounded (hb.bounded (ha.bounded hx))))).1 (slot i)) + (by simp only [List.length_append, length_childWords]; omega) + have hwe : words e = childWords x 0 := by + simp +decide [words, e, firstState, hpd, Outside, c, secondState, hpb, a, thirdState, + childWords, Data.set, slot, original, heavyBase, heavyLength] + change Stack.Rep w base capacity (words e ++ (childWords x 1 ++ (childWords x 2 ++ vs))) + (z.comap registers) at hrz + rw [hwe] at hrz + have hrun : Exec w pushChildren x z 207 := ha.seq (hb.seq (hc.seq (hd.seq (he.seq hz)))) + refine ⟨z, hrun, ?_, ?_, fun i hi => hrun.regs_eq (pushChildren_preserves i hi)⟩ + · simpa only [List.append_assoc] using hrz + · intro p hp + exact (hfz p hp).trans ((hfd p hp).trans (hfb p hp)) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalCost.lean new file mode 100644 index 0000000000..14d50fa184 --- /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 `card*m + m³`. -/ +theorem rootTime_fuel_square_le (n card m : ℕ) : + rootTime (fuel n) card m (2 * m ^ 2) ≤ + 70 + 6144 * (2 * Nat.log 2 n + 2) ^ 5 * + ((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 0000000000..7f0fc7f323 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalDeliver.lean @@ -0,0 +1,245 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall + +/-! +# Charged native output delivery for one traversal node + +The three actual heavy counts are read from the node allocation. The packed pointer is appended +to the reserved output table and the fresh heap advances past all node output and scratch cells. +Address computation is repeated explicitly instead of assuming scratch-register outputs that +the node builder's public contract does not export. + +## Main definitions + +* `deliveryState`: exact state after all 24 native delivery instructions. + +## Main results + +* `deliver_exec`: charged output delivery with derived address and counter fits. + +The layout and native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- All arithmetic results of the native delivery block, including its sole memory store. -/ +def deliveryState (x : Data 128) : Data 128 := + let b := x.regs heap + let n₀ := x.regs (slot 2) + let n₁ := x.regs (slot 4) + let n₂ := x.regs (slot 6) + let N := x.regs groups + let M := x.regs selected + let p := b + 3 + n₀ + n₁ + n₂ + let o := x.regs outputs + x.regs outputCount + let d₁ := b + 1 + n₀ + let d₂ := d₁ + 1 + n₁ + let a := ((((x.set packed 3).set packed (b + 3)).set packed (b + 3 + n₀)).set + packed (b + 3 + n₀ + n₁)).set packed p + let a := (((a.set temporary o).write o p).set outputCount (x.regs outputCount + 1)).set + (heavyLength 0) (x.mem b) + let a := (((a.set (heavyBase 0) (b + 1)).set temporary d₁).set + (heavyLength 1) (x.mem d₁)).set (heavyBase 1) (d₁ + 1) + let a := (((a.set temporary d₂).set (heavyLength 2) (x.mem d₂)).set + (heavyBase 2) (d₂ + 1)).set endpoint (N + N) + let a := ((((((a.set endpoint (N + N + (N + N))).set endpoint (p + 4 * N)).set + endpoint (p + 4 * N + n₀)).set endpoint (p + 4 * N + n₀ + n₁)).set + endpoint (p + 4 * N + n₀ + n₁ + n₂)).set endpoint (p + 4 * N + n₀ + n₁ + n₂ + M)).set + heap (p + 4 * N + n₀ + n₁ + n₂ + M) + a + +/-- Delivery writes exactly one output-pointer cell; all other memory retains the node output. -/ +theorem deliveryState_mem (x : Data 128) : (deliveryState x).mem = + Function.update x.mem (x.regs outputs + x.regs outputCount) + (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) := rfl + +/-- The appended pointer is the existing node layout's packed base. -/ +theorem deliveryState_packed (x : Data 128) : (deliveryState x).regs packed = + (NodePacking.layout ((nodeState x).comap kernel)).packed := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + omega + +/-- All allocated node output, residues and histogram lie below the new fresh heap. -/ +theorem deliveryState_heap (x : Data 128) : (deliveryState x).regs heap = + (NodePacking.layout ((nodeState x).comap kernel)).endpoint := by + change x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + x.regs selected = + x.regs heap + 3 + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + x.regs selected + omega + +/-- Exactly one node is appended to the output table. -/ +theorem deliveryState_count (x : Data 128) : + (deliveryState x).regs outputCount = x.regs outputCount + 1 := rfl + +/-- Each delivered count was read from its actual count-prefixed heavy array. -/ +theorem deliveryState_heavy_length (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + x.mem ((NodePacking.layout ((nodeState x).comap kernel)).descriptor j) := by + fin_cases j + · rfl + · rfl + · change x.mem (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) = + x.mem (x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4))) + congr 1 + omega + +/-- Each delivered data pointer is the existing heavy-array base. -/ +theorem deliveryState_heavy_base (x : Data 128) (j : Fin 3) : + (deliveryState x).regs (heavyBase j) = + (NodePacking.layout ((nodeState x).comap kernel)).heavy j := by + fin_cases j + · rfl + · rfl + · change x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1 = + x.regs heap + 2 + (x.regs (slot 2) + x.regs (slot 4)) + 1 + omega + +/-- Output delivery preserves the node builder's exact heavy/packed readback and allocation +frame. The sole external output-pointer store is recorded explicitly in the before-memory. -/ +theorem delivery_output (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + let L := NodePacking.layout ((nodeState x).comap kernel) + NodePacking.Output L values + (Function.update before (x.regs outputs + x.regs outputCount) L.packed) + (deliveryState x).mem := by + let L := NodePacking.layout ((nodeState x).comap kernel) + let o := x.regs outputs + x.regs outputCount + change NodePacking.Output L values before x.mem at h + change NodePacking.Output L values (Function.update before o L.packed) (deliveryState x).mem + have hm := deliveryState_mem x + change (deliveryState x).mem = Function.update x.mem o ((deliveryState x).regs packed) at hm + rw [deliveryState_packed] at hm + have ho : o < L.base := htable + have hkeep (a : ℕ) (ha : L.base ≤ a) : (deliveryState x).mem a = x.mem a := by + rw [hm, Function.update_of_ne (show a ≠ o by omega)] + refine ⟨?_, ?_, ?_⟩ + · intro j + obtain ⟨zs, hnd, hs, hc, hd⟩ := h.heavy j + refine ⟨zs, hnd, hs, (hkeep _ (L.base_le_descriptor j)).trans hc, ?_⟩ + intro k hk + have hb := L.base_le_descriptor j + have hbase : L.base ≤ L.heavy j + k := by + dsimp only [NodePacking.Layout.heavy] + omega + exact (hkeep _ hbase).trans (hd k hk) + · intro a ha + have hbase : L.base ≤ L.packed + a := by + dsimp only [NodePacking.Layout.packed] + omega + exact (hkeep _ hbase).trans (h.packed a ha) + · intro a ha + rw [hm] + by_cases he : a = o + · subst a + simp only [Function.update_self] + rfl + · simp only [Function.update_of_ne he] + exact h.frame a ha + +/-- The delivered register is the existing heavy-set cardinality, not a capacity or pair count. -/ +theorem delivery_heavy_card (x : Data 128) (values : Fin 3 → ℕ → ℤ) (before : ℕ → ℕ) + (h : NodePacking.Output (NodePacking.layout ((nodeState x).comap kernel)) values + before x.mem) (j : Fin 3) : + (deliveryState x).regs (heavyLength j) = + (Cslib.Algorithms.ThreeSum.ChanHe.heavy + ((Finset.range ((NodePacking.layout ((nodeState x).comap kernel)).n j)).image (values j)) + (NodePacking.layout ((nodeState x).comap kernel)).M).card := by + rw [deliveryState_heavy_length] + exact h.heavy_card j + +/-- All non-output controller parameters and the entire stack workspace survive delivery. -/ +theorem deliver_preserves : ∀ i : Fin 128, i ≠ heap → i ≠ outputCount → i ≠ temporary → + (i.val < 88 ∨ 96 ≤ i.val) → deliver.Preserves i := by decide + +/-- The explicit delivered state retains every non-output controller and stack register. -/ +theorem deliveryState_preserves (x : Data 128) (i : Fin 128) + (hh : i ≠ heap) (hc : i ≠ outputCount) (ht : i ≠ temporary) + (hi : i.val < 88 ∨ 96 ≤ i.val) : (deliveryState x).regs i = x.regs i := by + have hp : i ≠ packed := by intro he; subst i; norm_num [packed] at hi + have he : i ≠ endpoint := by intro he; subst i; norm_num [endpoint] at hi + have hl (j : Fin 3) : i ≠ heavyLength j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyLength] at hv + have := j.isLt + omega + have hb (j : Fin 3) : i ≠ heavyBase j := by + intro hj + have hv := congrArg Fin.val hj + dsimp only [heavyBase] at hv + have := j.isLt + omega + simp only [deliveryState, Data.set_regs, Data.write_regs, Function.update_of_ne hh, + Function.update_of_ne hc, Function.update_of_ne ht, Function.update_of_ne hp, + Function.update_of_ne he, Function.update_of_ne (hl 0), Function.update_of_ne (hl 1), + Function.update_of_ne (hl 2), Function.update_of_ne (hb 0), Function.update_of_ne (hb 1), + Function.update_of_ne (hb 2)] + +/-- Every intermediate address and the output-count increment fit once the final endpoint fits. +The output table precedes the fresh allocation, so its store cannot change a heavy-count load. -/ +theorem deliver_exec {w : ℕ} (x : Data 128) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) + (hfit : Fits w (x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected)) : + Exec w deliver x (deliveryState x) 24 := by + have hbound (v : ℕ) (hv : v ≤ x.regs heap + 3 + 2 * + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 4 * x.regs groups + x.regs selected) : Fits w v := hfit.of_le hv + have h3 : Fits w 3 := hbound _ (by omega) + have hp₀ := hbound (x.regs heap + 3) (by omega) + have hp₁ := hbound (x.regs heap + 3 + x.regs (slot 2)) (by omega) + have hp₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have hp := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have ho := hbound (x.regs outputs + x.regs outputCount) (by omega) + have hc := hbound (x.regs outputCount + 1) (by omega) + have hd₀ := hbound (x.regs heap + 1) (by omega) + have hd₁ := hbound (x.regs heap + 1 + x.regs (slot 2)) (by omega) + have hd₁' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1) (by omega) + have hd₂ := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4)) (by omega) + have hd₂' := hbound (x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) + 1) (by omega) + have he₀ := hbound (x.regs groups + x.regs groups) (by omega) + have he₁ := hbound (4 * x.regs groups) (by omega) + have he₂ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups) (by omega) + have he₃ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2)) (by omega) + have he₄ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4)) (by omega) + have he₅ := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6)) (by omega) + have he := hbound (x.regs heap + 3 + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + 4 * x.regs groups + x.regs (slot 2) + x.regs (slot 4) + + x.regs (slot 6) + x.regs selected) (by omega) + have hne₀ : x.regs heap ≠ x.regs outputs + x.regs outputCount := by omega + have hne₁ : x.regs heap + 1 + x.regs (slot 2) ≠ + x.regs outputs + x.regs outputCount := by omega + have hne₂ : x.regs heap + 1 + x.regs (slot 2) + 1 + x.regs (slot 4) ≠ + x.regs outputs + x.regs outputCount := by omega + dsimp only [heap, slot, groups, selected, outputs, outputCount, one] at * + ram_eval [deliver, deliveryState, heavyBase, heavyLength, heap, slot, groups, selected, + outputs, outputCount, packed, temporary, endpoint, one, hone, h3, hp₀, hp₁, hp₂, hp, ho, + hc, hd₀, hd₁, hd₁', hd₂, hd₂', he₀, he₁, he₂, he₃, he₄, he₅, he, + hne₀, hne₁, hne₂, Function.update_of_ne, Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc, + show ∀ n : ℕ, n + n + (n + n) = 4 * n by omega] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean new file mode 100644 index 0000000000..d21e5ff860 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmission.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Exact emissions from the complete native traversal visit + +This packages the executed modulus and node calls into the worklist's semantic entry format. +The heavy lists are witnesses to actual native memory, not program inputs or advice. The old +prefix survives before the single output-pointer store, and every new packed cell equals the +existing finite node array. + +## Main definitions + +* `Emission`: exact metadata, old-prefix frame and heavy/packed readback of an actual live visit. + +## Main results + +* `emit_entry`: the complete native visit produces an exact emission with its charged bound. + +These native composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Semantic readback of one completed actual native visit. -/ +structure Emission (Q : Finset ℕ) (Λ : ℕ) (e : Entry) (x y : Data 128) where + /-- The actual selected unsigned modulus. -/ + M : ℕ + /-- Selection agrees with the existing finite node. -/ + exactModulus : M = (e.task.node Q Λ).M + /-- The native fallback is positive. -/ + positive : 1 ≤ M + /-- Both selected candidates lie within the supplied bound. -/ + upper : M ≤ x.regs limit ^ 2 + /-- Actual native compact heavy enumerations. -/ + lists : Fin 3 → List ℤ + /-- Each enumeration is duplicate-free. -/ + nodup : ∀ j, (lists j).Nodup + /-- The enumerated sets are exactly the existing heavy subsets. -/ + sets : ∀ j, (lists j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M + /-- A heavy enumeration never exceeds its parent column. -/ + length_le : ∀ j, (lists j).length ≤ (e.data j).length + /-- The delivered unsigned lengths describe these actual lists. -/ + lengths : ∀ j, y.regs (heavyLength j) = (lists j).length + /-- The delivered unsigned bases are the concrete heavy-array bases. -/ + bases : ∀ j, y.regs (heavyBase j) = (recordLayout x M).heavy j + /-- Exact signed-encoded heavy-array readback. -/ + heavy : ∀ j, (Block.mk ((recordLayout x M).heavy j) (lists j).length).Realizes y.mem + (fun i => Signed.encode (lists j)[i.val]) + /-- Exact signed-encoded packed-node readback. -/ + packed : (Block.mk (recordLayout x M).packed (4 * x.regs groups)).Realizes y.mem + (fun i => Signed.encode ((e.task.node Q Λ).oneArray (x.regs magnitude) i.val)) + /-- The actual count increment. -/ + count : y.regs outputCount = x.regs outputCount + 1 + /-- The actual advanced allocation frontier. -/ + heap_eq : y.regs heap = (recordLayout x M).endpoint + /-- The complete node output before its single external pointer-table store. -/ + before : ℕ → ℕ + /-- The two actual modulus searches and the node call preserve every old prefix cell. -/ + prefixMemory : ∀ p < x.regs heap, before p = x.mem p + /-- The sole pointer-table store is an actual native memory update. -/ + memory : y.mem = Function.update before (x.regs outputs + x.regs outputCount) + (recordLayout x M).packed + /-- The current record, persistent controller parameters and stack dispatcher survive. -/ + registers : ∀ i : Fin 128, 64 ≤ i.val → i ≠ selected → i ≠ heap → + i ≠ outputCount → i ≠ temporary → (i.val < 88 ∨ 96 ≤ i.val) → y.regs i = x.regs i + +/-- Execute both complete searches and the complete node construction for an actual loaded +entry. All heavy witnesses come from the executed memory and all control work is charged. -/ +theorem emit_entry {w n : ℕ} (x : Data 128) (e : Entry) (hl : e.Loaded x) + (hv : e.Valid n (x.regs magnitude)) (hs : e.Stored x.mem) + (hm : 2 ≤ x.regs limit) + (hmod : GenericRAM.Signed.Fits w ((x.regs limit ^ 2 : ℕ) : ℤ)) + (hbudget : Fits (w + 2) (Modulus.budget ((modulusState x).comap kernel))) + (hnode : ∀ M, 1 ≤ M → M ≤ x.regs limit ^ 2 → + GenericRAM.Signed.Fits w ((recordLayout x M).budget : ℤ)) + (hN : 2 * x.regs limit ^ 2 ≤ x.regs groups) + (hcard : x.regs primeCount = (Nat.primesLE (x.regs limit)).card) + (hbefore : x.regs bitmap + x.regs limit < x.regs heap) + (hbitmap : ∀ q ≤ x.regs limit, + x.mem (x.regs bitmap + q) = if q.Prime then 1 else 0) + (hvalues : ∀ j i, i < (e.data j).length → GenericRAM.Signed.Fits w (e.read j i)) + (hsrc : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) + (hone : x.regs one = 1) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + ∃ y, ExecWithin (w + 2) visit x y + (visitTime n (x.regs limit) (x.regs groups)) ∧ + Nonempty (Emission (Nat.primesLE (x.regs limit)) (x.regs digits) e x y) := by + classical + obtain ⟨M, u, b, he, hu, hus, hprefix, hout, hb, hMpos, hMle, hM⟩ := + visit_exec x e.read hm hmod hbudget hnode hN hcard hbefore hbitmap + (by simpa only [hl.2.2] using hvalues) + (by intro j i hi; exact hv.natAbs_le j i ((hl.2.2 j) ▸ hi)) + (by simpa only [hl.2.1, hl.2.2] using hsrc) + (by simpa only [hl.2.1, hl.2.2] using hs.input) + (by intro j; simpa only [hl.2.2] using e.read_injOn j (hv.1 j)) hone htable + have hbp (i : Fin 128) (hi : 64 ≤ i.val) (hne : i ≠ selected) : + b.regs i = x.regs i := (hb i hi).trans (hu i hi hne) + have hL : NodePacking.layout ((nodeState b).comap kernel) = recordLayout x M := by + rw [nodeState_layout, hb selected (by decide), hus] + exact recordLayout_congr x b M hbp + have htableb : b.regs outputs + b.regs outputCount < b.regs heap := by + rw [hbp outputs (by decide) (by decide), hbp outputCount (by decide) (by decide), + hbp heap (by decide) (by decide)] + exact htable + have houtb : NodePacking.Output (NodePacking.layout ((nodeState b).comap kernel)) + e.read u.mem b.mem := by simpa only [hL] using hout + have hd := delivery_output b e.read u.mem houtb htableb + rw [hL] at hd + have hMexact : M = (e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits)).M := by + simpa only [hl.2.2, e.image_read, Math.Task.node, Entry.task] using hM + have hnodeExact : NodePacking.node (recordLayout x M) e.read = + e.task.node (Nat.primesLE (x.regs limit)) (x.regs digits) := by + simp only [NodePacking.node, recordLayout, hl.2.2, e.image_read, Math.Task.node, + Entry.task] + exact congrArg (fun q => Cslib.Algorithms.ThreeSum.ChanHe.Node.mk + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset q) hMexact + choose zs hnd hsets hcounts hdata using hd.heavy + have hsets' (j : Fin 3) : (zs j).toFinset = + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets j) M := by + simpa only [recordLayout, hl.2.2, e.image_read, Entry.task] using hsets j + have hlen (j : Fin 3) : (zs j).length ≤ (e.data j).length := by + have hh := Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset (e.task.sets j) M + rw [← hsets' j] at hh + have hc := Finset.card_le_card hh + simpa only [Entry.task, List.toFinset_card_of_nodup (hnd j), + List.toFinset_card_of_nodup (hv.1 j)] using hc + refine ⟨deliveryState b, he.mono (visit_cost_le x n (x.regs limit) (x.regs groups) M + (by intro j; rw [hl.2.2]; exact hv.length_le j) le_rfl le_rfl hMle), ?_⟩ + refine ⟨⟨M, hMexact, hMpos, hMle, zs, hnd, hsets', hlen, ?_, ?_, ?_, ?_, ?_, ?_, b.mem, + ?_, ?_, ?_⟩⟩ + · intro j + rw [deliveryState_heavy_length, hL, hout.heavy_card j, ← hsets j] + exact List.toFinset_card_of_nodup (hnd j) + · intro j + rw [deliveryState_heavy_base, hL] + · intro j i + exact hdata j i.val i.isLt + · intro i + have hp := hd.packed i.val i.isLt + rw [hnodeExact] at hp + simpa only [Block.read, recordLayout] using hp + · rw [deliveryState_count, hbp outputCount (by decide) (by decide)] + · rw [deliveryState_heap, hL] + · intro p hp + exact (hout.frame p (Or.inl hp)).trans (hprefix p hp) + · rw [deliveryState_mem, hbp outputs (by decide) (by decide), + hbp outputCount (by decide) (by decide)] + congr 1 + exact (deliveryState_packed b).trans (congrArg NodePacking.Layout.packed hL) + · intro i hi hsel hh hc ht hw + exact (deliveryState_preserves b i hh hc ht hw).trans (hbp i hi hsel) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean new file mode 100644 index 0000000000..bdd66b224d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalEmissionFrame.lean @@ -0,0 +1,272 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmission + +/-! +# Retained memory and pending children after a native emission + +The actual pointer-table store is below the fresh heap and outside every live input block. +These frame lemmas retain old arrays and controller parameters, and identify the three actual +compact heavy buffers with the existing finite recursion's children. + +## Main results + +* `Emission.controls`, `Emission.loaded`: retained caller registers and current entry. +* `Emission.stored`: preservation of any live array around the reserved gap. +* `Emission.children_tasks`: the exact ordered mathematical worklist after child pushes. + +These memory-frame and representation proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- An actual emission preserves each old-prefix cell except its one explicit table store. -/ +theorem Emission.prefix_preserved {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (p : ℕ) (hp : p < x.regs heap) + (hne : p ≠ x.regs outputs + x.regs outputCount) : y.mem p = x.mem p := by + rw [h.memory, Function.update_of_ne hne] + exact h.prefixMemory p hp + +/-- All persistent caller parameters survive the actual complete visit. -/ +theorem Emission.controls {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) : a.Controls y := by + apply hc.congr + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + apply h.registers i <;> + first | omega | (intro he; have := congrArg Fin.val he; norm_num at this; omega) + +/-- The current root record survives the visit until the actual child-preparation instructions. -/ +theorem Emission.loaded {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : e.Loaded y := by + have hs (i : Fin 7) : y.regs (slot i) = x.regs (slot i) := by + apply h.registers <;> + first | (dsimp [slot]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [slot] at this; omega) + refine ⟨(hs 0).trans hl.1, ?_, ?_⟩ + · intro j + fin_cases j + · exact (hs 1).trans (hl.2.1 0) + · exact (hs 3).trans (hl.2.1 1) + · exact (hs 5).trans (hl.2.1 2) + · intro j + fin_cases j + · exact (hs 2).trans (hl.2.2 0) + · exact (hs 4).trans (hl.2.2 1) + · exact (hs 6).trans (hl.2.2 2) + +/-- Old input columns below the stack or above the output table cannot be overwritten by the +single native pointer store, and both actual kernels preserve the old prefix. -/ +theorem Emission.stored {Q : Finset ℕ} {Λ : ℕ} {e d : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hs : d.Stored x.mem) (hg : d.Around a.stack (a.floor - a.stack)) + (hend : ∀ j, d.source j + (d.data j).length ≤ x.regs heap) + (htable : x.regs outputs + x.regs outputCount < a.floor) : d.Stored y.mem := by + apply hs.congr + intro j i hi + apply h.prefix_preserved _ (by have := hend j; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hstack := ha.2.1 + have hfloor := ha.2.2.1 + rcases hg j with hj | hj <;> omega + +/-- Top-first native child entries with the three actual compact heavy buffers. -/ +def Emission.children {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : List Entry := + [e.child 0 ((recordLayout x h.M).heavy 0) (h.lists 0), + e.child 1 ((recordLayout x h.M).heavy 1) (h.lists 1), + e.child 2 ((recordLayout x h.M).heavy 2) (h.lists 2)] + +/-- Actual heavy-buffer readback represents exactly the existing ordered three-child recursion. -/ +theorem Emission.children_tasks {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : + h.children.map Entry.task = e.task.children Q Λ (e.fuel - 1) := by + simp only [Emission.children, List.map_cons, List.map_nil] + apply e.task_children Q Λ (recordLayout x h.M).heavy h.lists + intro j + rw [h.sets j, h.exactModulus] + +/-- Each of the actual three heavy-child arrays retains the existing admissibility promises. -/ +theorem Emission.children_valid {Q : Finset ℕ} {Λ n U : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hv : e.Valid n U) : ∀ d ∈ h.children, d.Valid n U := by + intro d hd + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Valid n U := by + apply hv.child j _ _ (h.nodup j) + rw [h.sets j] + exact Cslib.Algorithms.ThreeSum.ChanHe.heavy_subset _ _ + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- Each child reuses the two surviving original columns and one actual compact heavy buffer. -/ +theorem Emission.children_stored {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hs : e.Stored y.mem) : ∀ d ∈ h.children, d.Stored y.mem := by + intro d hd + have hc (j : Fin 3) := hs.child j ((recordLayout x h.M).heavy j) (h.lists j) (h.heavy j) + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The actual heap pointer advances past the old frontier. -/ +theorem Emission.heap_mono {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) : x.regs heap ≤ y.regs heap := by + rw [h.heap_eq, recordLayout_endpoint] + omega + +/-- Every actual child column lies below the advanced native allocation frontier. -/ +theorem Emission.children_ends {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) + (hend : ∀ j, e.source j + (e.data j).length ≤ x.regs heap) : + ∀ d ∈ h.children, ∀ k, d.source k + (d.data k).length ≤ y.regs heap := by + have hpacked : (recordLayout x h.M).packed ≤ (recordLayout x h.M).endpoint := by + dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, NodePacking.Layout.scratch] + omega + have heavyEnd (j : Fin 3) : + (recordLayout x h.M).heavy j + (h.lists j).length ≤ y.regs heap := by + rw [h.heap_eq] + calc (recordLayout x h.M).heavy j + (h.lists j).length + ≤ (recordLayout x h.M).heavy j + (recordLayout x h.M).n j := by + apply Nat.add_le_add_left + simpa only [recordLayout, hl.2.2] using h.length_le j + _ ≤ (recordLayout x h.M).packed := (recordLayout x h.M).heavy_end_le_packed j + _ ≤ (recordLayout x h.M).endpoint := hpacked + have hc (j k : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).source k + + ((e.child j ((recordLayout x h.M).heavy j) (h.lists j)).data k).length ≤ + y.regs heap := by + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using heavyEnd j + · simpa only [Entry.child, Function.update_of_ne he] using (hend k).trans h.heap_mono + intro d hd k + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 k + · exact hc 1 k + · exact hc 2 k + +/-- Every new heavy child begins above the reserved stack/table gap. -/ +theorem Emission.children_around {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {base capacity : ℕ} (hg : e.Around base capacity) + (hfloor : base + capacity ≤ x.regs heap) : + ∀ d ∈ h.children, d.Around base capacity := by + have hc (j : Fin 3) : + (e.child j ((recordLayout x h.M).heavy j) (h.lists j)).Around base capacity := by + apply hg.child + have hb := (recordLayout x h.M).base_le_descriptor j + change x.regs heap ≤ (recordLayout x h.M).descriptor j at hb + dsimp only [NodePacking.Layout.heavy] + omega + intro d hd + simp only [Emission.children, List.mem_cons, List.not_mem_nil, or_false] at hd + rcases hd with rfl | rfl | rfl + · exact hc 0 + · exact hc 1 + · exact hc 2 + +/-- The exact native words prepared for the three pushes are the new semantic child records. -/ +theorem Emission.children_words {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (hl : e.Loaded x) : + childWords y 0 ++ childWords y 1 ++ childWords y 2 = h.children.flatMap Entry.words := by + rw [Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 0, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 1, + Entry.child_words (h.loaded hl) _ _ h.bases h.lengths 2] + simp only [Emission.children, List.flatMap_cons, List.flatMap_nil, List.append_nil, + List.append_assoc] + +/-- The complete visit retains the actual pending stack, including its native pointer and +length registers; its sole external memory store lies above the stack reservation. -/ +theorem Emission.stack {Q : Finset ℕ} {Λ w : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (ha : a.Separated) (hc : a.Controls x) + (hy : y.Bounded w) (vs : List ℕ) + (hr : Stack.Rep w a.stack a.capacity vs (x.comap Traversal.registers)) + (htable : x.regs outputs + x.regs outputCount < x.regs heap) : + Stack.Rep w a.stack a.capacity vs (y.comap Traversal.registers) := by + have hregs (i : Fin 9) : y.regs (Traversal.registers i) = x.regs (Traversal.registers i) := by + apply h.registers <;> + first | (dsimp [Traversal.registers]; omega) | + (intro he; have := congrArg Fin.val he; dsimp [Traversal.registers] at this; omega) + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + refine ⟨⟨fun i => hy.1 (Traversal.registers i), hy.2⟩, (hregs Stack.ptr).trans hr.pointer, + (hregs Stack.len).trans hr.length_eq, hr.capacity_le, ?_⟩ + intro i + have hi : i.val < a.capacity := lt_of_lt_of_le i.isLt hr.capacity_le + change y.mem (a.stack + i.val) = _ + rw [h.prefix_preserved _ (by have := ha.2.1; omega) + (by have := ha.2.1; omega)] + exact hr.contents i + +/-- Append the actual newly emitted packed pointer and array to the retained output sequence. -/ +theorem Emission.output {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) {a : Arena} (hc : a.Controls x) + (ns : List (ℕ × Cslib.Algorithms.ThreeSum.ChanHe.Node)) + (hs : StoredNodes a.U a.N a.table ns x.mem) + (hcount : x.regs outputCount = ns.length) + (hbounds : ∀ i : Fin ns.length, + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ x.regs heap) + (htable : a.table + ns.length < a.floor) (hfloor : a.floor ≤ x.regs heap) : + StoredNodes a.U a.N a.table + (ns ++ [((recordLayout x h.M).packed, e.task.node Q Λ)]) y.mem := by + have hU : x.regs magnitude = a.U := hc.2.2.1 + have hN : x.regs groups = a.N := hc.2.2.2.1 + have ht : x.regs outputs = a.table := hc.2.2.2.2.2.2.1 + have hpacked : x.regs heap ≤ (recordLayout x h.M).packed := by + dsimp only [recordLayout, NodePacking.Layout.packed] + omega + have hbefore : StoredNodes a.U a.N a.table ns h.before := by + apply hs.congr + · intro p _ hp + exact h.prefixMemory p (by omega) + · intro i u hu + exact h.prefixMemory _ (by have := (hbounds i).2; omega) + have hnew : (Block.mk (recordLayout x h.M).packed (4 * a.N)).Realizes h.before + (fun i => Signed.encode ((e.task.node Q Λ).oneArray a.U i.val)) := by + intro i + have hi : i.val < 4 * x.regs groups := by simpa only [hN] using i.isLt + have hp := h.packed ⟨i.val, hi⟩ + change y.mem ((recordLayout x h.M).packed + i.val) = _ at hp + rw [h.memory, Function.update_of_ne (show + (recordLayout x h.M).packed + i.val ≠ x.regs outputs + x.regs outputCount by + rw [ht, hcount]; omega)] at hp + simpa only [Block.read, hU] using hp + rw [h.memory, ht, hcount] + exact hbefore.append hnew (by intro i; have := (hbounds i).1; omega) (by omega) + +/-- For a live entry, the actual heavy children and emitted node are the exact finite step. -/ +theorem Emission.math_step {Q : Finset ℕ} {Λ : ℕ} {e : Entry} {x y : Data 128} + (h : Emission Q Λ e x y) (es : List Entry) (hf : e.fuel ≠ 0) + (hn : ∀ j, (e.data j).length ≠ 0) : + Math.step Q Λ e.task (es.map Entry.task) = + ((h.children ++ es).map Entry.task, some (e.task.node Q Λ)) := by + have hempty : ¬e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + intro he + rcases he with he | he | he + · exact hn 0 (by rw [he]; rfl) + · exact hn 1 (by rw [he]; rfl) + · exact hn 2 (by rw [he]; rfl) + rw [List.map_append, h.children_tasks] + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => + simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte, + Nat.add_sub_cancel] + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.lean new file mode 100644 index 0000000000..7010706bfe --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalExec.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.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 supplied-root traversal correctness is +proved in `TraversalRoot.root_exec`; complete initialized-input preprocessing is composed in +`PreprocessingInput.runs_input`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Even zero fuel executes all native setup and stack work. No node is emitted, and the root +record is recovered exactly. Existing output arrays and all non-stack memory are preserved. -/ +theorem oneRoot_zero {w capacity : ℕ} (x : Data 128) (hx : x.Bounded w) + (h2 : Fits w 2) (hcap : Fits w (x.regs stackBase + capacity)) + (hcapacity : 7 ≤ capacity) (hf : x.regs (slot 0) = 0) : + ∃ y, Exec w oneRoot x y 173 ∧ + Stack.Rep w (x.regs stackBase) capacity [] (y.comap registers) ∧ + (∀ i, y.regs (slot i) = x.regs (slot i)) ∧ + y.regs outputCount = x.regs outputCount ∧ y.regs heap = x.regs heap ∧ + Modifies (Stack.footprint (x.regs stackBase) capacity) x.mem y.mem := by + let a := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have ha : Exec (B := .standard) w + (.seq (.op (.const zero 0)) (.seq (.op (.const one 1)) + (.seq (.op (.copy (registers Stack.ptr) stackBase)) + (.op (.const (registers Stack.len) 0))))) x a 4 := by + ram_eval [a, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have haslots (i : Fin 7) : a.regs (slot i) = x.regs (slot i) := by + fin_cases i <;> simp +decide [a, slot, registers, Stack.ptr, Stack.len, zero, one] + have har : Stack.Rep w (x.regs stackBase) capacity [] (a.comap registers) := by + refine ⟨⟨fun i => (ha.bounded hx).1 (registers i), (ha.bounded hx).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + · simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hrb, hfb, hpb⟩ := pushFrame_exec a [] h2 hcap har + (fun i => (ha.bounded hx).1 (slot i)) (by simpa using hcapacity) + obtain ⟨c, hc, hrc, hcs, hfc, hpc⟩ := popFrame_exec b [] (fun i => a.regs (slot i)) h2 hcap hrb + have hbzero : b.regs zero = 0 := by + rw [hpb zero (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, zero, one] + have hczero : c.regs zero = 0 := by + rw [hpc zero (Or.inl (by decide)) (by decide), hbzero] + have hcf : c.regs (slot 0) = 0 := (hcs 0).trans ((haslots 0).trans hf) + have hblen : b.regs (registers Stack.len) = 7 := by + simpa only [Data.comap_regs, List.length_append, List.length_cons, List.length_nil, + Nat.reduceAdd] using hrb.length_eq + have hclen : c.regs (registers Stack.len) = 0 := hrc.length_eq + have hcopy : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) c c 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := c) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + have hbody : Exec w body b c 101 := by + exact hc.seq (Exec.branch_pos (by change c.regs (slot 0) = c.regs zero; omega) hcopy) + have hl : Exec w loop b c 105 := by + apply Exec.loop_once + · change b.regs zero < b.regs (registers Stack.len) + omega + · exact hbody + · change ¬c.regs zero < c.regs (registers Stack.len) + omega + refine ⟨c, ha.seq (hb.seq hl), hrc, fun i => (hcs i).trans (haslots i), ?_, ?_, ?_⟩ + · rw [hpc outputCount (Or.inl (by decide)) (by decide), + hpb outputCount (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, outputCount, zero, one] + · rw [hpc heap (Or.inl (by decide)) (by decide), hpb heap (Or.inl (by decide))] + simp +decide [a, registers, Stack.ptr, Stack.len, heap, zero, one] + · intro p hp + exact (hfc p hp).trans (hfb p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean new file mode 100644 index 0000000000..24503ab278 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalFrames.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalChildren +public import Mathlib.Data.List.Nodup + +/-! +# Semantic entries of the native Chan–He work stack + +An entry pairs the seven unsigned stack words with the three signed integer lists stored at +their bases. Memory readback reuses `Block.Realizes`; set contents use `List.toFinset`; and the +mathematical task is the existing Chan–He recursion. This is a proof invariant for native code, +not an additional machine or execution semantics. + +## Main definitions + +* `Entry`: fuel, three native bases and their represented integer lists. +* `Entry.words`, `Entry.task`: native serialization and the existing mathematical task. +* `Entry.Stored`, `Entry.Loaded`: block readback and the current record's register ABI. + +## Main results + +* `Entry.image_read`: the injective-array API represents exactly the listed finite set. +* `Entry.read_injOn`: duplicate-free lists meet the actual modulus/node call premise. +* `Entry.Stored.input`: exact signed-encoded native array readback. +* `Entry.loaded_words`: the seven register words match the pending entry's serialization. + +The native worklist invariants and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One pending native record and the integer arrays represented by its pointers and lengths. -/ +structure Entry where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three data bases. -/ + source : Fin 3 → ℕ + /-- The lists stored at those bases, in their actual enumeration order. -/ + data : Fin 3 → List ℤ + +/-- Seven unsigned stack words in the exact native pop order. -/ +def Entry.words (e : Entry) : List ℕ := + [e.fuel, e.source 0, (e.data 0).length, e.source 1, (e.data 1).length, + e.source 2, (e.data 2).length] + +/-- The unchanged finite recursion task represented by an entry. -/ +def Entry.task (e : Entry) : Math.Task := ⟨e.fuel, fun j => (e.data j).toFinset⟩ + +/-- Total extension of the compact list for the existing native array APIs. -/ +def Entry.read (e : Entry) (j : Fin 3) (i : ℕ) : ℤ := (e.data j).getD i 0 + +/-- Each signed-encoded list occupies its concrete block, using the generic memory API. -/ +def Entry.Stored (e : Entry) (mem : ℕ → ℕ) : Prop := + ∀ j, (Block.mk (e.source j) (e.data j).length).Realizes mem + (fun i => Signed.encode (e.data j)[i.val]) + +/-- The current seven record registers contain this entry's actual unsigned metadata. -/ +def Entry.Loaded (e : Entry) (x : Data 128) : Prop := + x.regs (slot 0) = e.fuel ∧ + (∀ j, x.regs (arraySource j) = e.source j) ∧ + ∀ j, x.regs (arrayLength j) = (e.data j).length + +/-- Replace exactly one column by its actual compact heavy list and decrease the fuel. -/ +def Entry.child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : Entry := + ⟨e.fuel - 1, Function.update e.source j base, Function.update e.data j zs⟩ + +/-- A pending entry always occupies seven physical stack cells, including empty arrays. -/ +theorem Entry.length_words (e : Entry) : e.words.length = 7 := rfl + +/-- Inside its valid range the total read function is the actual list element. -/ +theorem Entry.read_eq (e : Entry) (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + e.read j i = (e.data j)[i] := by + simp only [Entry.read, List.getD, List.getElem?_eq_getElem hi, Option.getD_some] + +/-- The finite-set input of each existing array kernel is exactly the entry's listed set. -/ +theorem Entry.image_read (e : Entry) (j : Fin 3) : + (Finset.range (e.data j).length).image (e.read j) = (e.data j).toFinset := by + ext z + constructor + · intro hz + obtain ⟨i, hi, rfl⟩ := Finset.mem_image.mp hz + have hi' := Finset.mem_range.mp hi + rw [e.read_eq j i hi', List.mem_toFinset] + exact List.getElem_mem hi' + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp (List.mem_toFinset.mp hz) + exact Finset.mem_image.mpr ⟨i, Finset.mem_range.mpr hi, (e.read_eq j i hi).trans he⟩ + +/-- A duplicate-free compact list meets the actual native collision-counting premise. -/ +theorem Entry.read_injOn (e : Entry) (j : Fin 3) (h : (e.data j).Nodup) : + Set.InjOn (e.read j) (↑(Finset.range (e.data j).length) : Set ℕ) := by + intro i hi k hk he + have hi' := Finset.mem_range.mp hi + have hk' := Finset.mem_range.mp hk + rw [e.read_eq j i hi', e.read_eq j k hk'] at he + exact h.getElem_inj_iff.mp he + +/-- Exact readback in the native array-call format, derived from the generic block realization. -/ +theorem Entry.Stored.input {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : + mem (e.source j + i) = Signed.encode (e.read j i) := by + rw [e.read_eq j i hi] + exact h j ⟨i, hi⟩ + +/-- Readback transfers along a memory equality on the three live blocks. -/ +theorem Entry.Stored.congr {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + (hf : ∀ j i, i < (e.data j).length → after (e.source j + i) = before (e.source j + i)) : + e.Stored after := by + intro j i + exact (hf j i.val i.isLt).trans (h j i) + +/-- A prefix frame preserves every pending list whose block ends before the fresh allocation. -/ +theorem Entry.Stored.of_prefix {e : Entry} {before after : ℕ → ℕ} (h : e.Stored before) + {frontier : ℕ} (hsrc : ∀ j, e.source j + (e.data j).length ≤ frontier) + (hf : ∀ a < frontier, after a = before a) : e.Stored after := by + apply h.congr + intro j i hi + exact hf _ (by have := hsrc j; omega) + +/-- The seven actual register words match the exact work-stack serialization. -/ +theorem Entry.loaded_words {e : Entry} {x : Data 128} (h : e.Loaded x) : + [x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] = e.words := by + rcases h with ⟨hf, hs, hn⟩ + change [x.regs (slot 0), x.regs (arraySource 0), x.regs (arrayLength 0), + x.regs (arraySource 1), x.regs (arrayLength 1), + x.regs (arraySource 2), x.regs (arrayLength 2)] = _ + simp only [hf, hs, hn, Entry.words] + +/-- Replacing a native column changes exactly the corresponding mathematical set. -/ +theorem Entry.task_child (e : Entry) (j : Fin 3) (base : ℕ) (zs : List ℤ) : + (e.child j base zs).task = + ⟨e.fuel - 1, Function.update e.task.sets j zs.toFinset⟩ := by + unfold Entry.child Entry.task + congr 1 + funext k + by_cases he : k = j + · subst k + simp only [Function.update_self] + · simp only [Function.update_of_ne he] + +/-- The native child-record words represent exactly the changed data block, with the two +other columns and their original enumeration orders preserved. -/ +theorem Entry.child_words {e : Entry} {x : Data 128} (h : e.Loaded x) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hb : ∀ j, x.regs (heavyBase j) = bases j) + (hn : ∀ j, x.regs (heavyLength j) = (zs j).length) (j : Fin 3) : + childWords x j = (e.child j (bases j) (zs j)).words := by + obtain ⟨hf, hs, hl⟩ := h + have hs₀ : x.regs (slot 1) = e.source 0 := hs 0 + have hs₁ : x.regs (slot 3) = e.source 1 := hs 1 + have hs₂ : x.regs (slot 5) = e.source 2 := hs 2 + have hl₀ : x.regs (slot 2) = (e.data 0).length := hl 0 + have hl₁ : x.regs (slot 4) = (e.data 1).length := hl 1 + have hl₂ : x.regs (slot 6) = (e.data 2).length := hl 2 + fin_cases j <;> simp +decide [childWords, Entry.child, Entry.words, hf, + hs₀, hs₁, hs₂, hl₀, hl₁, hl₂, hb, hn] + +/-- The size and magnitude assumptions are the existing finite three-set admissibility notion; +duplicate-free enumeration is the additional native-array obligation. -/ +def Entry.Valid (e : Entry) (n U : ℕ) : Prop := + (∀ j, (e.data j).Nodup) ∧ Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U + (e.data 0).toFinset (e.data 1).toFinset (e.data 2).toFinset + +/-- Native input lengths equal finite-set cardinalities under the duplicate-free obligation. -/ +theorem Entry.Valid.length_le {e : Entry} {n U : ℕ} (h : e.Valid n U) (j : Fin 3) : + (e.data j).length ≤ n := by + have he := List.toFinset_card_of_nodup (h.1 j) + rw [← he] + fin_cases j + · exact h.2.card₁ + · exact h.2.card₂ + · exact h.2.card₃ + +/-- The existing finite-set magnitude promise bounds every actual native array element. -/ +theorem Entry.Valid.natAbs_le {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (i : ℕ) (hi : i < (e.data j).length) : (e.read j i).natAbs ≤ U := by + have hz : e.read j i ∈ (e.data j).toFinset := by + rw [e.read_eq j i hi, List.mem_toFinset] + exact List.getElem_mem hi + have hb : |e.read j i| ≤ (U : ℤ) := by + fin_cases j + · exact h.2.bdd₁ _ hz + · exact h.2.bdd₂ _ hz + · exact h.2.bdd₃ _ hz + rw [← Int.natCast_natAbs] at hb + exact_mod_cast hb + +/-- Replacing a column by a duplicate-free subset preserves the existing size/magnitude promises. -/ +theorem Entry.Valid.child {e : Entry} {n U : ℕ} (h : e.Valid n U) + (j : Fin 3) (base : ℕ) (zs : List ℤ) (hnd : zs.Nodup) + (hsub : zs.toFinset ⊆ (e.data j).toFinset) : (e.child j base zs).Valid n U := by + refine ⟨?_, ?_⟩ + · intro k + by_cases he : k = j + · subst k + simpa only [Entry.child, Function.update_self] using hnd + · simpa only [Entry.child, Function.update_of_ne he] using h.1 k + · fin_cases j + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U zs.toFinset + (e.data 1).toFinset (e.data 2).toFinset + exact ⟨(Finset.card_le_card hsub).trans h.2.card₁, h.2.card₂, h.2.card₃, + h.2.bdd₁.mono hsub, h.2.bdd₂, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + zs.toFinset (e.data 2).toFinset + exact ⟨h.2.card₁, (Finset.card_le_card hsub).trans h.2.card₂, h.2.card₃, + h.2.bdd₁, h.2.bdd₂.mono hsub, h.2.bdd₃⟩ + · change Cslib.Algorithms.ThreeSum.ChanHe.Admissible n U (e.data 0).toFinset + (e.data 1).toFinset zs.toFinset + exact ⟨h.2.card₁, h.2.card₂, (Finset.card_le_card hsub).trans h.2.card₃, + h.2.bdd₁, h.2.bdd₂, h.2.bdd₃.mono hsub⟩ + +/-- A child reuses both untouched data blocks and the actual newly written heavy block. -/ +theorem Entry.Stored.child {e : Entry} {mem : ℕ → ℕ} (h : e.Stored mem) + (j : Fin 3) (base : ℕ) (zs : List ℤ) + (hz : (Block.mk base zs.length).Realizes mem (fun i => Signed.encode zs[i.val])) : + (e.child j base zs).Stored mem := by + intro k i + by_cases he : k = j + · subst k + have hi : i.val < zs.length := by + simpa only [Entry.child, Function.update_self] using i.isLt + simpa only [Entry.child, Block.read, Function.update_self] using hz ⟨i.val, hi⟩ + · have hi : i.val < (e.data k).length := by + simpa only [Entry.child, Function.update_of_ne he] using i.isLt + simpa only [Entry.child, Block.read, Function.update_of_ne he] using h k ⟨i.val, hi⟩ + +/-- Actual heavy lists and bases give exactly the existing ordered three-child worklist. -/ +theorem Entry.task_children (e : Entry) (Q : Finset ℕ) (Λ : ℕ) + (bases : Fin 3 → ℕ) (zs : Fin 3 → List ℤ) + (hz : ∀ j, (zs j).toFinset = Cslib.Algorithms.ThreeSum.ChanHe.heavy + (e.task.sets j) (e.task.node Q Λ).M) : + [(e.child 0 (bases 0) (zs 0)).task, (e.child 1 (bases 1) (zs 1)).task, + (e.child 2 (bases 2) (zs 2)).task] = e.task.children Q Λ (e.fuel - 1) := by + let M := (e.task.node Q Λ).M + have h₀ : Function.update e.task.sets 0 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M) = + ![Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 0) M, + e.task.sets 1, e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₁ : Function.update e.task.sets 1 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M) = + ![e.task.sets 0, Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 1) M, + e.task.sets 2] := by + funext j + fin_cases j <;> rfl + have h₂ : Function.update e.task.sets 2 + (Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M) = + ![e.task.sets 0, e.task.sets 1, + Cslib.Algorithms.ThreeSum.ChanHe.heavy (e.task.sets 2) M] := by + funext j + fin_cases j <;> rfl + rw [Entry.task_child, Entry.task_child, Entry.task_child, hz 0, hz 1, hz 2, h₀, h₁, h₂] + rfl + +/-- The native word-stack length records exactly seven cells per pending entry. -/ +theorem length_serialized (es : List Entry) : (es.flatMap Entry.words).length = 7 * es.length := by + induction es with + | nil => rfl + | cons e es ih => + simp only [List.flatMap_cons, List.length_append, Entry.length_words, ih, List.length_cons] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean new file mode 100644 index 0000000000..eb0a8b0a19 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalInvariant.lean @@ -0,0 +1,181 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalOutput +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalResources +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Concrete worklist invariant for the native Chan–He traversal + +The invariant connects the reserved native stack, existing finite recursion, retained arrays, +output-pointer table and fresh heap. Remaining work pays for both future output cells and future +allocations. In particular, allocation bounds decrease with the mathematical work measure rather +than granting an unbounded heap to each kernel call. + +## Main definitions + +* `Arena`: fixed caller parameters and disjoint stack/table/heap reservations. +* `Cursor`: represented pending entries and the already emitted native pointers and nodes. +* `Cursor.Invariant`: readback, frames, exact remaining output and concrete space reservations. + +These proof invariants are original and do not add a machine model or operational oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Fixed parameters and concrete memory reservations of a complete native root traversal. -/ +structure Arena where + /-- Maximum column cardinality. -/ + n : ℕ + /-- Candidate-prime bound. -/ + m : ℕ + /-- Label-bit parameter. -/ + Λ : ℕ + /-- Node input magnitude. -/ + U : ℕ + /-- Common number of four-cell packed groups. -/ + N : ℕ + /-- Root recursion fuel. -/ + fuel : ℕ + /-- Prime bitmap base. -/ + primes : ℕ + /-- Reserved native word-stack base. -/ + stack : ℕ + /-- Reserved native word-stack capacity. -/ + capacity : ℕ + /-- Unsigned output-pointer table base. -/ + table : ℕ + /-- Reserved output-pointer table capacity. -/ + outputCapacity : ℕ + /-- First address permitted for allocated heavy and packed arrays. -/ + floor : ℕ + /-- Final upper bound on the fresh heap pointer. -/ + ceiling : ℕ + /-- Protected original memory below the stack, including the complete frontend forest. -/ + retained : ℕ → ℕ + +/-- Persistent caller registers, including actual zero and one used by native branches. -/ +def Arena.Controls (a : Arena) (x : Data 128) : Prop := + x.regs limit = a.m ∧ x.regs digits = a.Λ ∧ x.regs magnitude = a.U ∧ + x.regs groups = a.N ∧ x.regs bitmap = a.primes ∧ + x.regs primeCount = (Nat.primesLE a.m).card ∧ x.regs outputs = a.table ∧ + x.regs stackBase = a.stack ∧ x.regs stackCapacity = a.capacity ∧ + x.regs zero = 0 ∧ x.regs one = 1 + +/-- Exact register preservation transfers all persistent caller parameters. The heap and +accumulated count are deliberately absent from this fixed register set. -/ +theorem Arena.Controls.congr {a : Arena} {x y : Data 128} (h : a.Controls x) + (hp : ∀ i : Fin 128, i.val ∈ [71, 72, 73, 74, 75, 76, 78, 80, 81, 82, 83] → + y.regs i = x.regs i) : a.Controls y := by + rcases h with ⟨hm, hΛ, hU, hN, hb, hc, ht, hs, hcap, hz, ho⟩ + exact ⟨(hp _ (by decide)).trans hm, (hp _ (by decide)).trans hΛ, + (hp _ (by decide)).trans hU, (hp _ (by decide)).trans hN, + (hp _ (by decide)).trans hb, (hp _ (by decide)).trans hc, + (hp _ (by decide)).trans ht, (hp _ (by decide)).trans hs, + (hp _ (by decide)).trans hcap, (hp _ (by decide)).trans hz, + (hp _ (by decide)).trans ho⟩ + +/-- Concrete non-overlap and capacity obligations for the reserved native layout. -/ +def Arena.Separated (a : Arena) : Prop := + a.primes + a.m < a.stack ∧ a.stack + a.capacity ≤ a.table ∧ + a.table + a.outputCapacity ≤ a.floor ∧ 7 * (2 * a.fuel + 1) ≤ a.capacity + +/-- A single signed envelope covers both actual native kernels at every worklist transition. -/ +def Arena.Ready (a : Arena) (w : ℕ) : Prop := + 2 ≤ a.m ∧ 2 * a.m ^ 2 ≤ a.N ∧ + GenericRAM.Signed.Fits w ((envelope a.ceiling a.n a.m a.Λ a.U a.N : ℕ) : ℤ) + +/-- The actual native bitmap is retained below the reserved stack. -/ +def Arena.Bitmap (a : Arena) (mem : ℕ → ℕ) : Prop := + ∀ q ≤ a.m, mem (a.primes + q) = if q.Prime then 1 else 0 + +/-- Proof state of the actual stack loop; no part of this structure is evaluated by the program. -/ +structure Cursor where + /-- Top-first pending entries. -/ + pending : List Entry + /-- Packed base pointers and their existing mathematical nodes in emitted order. -/ + emitted : List (ℕ × Node) + +/-- Remaining transitions include empty entries and zero-fuel leaves. -/ +def Cursor.work (s : Cursor) : ℕ := Math.potential (s.pending.map Entry.task) + +/-- Full readback and allocation invariant at the actual native loop header. -/ +structure Cursor.Invariant (a : Arena) (w : ℕ) (target : List Node) + (s : Cursor) (x : Data 128) : Prop where + /-- All actual native words fit. -/ + bounded : x.Bounded (w + 2) + /-- Persistent caller parameters have not changed. -/ + controls : a.Controls x + /-- The actual bitmap still represents all candidate primes. -/ + bitmap : a.Bitmap x.mem + /-- The actual word stack represents exactly the pending entries. -/ + stack : Stack.Rep (w + 2) a.stack a.capacity + (s.pending.flatMap Entry.words) (x.comap registers) + /-- The existing finite depth invariant bounds pending entries. -/ + depth : Math.Bounded a.fuel (s.pending.map Entry.task) + /-- Every pending entry retains its duplicate-free existing admissibility promise. -/ + valid : ∀ e ∈ s.pending, e.Valid a.n a.U + /-- Every pending array has exact signed-encoded native readback. -/ + stored : ∀ e ∈ s.pending, e.Stored x.mem + /-- Pending arrays avoid the entire reserved stack and output-table gap. -/ + around : ∀ e ∈ s.pending, e.Around a.stack (a.floor - a.stack) + /-- Every pending array lies below the current fresh allocation. -/ + ends : ∀ e ∈ s.pending, ∀ j, e.source j + (e.data j).length ≤ x.regs heap + /-- The fresh heap never enters the reserved table or stack. -/ + floor : a.floor ≤ x.regs heap + /-- Remaining work reserves enough space for every later allocation. -/ + heapBudget : x.regs heap + s.work * allocationSize a.n a.m a.N ≤ a.ceiling + /-- The accumulated native count equals the actual emitted sequence length. -/ + count : x.regs outputCount = s.emitted.length + /-- Remaining work reserves enough output-table cells. -/ + tableBudget : s.emitted.length + s.work ≤ a.outputCapacity + /-- Every emitted pointer and every packed cell has exact existing-node readback. -/ + output : StoredNodes a.U a.N a.table s.emitted x.mem + /-- Retained packed arrays lie above the reserved output table and below the fresh heap. -/ + outputBounds : ∀ i : Fin s.emitted.length, + a.floor ≤ s.emitted[i.val].1 ∧ s.emitted[i.val].1 + 4 * a.N ≤ x.regs heap + /-- The emitted prefix followed by the exact pending recursion is the target sequence. -/ + exactOutput : s.emitted.map Prod.snd ++ + Math.output (Nat.primesLE a.m) a.Λ (s.pending.map Entry.task) = target + /-- Every original cell below the reserved stack remains available to subsequent roots. -/ + protectedMemory : ∀ p < a.stack, x.mem p = a.retained p + +/-- A live loop has at least one pending entry, since each entry occupies seven actual words. -/ +theorem Cursor.Invariant.nonempty {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : (Cond.lt zero (registers Stack.len)).Holds x) : s.pending ≠ [] := by + intro he + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + change x.regs zero < x.regs (registers Stack.len) at hc + simp only [he, List.flatMap_nil, List.length_nil, Data.comap_regs] at hl + omega + +/-- A finished actual loop has emitted the complete target, with every array already read back. -/ +theorem Cursor.Invariant.finished {a : Arena} {w : ℕ} {target : List Node} + {s : Cursor} {x : Data 128} (h : s.Invariant a w target x) + (hc : ¬(Cond.lt zero (registers Stack.len)).Holds x) : + s.pending = [] ∧ s.emitted.map Prod.snd = target := by + have hz := h.controls.2.2.2.2.2.2.2.2.2.1 + have hl := h.stack.length_eq + rw [length_serialized] at hl + change ¬x.regs zero < x.regs (registers Stack.len) at hc + change x.regs (registers Stack.len) = 7 * s.pending.length at hl + have he : s.pending = [] := List.eq_nil_of_length_eq_zero (by omega) + refine ⟨he, ?_⟩ + simpa only [he, List.map_nil, Math.output, List.flatMap_nil, List.append_nil] using h.exactOutput + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean new file mode 100644 index 0000000000..efed94f724 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLive.lean @@ -0,0 +1,259 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalAdmitted +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalEmissionFrame +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete live transitions of the native Chan–He worklist + +One transition executes the seven pops, both complete modulus searches, all three actual +histogram/packing calls, output delivery and the three reversed child pushes. Its invariant +preserves exact existing-node output, live arrays, the actual prime bitmap, and the space +reservations paid for by the strictly decreasing work measure. + +## Main results + +* `live_transition`: complete actual native body execution and full invariant preservation. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Execute and verify the whole live native worklist transition. -/ +theorem live_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) : + ∃ (s : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s.Invariant a w target y ∧ s.work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨p, hp, hstack, hl, hframe, hpr⟩ := pop_entry x e es h2 hcap h.stack + have preg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + p.regs i = x.regs i := hpr i (Or.inl hi') (Or.inr hi) + have ph : p.regs heap = x.regs heap := preg _ (by decide) (by decide) + have pc : p.regs outputCount = ns.length := (preg _ (by decide) (by decide)).trans h.count + have hc : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact preg i (by omega) (by omega)) + have frame (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : p.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hh := ha.2.2.1 + omega + have oldStored (d : Entry) (hd : d ∈ e :: es) : d.Stored p.mem := + (h.stored d hd).of_stack_frame ((h.around d hd).mono_capacity gap) hframe + have oldEnds (d : Entry) (hd : d ∈ e :: es) (j : Fin 3) : + d.source j + (d.data j).length ≤ p.regs heap := by rw [ph]; exact h.ends d hd j + have oldBitmap : a.Bitmap p.mem := by + intro q hq + rw [frame _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + have oldOutput : StoredNodes a.U a.N a.table ns p.mem := by + apply h.output.congr + · intro q hq _ + exact frame _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact frame _ (Or.inr (by + have hh := (h.outputBounds i).1 + have ht := ha.2.1 + have hh' := ha.2.2.1 + omega)) + have oldBounds (i : Fin ns.length) : + a.floor ≤ ns[i.val].1 ∧ ns[i.val].1 + 4 * a.N ≤ p.regs heap := by + rw [ph] + exact h.outputBounds i + have hwork : 1 ≤ (Cursor.mk (e :: es) ns).work := by + have hw := Math.work_pos e.fuel + change 1 ≤ Math.work e.fuel + Math.potential (es.map Entry.task) + omega + have htable : a.table + ns.length < a.floor := by + have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + have hh := ha.2.2.1 + omega + have hfloor : a.floor ≤ p.regs heap := by rw [ph]; exact h.floor + have hheap : p.regs heap ≤ a.ceiling := by + rw [ph] + have hb := h.heapBudget + omega + have htablep : p.regs outputs + p.regs outputCount < a.floor := by + rw [hc.2.2.2.2.2.2.1, pc] + exact htable + obtain ⟨v, hv, ⟨em⟩⟩ := emit_admitted p e ha hw hc hl (h.valid e (by simp)) + (oldStored e (by simp)) oldBitmap hfloor hheap (oldEnds e (by simp)) (by omega) + let s : Cursor := ⟨em.children ++ es, ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)]⟩ + have mathStep := em.math_step es hf hn + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at hdec + change s.work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + have depth : Math.Bounded a.fuel (s.pending.map Entry.task) := by + have hd := Math.bounded_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) h.depth + rw [mathStep] at hd + exact hd + have capacity : 21 + (es.flatMap Entry.words).length ≤ a.capacity := by + have hd := depth.length_le + rw [List.length_map] at hd + change (em.children ++ es).length ≤ 2 * a.fuel + 1 at hd + simp only [Emission.children, List.length_append, List.length_cons, List.length_nil] at hd + rw [length_serialized] + have hh := ha.2.2.2 + omega + have vcontrols := em.controls hc + have vstack := em.stack ha hc (hv.bounded (hp.bounded h.bounded)) + (es.flatMap Entry.words) hstack (by omega) + obtain ⟨y, hy, hystack, hyframe, hyr⟩ := pushChildren_exec v (es.flatMap Entry.words) + (hv.bounded (hp.bounded h.bounded)) vcontrols.2.2.2.2.2.2.2.2.2.2 h2 hcap vstack capacity + have yreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = v.regs i := hyr i (Or.inl ⟨hi, hi'⟩) + have yframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : y.mem q = v.mem q := by + apply hyframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hbody := body_live hp hl hc.2.2.2.2.2.2.2.2.2.1 hf hn hv hy + have vsStored := em.stored ha hc (oldStored e (by simp)) (h.around e (by simp)) + (oldEnds e (by simp)) htablep + have newStored : ∀ d ∈ s.pending, d.Stored v.mem := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_stored vsStored d hd + · exact em.stored ha hc (oldStored d (List.mem_cons_of_mem e hd)) + (h.around d (List.mem_cons_of_mem e hd)) (oldEnds d (List.mem_cons_of_mem e hd)) htablep + have newAround : ∀ d ∈ s.pending, d.Around a.stack (a.floor - a.stack) := by + intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_around (h.around e (by simp)) (by omega) d hd + · exact h.around d (List.mem_cons_of_mem e hd) + have newEnds : ∀ d ∈ s.pending, ∀ j, d.source j + (d.data j).length ≤ v.regs heap := by + intro d hd j + rcases List.mem_append.mp hd with hd | hd + · exact em.children_ends hl (oldEnds e (by simp)) d hd j + · exact (oldEnds d (List.mem_cons_of_mem e hd) j).trans em.heap_mono + have houtput := em.output hc ns oldOutput pc oldBounds htable hfloor + have yh : y.regs heap = v.regs heap := yreg _ (by decide) (by decide) + have heapAllocation : v.regs heap ≤ p.regs heap + allocationSize a.n a.m a.N := by + rw [em.heap_eq] + apply allocation_le + · intro j + rw [hl.2.2] + exact (h.valid e (by simp)).length_le j + · exact hc.2.2.2.1.le + · simpa only [hc.1] using em.upper + refine ⟨s, y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, vcontrols.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact yreg i (by omega) (by omega)), ?_, ?_, depth, ?_, ?_, newAround, ?_, ?_, ?_, ?_, + ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [yframe _ (Or.inl (by have := ha.1; omega)), + em.prefix_preserved _ (by have := ha.1; have := ha.2.1; omega) (by + have ht := hc.2.2.2.2.2.2.1 + have hq' := ha.1 + have hh := ha.2.1 + omega)] + exact oldBitmap q hq + · simpa only [s, em.children_words hl, List.flatMap_append, List.append_assoc] using hystack + · intro d hd + rcases List.mem_append.mp hd with hd | hd + · exact em.children_valid (h.valid e (by simp)) d hd + · exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (newStored d hd).of_stack_frame ((newAround d hd).mono_capacity gap) hyframe + · intro d hd j + rw [yh] + exact newEnds d hd j + · rw [yh] + exact hfloor.trans em.heap_mono + · rw [yh] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) hdec + rw [Nat.add_mul, Nat.one_mul] at hd + rw [ph] at heapAllocation + omega + · rw [yreg outputCount (by decide) (by decide), em.count, pc] + simp only [s, List.length_append, List.length_singleton] + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change (ns ++ [_]).length + s.work ≤ a.outputCapacity + simp only [List.length_append, List.length_singleton] + omega + · apply houtput.congr + · intro q hq _ + exact yframe _ (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + have low : a.floor ≤ s.emitted[i.val].1 := by + by_cases hi : i.val < ns.length + · simpa only [s, List.getElem_append_left hi] using (oldBounds ⟨i.val, hi⟩).1 + · have hi' := i.isLt + have he : i.val = ns.length := by simp only + [List.length_append, List.length_singleton] at hi'; omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 + simp only [he, hg] + dsimp only [NodePacking.Layout.packed, recordLayout] + omega + change a.floor ≤ (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[i.val].1 at low + exact yframe _ (Or.inr (by have ht := ha.2.1; have hh := ha.2.2.1; omega)) + · intro i + rw [yh, em.heap_eq] + by_cases hi : i.val < ns.length + · simp only [s, List.getElem_append_left hi] + exact ⟨(oldBounds ⟨i.val, hi⟩).1, + (oldBounds ⟨i.val, hi⟩).2.trans (em.heap_eq ▸ em.heap_mono)⟩ + · have hi' := i.isLt + have he : i.val = ns.length := by + dsimp only [s] at hi' + simp only [List.length_append, List.length_singleton] at hi' + omega + have hg : (ns ++ [((recordLayout p em.M).packed, + e.task.node (Nat.primesLE a.m) a.Λ)])[ns.length] = + ((recordLayout p em.M).packed, e.task.node (Nat.primesLE a.m) a.Λ) := by simp + change a.floor ≤ (ns ++ [_])[i.val].1 ∧ (ns ++ [_])[i.val].1 + 4 * a.N ≤ _ + simp only [he, hg] + constructor + · dsimp only [NodePacking.Layout.packed, recordLayout] + omega + · dsimp only [NodePacking.Layout.endpoint, NodePacking.Layout.hist, + NodePacking.Layout.scratch, recordLayout] + have hN := hc.2.2.2.1 + omega + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [mathStep] at he + simpa only [s, List.map_append, List.map_singleton, List.map_cons, Option.toList_some, + List.singleton_append, List.append_assoc, List.map_nil, List.nil_append, he] + using h.exactOutput + · intro q hq + rw [yframe q (Or.inl hq), em.prefix_preserved q (by + have ht := ha.2.1 + have hf' := ha.2.2.1 + omega) (by + have ht := ha.2.1 + have hp' := hc.2.2.2.2.2.2.1 + omega), frame q (Or.inl hq)] + exact h.protectedMemory q hq + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean new file mode 100644 index 0000000000..12a14f3727 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalLoop.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalLive +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalSkip +public import Complexity.Computability.WordRAM.GhostLoop + +/-! +# Complete bounded native Chan–He worklist loop + +Each body execution is discharged by the actual pop, modulus, histogram, packing and push +programs. The explicit proof-state loop rule then counts every pending entry, including skipped +calls. Termination returns the exact finite preorder, whose native pointer table and packed +arrays have full readback. The arena bounds cover all live memory throughout the loop. + +## Main results + +* `transition`: actual native body execution and invariant preservation for every pending entry. +* `loop_exec`: the complete actual native loop terminates with exact finite node output. + +These native composition and resource proofs are original. Initialized-input preprocessing +composition is a separate obligation. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Every actual live loop header admits a native body execution with the same finite program. -/ +theorem transition {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) (hc : (Cond.lt zero (registers Stack.len)).Holds x) : + ∃ (s' : Cursor) (y : Data 128), + ExecWithin (w + 2) body x y (visitTime a.n a.m a.N + 314) ∧ + s'.Invariant a w target y ∧ s'.work < s.work := by + rcases s with ⟨pending, ns⟩ + cases pending with + | nil => exact (h.nonempty hc rfl).elim + | cons e es => + by_cases hs : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0 + · obtain ⟨y, hy, hi, hd⟩ := skip_transition e es ns x ha h2 hcap h hs + exact ⟨⟨es, ns⟩, y, hy.mono (by omega), hi, hd⟩ + · apply live_transition e es ns x ha hw h2 hcap h + · intro he + exact hs (Or.inl he) + · intro j + fin_cases j + · exact fun he => hs (Or.inr (Or.inl he)) + · exact fun he => hs (Or.inr (Or.inr (Or.inl he))) + · exact fun he => hs (Or.inr (Or.inr (Or.inr he))) + +/-- Execute the complete actual native loop. Every call and fit is discharged by `transition`; +the final emitted list equals the entire target, with exact pointer and packed-array readback. -/ +theorem loop_exec {a : Arena} {w : ℕ} {target : List Node} + (ha : a.Separated) (hw : a.Ready w) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) (s : Cursor) (x : Data 128) + (h : s.Invariant a w target x) : + ∃ (ns : List (ℕ × Node)) (y : Data 128), + ExecWithin (w + 2) loop x y (s.work * (visitTime a.n a.m a.N + 316) + 2) ∧ + (Cursor.mk [] ns).Invariant a w target y ∧ ns.map Prod.snd = target := by + obtain ⟨s', y, he, hi, hexit⟩ := ExecWithin.loop_invariant_state + (fun s x => s.Invariant a w target x) Cursor.work + (transition ha hw h2 hcap) h + obtain ⟨hp, hout⟩ := hi.finished hexit + refine ⟨s'.emitted, y, ?_, ?_, hout⟩ + · simpa only [loop, Nat.add_assoc, Nat.reduceAdd] using he + · cases s' with + | mk pending ns => + change pending = [] at hp + subst pending + exact hi + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean new file mode 100644 index 0000000000..3281b4adb9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalModel.lean @@ -0,0 +1,169 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.Recursion + +/-! +# A bounded worklist for the exact Chan–He recursion + +The worklist expands the existing `ChanHe.nodes` in its existing preorder. Empty triples and +zero fuel are popped without emitting an instance. Three children replace one set each by its +existing heavy subset, in the order used by the finite reduction. + +This is the mathematical invariant for the native traversal, not a native execution contract. +In particular, expanding a worklist entry here does not discharge a native modulus or node call. +The stack bound counts pending entries, including empty and zero-fuel entries. Those entries +still require charged native pop and branch instructions. + +## Main definitions + +* `Task`: one pending three-set call, including its remaining fuel. +* `step`: one exact worklist transition and its optional emitted node. +* `potential`: a natural-valued bound on all remaining transitions, including skipped entries. +* `Bounded`: a stack-depth invariant giving at most `2f+1` pending entries for root fuel `f`. + +## Main results + +* `output_step`: one transition preserves the exact preorder output. +* `potential_step`: every transition strictly decreases the charged-transition bound. +* `bounded_step`, `Bounded.length_le`: the explicit stack has linear capacity in root fuel. + +These worklist invariants are original. They reuse the finite recursion of [ChanHe2020]. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +namespace Math + +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- One pending call of the existing finite recursion. -/ +structure Task where + /-- Remaining recursion levels. -/ + fuel : ℕ + /-- The three mathematical sets represented by the native descriptor. -/ + sets : Fin 3 → Finset ℤ + +/-- The exact existing preorder associated with one pending call. -/ +def Task.output (Q : Finset ℕ) (Λ : ℕ) (t : Task) : List Node := + nodes Q Λ t.fuel (t.sets 0) (t.sets 1) (t.sets 2) + +/-- The exact existing modulus and three sets of a nonempty call. -/ +def Task.node (Q : Finset ℕ) (Λ : ℕ) (t : Task) : Node := + ⟨t.sets 0, t.sets 1, t.sets 2, modulus Q Λ (t.sets 0) (t.sets 1) (t.sets 2)⟩ + +/-- Whether a call is skipped because one of its sets is empty. -/ +abbrev Task.empty (t : Task) : Prop := t.sets 0 = ∅ ∨ t.sets 1 = ∅ ∨ t.sets 2 = ∅ + +/-- The three children, with exactly the mathematical reduction's order and heavy sets. -/ +def Task.children (Q : Finset ℕ) (Λ f : ℕ) (t : Task) : List Task := + let M := (t.node Q Λ).M + [⟨f, ![heavy (t.sets 0) M, t.sets 1, t.sets 2]⟩, + ⟨f, ![t.sets 0, heavy (t.sets 1) M, t.sets 2]⟩, + ⟨f, ![t.sets 0, t.sets 1, heavy (t.sets 2) M]⟩] + +/-- The sequence still to be emitted from a top-first worklist. -/ +def output (Q : Finset ℕ) (Λ : ℕ) (ts : List Task) : List Node := + ts.flatMap (Task.output Q Λ) + +/-- One pop, followed either by a skip or by one emission and three children. -/ +def step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : List Task × Option Node := + match t.fuel with + | 0 => (ts, none) + | f + 1 => if t.empty then (ts, none) else (t.children Q Λ f ++ ts, some (t.node Q Λ)) + +/-- One worklist transition preserves the exact output list, including order and skipped calls. -/ +theorem output_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + output Q Λ (t :: ts) = + (step Q Λ t ts).2.toList ++ output Q Λ (step Q Λ t ts).1 := by + cases t with + | mk f S => + cases f with + | zero => simp [output, step, Task.output, nodes] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes] + · simp only [Task.empty] at he + simp [output, step, Task.empty, he, Task.output, nodes, Task.children, Task.node, + List.append_assoc] + +/-- Number of transitions for the full ternary tree, counting its zero-fuel leaves. -/ +def work : ℕ → ℕ + | 0 => 1 + | f + 1 => 1 + 3 * work f + +/-- Every pending entry costs at least one pop/skip transition. -/ +theorem work_pos (f : ℕ) : 1 ≤ work f := by + cases f <;> simp [work] + +/-- Closed form without a truncated quotient. -/ +theorem work_eq (f : ℕ) : 2 * work f + 1 = 3 ^ (f + 1) := by + induction f with + | zero => decide + | succ f ih => + simp only [work, pow_succ] at * + omega + +/-- The sum of the transition budgets of all pending entries. -/ +def potential (ts : List Task) : ℕ := (ts.map fun t => work t.fuel).sum + +/-- One transition spends at least one unit, including the empty and zero-fuel cases. -/ +theorem potential_step (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) : + potential (step Q Λ t ts).1 + 1 ≤ potential (t :: ts) := by + cases t with + | mk f S => + cases f with + | zero => simp [step, potential, work] + | succ f => + by_cases he : Task.empty ⟨f + 1, S⟩ + · simp only [step, he, ↓reduceIte, potential, List.map_cons, List.sum_cons] + have := work_pos (f + 1) + omega + · simp only [step, he, ↓reduceIte, potential, Task.children, List.map_append, + List.map_cons, List.map_nil, List.sum_append, List.sum_cons, List.sum_nil, work] + omega + +/-- Each entry's fuel plus the pending suffix gives a bound measured from the stack bottom. -/ +def Bounded (f : ℕ) : List Task → Prop + | [] => True + | t :: ts => 2 * t.fuel + ts.length ≤ 2 * f ∧ Bounded f ts + +/-- A single root of fuel `f` satisfies the stack invariant. -/ +theorem bounded_singleton (t : Task) : Bounded t.fuel [t] := by + simp [Bounded] + +/-- The explicit stack requires at most `2f+1` entries, even with zero-fuel entries present. -/ +theorem Bounded.length_le {f : ℕ} {ts : List Task} (h : Bounded f ts) : + ts.length ≤ 2 * f + 1 := by + cases ts with + | nil => simp + | cons t ts => + simp only [Bounded] at h + simp only [List.length_cons] + omega + +/-- A native pop/skip or three-child push preserves the same fixed stack capacity. -/ +theorem bounded_step {f : ℕ} (Q : Finset ℕ) (Λ : ℕ) (t : Task) (ts : List Task) + (h : Bounded f (t :: ts)) : Bounded f (step Q Λ t ts).1 := by + obtain ⟨hf, hts⟩ := h + cases ht : t.fuel with + | zero => simpa only [step, ht] using hts + | succ g => + by_cases he : t.empty + · simpa only [step, ht, he, ↓reduceIte] using hts + · simp only [step, ht, he, ↓reduceIte, Task.children, List.cons_append, + List.nil_append, Bounded, List.length_cons] at * + exact ⟨by omega, by omega, by omega, hts⟩ + +end Math + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean new file mode 100644 index 0000000000..390e750a8e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalOutput.lean @@ -0,0 +1,94 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ChanHe.FromNumbers +public import Complexity.Computability.WordRAM.ListArray +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Exact memory output of native Chan–He traversal + +The pointer table is an unsigned word list. Each pointed-to block contains the existing +`Node.oneArray`, encoded with the signed integer encoding, at the common finite length `4N`. +This reuses the generic block/list realization API and introduces no alternative convolution +predicate or execution model. + +## Main definitions + +* `StoredNodes`: exact pointer-table and packed-array readback for the emitted preorder. + +## Main results + +* `StoredNodes.congr`: transfer exact readback along a frame on its live cells. +* `StoredNodes.append`: one actual pointer-table store appends a represented native node. + +The concrete output invariant and proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe + +/-- The exact finite output table and its signed-encoded packed node arrays. -/ +structure StoredNodes (U N table : ℕ) (ns : List (ℕ × Node)) (mem : ℕ → ℕ) : Prop where + /-- The table contains exactly the native base pointers in emitted order. -/ + pointers : Block.HoldsList table (ns.map Prod.fst) mem + /-- Every pointed-to word equals the existing mathematical packed array cell. -/ + cells : ∀ i : Fin ns.length, (Block.mk ns[i.val].1 (4 * N)).Realizes mem + (fun u => Signed.encode (ns[i.val].2.oneArray U u.val)) + +/-- Empty traversal output imposes no pointed-to array obligations. -/ +theorem storedNodes_nil (U N table : ℕ) (mem : ℕ → ℕ) : StoredNodes U N table [] mem := + ⟨Block.holdsList_nil table mem, fun i => Fin.elim0 i⟩ + +/-- Pointer-table and packed-array readback survive a frame on all their live cells. -/ +theorem StoredNodes.congr {U N table : ℕ} {ns : List (ℕ × Node)} {before after : ℕ → ℕ} + (h : StoredNodes U N table ns before) + (ht : ∀ a, table ≤ a → a < table + ns.length → after a = before a) + (hc : ∀ i : Fin ns.length, ∀ u < 4 * N, + after (ns[i.val].1 + u) = before (ns[i.val].1 + u)) : + StoredNodes U N table ns after := by + refine ⟨h.pointers.frame (by simpa only [List.length_map] using ht), ?_⟩ + intro i u + exact (hc i u.val u.isLt).trans (h.cells i u) + +/-- Appending one native pointer preserves all packed readback. The stored pointer lies below +both the old arrays and the fresh array, so the actual store cannot overwrite array data. -/ +theorem StoredNodes.append {U N table p : ℕ} {ν : Node} {ns : List (ℕ × Node)} + {mem : ℕ → ℕ} (h : StoredNodes U N table ns mem) + (hnew : (Block.mk p (4 * N)).Realizes mem (fun u => Signed.encode (ν.oneArray U u.val))) + (hold : ∀ i : Fin ns.length, table + ns.length < ns[i.val].1) + (hp : table + ns.length < p) : + StoredNodes U N table (ns ++ [(p, ν)]) (Function.update mem (table + ns.length) p) := by + refine ⟨?_, ?_⟩ + · simpa only [List.map_append, List.map_singleton, List.length_map] using + h.pointers.append_one p + · intro i + by_cases hi : i.val < ns.length + · rw [List.getElem_append_left hi] + apply (h.cells ⟨i.val, hi⟩).update_of_not_owns + intro ha + have hb := hold ⟨i.val, hi⟩ + exact (not_le_of_gt hb) ha.1 + · have hil := i.isLt + simp only [List.length_append, List.length_singleton] at hil + have he : i.val = ns.length := by omega + have hg : (ns ++ [(p, ν)])[ns.length] = (p, ν) := by simp + intro u + change Function.update mem (table + ns.length) p + ((ns ++ [(p, ν)])[i.val].1 + u.val) = + Signed.encode ((ns ++ [(p, ν)])[i.val].2.oneArray U u.val) + simp only [he, hg] + rw [Function.update_of_ne (show p + u.val ≠ table + ns.length by omega)] + exact hnew u + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean new file mode 100644 index 0000000000..0b56da19f9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalPending.lean @@ -0,0 +1,98 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalFrames + +/-! +# Live arrays around the native traversal stack + +The word stack occupies a reserved gap between the original arrays and the fresh heap. +Pending arrays lie entirely below the gap or start above it. Actual stack writes therefore +preserve their signed readback. Popping a serialized entry restores its seven registers and +retains every pending array. + +## Main definitions + +* `Entry.Around`: an array ends below the stack or starts above its reserved end. + +## Main results + +* `Entry.Stored.of_stack_frame`: stack writes preserve live arrays. +* `pop_entry`: the actual seven-pop program restores the semantic entry. + +These memory-layout and composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Every column lies entirely on one side of the reserved stack interval. -/ +def Entry.Around (e : Entry) (base capacity : ℕ) : Prop := + ∀ j, e.source j + (e.data j).length ≤ base ∨ base + capacity ≤ e.source j + +/-- Avoiding a larger reserved gap also avoids each smaller gap with the same base. -/ +theorem Entry.Around.mono_capacity {e : Entry} {base small large : ℕ} + (h : e.Around base large) (hle : small ≤ large) : e.Around base small := by + intro j + rcases h j with hj | hj + · exact Or.inl hj + · exact Or.inr (by omega) + +/-- A concrete stack frame cannot alter any live column around its reserved interval. -/ +theorem Entry.Stored.of_stack_frame {e : Entry} {before after : ℕ → ℕ} + {base capacity : ℕ} (h : e.Stored before) (hg : e.Around base capacity) + (hf : Modifies (Stack.footprint base capacity) before after) : e.Stored after := by + apply h.congr + intro j i hi + apply hf + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + rcases hg j with hj | hj <;> omega + +/-- A newly allocated heavy block remains above the stack; untouched columns retain their gap. -/ +theorem Entry.Around.child {e : Entry} {base capacity p : ℕ} (h : e.Around base capacity) + (j : Fin 3) (zs : List ℤ) (hp : base + capacity ≤ p) : + (e.child j p zs).Around base capacity := by + intro k + by_cases he : k = j + · subst k + exact Or.inr (by simpa only [Entry.child, Function.update_self] using hp) + · simpa only [Entry.child, Function.update_of_ne he] using h k + +/-- Actual native popping restores the semantic entry, preserves the remaining stack and +changes memory only inside the reserved stack interval. -/ +theorem pop_entry {w base capacity : ℕ} (x : Data 128) (e : Entry) (es : List Entry) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity ((e :: es).flatMap Entry.words) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ + Stack.Rep w base capacity (es.flatMap Entry.words) (y.comap registers) ∧ + e.Loaded y ∧ Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + let a : Fin 7 → ℕ := ![e.fuel, e.source 0, (e.data 0).length, e.source 1, + (e.data 1).length, e.source 2, (e.data 2).length] + have hrep : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ es.flatMap Entry.words) + (x.comap registers) := by + simpa +decide [a, List.flatMap_cons, Entry.words] using hr + obtain ⟨y, hy, hry, hslots, hframe, hp⟩ := popFrame_exec x _ a h2 hcap hrep + refine ⟨y, hy, hry, ⟨hslots 0, ?_, ?_⟩, hframe, hp⟩ + · intro j + fin_cases j + · exact hslots 1 + · exact hslots 3 + · exact hslots 5 + · intro j + fin_cases j + · exact hslots 2 + · exact hslots 4 + · exact hslots 6 + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean new file mode 100644 index 0000000000..7a634d2f6d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalProgram.lean @@ -0,0 +1,234 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStack +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.NodeSpec + +/-! +# Fixed native controller for one Chan–He recursion tree + +The controller pops actual seven-word records, skips zero fuel or empty inputs, computes the +modulus, constructs the complete heavy and packed outputs, records the packed pointer, and +pushes the three children. Children are pushed third, second, first so the existing native +stack visits them in the mathematical preorder. Fresh allocations follow every live node; +all earlier arrays, the prime bitmap, stack reservation and output table precede the heap. + +This module defines executable code. Its correctness and resource proofs belong to the +traversal development; these definitions alone do not certify a native preprocessing bound. +The frontend and initialized-input composition remain separate. + +## Main definitions + +* `oneRoot`: the finite native traversal controller, independent of fuel, input sizes and width. +* `visit`: complete native modulus and node construction, including output delivery. +* `pushChildren`: actual reverse-order stack pushes preserving the original three descriptors. + +The native code is original and reuses the existing finite Chan–He construction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- The complete node kernels occupy registers 0–63. -/ +def kernel (i : Fin 64) : Fin 128 := ⟨i.val, by omega⟩ + +/-- The supplied candidate-prime bound. -/ +abbrev limit : Fin 128 := 71 +/-- The supplied label-bit parameter. -/ +abbrev digits : Fin 128 := 72 +/-- The common input-magnitude bound of the tree. -/ +abbrev magnitude : Fin 128 := 73 +/-- The common number of packed four-cell groups. -/ +abbrev groups : Fin 128 := 74 +/-- The prime bitmap, retained below the heap. -/ +abbrev bitmap : Fin 128 := 75 +/-- The exact number of candidate primes. -/ +abbrev primeCount : Fin 128 := 76 +/-- The current fresh allocation base. -/ +abbrev heap : Fin 128 := 77 +/-- The reserved output-pointer table. -/ +abbrev outputs : Fin 128 := 78 +/-- Accumulated output count; this is not reset between roots. -/ +abbrev outputCount : Fin 128 := 79 +/-- The reserved word-stack base. -/ +abbrev stackBase : Fin 128 := 80 +/-- The reserved word-stack capacity, used by the safety specification. -/ +abbrev stackCapacity : Fin 128 := 81 +/-- Controller constant zero. -/ +abbrev zero : Fin 128 := 82 +/-- Controller constant one. -/ +abbrev one : Fin 128 := 83 +/-- Controller address temporary. -/ +abbrev temporary : Fin 128 := 85 +/-- The computed unsigned modulus. -/ +abbrev selected : Fin 128 := 87 +/-- The three computed heavy lengths. -/ +def heavyLength (j : Fin 3) : Fin 128 := ⟨88 + j.val, by omega⟩ +/-- The three computed heavy bases. -/ +def heavyBase (j : Fin 3) : Fin 128 := ⟨91 + j.val, by omega⟩ +/-- Current packed-array base. -/ +abbrev packed : Fin 128 := 94 +/-- One-past-end of the current node allocation. -/ +abbrev endpoint : Fin 128 := 95 +/-- Saved original descriptor words, outside both kernels and the stack dispatcher. -/ +def original (i : Fin 4) : Fin 128 := ⟨105 + i.val, by omega⟩ + +/-- Fourteen actual instructions deliver the modulus program's complete caller ABI. -/ +def prepareModulus : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (CountCall.source 0) + let l₀ := kernel (CountCall.length 0) + let s₁ := kernel (CountCall.source 1) + let l₁ := kernel (CountCall.length 1) + let s₂ := kernel (CountCall.source 2) + let l₂ := kernel (CountCall.length 2) + let scratch := kernel CountCall.scratch + let histogram := kernel CountCall.histogram + let m := kernel Search.limit + let b := kernel Search.primes + let c := kernel CollisionTest.card + let d := kernel Modulus.digits + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + scratch ← heap; histogram ← heap + n₀; histogram ← histogram + n₁; + histogram ← histogram + n₂; m ← limit; b ← bitmap; c ← primeCount; d ← digits ] + +/-- Ten actual copies deliver the complete node builder's caller ABI. -/ +def prepareNode : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let s₀ := kernel (NodePacking.source 0) + let l₀ := kernel (NodePacking.length 0) + let s₁ := kernel (NodePacking.source 1) + let l₁ := kernel (NodePacking.length 1) + let s₂ := kernel (NodePacking.source 2) + let l₂ := kernel (NodePacking.length 2) + let b := kernel NodePacking.base + let n := kernel NodePacking.groups + let u := kernel NodePacking.magnitude + let m := kernel NodePacking.modulus + ram[ s₀ ← p₀; l₀ ← n₀; s₁ ← p₁; l₁ ← n₁; s₂ ← p₂; l₂ ← n₂; + b ← heap; n ← groups; u ← magnitude; m ← selected ] + +/-- Read the three actual count cells, record the packed pointer, and advance the fresh heap. -/ +def deliver : Code .standard 128 := + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let h₀ := heavyBase 0 + let h₁ := heavyBase 1 + let h₂ := heavyBase 2 + let c₀ := heavyLength 0 + let c₁ := heavyLength 1 + let c₂ := heavyLength 2 + ram[ packed ← #3; packed ← heap + packed; packed ← packed + n₀; + packed ← packed + n₁; packed ← packed + n₂; + temporary ← outputs + outputCount; *temporary ← packed; + outputCount ← outputCount + one; c₀ ← *heap; h₀ ← heap + one; + temporary ← h₀ + n₀; c₁ ← *temporary; h₁ ← temporary + one; + temporary ← h₁ + n₁; c₂ ← *temporary; h₂ ← temporary + one; + endpoint ← groups + groups; endpoint ← endpoint + endpoint; + endpoint ← packed + endpoint; endpoint ← endpoint + n₀; endpoint ← endpoint + n₁; + endpoint ← endpoint + n₂; endpoint ← endpoint + selected; heap ← endpoint ] + +/-- Complete actual native calls, with argument preparation, modulus copy and output delivery. -/ +def visit : Code .standard 128 := + .seq prepareModulus (.seq (Modulus.program.rename kernel) + (.seq (.op (.copy selected (kernel Modulus.output))) + (.seq prepareNode (.seq (NodePacking.program.rename kernel) deliver)))) + +/-- Decrease fuel, retain both descriptors needed for restoration, and prepare the third child. -/ +def prepareThird : Code .standard 128 := + let f := slot 0 + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₁ := original 0 + let on₁ := original 1 + let op₂ := original 2 + let on₂ := original 3 + let h₂ := heavyBase 2 + let c₂ := heavyLength 2 + ram[ f ← f - one; op₁ ← p₁; on₁ ← n₁; op₂ ← p₂; on₂ ← n₂; + p₂ ← h₂; n₂ ← c₂ ] + +/-- Restore the third original descriptor and prepare the second child. -/ +def prepareSecond : Code .standard 128 := + let p₁ := slot 3 + let n₁ := slot 4 + let p₂ := slot 5 + let n₂ := slot 6 + let op₂ := original 2 + let on₂ := original 3 + let h₁ := heavyBase 1 + let c₁ := heavyLength 1 + ram[ p₂ ← op₂; n₂ ← on₂; p₁ ← h₁; n₁ ← c₁ ] + +/-- Restore the second original descriptor and prepare the first child. -/ +def prepareFirst : Code .standard 128 := + let p₀ := slot 1 + let n₀ := slot 2 + let p₁ := slot 3 + let n₁ := slot 4 + let op₁ := original 0 + let on₁ := original 1 + let h₀ := heavyBase 0 + let c₀ := heavyLength 0 + ram[ p₁ ← op₁; n₁ ← on₁; p₀ ← h₀; n₀ ← c₀ ] + +/-- Push third, second, first. Every child retains the two other original set descriptors. -/ +def pushChildren : Code .standard 128 := + .seq prepareThird (.seq pushFrame (.seq prepareSecond + (.seq pushFrame (.seq prepareFirst pushFrame)))) + +/-- A popped record either skips exactly the mathematical guard or executes the complete node. -/ +def body : Code .standard 128 := + let f := slot 0 + let n₀ := slot 2 + let n₁ := slot 4 + let n₂ := slot 6 + let skip := Code.op (B := Basis.standard) (.copy f f) + .seq popFrame (.branch (.eq f zero) skip + (.branch (.eq n₀ zero) skip (.branch (.eq n₁ zero) skip + (.branch (.eq n₂ zero) skip (.seq visit pushChildren))))) + +/-- The actual pending-word stack, including zero-fuel and empty entries, controls the loop. -/ +def loop : Code .standard 128 := .loop (.lt zero (registers Stack.len)) body + +/-- The complete finite native controller for one supplied root. It appends to the caller's +output table, retaining the accumulated count and advancing the fresh allocation base. -/ +def oneRoot : Code .standard 128 := + let ptr := registers Stack.ptr + let len := registers Stack.len + .seq (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) + (.seq pushFrame loop) + +/-- The same finite controller is used at every admitted word width. -/ +theorem oneRoot_not_readsWidth : ¬oneRoot.ReadsWidth := by decide + +/-- The native controller has a fixed finite instruction list, independent of recursion fuel. -/ +theorem oneRoot_size : oneRoot.size ≤ 65536 := by decide + +/-- Registers 109–127 are retained for the outer frontend-forest controller. -/ +theorem oneRoot_preserves : ∀ i : Fin 128, 109 ≤ i.val → oneRoot.Preserves i := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean new file mode 100644 index 0000000000..36ec20a7bf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalResources.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalCall + +/-! +# A shared polynomial envelope for native traversal calls + +The dynamic heap and all input blocks lie below `H`; each list has at most `n` elements. +The candidate bound, label parameter, magnitude and common group count have fixed bounds +`m,Λ,U,N`. A fourth-degree polynomial covers both existing kernel envelopes, including their +native setup. This bound is later instantiated at the worklist's global heap frontier. + +## Main definitions + +* `envelope`: a common polynomial bound for the actual native modulus and node calls. + +## Main results + +* `modulus_budget_le_envelope`: the concrete selector budget is covered. +* `node_budget_le_envelope`: every selected modulus at most `m²` is covered. + +The envelope and elementary inequalities are original resource estimates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- One fixed polynomial in the heap, list, candidate, label, magnitude and output-size bounds. -/ +def envelope (H n m Λ U N : ℕ) : ℕ := + 65536 * (H + n + m ^ 2 + Λ + m + U + N + 100) ^ 4 + +/-- The common envelope covers each size parameter and every smaller native word. -/ +theorem envelope_ge_sum (H n m Λ U N : ℕ) : + H + n + m ^ 2 + Λ + m + U + N + 100 ≤ envelope H n m Λ U N := by + have h := Nat.le_self_pow (by decide : 4 ≠ 0) + (H + n + m ^ 2 + Λ + m + U + N + 100) + dsimp only [envelope] + omega + +private theorem square_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 2 ≤ S ^ 4 := by + have hsq : 1 ≤ S ^ 2 := Nat.one_le_pow _ _ hS + calc S ^ 2 ≤ S ^ 2 * S ^ 2 := Nat.le_mul_of_pos_right _ hsq + _ = S ^ 4 := by ring + +private theorem cube_le_fourth {S : ℕ} (hS : 1 ≤ S) : S ^ 3 ≤ S ^ 4 := by + calc S ^ 3 ≤ S ^ 3 * S := Nat.le_mul_of_pos_right _ hS + _ = S ^ 4 := by ring + +/-- The exact selector envelope, including all copied source pointers and histogram addresses, +is bounded by one polynomial in the runtime size bounds. -/ +theorem modulus_budget_le_envelope (x : Data 128) (H n m Λ U N : ℕ) + (hheap : x.regs heap ≤ H) + (hsrc : ∀ j, x.regs (arraySource j) + x.regs (arrayLength j) ≤ H) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hm : x.regs limit ≤ m) (hΛ : x.regs digits ≤ Λ) + (hcard : x.regs primeCount ≤ m) (hbitmap : x.regs bitmap ≤ H) : + Modulus.budget ((modulusState x).comap kernel) ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hline : 7 * H + 3 * n + m ^ 2 + m + 100 ≤ 7 * S := by dsimp [S]; omega + have ha : 3 * n + 1 ≤ 3 * S := by dsimp [S]; omega + have hb : 3 * Λ + m + 1 ≤ 3 * S := by dsimp [S]; omega + have hprod : (3 * n + 1) ^ 2 * (3 * Λ + m + 1) ≤ 27 * S ^ 3 := by + calc (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + ≤ (3 * S) ^ 2 * (3 * S) := Nat.mul_le_mul (Nat.pow_le_pow_left ha 2) hb + _ = 27 * S ^ 3 := by ring + have hns : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hss : x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) ≤ 3 * H := by + have h₀ := hsrc 0 + have h₁ := hsrc 1 + have h₂ := hsrc 2 + change x.regs (slot 1) + x.regs (slot 2) ≤ H at h₀ + change x.regs (slot 3) + x.regs (slot 4) ≤ H at h₁ + change x.regs (slot 5) + x.regs (slot 6) ≤ H at h₂ + omega + have hbudget : Modulus.budget ((modulusState x).comap kernel) ≤ + 7 * H + 3 * n + (3 * n + 1) ^ 2 * (3 * Λ + m + 1) + m ^ 2 + m + 100 := by + change x.regs heap + x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + + x.regs heap + x.regs bitmap + x.regs (slot 1) + x.regs (slot 3) + x.regs (slot 5) + + (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) + 1) ^ 2 * + (3 * x.regs digits + x.regs primeCount + 1) + x.regs limit ^ 2 + x.regs limit + 100 ≤ _ + have hp := Nat.mul_le_mul + (Nat.pow_le_pow_left (Nat.add_le_add_right hns 1) 2) + (show 3 * x.regs digits + x.regs primeCount + 1 ≤ 3 * Λ + m + 1 by omega) + have hsq := Nat.pow_le_pow_left hm 2 + omega + have hsf : S ≤ S ^ 4 := Nat.le_self_pow (by decide) S + have hcf := cube_le_fourth hS + change _ ≤ 65536 * S ^ 4 + omega + +/-- The node layout, including every allocated output/scratch cell and tag value, is covered +uniformly for every selected product at most `m²`. -/ +theorem node_budget_le_envelope (x : Data 128) (H n m Λ U N M : ℕ) + (hheap : x.regs heap ≤ H) (hlen : ∀ j, x.regs (arrayLength j) ≤ n) + (hM : M ≤ m ^ 2) (hU : x.regs magnitude ≤ U) (hN : x.regs groups ≤ N) : + (recordLayout x M).budget ≤ envelope H n m Λ U N := by + let S := H + n + m ^ 2 + Λ + m + U + N + 100 + have hS : 1 ≤ S := by dsimp [S]; omega + have hns : (recordLayout x M).total ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (arrayLength 0) + x.regs (arrayLength 1) + x.regs (arrayLength 2) ≤ 3 * n + omega + have hinner : (recordLayout x M).base + (recordLayout x M).total + M + + (recordLayout x M).N + (recordLayout x M).U + 1 ≤ 3 * S := by + change x.regs heap + (recordLayout x M).total + M + x.regs groups + + x.regs magnitude + 1 ≤ 3 * S + dsimp only [S] + omega + calc (recordLayout x M).budget + ≤ 4096 * (3 * S) ^ 2 := Nat.mul_le_mul_left _ (Nat.pow_le_pow_left hinner 2) + _ = 36864 * S ^ 2 := by ring + _ ≤ 65536 * S ^ 4 := by have := square_le_fourth hS; omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean new file mode 100644 index 0000000000..32b8f31ed4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalRoot.lean @@ -0,0 +1,218 @@ +/- +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. +The caller chooses `m`, `Λ` and fuel. Setting `m = mPar n U`, `Λ = labelBits U` and +`e.fuel = fuel n` makes `e.task.output (Nat.primesLE a.m) a.Λ` the existing Chan–He +`reduction n U` of the root sets; the initialized-input bridge is in `PreprocessingEntries`. -/ +theorem root_exec {a : Arena} {w : ℕ} (e : Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (hw : a.Ready w) (h : RootInput a w e ns x) : + ∃ (out : List (ℕ × Node)) (y : Data 128), + 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 := Signed.unsigned_fits_of_signed hw.2.2 + have hsum := envelope_ge_sum a.ceiling a.n a.m a.Λ a.U a.N + have h2 : Fits (w + 2) 2 := hunsigned.of_le (by omega) + have hcap : Fits (w + 2) (a.stack + a.capacity) := hunsigned.of_le (by + have hb := h.heapBudget + have hf := h.floor + have ht := ha.2.1 + have hgap := ha.2.2.1 + omega) + let p := (((x.set zero 0).set one 1).set (registers Stack.ptr) (x.regs stackBase)).set + (registers Stack.len) 0 + let ptr := registers Stack.ptr + let len := registers Stack.len + have h1 : Fits (w + 2) 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) (w + 2) + (ram[ zero ← #0; one ← #1; ptr ← stackBase; len ← #0 ]) x p 4 := by + ram_eval [p, ptr, len, zero, one, registers, Stack.ptr, Stack.len, stackBase, h1] + have preg (i : Fin 128) (hi : i.val < 96) : + p.regs i = ((x.set zero 0).set one 1).regs i := by + have hp' : i ≠ registers Stack.ptr := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.ptr] at this; omega + have hl' : i ≠ registers Stack.len := by + intro he; have := congrArg Fin.val he; norm_num [registers, Stack.len] at this; omega + simp only [p, Data.set_regs, Function.update_of_ne hp', Function.update_of_ne hl'] + have pmeta (i : Fin 128) (hi : i.val < 96) : p.regs i = x.regs i := by + rw [preg i hi] + by_cases hz : i = zero + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.1.symm + · by_cases ho : i = one + · subst i + simpa +decide [Data.set, zero, one] using h.controls.2.2.2.2.2.2.2.2.2.2.symm + · simp only [Data.set_regs, Function.update_of_ne ho, Function.update_of_ne hz] + have pcontrols : a.Controls p := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact pmeta i (by omega)) + have ploaded : e.Loaded p := by + refine ⟨(pmeta _ (by decide)).trans h.loaded.1, ?_, ?_⟩ + · intro j + exact (pmeta _ (by dsimp [arraySource, slot]; omega)).trans (h.loaded.2.1 j) + · intro j + exact (pmeta _ (by dsimp [arrayLength, slot]; omega)).trans (h.loaded.2.2 j) + have pstack : Stack.Rep (w + 2) a.stack a.capacity [] (p.comap registers) := by + refine ⟨⟨fun i => (hp.bounded h.bounded).1 (registers i), (hp.bounded h.bounded).2⟩, + ?_, ?_, Nat.zero_le _, fun i => Fin.elim0 i⟩ + · simpa +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + using h.controls.2.2.2.2.2.2.2.1 + · simp +decide [p, Data.comap, Data.set, registers, Stack.ptr, Stack.len, zero, one] + obtain ⟨b, hb, hstack, hframe, hbr⟩ := pushFrame_exec p [] h2 hcap pstack + (fun i => (hp.bounded h.bounded).1 (slot i)) (by + have := ha.2.2.2 + simp only [List.length_nil] + omega) + have breg (i : Fin 128) (hi : i.val < 96) : b.regs i = x.regs i := + (hbr i (Or.inl hi)).trans (pmeta i hi) + have bframe (q : ℕ) (hq : q < a.stack ∨ a.stack + a.capacity ≤ q) : b.mem q = x.mem q := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + let s : Cursor := ⟨[e], ns⟩ + have work : s.work = Math.work e.fuel := by + simp only [s, Cursor.work, List.map_cons, List.map_nil, Math.potential, List.sum_cons, + List.sum_nil, Nat.add_zero, Entry.task] + have gap : a.capacity ≤ a.floor - a.stack := by + have ht := ha.2.1 + have hg := ha.2.2.1 + omega + have hi : s.Invariant a w + (ns.map Prod.snd ++ e.task.output (Nat.primesLE a.m) a.Λ) b := by + refine ⟨hb.bounded (hp.bounded h.bounded), h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact breg i (by omega)), ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [bframe _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · simpa only [s, List.flatMap_cons, List.flatMap_nil, Entry.loaded_words ploaded, + List.append_nil] using hstack + · rw [← h.fuel] + exact Math.bounded_singleton e.task + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.valid + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + exact h.stored.of_stack_frame (h.around.mono_capacity gap) hframe + · intro d hd + have he : d = e := by simpa only [s, List.mem_singleton] using hd + exact he ▸ h.around + · intro d hd j + have he : d = e := by simpa only [s, List.mem_singleton] using hd + subst d + rw [breg heap (by decide)] + exact h.ends j + · rw [breg heap (by decide)] + exact h.floor + · rw [breg heap (by decide), work] + exact h.heapBudget + · exact (breg outputCount (by decide)).trans h.count + · rw [work] + exact h.tableBudget + · apply h.output.congr + · intro q hq _ + exact bframe q (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact bframe _ (Or.inr (by + have hp' := (h.outputBounds i).1 + have ht := ha.2.1 + have hg := ha.2.2.1 + omega)) + · intro i + rw [breg heap (by decide)] + exact h.outputBounds i + · simp only [s, List.map_cons, List.map_nil, Math.output, List.flatMap_cons, + List.flatMap_nil, List.append_nil] + · intro q hq + exact (bframe q (Or.inl hq)).trans (h.protectedMemory q hq) + obtain ⟨out, y, he, hfinal, hout⟩ := loop_exec ha hw h2 hcap s b hi + have hexec : ExecWithin (w + 2) oneRoot x y (rootTime e.fuel a.n a.m a.N) := by + have hc := hp.within.seq (hb.within.seq he) + exact hc.mono (by rw [work]; dsimp only [rootTime]; omega) + refine ⟨out, y, hexec, hfinal, hout, ?_, ?_⟩ + · intro q hq + exact (hfinal.protectedMemory q hq).trans (h.protectedMemory q hq).symm + · intro i hi + exact hexec.regs_eq (oneRoot_preserves i hi) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean new file mode 100644 index 0000000000..d52730a9ba --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalSkip.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalInvariant +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalStep + +/-! +# Complete skipped transitions of the native worklist + +Zero-fuel and empty entries still execute the seven native pops and their actual branch paths. +The remaining arrays, bitmap and previously emitted packed output retain exact readback. +Both space reservations improve because the transition measure decreases without allocation. + +## Main results + +* `skip_transition`: the actual native body preserves the full worklist invariant on every + skipped entry and strictly decreases its remaining charged-transition measure. + +These native composition and invariant proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum.ChanHe (Node) + +/-- Popping a skipped entry executes the complete native body and preserves every part of the +worklist invariant, including exact output, all live memory and concrete space reservations. -/ +theorem skip_transition {a : Arena} {w : ℕ} {target : List Node} + (e : Entry) (es : List Entry) (ns : List (ℕ × Node)) (x : Data 128) + (ha : a.Separated) (h2 : Fits (w + 2) 2) + (hcap : Fits (w + 2) (a.stack + a.capacity)) + (h : (Cursor.mk (e :: es) ns).Invariant a w target x) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ∃ y, ExecWithin (w + 2) body x y 107 ∧ + (Cursor.mk es ns).Invariant a w target y ∧ + (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work := by + obtain ⟨y, hy, hstack, hloaded, hframe, hp⟩ := pop_entry x e es h2 hcap h.stack + have hreg (i : Fin 128) (hi : 71 ≤ i.val) (hi' : i.val < 96) : + y.regs i = x.regs i := hp i (Or.inl hi') (Or.inr hi) + have hheap : y.regs heap = x.regs heap := hreg _ (by decide) (by decide) + have hcount : y.regs outputCount = x.regs outputCount := hreg _ (by decide) (by decide) + have hcontrols : a.Controls y := h.controls.congr (by + intro i hi + simp only [List.mem_cons, List.not_mem_nil, or_false] at hi + exact hreg i (by omega) (by omega)) + have hbody := body_skip hy hloaded hcontrols.2.2.2.2.2.2.2.2.2.1 hskip + have hmem (p : ℕ) (hp : p < a.stack ∨ a.stack + a.capacity ≤ p) : y.mem p = x.mem p := by + apply hframe + simp only [Stack.footprint, Block.mem_footprint, Block.Owns] + omega + have hstep : Math.step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) = + (es.map Entry.task, none) := by + by_cases hf : e.fuel = 0 + · simp only [Math.step, Entry.task, hf] + · have hempty : e.task.empty := by + simp only [Math.Task.empty, Entry.task, List.toFinset_eq_empty_iff] + rcases hskip with hf' | h₀ | h₁ | h₂ + · exact (hf hf').elim + · exact Or.inl (List.eq_nil_of_length_eq_zero h₀) + · exact Or.inr (Or.inl (List.eq_nil_of_length_eq_zero h₁)) + · exact Or.inr (Or.inr (List.eq_nil_of_length_eq_zero h₂)) + cases he : e.fuel with + | zero => exact (hf he).elim + | succ f => simp only [Math.step, show e.task.fuel = f + 1 from he, hempty, ↓reduceIte] + have hdec := Math.potential_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at hdec + change (Cursor.mk es ns).work + 1 ≤ (Cursor.mk (e :: es) ns).work at hdec + refine ⟨y, hbody, ?_, by omega⟩ + refine ⟨hbody.bounded h.bounded, hcontrols, ?_, hstack, ?_, ?_, ?_, ?_, ?_, ?_, ?_, + hcount.trans h.count, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [hmem _ (Or.inl (by have := ha.1; omega))] + exact h.bitmap q hq + · exact h.depth.2 + · intro d hd + exact h.valid d (List.mem_cons_of_mem e hd) + · intro d hd + exact (h.stored d (List.mem_cons_of_mem e hd)).of_stack_frame + ((h.around d (List.mem_cons_of_mem e hd)).mono_capacity (by + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) hframe + · intro d hd + exact h.around d (List.mem_cons_of_mem e hd) + · intro d hd j + rw [hheap] + exact h.ends d (List.mem_cons_of_mem e hd) j + · rw [hheap] + exact h.floor + · rw [hheap] + have hb := h.heapBudget + have hd := Nat.mul_le_mul_right (allocationSize a.n a.m a.N) (Nat.le_of_lt + (show (Cursor.mk es ns).work < (Cursor.mk (e :: es) ns).work by omega)) + omega + · have hb := h.tableBudget + change ns.length + (Cursor.mk (e :: es) ns).work ≤ a.outputCapacity at hb + change ns.length + (Cursor.mk es ns).work ≤ a.outputCapacity + omega + · apply h.output.congr + · intro p hp _ + exact hmem p (Or.inr (by have := ha.2.1; omega)) + · intro i u _ + exact hmem _ (Or.inr (by + have hi := (h.outputBounds i).1 + have ht := ha.2.1 + have hf := ha.2.2.1 + omega)) + · intro i + rw [hheap] + exact h.outputBounds i + · have he := Math.output_step (Nat.primesLE a.m) a.Λ e.task (es.map Entry.task) + rw [hstep] at he + simp only [Option.toList_none, List.nil_append] at he + simpa only [Cursor.pending, Cursor.emitted, List.map_cons, he] using h.exactOutput + · intro p hp + exact (hmem p (Or.inl hp)).trans (h.protectedMemory p hp) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean new file mode 100644 index 0000000000..5efb4c814b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStack.lean @@ -0,0 +1,342 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.Stack +import Mathlib.Tactic.FinCases + +/-! +# Native stack calls for Chan–He traversal records + +Traversal keeps a seven-word record in registers 64–70 and the existing bounded stack in +registers 96–104. Registers below 64 remain available to the complete modulus and node programs. +Each record contains fuel followed by three base/length pairs. The stack stores words, so a +record is pushed in reverse order and popped in forward order. + +The dispatcher, representation and memory frame are reused from `Cslib.WordRAM.Stack`. +Argument delivery and result copies here are actual native instructions and are charged. + +## Main definitions + +* `slot`: the seven persistent traversal-record registers. +* `registers`: the existing nine-register stack dispatcher inside the 128-register controller. +* `pushSlot`: native argument delivery followed by the complete bounded-stack dispatcher. +* `popSlot`: native dispatch and result delivery to one traversal-record register. + +## Main results + +* `pushSlot_exec`: a nine-instruction push, with the exact stack, memory and register frames. +* `popSlot_exec`: a fourteen-instruction nonempty pop with the same exact frames. +* `pushFrame_exec`, `popFrame_exec`: complete seven-word record transfers in 64 and 99 instructions. + +The native controller and its composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Fuel and the three base/length pairs, retained across both 64-register native kernels. -/ +def slot (i : Fin 7) : Fin 128 := ⟨64 + i.val, by omega⟩ + +/-- Pointer word of the selected pending set. -/ +def arraySource (j : Fin 3) : Fin 128 := slot ⟨1 + 2 * j.val, by omega⟩ + +/-- Length word of the selected pending set. -/ +def arrayLength (j : Fin 3) : Fin 128 := slot ⟨2 + 2 * j.val, by omega⟩ + +/-- The existing stack's workspace lies above every 64-register node-kernel register. -/ +def registers (i : Fin 9) : Fin 128 := ⟨96 + i.val, by omega⟩ + +private theorem registers_injective : Function.Injective registers := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp [registers] at this + omega + +/-- Caller-owned registers outside the stack dispatcher. -/ +abbrev Outside (i : Fin 128) : Prop := i.val < 96 ∨ 105 ≤ i.val + +private theorem outside_ne (i : Fin 128) (hi : Outside i) (j : Fin 9) : + i ≠ registers j := by + intro h + have := congrArg Fin.val h + dsimp [registers, Outside] at * + omega + +private theorem extend_outside (a : Data 9) (x : Data 128) (i : Fin 128) (hi : Outside i) : + (a.extend registers x).regs i = x.regs i := by + change Function.extend registers a.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + exact outside_ne i hi j hj.symm + +/-- Deliver the push tag and actual record word, then execute the existing stack program. -/ +def pushSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + let src := slot i + .seq (ram[ tag ← #0; arg ← src ]) (Stack.program.rename registers) + +/-- Each native push includes both argument instructions, not a host-level stack operation. +Every register outside 96–104 and every memory cell outside the reserved stack is preserved. -/ +theorem pushSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : Fits w (x.regs (slot i))) (hv : vs.length < capacity) : + ∃ y, Exec w (pushSlot i) x y 9 ∧ + Stack.Rep w base capacity (x.regs (slot i) :: vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 0).set (registers Stack.arg) (x.regs (slot i)) + have hs : slot i ≠ registers Stack.tag := outside_ne _ (Or.inl (by dsimp [slot]; omega)) _ + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 0)) + (.op (.copy (registers Stack.arg) (slot i)))) x q 2 := by + ram_eval [q, hs] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare (.push (x.regs (slot i))) + (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, _, hra, hframe⟩ := Stack.execute h2 hcap hr (.push (x.regs (slot i))) ⟨hv, hw⟩ + have hcall : Exec w Stack.program (q.comap registers) a 7 := by + simpa only [hq, Stack.bodyCost] using ha + let y := a.extend registers q + refine ⟨y, ?_, ?_, hframe, ?_⟩ + · exact hp.seq (hcall.rename registers_injective) + · simpa only [y, Data.comap_extend registers_injective] using hra + · intro j hj + rw [extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def pushSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (pushSlot i) (pushSlots is) + +private theorem pushSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (x : Data 128) + (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : is.length + vs.length ≤ capacity) : + ∃ y, Exec w (pushSlots is) x y (9 * is.length + 1) ∧ + Stack.Rep w base capacity (is.reverse.map (fun i => x.regs (slot i)) ++ vs) + (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + induction is generalizing x vs with + | nil => + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + have hv' : vs.length < capacity := by simp only [List.length_cons] at hv; omega + obtain ⟨z, hz, hrz, hfz, hpz⟩ := pushSlot_exec i x vs h2 hcap hr (hw i) hv' + have hslots : (fun i => z.regs (slot i)) = fun i => x.regs (slot i) := by + funext i + exact hpz _ (Or.inl (by dsimp [slot]; omega)) + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih z (x.regs (slot i) :: vs) hrz + (by intro j; rw [hpz _ (Or.inl (by dsimp [slot]; omega))]; exact hw j) + (by simp only [List.length_cons] at hv ⊢; omega) + refine ⟨y, ?_, ?_, ?_, fun j hj => (hpy j hj).trans (hpz j hj)⟩ + · simpa only [pushSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · simpa only [hslots, List.reverse_cons, List.map_append, List.map_singleton, + List.append_assoc, List.singleton_append] using hry + · intro a ha + exact (hfy a ha).trans (hfz a ha) + +/-- Push all seven words in reverse order, so the next seven pops recover the record in order. +The trailing self-copy gives the fixed empty suffix an ordinary native instruction. -/ +def pushFrame : Code .standard 128 := + .seq (pushSlot 6) (.seq (pushSlot 5) (.seq (pushSlot 4) (.seq (pushSlot 3) + (.seq (pushSlot 2) (.seq (pushSlot 1) (.seq (pushSlot 0) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete native frame push charges all seven dispatches and argument delivery. +There is no host-level record write, and the exact seven-word sequence is at the stack top. -/ +theorem pushFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity vs (x.comap registers)) + (hw : ∀ i, Fits w (x.regs (slot i))) (hv : 7 + vs.length ≤ capacity) : + ∃ y, Exec w pushFrame x y 64 ∧ + Stack.Rep w base capacity + ([x.regs (slot 0), x.regs (slot 1), x.regs (slot 2), x.regs (slot 3), + x.regs (slot 4), x.regs (slot 5), x.regs (slot 6)] ++ vs) (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = x.regs j := by + simpa only [pushFrame, pushSlots, List.length_cons, List.length_nil, List.reverse_cons, + List.reverse_nil, List.nil_append, List.cons_append, List.append_assoc, List.singleton_append, + List.map_cons, List.map_nil, List.map_append, List.map_singleton, Nat.reduceAdd, + Nat.reduceMul] using pushSlots_exec [6, 5, 4, 3, 2, 1, 0] x vs h2 hcap hr hw hv + +private theorem comap_set_slot (x : Data 128) (i : Fin 7) (v : ℕ) : + (x.set (slot i) v).comap registers = x.comap registers := by + ext j + · change Function.update x.regs (slot i) v (registers j) = x.regs (registers j) + apply Function.update_of_ne + exact (outside_ne _ (Or.inl (by dsimp [slot]; omega)) j).symm + · rfl + +/-- Deliver the pop tag, execute the complete dispatcher, and copy its answer to the record. -/ +def popSlot (i : Fin 7) : Code .standard 128 := + let tag := registers Stack.tag + let arg := registers Stack.arg + .seq (ram[ tag ← #1; arg ← #0 ]) + (.seq (Stack.program.rename registers) (.op (.copy (slot i) (registers Stack.out)))) + +/-- A nonempty native pop includes both argument instructions and the actual result copy. +Only the selected record register, stack workspace and reserved stack memory may change. -/ +theorem popSlot_exec {w base capacity : ℕ} (i : Fin 7) (x : Data 128) (v : ℕ) (vs : List ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (v :: vs) (x.comap registers)) : + ∃ y, Exec w (popSlot i) x y 14 ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ y.regs (slot i) = v ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → j ≠ slot i → y.regs j = x.regs j := by + let q := (x.set (registers Stack.tag) 1).set (registers Stack.arg) 0 + have h1 : Fits w 1 := h2.of_le (by decide) + have hp : Exec (B := .standard) w + (.seq (.op (.const (registers Stack.tag) 1)) + (.op (.const (registers Stack.arg) 0))) x q 2 := by + ram_eval [q, h1] + have hq : q.comap registers = + (Stack.implementation w capacity).prepare .pop (x.comap registers) := by + ext j + · fin_cases j <;> + simp +decide [q, Data.comap, Data.set, registers, Stack.implementation, + DynamicProgram.prepare, Stack.tag, Stack.arg] + · rfl + obtain ⟨a, ha, hans, hra, hframe⟩ := Stack.execute h2 hcap hr .pop trivial + have hcall : Exec w Stack.program (q.comap registers) a 11 := by + simpa only [hq, Stack.bodyCost, List.cons_ne_nil, ↓reduceIte] using ha + have hout : a.regs Stack.out = v := by + change some (if a.regs Stack.present = 0 then none else some (a.regs Stack.out)) = + some (some v) at hans + split at hans + · cases hans + · exact Option.some.inj (Option.some.inj hans) + have hread : (a.extend registers q).regs (registers Stack.out) = v := by + change Function.extend registers a.regs q.regs (registers Stack.out) = v + rw [registers_injective.extend_apply, hout] + let y := (a.extend registers q).set (slot i) v + have hcopy : Exec (B := .standard) w + (.op (.copy (slot i) (registers Stack.out))) (a.extend registers q) y 1 := by + simpa only [y, hread] using + (Exec.copy (B := Basis.standard) (w := w) (x := a.extend registers q) + (d := slot i) (s := registers Stack.out)) + refine ⟨y, hp.seq ((hcall.rename registers_injective).seq hcopy), ?_, ?_, hframe, ?_⟩ + · simpa only [y, comap_set_slot, Data.comap_extend registers_injective, List.tail_cons] using hra + · exact Function.update_self .. + · intro j hj hji + change Function.update (a.extend registers q).regs (slot i) v j = x.regs j + rw [Function.update_of_ne hji, extend_outside a q j hj] + simp only [q, Data.set_regs, Function.update_of_ne (outside_ne j hj Stack.arg), + Function.update_of_ne (outside_ne j hj Stack.tag)] + +private def popSlots : List (Fin 7) → Code .standard 128 + | [] => .op (.copy (slot 0) (slot 0)) + | i :: is => .seq (popSlot i) (popSlots is) + +private def restore : List (Fin 7) → List ℕ → (Fin 128 → ℕ) → Fin 128 → ℕ + | i :: is, v :: vs, rs => restore is vs (Function.update rs (slot i) v) + | _, _, rs => rs + +private theorem restore_congr_at (is : List (Fin 7)) (vs : List ℕ) + (rs ss : Fin 128 → ℕ) (j : Fin 128) (h : rs j = ss j) : + restore is vs rs j = restore is vs ss j := by + induction is generalizing vs rs ss with + | nil => exact h + | cons i is ih => + cases vs with + | nil => exact h + | cons v vs => + apply ih + by_cases he : j = slot i + · simp [he] + · simpa only [Function.update_of_ne he] using h + +private theorem popSlots_exec {w base capacity : ℕ} (is : List (Fin 7)) (ws : List ℕ) + (x : Data 128) (vs : List ℕ) (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity (ws ++ vs) (x.comap registers)) + (hlen : ws.length = is.length) : + ∃ y, Exec w (popSlots is) x y (14 * is.length + 1) ∧ + Stack.Rep w base capacity vs (y.comap registers) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → y.regs j = restore is ws x.regs j := by + induction is generalizing ws x with + | nil => + have hws : ws = [] := List.length_eq_zero_iff.mp hlen + subst ws + have he : Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext j <;> simp [Data.set] + exact ⟨x, he, hr, fun _ _ => rfl, fun _ _ => rfl⟩ + | cons i is ih => + cases ws with + | nil => simp at hlen + | cons v ws => + obtain ⟨z, hz, hrz, hout, hfz, hpz⟩ := popSlot_exec i x v (ws ++ vs) h2 hcap hr + obtain ⟨y, hy, hry, hfy, hpy⟩ := ih ws z hrz (by simpa using hlen) + refine ⟨y, ?_, hry, ?_, ?_⟩ + · simpa only [popSlots, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using hz.seq hy + · intro a ha + exact (hfy a ha).trans (hfz a ha) + · intro j hj + rw [hpy j hj] + apply restore_congr_at + by_cases he : j = slot i + · simpa only [he, Function.update_self] using hout + · simpa only [Function.update_of_ne he] using hpz j hj he + +/-- Pop all seven words into the record, in forward order, using the native dispatcher. -/ +def popFrame : Code .standard 128 := + .seq (popSlot 0) (.seq (popSlot 1) (.seq (popSlot 2) (.seq (popSlot 3) + (.seq (popSlot 4) (.seq (popSlot 5) (.seq (popSlot 6) (.op (.copy (slot 0) (slot 0))))))))) + +/-- The complete record pop charges every argument, dispatch and result-copy instruction. +The seven restored words have their original order; other caller registers are preserved. -/ +theorem popFrame_exec {w base capacity : ℕ} (x : Data 128) (vs : List ℕ) (a : Fin 7 → ℕ) + (h2 : Fits w 2) (hcap : Fits w (base + capacity)) + (hr : Stack.Rep w base capacity + ([a 0, a 1, a 2, a 3, a 4, a 5, a 6] ++ vs) (x.comap registers)) : + ∃ y, Exec w popFrame x y 99 ∧ Stack.Rep w base capacity vs (y.comap registers) ∧ + (∀ i, y.regs (slot i) = a i) ∧ + Modifies (Stack.footprint base capacity) x.mem y.mem ∧ + ∀ j, Outside j → (j.val < 64 ∨ 71 ≤ j.val) → y.regs j = x.regs j := by + obtain ⟨y, hy, hry, hfy, hpy⟩ := + popSlots_exec [0, 1, 2, 3, 4, 5, 6] [a 0, a 1, a 2, a 3, a 4, a 5, a 6] + x vs h2 hcap hr rfl + refine ⟨y, hy, hry, ?_, hfy, ?_⟩ + · intro i + have h := hpy (slot i) (Or.inl (by dsimp [slot]; omega)) + fin_cases i <;> simpa +decide [restore, slot] using h + · intro j hj hj' + have hne (i : Fin 7) : j ≠ slot i := by + intro h + have := congrArg Fin.val h + dsimp [slot] at this + omega + simpa only [restore, Function.update_of_ne (hne 0), Function.update_of_ne (hne 1), + Function.update_of_ne (hne 2), Function.update_of_ne (hne 3), + Function.update_of_ne (hne 4), Function.update_of_ne (hne 5), + Function.update_of_ne (hne 6)] using hpy j hj + +/-- Record transfers do not inspect the physical word width. -/ +theorem not_readsWidth : ¬pushFrame.ReadsWidth ∧ ¬popFrame.ReadsWidth := by decide + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean new file mode 100644 index 0000000000..9215e4fc86 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalStep.lean @@ -0,0 +1,94 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalPending +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalTime + +/-! +# Native dispatch after popping a Chan–He entry + +The zero-fuel and empty-array cases execute their actual branch paths. Live calls execute the +complete visit followed by all three native child pushes. No guard is evaluated by a host +program, and skipped entries retain the same charged pop as live entries. + +## Main results + +* `body_skip`: actual pop and dispatch for every skipped entry. +* `body_live`: the four actual negative branches connect a pop to the complete live suffix. + +These control-flow composition proofs are original. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +private theorem copy_self {w : ℕ} (x : Data 128) : + Exec (B := .standard) w (.op (.copy (slot 0) (slot 0))) x x 1 := by + convert (Exec.copy (B := Basis.standard) (w := w) (x := x) + (d := slot 0) (s := slot 0)) using 1 + ext i <;> simp [Data.set] + +/-- Every skipped entry executes the actual seven pops and the appropriate guard path. +The resulting data is exactly the pop result; the self-copy does not change it. -/ +theorem body_skip {w : ℕ} {x y : Data 128} {e : Entry} + (hp : Exec w popFrame x y 99) (hl : e.Loaded y) (hz : y.regs zero = 0) + (hskip : e.fuel = 0 ∨ (e.data 0).length = 0 ∨ + (e.data 1).length = 0 ∨ (e.data 2).length = 0) : + ExecWithin w body x y 107 := by + have hf : y.regs (slot 0) = e.fuel := hl.1 + have hn₀ : y.regs (slot 2) = (e.data 0).length := hl.2.2 0 + have hn₁ : y.regs (slot 4) = (e.data 1).length := hl.2.2 1 + have hn₂ : y.regs (slot 6) = (e.data 2).length := hl.2.2 2 + by_cases h₀ : e.fuel = 0 + · exact (hp.seq (Exec.branch_pos + (by change y.regs (slot 0) = y.regs zero; omega) (copy_self y))).within.mono (by omega) + · by_cases h₁ : (e.data 0).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 2) = y.regs zero; omega) + (copy_self y)))).within.mono (by omega) + · by_cases h₂ : (e.data 1).length = 0 + · exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 4) = y.regs zero; omega) + (copy_self y))))).within.mono (by omega) + · have h₃ : (e.data 2).length = 0 := by omega + exact (hp.seq (Exec.branch_neg + (by change ¬y.regs (slot 0) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 2) = y.regs zero; omega) + (Exec.branch_neg (by change ¬y.regs (slot 4) = y.regs zero; omega) + (Exec.branch_pos (by change y.regs (slot 6) = y.regs zero; omega) + (copy_self y)))))).within.mono (by omega) + +/-- A live entry takes all four native negative branches before executing the supplied actual +visit and three-push executions. The bound charges the seven pops and eight branch/jump steps. -/ +theorem body_live {w T : ℕ} {x a b y : Data 128} {e : Entry} + (hp : Exec w popFrame x a 99) (hl : e.Loaded a) (hz : a.regs zero = 0) + (hf : e.fuel ≠ 0) (hn : ∀ j, (e.data j).length ≠ 0) + (hv : ExecWithin w visit a b T) (hc : Exec w pushChildren b y 207) : + ExecWithin w body x y (T + 314) := by + obtain ⟨t, ht, he⟩ := hv + have hg₀ : ¬(Cond.eq (slot 0) zero).Holds a := by + change a.regs (slot 0) ≠ a.regs zero + rw [hl.1, hz] + exact hf + have hg (j : Fin 3) : ¬(Cond.eq (arrayLength j) zero).Holds a := by + change a.regs (arrayLength j) ≠ a.regs zero + rw [hl.2.2 j, hz] + exact hn j + have he' : Exec w body x y (99 + (t + 207 + 2 + 2 + 2 + 2)) := + hp.seq (Exec.branch_neg hg₀ (Exec.branch_neg (hg 0) (Exec.branch_neg (hg 1) + (Exec.branch_neg (hg 2) (he.seq hc))))) + exact he'.within.mono (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean new file mode 100644 index 0000000000..f20a88b5bf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalTime.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalVisit +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.TraversalModel + +/-! +# Charged bounds for the native Chan–He worklist + +The per-node estimate includes both complete modulus searches and the complete packing program. +It is cubic in the candidate bound, rather than quadratic in the input length. The root estimate +also charges skipped calls, stack transfers, branches and loop control. + +## Main definitions + +* `visitTime`, `rootTime`: uniform charged bounds for one live visit and one complete root. +* `allocationSize`: maximum fresh allocation for one live visit. + +## Main results + +* `visit_cost_le`: the exact executed call bound is covered by `visitTime`. +* `allocation_le`: the concrete node allocation is covered by `allocationSize`. + +These estimates are original; their starting costs are the proved native execution bounds. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal + +open Cslib.WordRAM + +/-- Both searches, the actual node program, argument delivery and the output-table store. -/ +def visitTime (n m N : ℕ) : ℕ := + 2 * (m + 1) * (102 * n + 15 * m ^ 2 + 102) + + 300 * n + 15 * m ^ 2 + 15 * m + 12 * N + 386 + +/-- Root setup and every possible transition, including empty and zero-fuel entries. -/ +def rootTime (f n m N : ℕ) : ℕ := 70 + Math.work f * (visitTime n m N + 316) + +/-- Three heavy count cells, three heavy buffers, four packed blocks, scratch and histogram. -/ +def allocationSize (n m N : ℕ) : ℕ := 3 + 6 * n + 4 * N + m ^ 2 + +/-- The exact charged cost from the complete native visit is uniformly bounded. -/ +theorem visit_cost_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hm : x.regs limit ≤ m) + (hN : x.regs groups ≤ N) (hM : M ≤ m ^ 2) : + Modulus.cost ((modulusState x).comap kernel) + + 66 * (recordLayout x M).total + 15 * M + 12 * x.regs groups + 277 ≤ + visitTime n m N := by + have hs : x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6) ≤ 3 * n := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + omega + have hp := Nat.mul_le_mul (Nat.add_le_add_right hm 1) + (show 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102 ≤ 102 * n + 15 * m ^ 2 + 102 by + have := Nat.pow_le_pow_left hm 2 + omega) + change 2 * ((x.regs limit + 1) * + (34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit ^ 2 + 102) + 9) + + 34 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * x.regs limit + 91 + + 66 * (x.regs (slot 2) + x.regs (slot 4) + x.regs (slot 6)) + + 15 * M + 12 * x.regs groups + 277 ≤ _ + dsimp only [visitTime] + rw [Nat.mul_assoc] + omega + +/-- The actual next heap pointer advances by at most the uniform per-node allocation. -/ +theorem allocation_le (x : Data 128) (n m N M : ℕ) + (hlen : ∀ j, x.regs (arrayLength j) ≤ n) (hN : x.regs groups ≤ N) + (hM : M ≤ m ^ 2) : + (recordLayout x M).endpoint ≤ x.regs heap + allocationSize n m N := by + have h₀ := hlen 0 + have h₁ := hlen 1 + have h₂ := hlen 2 + rw [recordLayout_endpoint] + change x.regs (slot 2) ≤ n at h₀ + change x.regs (slot 4) ≤ n at h₁ + change x.regs (slot 6) ≤ n at h₂ + dsimp only [allocationSize] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean b/Complexity/Algorithms/WordRAM/ThreeSum/ChanHe/TraversalVisit.lean new file mode 100644 index 0000000000..f02451568a --- /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) := + (Signed.unsigned_fits_of_signed (hnode M hMpos hMle)).of_le + (recordLayout x M).bounds.1 + have hEndEq : (recordLayout x M).endpoint = b.regs heap + 3 + 2 * + (b.regs (slot 2) + b.regs (slot 4) + b.regs (slot 6)) + + 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 0000000000..e2157518c9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend.lean @@ -0,0 +1,103 @@ +/- +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.Computability.WordRAM.Width +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 + +/-- Physical word width is never read, so it never determines the radix iteration count. -/ +theorem not_readsWidth (κ : ℕ) : ¬(program κ).ReadsWidth := by + have hp := (Code.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)) + +/-- 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 := Runs.prefix_at_smaller_width + (Code.not_readsWidth_program (not_readsWidth κ) descriptorCount) hw hr he + have hb := hsmall.bounded_init (input_fits input le_rfl hvalues) + have hpoly := two_pow_width_le κ input.length + exact ⟨fun i => (hb.1 i).trans_le hpoly, fun a => (hb.2 a).trans_le hpoly⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean new file mode 100644 index 0000000000..368f2ec4cc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Arrays.lean @@ -0,0 +1,343 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Program + +/-! +# Native frontend array and descriptor execution + +## Main results + +The compact scan contract is composed with every array setup and descriptor store. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Local arithmetic budget covers array addresses, native labels and loop setup. -/ +def budget (l : Layout) (U : ℕ) : ℕ := l.endpoint + 8 * U + 8 * l.digits + 64 + +/-- The hypotheses of a concrete compact scan, before choosing its bit coordinates and mode. -/ +structure ScanReady (w : ℕ) (rs : List (ℕ × ℕ)) (x : Data 48) : Prop where + /-- The source length is the distinct-key count. -/ + length : x.regs len = rs.length + /-- Native small constants. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + /-- Both radix arrays have exact contents. -/ + keys : Block.HoldsList (x.regs Scan.keys) (rs.map Prod.fst) x.mem + counts : Block.HoldsList (x.regs Scan.counts) (rs.map Prod.snd) x.mem + /-- Every key and the native label arithmetic fit. -/ + words : ∀ p ∈ rs, Fits w p.1 + absolute : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude + double : ∀ p ∈ rs, Fits w (2 * p.1) + labels : Fits w (4 * x.regs magnitude + 2) + small : Fits w 5 + /-- Source and output endpoints fit. -/ + keyAddress : Fits w (x.regs Scan.keys + rs.length) + countAddress : Fits w (x.regs Scan.counts + rs.length) + outputAddress : Fits w (x.regs dst + rs.length + 1) + /-- Both source blocks precede the output. -/ + keysBefore : x.regs Scan.keys + rs.length ≤ x.regs dst + countsBefore : x.regs Scan.counts + rs.length ≤ x.regs dst + +/-- Parameters changed by split/mode setup. -/ +def ScanParametersPreserved (x y : Data 48) : Prop := + y.mem = x.mem ∧ ∀ j, j.val < 27 → j ≠ beta → j ≠ beta' → j ≠ digit → j ≠ mode → + y.regs j = x.regs j + +/-- A mode/bit setup preserves every actual scan precondition. -/ +theorem ScanReady.transfer {w : ℕ} {rs : List (ℕ × ℕ)} {x y : Data 48} + (h : ScanReady w rs x) (he : ScanParametersPreserved x y) : ScanReady w rs y := by + obtain ⟨hm, hr⟩ := he + have eq (j : Fin 48) (hlt : j.val < 27) (hj : j ≠ beta ∧ j ≠ beta' ∧ j ≠ digit ∧ j ≠ mode) : + y.regs j = x.regs j := hr j hlt hj.1 hj.2.1 hj.2.2.1 hj.2.2.2 + constructor + · simpa only [eq len (by decide) (by decide)] using h.length + · simpa only [eq zero (by decide) (by decide), eq one (by decide) (by decide), + eq two (by decide) (by decide), + eq three (by decide) (by decide), eq four (by decide) (by decide), + eq five (by decide) (by decide)] using h.constants + · simpa only [eq Scan.keys (by decide) (by decide), hm] using h.keys + · simpa only [eq Scan.counts (by decide) (by decide), hm] using h.counts + · exact h.words + · simpa only [eq magnitude (by decide) (by decide)] using h.absolute + · exact h.double + · simpa only [eq magnitude (by decide) (by decide)] using h.labels + · exact h.small + · simpa only [eq Scan.keys (by decide) (by decide)] using h.keyAddress + · simpa only [eq Scan.counts (by decide) (by decide)] using h.countAddress + · simpa only [eq dst (by decide) (by decide)] using h.outputAddress + · simpa only [eq Scan.keys (by decide) (by decide), + eq dst (by decide) (by decide)] using h.keysBefore + · simpa only [eq Scan.counts (by decide) (by decide), + eq dst (by decide) (by decide)] using h.countsBefore + +/-- Invoke the proved native scan from its actual arithmetic/layout context. -/ +theorem ScanReady.exec {w : ℕ} {rs : List (ℕ × ℕ)} {x : Data 48} + (h : ScanReady w rs x) : + ∃ y, ExecWithin w Scan.program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, Scan.body.Preserves j → y.regs j = x.regs j) := + Scan.exec rs x h.length h.constants.1 h.constants.2.1 h.constants.2.2.1 + h.constants.2.2.2.1 h.constants.2.2.2.2.1 h.constants.2.2.2.2.2 + h.keys h.counts h.words h.absolute h.double h.labels h.small h.keyAddress + h.countAddress h.outputAddress h.keysBefore h.countsBefore + +private theorem exec_mode {w m : ℕ} (r : Fin 48) (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hr : x.regs r = m) : + ∃ y, ExecWithin w (.seq (ram[ mode ← r ]) Scan.program) x y (43 * rs.length + 5) ∧ + Block.HoldsList (x.regs dst) (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) + (x.regs beta') (x.regs digit) m rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem := by + let a := x.set mode m + have ha : ExecWithin (B := .standard) w (ram[ mode ← r ]) x a 1 := by + ram_eval [a, hr] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro j _ _ _ _ hj + exact Function.update_of_ne hj _ _ + obtain ⟨y, hy, hmem, hout, hframe, _⟩ := (h.transfer har).exec + refine ⟨y, (ha.seq hy).mono (by omega), ?_, ?_, hframe⟩ + · simpa +decide [a] using hmem + · simpa +decide [a] using hout + +private theorem exec_singleton {w : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) : + ∃ y, ExecWithin w singleton x y 2 ∧ + Block.HoldsList (x.regs dst) [0] y.mem ∧ y.regs out = 1 ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have f1 : Fits w 1 := h.small.of_le (by decide) + have fd : Fits w (x.regs dst) := h.outputAddress.of_le (by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [singleton, h.constants.1, h.constants.2.1, f1, fd] + · intro i + simp [Block.read, Data.set_mem, Data.write_mem] + · simp + · intro a ha + have hne : a ≠ x.regs dst := by + intro he + apply ha + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + (rs.length + 1) + omega + simp only [Data.set_mem, Data.write_mem, Function.update_of_ne hne] + +/-- Select exactly the array named by one physical slot. Every split/multiplicity setup is +executed; the width and label budget are scalar runtime values, independent of code size. -/ +theorem pick_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (h : ScanReady w rs x) (hU : x.regs magnitude = U) + (hΛ : x.regs digits = Λ) (hs : x.regs splits = 2 * Λ ^ 2) + (ht : x.regs triple = j / 3) (hslot : x.regs slot = j % 3) + (hjfit : Fits w j) : + ∃ y, ExecWithin w pick x y (43 * rs.length + 20) ∧ + Block.HoldsList (x.regs dst) (Math.array U Λ rs j) y.mem ∧ + y.regs out = (Math.array U Λ rs j).length ∧ + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem y.mem := by + have hz := h.constants.1 + have h1 := h.constants.2.1 + have h2 := h.constants.2.2.1 + have h3 := h.constants.2.2.2.1 + have h4 := h.constants.2.2.2.2.1 + have h5 := h.constants.2.2.2.2.2 + have frame {mem : ℕ → ℕ} + (hf : Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem mem) : + Modifies (Block.mk (x.regs dst) (rs.length + 1)).footprint x.mem mem := by + intro a ha + apply hf a + intro hmem + apply ha + rw [Block.mem_footprint] at hmem ⊢ + dsimp only [Block.Owns] at hmem ⊢ + omega + by_cases hsplit : j / 3 < 2 * Λ ^ 2 + · let a := ((((x.set beta (j / 3 / 2)).set beta' (j / 3 / 2 % Λ)).set + beta (j / 3 / 2 / Λ)).set digit (j / 3 % 2)).set mode (j % 3) + have f0 := hjfit.of_le (show j / 3 / 2 ≤ j by omega) + have f1' := hjfit.of_le (show j / 3 / 2 % Λ ≤ j from + (Nat.mod_le _ _).trans (by omega)) + have f2' := hjfit.of_le (show j / 3 / 2 / Λ ≤ j from + (Nat.div_le_self _ _).trans (by omega)) + have f3' := h.small.of_le (show j / 3 % 2 ≤ 5 by omega) + have f4' := h.small.of_le (show j % 3 ≤ 5 by omega) + have ha : ExecWithin w splitSetup x a 5 := by + ram_eval [splitSetup, a, h2, ht, hslot, hΛ, f0, f1', f2', f3', f4'] + have har : ScanParametersPreserved x a := by + refine ⟨rfl, ?_⟩ + intro i _ hb hb' hv hm + simp only [a, Data.set_regs, Function.update_of_ne hb, Function.update_of_ne hb', + Function.update_of_ne hv, Function.update_of_ne hm] + obtain ⟨y, hy, hm, ho, hf, _⟩ := (h.transfer har).exec + have he : Math.array U Λ rs j = Math.select U (j / 3 / 2 / Λ) + (j / 3 / 2 % Λ) (j / 3 % 2) (j % 3) rs := by + have hn : j % 3 ≠ 6 := by omega + simp [Math.array, Math.mode, Math.beta, Math.beta', Math.digit, hsplit, hn] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((ha.seq hy).branch_pos (by simpa only [Cond.holds_lt, ht, hs] using hsplit)).mono + (by omega) + · simpa +decide [a, hU, he] using hm + · simpa +decide [a, hU, he] using ho + · have hcond : ¬(Cond.lt triple splits).Holds x := by + simpa only [Cond.holds_lt, ht, hs] using hsplit + by_cases htwice : j / 3 = 2 * Λ ^ 2 + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode three x rs h h3 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 3 rs := by + simp [Math.array, Math.mode, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hone : j % 3 = 1 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, htwice, hone] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact ((((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode five x rs h h5 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 5 rs := by + simp [Math.array, Math.mode, htwice, hzero, hone, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact ((((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, + h1] using hone)).branch_neg + (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_pos + (by simpa only [Cond.holds_eq, ht, + hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · by_cases hzero : j % 3 = 0 + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_mode four x rs h h4 + have he : Math.array U Λ rs j = Math.select U (x.regs beta) (x.regs beta') + (x.regs digit) 4 rs := by + simp [Math.array, Math.mode, hsplit, htwice, hzero, Math.select_extra] + refine ⟨y, ?_, ?_, ?_, frame hf⟩ + · exact (((hy.branch_pos (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + · simpa only [hU, he] using hm + · simpa only [hU, he] using ho + · obtain ⟨y, hy, hm, ho, hf⟩ := exec_singleton x rs h + have he : Math.array U Λ rs j = [0] := by + simp [Math.array, Math.mode, hsplit, htwice, hzero] + refine ⟨y, ?_, by simpa only [he] using hm, by simpa only [he, + List.length_singleton] using ho, hf⟩ + exact (((hy.branch_neg (by simpa only [Cond.holds_eq, hslot, hz] using hzero)).branch_neg + (by simpa only [Cond.holds_eq, ht, hs] using htwice)).branch_neg hcond).mono (by omega) + +private theorem write_exec {w : ℕ} (x : Data 48) (h1 : x.regs one = 1) + (ha : Fits w (x.regs descriptors + 2 * x.regs current + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) + (hi : Fits w (x.regs current + 1)) : + ∃ y, ExecWithin w writeDescriptor x y 7 ∧ + y.regs current = x.regs current + 1 ∧ + y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem = Function.update (Function.update x.mem + (x.regs descriptors + 2 * x.regs current) (x.regs out)) + (x.regs descriptors + 2 * x.regs current + 1) (x.regs dst) := by + have f0 := ha.of_le (show x.regs current + x.regs current ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f1 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + have f2 := ha.of_le (show x.regs current + x.regs current + x.regs descriptors + 1 ≤ + x.regs descriptors + 2 * x.regs current + 2 by omega) + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [writeDescriptor, h1, f0, f1, f2, hh, hi] + all_goals simp +decide [Data.set_regs, Data.set_mem, Data.write_mem, + show x.regs current + x.regs current + x.regs descriptors = + x.regs descriptors + 2 * x.regs current by omega] + +/-- One complete array, its exact descriptor words, allocation advance and local frame. -/ +theorem body_exec {w U Λ j : ℕ} (x : Data 48) (rs : List (ℕ × ℕ)) + (hready : ScanReady w rs (x.set dst (x.regs heap))) + (hU : x.regs magnitude = U) (hΛ : x.regs digits = Λ) + (hs : x.regs splits = 2 * Λ ^ 2) (hj : x.regs current = j) + (hcap : rs.length + 1 ≤ x.regs capacity) + (hbefore : x.regs descriptors + 2 * j + 2 ≤ x.regs heap) + (ha : Fits w (x.regs descriptors + 2 * j + 2)) + (hh : Fits w (x.regs heap + x.regs capacity)) (hi : Fits w (j + 1)) : + ∃ y, ExecWithin w body x y (43 * rs.length + 30) ∧ + y.regs current = j + 1 ∧ y.regs heap = x.regs heap + x.regs capacity ∧ + y.mem (x.regs descriptors + 2 * j) = (Math.array U Λ rs j).length ∧ + y.mem (x.regs descriptors + 2 * j + 1) = x.regs heap ∧ + Block.HoldsList (x.regs heap) (Math.array U Λ rs j) y.mem ∧ + (∀ a, (a < x.regs heap ∨ x.regs heap + x.regs capacity ≤ a) → + a ≠ x.regs descriptors + 2 * j → a ≠ x.regs descriptors + 2 * j + 1 → + y.mem a = x.mem a) := by + have h3 : x.regs three = 3 := by + simpa +decide using hready.constants.2.2.2.1 + have h1 : x.regs one = 1 := by simpa +decide using hready.constants.2.1 + let a := ((x.set triple (j / 3)).set slot (j % 3)).set dst (x.regs heap) + have f0 := hi.of_le (show j / 3 ≤ j + 1 by omega) + have f1 := hi.of_le (show j % 3 ≤ j + 1 from (Nat.mod_le _ _).trans (by omega)) + have he : ExecWithin w + (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) x a 3 := by + ram_eval [a, h3, hj, f0, f1] + have hr : ScanReady w rs a := by + apply hready.transfer + refine ⟨rfl, ?_⟩ + intro i hil _ _ _ _ + have hit : i ≠ triple := by intro h; subst i; norm_num [triple] at hil + have his : i ≠ slot := by intro h; subst i; norm_num [slot] at hil + simp only [a, Data.set_regs, Function.update_apply] + by_cases hid : i = dst + · subst i; simp +decide + · simp only [hid, hit, his, ↓reduceIte] + obtain ⟨u, hu, hmem, hout, hf⟩ := pick_exec (j := j) a rs hr + (by simpa +decide [a] using hU) (by simpa +decide [a] using hΛ) + (by simpa +decide [a] using hs) (by simp +decide [a]) (by simp +decide [a]) + (hi.of_le (by omega)) + have hreg (i : Fin 48) (hp : pick.Preserves i) + (ht : i ≠ triple) (hs : i ≠ slot) (hd : i ≠ dst) : u.regs i = x.regs i := by + simpa only [a, Data.set_regs, Function.update_of_ne ht, Function.update_of_ne hs, + Function.update_of_ne hd] using hu.regs_eq hp + have huc : u.regs current = j := (hreg current (by decide) (by decide) (by decide) + (by decide)).trans hj + have huh : u.regs heap = x.regs heap := hreg heap (by decide) (by decide) (by decide) (by decide) + have hucap := hreg capacity (by decide) (by decide) (by decide) (by decide) + have hudesc := hreg descriptors (by decide) (by decide) (by decide) (by decide) + have hu1 : u.regs one = 1 := (hreg one (by decide) (by decide) (by decide) (by decide)).trans h1 + have hud : u.regs dst = x.regs heap := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hum : Block.HoldsList (x.regs heap) (Math.array U Λ rs j) u.mem := by + simpa +decide [a] using hmem + obtain ⟨y, hy, hyc, hyh, hym⟩ := write_exec u hu1 + (by simpa only [huc, hudesc] using ha) (by simpa only [huh, hucap] using hh) + (by simpa only [huc] using hi) + rw [huc, hudesc, hout, hud] at hym + refine ⟨y, (he.seq (hu.seq hy)).mono (by omega), by simpa only [huc] using hyc, + by simpa only [huh, hucap] using hyh, ?_, ?_, ?_, ?_⟩ + · rw [hym, Function.update_of_ne (by omega), Function.update_self] + · rw [hym, Function.update_self] + · apply hum.frame + intro b hlo hhi + rw [hym, Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + · intro b hb hb0 hb1 + rw [hym, Function.update_of_ne hb1, Function.update_of_ne hb0] + apply hf b + rw [Block.mem_footprint] + change ¬(a.regs dst ≤ b ∧ b < a.regs dst + (rs.length + 1)) + have had : a.regs dst = x.regs heap := by simp [a] + rw [had] + omega + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean new file mode 100644 index 0000000000..7a778c6c4a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Bounds.lean @@ -0,0 +1,184 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Execute + +/-! +# Derived native frontend word width and charged cost + +The physical unsigned width is four times the existing complete parameter width. This coarse +positive logarithmic threshold covers all descriptor/payload addresses and native intermediates. +The bit budget passed to radix is the explicit magnitude-derived label count, never physical width. + +## Main definitions + +* `layout`: concrete runtime layout at fixed exponent and input length. +* `width`: positive logarithmic physical word-width threshold. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Layout computed from the actual unsigned input-length header and polynomial promise. -/ +def layout (κ n : ℕ) : Layout := + ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * (n + 2) ^ κ)⟩ + +/-- Physical unsigned width, including the two bits reserved when composing signed kernels. -/ +def width (κ n : ℕ) : ℕ := 4 * ChanHe.Parameters.width κ n + +/-- Concrete charged instruction bound, including the final halt. -/ +def cost (κ n : ℕ) : ℕ := + let U := (n + 2) ^ κ + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ChanHe.Parameters.time κ n U + RadixSort.deduplicateTime n Λ + + (layout κ n).arrays * (43 * n + 32) + 38 + +/-- The width coefficient is positive, including exponent zero and empty input. -/ +theorem width_pos (κ n : ℕ) : 0 < width κ n := by + have := ChanHe.Parameters.width_pos κ n + dsimp only [width] + omega + +/-- The physical width is explicitly `Cκ * clog₂(n+2)` with a positive coefficient. -/ +theorem width_eq (κ n : ℕ) : + width κ n = (80 * κ + 484) * Nat.clog 2 (n + 2) := by + dsimp only [width, ChanHe.Parameters.width] + ring + +/-- All frontend arithmetic is bounded by the fourth power of the existing parameter budget. -/ +theorem array_budget_le (n U : ℕ) : + Arrays.budget ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U ≤ + ChanHe.Parameters.Math.budget n U ^ 4 := by + let P := ChanHe.Parameters.Math.budget n U + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + let A := 3 * (2 * Λ ^ 2 + 2) + have hP : 8 * U + 4 * n + 64 ≤ P := by dsimp [P, ChanHe.Parameters.Math.budget]; omega + have h64 : 64 ≤ P := by omega + have hn : n + 3 ≤ P := by omega + have hΛ : Λ ≤ P := by + have hlog := Nat.log_le_self 2 (4 * U) + dsimp only [Λ, Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + omega + have hsq : Λ ^ 2 ≤ P ^ 2 := Nat.pow_le_pow_left hΛ 2 + have hA : A ≤ 12 * P ^ 2 := by dsimp only [A]; nlinarith + have hm := Nat.mul_le_mul hn hA + have hbudget : Arrays.budget ⟨n, Λ⟩ U = 1 + 4 * n + (n + 3) * A + 8 * U + 8 * Λ + 64 := by + dsimp only [Arrays.budget, Layout.endpoint, Layout.pointer, Layout.payloads, + Layout.descriptors, Layout.capacity, Layout.arrays, Layout.count, Layout.splits, A] + ring + have hP3 : P ≤ P ^ 3 := by nlinarith + have hP4 : 64 * P ^ 3 ≤ P ^ 4 := by + have := Nat.mul_le_mul_right (P ^ 3) h64 + nlinarith + change Arrays.budget ⟨n, Λ⟩ U ≤ P ^ 4 + rw [hbudget] + nlinarith + +/-- The derived physical width discharges the complete native frontend budget. -/ +theorem array_budget_fits {κ n w : ℕ} (hw : width κ n ≤ w) : + Fits w (Arrays.budget (layout κ n) ((n + 2) ^ κ)) := by + have hp : Fits (ChanHe.Parameters.width κ n) + (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ)) := ChanHe.Parameters.budget_fits le_rfl + have hpow := Nat.pow_lt_pow_left hp (by decide : (4 : ℕ) ≠ 0) + have hf : Fits (width κ n) (ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4) := by + simpa only [Fits, width, ← pow_mul, Nat.mul_comm] using hpow + exact (hf.of_le (array_budget_le n ((n + 2) ^ κ))).mono hw + +/-- Input/header fitting follows from the same global magnitude and width promises. -/ +theorem input_fits {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + InputFits w (input.map Signed.encode) := by + apply ChanHe.Parameters.inputFits input (le_trans ?_ hw) hvalues + dsimp only [width] + omega + +/-- The lower-level radix bit bound includes the largest signed key, even when `κ=0`. -/ +theorem key_lt_two_pow {U : ℕ} (hU : 1 ≤ U) {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z < 2 ^ Cslib.Algorithms.ThreeSum.labelBits (2 * U) := by + have hk := (Math.key_bounds hz).1 + have hp := Nat.lt_pow_succ_log_self (by decide : 1 < 2) (4 * U) + change Signed.encode z < 2 ^ (Nat.log 2 (2 * (2 * U)) + 1) + rw [show 2 * (2 * U) = 4 * U by omega] + exact lt_of_le_of_lt (show Signed.encode z ≤ 4 * U by omega) hp + +/-- The explicit radix bit count has a logarithmic bound independent of admitted physical width. -/ +theorem digits_bound (κ n : ℕ) : + (layout κ n).digits ≤ (20 * κ + 121) * Nat.clog 2 (n + 2) := by + let E := 20 * κ + 120 + let L := Nat.clog 2 (n + 2) + let U := (n + 2) ^ κ + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hp : ChanHe.Parameters.Math.budget n U ≤ 2 ^ (E * L) := by + calc + _ ≤ (n + 2) ^ E := ChanHe.Parameters.Math.budget_polynomial κ n + _ ≤ (2 ^ L) ^ E := Nat.pow_le_pow_left (Nat.le_pow_clog (by decide) _) _ + _ = _ := by rw [← pow_mul]; congr 1; ring + have h4 : 4 * U ≤ ChanHe.Parameters.Math.budget n U := by + dsimp only [ChanHe.Parameters.Math.budget] + omega + have hlog : Nat.log 2 (4 * U) ≤ E * L := by + calc + _ ≤ Nat.log 2 (2 ^ (E * L)) := Nat.log_mono_right (h4.trans hp) + _ = _ := Nat.log_pow (by decide) _ + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ _ + dsimp only [Cslib.Algorithms.ThreeSum.labelBits] + rw [show 2 * (2 * U) = 4 * U by omega] + dsimp only [E, L] at * + nlinarith + +/-- The entire allocated region and arithmetic budget are polynomial in actual input length. -/ +theorem array_budget_polynomial (κ n : ℕ) : + Arrays.budget (layout κ n) ((n + 2) ^ κ) ≤ (n + 2) ^ (80 * κ + 480) := by + calc + _ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) ^ 4 := array_budget_le _ _ + _ ≤ ((n + 2) ^ (20 * κ + 120)) ^ 4 := + Nat.pow_le_pow_left (ChanHe.Parameters.Math.budget_polynomial κ n) 4 + _ = _ := by rw [← pow_mul]; congr 1; omega + +/-- Every compact output integer fits the parent's signed kernel width `physicalWidth-2`. +Thus the parent's native signed arithmetic compiler runs at exactly the physical width here. -/ +theorem signed_output_fits {κ n w : ℕ} (hw : width κ n ≤ w) {z : ℤ} + (hz : |z| ≤ (2 * (n + 2) ^ κ : ℕ)) : GenericRAM.Signed.Fits (w - 2) z := by + have hP := ChanHe.Parameters.budget_fits (κ := κ) (n := n) (w := ChanHe.Parameters.width κ n) + le_rfl + have hpos := ChanHe.Parameters.width_pos κ n + have hW : ChanHe.Parameters.width κ n + 3 ≤ w := by dsimp only [width] at hw; omega + have hfit : 2 * (n + 2) ^ κ < 2 ^ (w - 3) := by + have hmag : 2 * (n + 2) ^ κ ≤ ChanHe.Parameters.Math.budget n ((n + 2) ^ κ) := by + dsimp only [ChanHe.Parameters.Math.budget]; omega + exact (hmag.trans_lt hP).trans_le (Nat.pow_le_pow_right (by decide) (by omega)) + have habs := abs_le.mp hz + have hfi : ((2 * (n + 2) ^ κ : ℕ) : ℤ) < (2 : ℤ) ^ (w - 3) := by exact_mod_cast hfit + refine ⟨by omega, ?_, ?_⟩ + all_goals rw [show w - 2 - 1 = w - 3 by omega] + all_goals omega + +/-- A word at the canonical physical width has polynomial magnitude. -/ +theorem two_pow_width_le (κ n : ℕ) : 2 ^ width κ n ≤ (n + 2) ^ (160 * κ + 968) := by + let L := Nat.clog 2 (n + 2) + have hL : 0 < L := Nat.clog_pos (by decide) (by omega) + have hp : 2 ^ (L - 1) < n + 2 := + Nat.pow_pred_clog_lt_self (b := 2) (x := n + 2) (by decide) (by omega) + have heq : 2 ^ L = 2 * 2 ^ (L - 1) := by + conv_lhs => rw [show L = (L - 1) + 1 by omega] + rw [pow_succ, Nat.mul_comm] + have hceil : 2 ^ L ≤ (n + 2) ^ 2 := by rw [heq]; nlinarith + rw [width_eq] + calc + _ = (2 ^ L) ^ (80 * κ + 484) := by rw [← pow_mul]; congr 1; ring + _ ≤ ((n + 2) ^ 2) ^ (80 * κ + 484) := Nat.pow_le_pow_left hceil _ + _ = _ := by + rw [← pow_mul] + exact congrArg (fun e : ℕ => (n + 2) ^ e) (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean new file mode 100644 index 0000000000..578321d60c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Execute.lean @@ -0,0 +1,315 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +/-! +# Complete descriptor-table execution + +The invariant retains every completed descriptor and compact payload, both full radix arrays, +and all memory outside the allocated frontend region. + +## Main results + +The enumeration loop charges each native array scan, descriptor store and allocation advance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays + +open Cslib.WordRAM Select Scan + +/-- Immutable registers of the complete enumeration loop. -/ +structure Registers (l : Layout) (U k : ℕ) (x : Data 48) : Prop where + /-- Original magnitude promise. -/ + magnitude : x.regs Select.magnitude = U + /-- Original input length and explicit bit budget. -/ + inputLen : x.regs Arrays.inputLen = l.n + digits : x.regs Arrays.digits = l.digits + /-- Exact radix key/count pointers and distinct-key length. -/ + keys : x.regs Scan.keys = 1 + 2 * l.n + counts : x.regs Scan.counts = 1 + 3 * l.n + len : x.regs Scan.len = k + /-- Computed layout values. -/ + splits : x.regs Arrays.splits = l.splits + triples : x.regs Arrays.triples = l.count + arrays : x.regs Arrays.arrays = l.arrays + capacity : x.regs Arrays.capacity = l.capacity + descriptors : x.regs Arrays.descriptors = l.descriptors + heapEnd : x.regs Arrays.heapEnd = l.endpoint + /-- All small constants are real native registers. -/ + constants : x.regs zero = 0 ∧ x.regs one = 1 ∧ x.regs two = 2 ∧ + x.regs three = 3 ∧ x.regs four = 4 ∧ x.regs five = 5 + +/-- The body preserves every immutable layout and source register. -/ +theorem Registers.transfer {l : Layout} {U k : ℕ} {x y : Data 48} + (h : Registers l U k x) (hr : ∀ i, body.Preserves i → y.regs i = x.regs i) : + Registers l U k y := by + constructor + · exact (hr _ (by decide)).trans h.magnitude + · exact (hr _ (by decide)).trans h.inputLen + · exact (hr _ (by decide)).trans h.digits + · exact (hr _ (by decide)).trans h.keys + · exact (hr _ (by decide)).trans h.counts + · exact (hr _ (by decide)).trans h.len + · exact (hr _ (by decide)).trans h.splits + · exact (hr _ (by decide)).trans h.triples + · exact (hr _ (by decide)).trans h.arrays + · exact (hr _ (by decide)).trans h.capacity + · exact (hr _ (by decide)).trans h.descriptors + · exact (hr _ (by decide)).trans h.heapEnd + · obtain ⟨h0, h1, h2, h3, h4, h5⟩ := h.constants + exact ⟨(hr _ (by decide)).trans h0, (hr _ (by decide)).trans h1, + (hr _ (by decide)).trans h2, (hr _ (by decide)).trans h3, + (hr _ (by decide)).trans h4, (hr _ (by decide)).trans h5⟩ + +/-- Every layout scalar and partial allocation is below the explicit arithmetic budget. -/ +theorem layout_bounds (l : Layout) (U : ℕ) : + l.n + 1 ≤ budget l U ∧ l.digits * l.digits ≤ budget l U ∧ + l.splits ≤ budget l U ∧ l.count ≤ budget l U ∧ l.arrays ≤ budget l U ∧ + 2 * l.arrays ≤ budget l U ∧ l.payloads ≤ budget l U ∧ + l.arrays * l.capacity ≤ budget l U ∧ l.endpoint ≤ budget l U ∧ + 4 * U + 2 ≤ budget l U ∧ 5 ≤ budget l U := by + have hb : budget l U = l.endpoint + 8 * U + 8 * l.digits + 64 := rfl + have he : l.endpoint = l.payloads + l.capacity * l.arrays := rfl + have hp : l.payloads = l.descriptors + 2 * l.arrays := rfl + have hd : l.descriptors = 1 + 4 * l.n := rfl + have hs : l.splits = 2 * l.digits ^ 2 := rfl + have hc : l.count = l.splits + 2 := rfl + have ha : l.arrays = 3 * l.count := rfl + have hmul : l.arrays * l.capacity = l.capacity * l.arrays := Nat.mul_comm _ _ + have hsq : l.digits * l.digits = l.digits ^ 2 := (pow_two _).symm + omega + +/-- Setup computes all constants and addresses with actual instructions, preserving all memory. -/ +theorem setup_exec {w U : ℕ} (l : Layout) (k : ℕ) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hk : x.regs Scan.keys = 1 + 2 * l.n) + (hc : x.regs Scan.counts = 1 + 3 * l.n) (hlen : x.regs len = k) + (hd : x.regs descriptors = l.descriptors) (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w setup x y 19 ∧ Registers l U k y ∧ + y.regs current = 0 ∧ y.regs heap = l.pointer 0 ∧ y.mem = x.mem := by + obtain ⟨fN, fSq, fS, fD, fA, f2A, fP, fAC, fE, _, f5⟩ := layout_bounds l U + have f0 : Fits w 0 := fits_zero w + have f1 : Fits w 1 := hfit.of_le (by omega) + have f2 : Fits w 2 := hfit.of_le (by omega) + have f3 : Fits w 3 := hfit.of_le (by omega) + have f4 : Fits w 4 := hfit.of_le (by omega) + have f5' : Fits w 5 := hfit.of_le f5 + have fn := hfit.of_le fN + have fsq := hfit.of_le fSq + have fs : Fits w (l.digits * l.digits * 2) := by + simpa only [Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fS + have fd : Fits w (l.digits * l.digits * 2 + 2) := by + simpa only [Layout.count, Layout.splits, pow_two, Nat.mul_comm] using hfit.of_le fD + have fa : Fits w ((l.digits * l.digits * 2 + 2) * 3) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm] + using hfit.of_le fA + have f2a : Fits w ((l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le f2A + have fp : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2) := by + simpa only [Layout.payloads, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm, Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fP + have fac : Fits w ((l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.capacity, Layout.arrays, Layout.count, Layout.splits, pow_two, + Nat.mul_comm] using hfit.of_le fAC + have fe : Fits w (l.descriptors + (l.digits * l.digits * 2 + 2) * 3 * 2 + + (l.digits * l.digits * 2 + 2) * 3 * (l.n + 1)) := by + simpa only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.capacity, + Layout.arrays, Layout.count, Layout.splits, pow_two, Nat.mul_comm, + Nat.mul_left_comm, Nat.mul_assoc] using hfit.of_le fE + refine ⟨?y, ?run, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [setup, hn, hΛ, hd, f0, f1, f2, f3, f4, f5', fn, fsq, + fs, fd, fa, f2a, fp, fac, fe] + · constructor + all_goals simp +decide [Data.set_regs, hU, hn, hΛ, hk, hc, hlen, hd, + Layout.splits, Layout.count, Layout.arrays, Layout.capacity, Layout.endpoint, + Layout.pointer, Layout.payloads, pow_two, Nat.mul_comm, Nat.mul_assoc] + · simp + · simp +decide [Data.set_regs, Layout.pointer, Layout.payloads, Layout.arrays, + Layout.count, Layout.splits, pow_two, Nat.mul_comm, Nat.mul_assoc] + · rfl + +/-- All compact arrays and six-word descriptors are produced by the complete native loop. -/ +theorem loop_exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hr : Registers l U rs.length x) (hidx : x.regs current = 0) + (hheap : x.regs heap = l.pointer 0) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w (.loop (.lt current arrays) body) x y + (l.arrays * (43 * rs.length + 32) + 2) ∧ + Output l U rs y.mem ∧ y.regs current = l.arrays ∧ y.regs heap = l.endpoint ∧ + Registers l U rs.length y ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + have hb := layout_bounds l U + have he := hb.2.2.2.2.2.2.2.2.1 + have hlabel := hb.2.2.2.2.2.2.2.2.2.1 + have hsmall := hb.2.2.2.2.2.2.2.2.2.2 + have hbase : l.descriptors ≤ l.pointer 0 := by dsimp [Layout.pointer, Layout.payloads]; omega + have hN : 1 + 4 * l.n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors] + omega + let I (y : Data 48) : Prop := + Registers l U rs.length y ∧ y.regs current ≤ l.arrays ∧ + y.regs heap = l.pointer (y.regs current) ∧ + (∀ i < y.regs current, + y.mem (l.descriptors + 2 * i) = (Math.array U l.digits rs i).length ∧ + y.mem (l.descriptors + 2 * i + 1) = l.pointer i ∧ + Block.HoldsList (l.pointer i) (Math.array U l.digits rs i) y.mem) ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt current arrays).Holds y) : + ∃ z, ExecWithin w body y z (43 * rs.length + 30) ∧ I z ∧ + l.arrays - z.regs current < l.arrays - y.regs current := by + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hy + have hj : y.regs current < l.arrays := by + simpa only [Cond.holds_lt, hyr.arrays] using ht + have hnext : l.pointer (y.regs current + 1) ≤ l.endpoint := + l.pointer_mono (by omega) + have hcur : l.pointer (y.regs current) ≤ l.endpoint := l.pointer_mono (by omega) + have hcappos : 0 < l.capacity := by dsimp [Layout.capacity]; omega + have hsource : 1 + 4 * l.n ≤ l.pointer (y.regs current) := by + have := l.pointer_mono (i := 0) (j := y.regs current) (by omega) + change l.descriptors ≤ _ + omega + have hready : ScanReady w rs (y.set dst (y.regs heap)) := by + constructor + · simpa +decide using hyr.len + · simpa +decide using hyr.constants + · simpa +decide [hyr.keys] using hykeys + · simpa +decide [hyr.counts] using hycounts + · intro p hp + exact hfit.of_le ((hkeys p hp).1.trans (by omega)) + · intro p hp + simpa +decide [hyr.magnitude] using (hkeys p hp).2.trans (show U ≤ 2 * U by omega) + · intro p hp + exact hfit.of_le (show 2 * p.1 ≤ budget l U by have := (hkeys p hp).1; omega) + · simpa +decide [hyr.magnitude] using hfit.of_le hlabel + · exact hfit.of_le hsmall + · simpa +decide [hyr.keys] using hfit.of_le + (show 1 + 2 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyr.counts] using hfit.of_le + (show 1 + 3 * l.n + rs.length ≤ budget l U by omega) + · simpa +decide [hyheap] using hfit.of_le + (show l.pointer (y.regs current) + rs.length + 1 ≤ budget l U by + rw [Layout.pointer_succ] at hnext + dsimp only [Layout.capacity] at hnext + omega) + · simpa +decide [hyr.keys, hyheap] using + (show 1 + 2 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + · simpa +decide [hyr.counts, hyheap] using + (show 1 + 3 * l.n + rs.length ≤ l.pointer (y.regs current) by omega) + obtain ⟨z, hz, hzi, hzh, hzd0, hzd1, hzp, hzf⟩ := body_exec + (j := y.regs current) y rs hready hyr.magnitude hyr.digits + (by simpa only [Layout.splits] using hyr.splits) rfl + (by rw [hyr.capacity]; dsimp [Layout.capacity]; omega) + (by rw [hyr.descriptors, hyheap]; exact l.descriptor_before hj _) + (hfit.of_le (by + rw [hyr.descriptors] + have := l.descriptor_before hj 0 + have hp0 : l.pointer 0 ≤ l.endpoint := l.pointer_mono (i := 0) (j := l.arrays) (by omega) + omega)) + (by rw [hyheap, hyr.capacity, ← Layout.pointer_succ]; exact hfit.of_le (hnext.trans he)) + (hfit.of_le (by have := hb.2.2.2.2.1; omega)) + simp only [hyr.descriptors, hyheap, hyr.capacity] at hzd0 hzd1 hzp hzf hzh + have hf (b : ℕ) (houtside : b < l.pointer (y.regs current) ∨ + l.pointer (y.regs current + 1) ≤ b) + (hb0 : b ≠ l.descriptors + 2 * y.regs current) + (hb1 : b ≠ l.descriptors + 2 * y.regs current + 1) : z.mem b = y.mem b := by + apply hzf b _ hb0 hb1 + simpa only [Layout.pointer_succ] using houtside + refine ⟨z, hz, ?_, by omega⟩ + refine ⟨hyr.transfer (fun i hi => hz.regs_eq hi), by omega, ?_, ?_, ?_, ?_, ?_⟩ + · rw [hzi, Layout.pointer_succ] + exact hzh + · intro i hi + by_cases hij : i = y.regs current + · subst i + exact ⟨hzd0, hzd1, hzp⟩ + · have hi' : i < y.regs current := by omega + obtain ⟨h0, h1, hp⟩ := hyout i hi' + have hbefore := l.descriptor_before (hi'.trans hj) (y.regs current) + refine ⟨(hf _ (.inl (by omega)) (by omega) (by omega)).trans h0, + (hf _ (.inl (by omega)) (by omega) (by omega)).trans h1, ?_⟩ + apply hp.frame + intro b hlo hhi + have hlen' := (Math.length_array_le U l.digits rs i).trans + (show rs.length + 1 ≤ l.capacity by dsimp [Layout.capacity]; omega) + have hptr := l.pointer_mono (show i + 1 ≤ y.regs current by omega) + rw [Layout.pointer_succ] at hptr + have htable := l.descriptor_before hj i + exact hf b (.inl (by omega)) (by omega) (by omega) + · apply hykeys.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · apply hycounts.frame + intro b hlo hhi + simp only [List.length_map] at hhi + exact hf b (.inl (by omega)) (by dsimp [Layout.descriptors]; omega) + (by dsimp [Layout.descriptors]; omega) + · intro b hnot + have hb : b < l.descriptors ∨ l.endpoint ≤ b := by + rw [Block.mem_footprint] at hnot + dsimp only [Block.Owns] at hnot + have := hbase.trans (l.pointer_mono (show 0 ≤ l.arrays by omega)) + omega + have htable := l.descriptor_before hj (y.regs current) + have hb0 : b ≠ l.descriptors + 2 * y.regs current := by omega + have hb1 : b ≠ l.descriptors + 2 * y.regs current + 1 := by omega + exact (hf b (by rcases hb with hb | hb <;> omega) hb0 hb1).trans (hyframe b hnot) + have hx : I x := by + refine ⟨hr, by rw [hidx]; omega, by simpa only [hidx] using hheap, ?_, hk, hc, + Modifies.refl _ _⟩ + intro i hi + rw [hidx] at hi + omega + obtain ⟨y, hy, hI, ht⟩ := ExecWithin.loop_invariant I + (fun y => l.arrays - y.regs current) hbody hx + obtain ⟨hyr, hyidx, hyheap, hyout, hykeys, hycounts, hyframe⟩ := hI + have hend : y.regs current = l.arrays := by + simp only [Cond.holds_lt, hyr.arrays] at ht + omega + refine ⟨y, ?_, ?_, hend, ?_, hyr, hykeys, hycounts, hyframe⟩ + · simpa only [hidx, Nat.sub_zero, Nat.mul_comm] using hy + · intro j hj + exact hyout j (by omega) + · simpa only [hend, Layout.endpoint] using hyheap + +/-- Complete array/descriptor construction, including all native constant and layout setup. -/ +theorem exec {w U : ℕ} (l : Layout) (rs : List (ℕ × ℕ)) (x : Data 48) + (hU : x.regs Select.magnitude = U) (hn : x.regs inputLen = l.n) + (hΛ : x.regs digits = l.digits) (hkreg : x.regs Scan.keys = 1 + 2 * l.n) + (hcreg : x.regs Scan.counts = 1 + 3 * l.n) (hlenreg : x.regs len = rs.length) + (hd : x.regs descriptors = l.descriptors) (hlen : rs.length ≤ l.n) + (hk : Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) x.mem) + (hkeys : ∀ p ∈ rs, p.1 ≤ 2 * U + 1 ∧ p.1 / 2 ≤ U) + (hfit : Fits w (budget l U)) : + ∃ y, ExecWithin w program x y (l.arrays * (43 * rs.length + 32) + 21) ∧ + Output l U rs y.mem ∧ Registers l U rs.length y ∧ y.regs heap = l.endpoint ∧ + Block.HoldsList (1 + 2 * l.n) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * l.n) (rs.map Prod.snd) y.mem ∧ + Modifies (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint x.mem y.mem := by + obtain ⟨a, ha, har, hai, hah, ham⟩ := setup_exec l rs.length x hU hn hΛ hkreg hcreg + hlenreg hd hfit + obtain ⟨y, hy, hout, _, hyh, hyr, hykeys, hycounts, hyframe⟩ := loop_exec l rs a har hai hah + hlen (by simpa only [ham] using hk) (by simpa only [ham] using hc) hkeys hfit + exact ⟨y, (ha.seq hy).mono (by omega), hout, hyr, hyh, hykeys, hycounts, + by simpa only [ham] using hyframe⟩ + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Arrays diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean new file mode 100644 index 0000000000..7c0fa31f7e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Frame.lean @@ -0,0 +1,50 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout + +/-! +# Preserving complete frontend output + +Later native programs may allocate beyond the frontend's endpoint. Its exact descriptor and +payload interpretation survives a frame on the concrete allocated prefix. + +## Main results + +* `Output.frame`: preserve all descriptors and compact payloads below the frontend endpoint. + +This memory-frame bridge is original and reuses the existing output and block predicates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- A frame on the frontend allocation preserves every descriptor and actual payload word. -/ +theorem Output.frame {l : Layout} {U : ℕ} {rs : List (ℕ × ℕ)} {before after : ℕ → ℕ} + (h : Output l U rs before) (hcard : rs.length ≤ l.n) + (hp : ∀ p < l.endpoint, after p = before p) : Output l U rs after := by + intro j hj + have hlen : (Math.array U l.digits rs j).length ≤ l.capacity := + (Math.length_array_le U l.digits rs j).trans (by dsimp only [Layout.capacity]; omega) + have hend : l.pointer j + (Math.array U l.digits rs j).length ≤ l.endpoint := by + calc l.pointer j + (Math.array U l.digits rs j).length + ≤ l.pointer j + l.capacity := Nat.add_le_add_left hlen _ + _ = l.pointer (j + 1) := (l.pointer_succ j).symm + _ ≤ l.pointer l.arrays := l.pointer_mono (by omega) + have hdesc := l.descriptor_before hj j + refine ⟨?_, ?_, (h j hj).2.2.frame ?_⟩ + · exact (hp _ (by omega)).trans (h j hj).1 + · exact (hp _ (by omega)).trans (h j hj).2.1 + · intro p _ hlt + exact hp p (by omega) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean new file mode 100644 index 0000000000..d2662e8de4 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Input.lean @@ -0,0 +1,243 @@ +/- +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 + +* `exec_input`: complete initialized-input execution, exact descriptors and full multiplicities. +* `construct_preserves_parameters`: computed parameters remain available to the traversal caller. + +Execution uses a positive logarithmic physical width and fixed code per exponent. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +theorem parameterRegisters_injective : Function.Injective parameterRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [parameterRegisters] at this + omega + +theorem radixRegisters_injective : Function.Injective radixRegisters := by + intro i j h + exact Fin.ext (congrArg (fun z : Fin 128 => z.val) h) + +theorem arrayRegisters_injective : Function.Injective arrayRegisters := by + intro i j h + apply Fin.ext + have := congrArg Fin.val h + dsimp only [arrayRegisters] at this + omega + +/-- Parameter outputs remain available to the parent traversal without recomputation. -/ +theorem construct_preserves_parameters (i : Fin 24) : + construct.Preserves (parameterRegisters i) := by + fin_cases i <;> decide + +private theorem cost_sum_bound (p r A n k : ℕ) (hk : k ≤ n) : + p + (4 + (r + (9 + (A * (43 * k + 32) + 21 + 3)))) ≤ + p + r + A * (43 * n + 32) + 38 - 1 := by + have := Nat.mul_le_mul_left A (show 43 * k + 32 ≤ 43 * n + 32 by omega) + omega + +/-- Complete initialized-input native construction, with no supplied algorithm or fit advice. -/ +theorem exec_input {κ w : ℕ} (input : List ℤ) (hw : width κ input.length ≤ w) + (hvalues : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (y : Data 128) (rs : List (ℕ × ℕ)), + ExecWithin w (program κ) (Data.init (input.map Signed.encode)) y (cost κ input.length - 1) ∧ + Output (layout κ input.length) ((input.length + 2) ^ κ) rs y.mem ∧ + y.regs descriptorBase = (layout κ input.length).descriptors ∧ + y.regs descriptorCount = (layout κ input.length).count ∧ + y.regs heapEnd = (layout κ input.length).endpoint ∧ + (rs.map Prod.fst).Nodup ∧ (rs.map Prod.fst).Pairwise (· < ·) ∧ + (∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) ∧ + (∀ p ∈ rs, 0 < p.2 ∧ (input.map Signed.encode).count p.1 = p.2) ∧ + Block.HoldsList (1 + 2 * input.length) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (1 + 3 * input.length) (rs.map Prod.snd) y.mem ∧ + ChanHe.Parameters.Output input.length ((input.length + 2) ^ κ) + (y.comap parameterRegisters) ∧ + Modifies (Block.mk 1 ((layout κ input.length).endpoint - 1)).footprint + (Data.init (input.map Signed.encode) : Data 128).mem y.mem := by + let n := input.length + let U := (n + 2) ^ κ + let l := layout κ n + let x : Data 128 := Data.init (input.map Signed.encode) + have hparam : ChanHe.Parameters.width κ n ≤ w := by + change ChanHe.Parameters.width κ input.length ≤ w + dsimp only [width] at hw + omega + have hp := ChanHe.Parameters.budget_fits hparam + have hfront := array_budget_fits hw + have hN : Fits w (1 + 4 * n) := hp.of_le (by + dsimp only [ChanHe.Parameters.Math.budget]; omega) + have h1 : Fits w 1 := hN.of_le (by omega) + have h2 : Fits w 2 := hp.of_le (by dsimp only [ChanHe.Parameters.Math.budget]; omega) + have hB : Fits w l.digits := hp.of_le (by + have hl := Nat.log_le_self 2 (4 * U) + change Cslib.Algorithms.ThreeSum.labelBits (2 * U) ≤ ChanHe.Parameters.Math.budget n U + dsimp only [Cslib.Algorithms.ThreeSum.labelBits, ChanHe.Parameters.Math.budget] + rw [show 2 * (2 * U) = 4 * U by omega] + omega) + have hU : 1 ≤ U := Nat.one_le_pow _ _ (by omega) + obtain ⟨p, hep, hop, hmp⟩ := ChanHe.Parameters.exec κ (x.comap parameterRegisters) + (by simpa only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] using hp) + simp only [Data.comap_mem, x, Data.init_mem_zero, List.length_map] at hep hop + let a := p.extend parameterRegisters x + have ha := hep.rename parameterRegisters_injective + have hpr (i : Fin 24) : a.regs (parameterRegisters i) = p.regs i := by + exact congrArg (fun z : Data 24 => z.regs i) + (Data.comap_extend parameterRegisters_injective p x) + have han : a.regs (parameterRegisters ChanHe.Parameters.len) = n := + (hpr _).trans hop.len + have haU : a.regs (parameterRegisters ChanHe.Parameters.magnitude) = U := + (hpr _).trans hop.magnitude + have haB : a.regs (parameterRegisters ChanHe.Parameters.digits) = l.digits := + (hpr _).trans hop.digits + have ham : a.mem = x.mem := hmp + let b := (((a.set (radixRegisters RadixSort.len) n).set (radixRegisters RadixSort.src) 1).set + (radixRegisters RadixSort.dst) (n + 1)).set (radixRegisters RadixSort.bits) l.digits + have hb : ExecWithin w radixSetup a b 4 := by + have hn1 := hN.of_le (show n + 1 ≤ 1 + 4 * n by omega) + ram_eval [radixSetup, b, han, haB, h1, hB, hn1] + have hmem : Block.HoldsList 1 (input.map Signed.encode) (b.comap radixRegisters).mem := by + intro i + change a.mem (1 + i.val) = (input.map Signed.encode)[i.val] + rw [ham] + dsimp only [x] + rw [Nat.add_comm, Data.init_mem_succ] + simp only [List.getD, List.getElem?_eq_getElem i.isLt, Option.getD_some] + obtain ⟨q, rs, heq, hqout, hqkeys, hqcounts, hnodup, horder, hset, hcounts, hframe⟩ := + RadixSort.deduplicate_exec (input.map Signed.encode) (b.comap radixRegisters) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]) + (by simp +decide [b, Data.comap_regs, radixRegisters]) + (by simp +decide [b, Data.comap_regs, radixRegisters, n]; omega) + (by simp +decide [b, Data.comap_regs, radixRegisters]) hmem + (by + intro u hu + obtain ⟨z, hz, rfl⟩ := List.mem_map.mp hu + exact key_lt_two_pow hU (hvalues z hz)) + (input_fits input hw hvalues).2 (by simpa only [List.length_map] using hN) hB h2 + have hrlen : rs.length ≤ n := by + have ht := hnodup.length_le_of_subset (fun k hk => (hset k).mp hk) + simpa only [List.length_map] using ht + let c := q.extend radixRegisters b + have hradix := heq.rename radixRegisters_injective + have hcout : c.regs (radixRegisters Deduplicate.out) = rs.length := by + have he := congrArg (fun z : Data 16 => z.regs Deduplicate.out) + (Data.comap_extend radixRegisters_injective q b) + exact he.trans hqout + have hhigh (i : Fin 24) : c.regs (parameterRegisters i) = a.regs (parameterRegisters i) := by + have hnot : ¬∃ j, radixRegisters j = parameterRegisters i := by + rintro ⟨j, he⟩ + have := congrArg Fin.val he + dsimp only [radixRegisters, parameterRegisters] at this + omega + simp only [c, Data.extend, Function.extend_apply' _ _ _ hnot] + have hne (j : Fin 16) : parameterRegisters i ≠ radixRegisters j := by + intro he; exact hnot ⟨j, he.symm⟩ + simp only [b, Data.set_regs, Function.update_of_ne (hne _)] + have hcn := (hhigh _).trans han + have hcU := (hhigh _).trans haU + have hcB := (hhigh _).trans haB + let d := ((((((((c.set (arrayRegisters Scan.len) rs.length).set + (arrayRegisters Select.magnitude) U).set (arrayRegisters Arrays.inputLen) n).set + (arrayRegisters Arrays.digits) l.digits).set (arrayRegisters Select.one) 1).set + (arrayRegisters Scan.keys) (n + n)).set (arrayRegisters Scan.keys) (n + n + 1)).set + (arrayRegisters Scan.counts) (n + n + 1 + n)).set + (arrayRegisters Arrays.descriptors) (n + n + 1 + n + n) + have hd : ExecWithin w arraySetup c d 9 := by + have f0 := hN.of_le (show n + n ≤ 1 + 4 * n by omega) + have f1 := hN.of_le (show n + n + 1 ≤ 1 + 4 * n by omega) + have f2 := hN.of_le (show n + n + 1 + n ≤ 1 + 4 * n by omega) + have f3 := hN.of_le (show n + n + 1 + n + n ≤ 1 + 4 * n by omega) + ram_eval [arraySetup, d, hcn, hcU, hcB, hcout, h1, f0, f1, f2, f3] + have hrawkeys : ∀ v ∈ rs, v.1 ≤ 2 * U + 1 ∧ v.1 / 2 ≤ U := by + intro v hv + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hset v.1).mp (List.mem_map.mpr ⟨v, hv, rfl⟩)) + rw [← he] + exact ⟨(Math.key_bounds (hvalues z hz)).1, + by + have hz' := hvalues z hz + rw [Signed.encode_div_two] + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by + simpa only [Int.natCast_natAbs] using hz')⟩ + obtain ⟨r, her, hor, hregs, hrheap, hrkeys, hrcounts, hrframe⟩ := Arrays.exec l rs + (d.comap arrayRegisters) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters, l, layout]; omega) + (by simp +decide [d, Data.comap_regs, arrayRegisters]) + (by simp +decide [d, Data.comap_regs, arrayRegisters, Layout.descriptors, l, layout]; omega) + hrlen (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqkeys) + (by simpa only [Data.comap_mem, d, c, Data.set_mem, Data.extend, List.length_map, l, + layout] using hqcounts) hrawkeys hfront + let e := r.extend arrayRegisters d + have harray := her.rename arrayRegisters_injective + have herg (i : Fin 48) : e.regs (arrayRegisters i) = r.regs i := + congrArg (fun z : Data 48 => z.regs i) (Data.comap_extend arrayRegisters_injective r d) + let y := ((e.set descriptorBase l.descriptors).set descriptorCount l.count).set heapEnd l.endpoint + have hy : ExecWithin w finish e y 3 := by + ram_eval [finish, y, herg Arrays.descriptors, herg Arrays.triples, herg Arrays.heapEnd, + hregs.descriptors, hregs.triples, hregs.heapEnd] + have hall := ha.seq (hb.seq (hradix.seq (hd.seq (harray.seq hy)))) + refine ⟨y, rs, ?_, hor, ?_, ?_, ?_, hnodup, horder, hset, hcounts, hrkeys, hrcounts, ?_, ?_⟩ + · apply hall.mono + simpa only [cost, List.length_map, n, U, l, layout] using cost_sum_bound + (ChanHe.Parameters.time κ n U) (RadixSort.deduplicateTime n l.digits) + l.arrays n rs.length hrlen + · rfl + · rfl + · rfl + · have hconstruct := hb.seq (hradix.seq (hd.seq (harray.seq hy))) + have hprf (i : Fin 24) : y.regs (parameterRegisters i) = p.regs i := + (hconstruct.regs_eq (construct_preserves_parameters i)).trans (hpr i) + -- Only register equalities are needed: the parameter output does not constrain memory. + exact ⟨(hprf _).trans hop.len, (hprf _).trans hop.magnitude, + (hprf _).trans hop.digits, (hprf _).trans hop.height, (hprf _).trans hop.fuel, + (hprf _).trans hop.want, (hprf _).trans hop.primeBound, + (hprf _).trans hop.packedLen, (hprf _).trans hop.padding⟩ + · intro baddr hbaddr + have hbfront : baddr ∉ (Block.mk l.descriptors (l.endpoint - l.descriptors)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hdesc : 1 ≤ l.descriptors := by dsimp [Layout.descriptors]; omega + omega + have hbradix : baddr ∉ (Block.mk 1 (4 * n)).footprint := by + intro hba + apply hbaddr + change baddr ∈ (Block.mk 1 (l.endpoint - 1)).footprint + rw [Block.mem_footprint] at hba ⊢ + dsimp only [Block.Owns] at hba ⊢ + have hbe : 1 + 4 * n ≤ l.endpoint := by + dsimp only [Layout.endpoint, Layout.pointer, Layout.payloads, Layout.descriptors, l, layout] + omega + omega + exact (hrframe baddr hbfront).trans ((hframe baddr + (by simpa only [List.length_map, n] using hbradix)).trans (congrFun ham baddr)) + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean new file mode 100644 index 0000000000..349ba2ea31 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Layout.lean @@ -0,0 +1,358 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Native frontend descriptor layout + +Descriptors have six words, three consecutive `(length,pointer)` pairs. Arrays occupy +disjoint blocks of capacity `n+1`; unused capacity is outside the compact enumeration. +The first four `n`-word blocks remain reserved for radix sorting and full multiplicities. + +## Main definitions + +* `Layout`: concrete descriptor and payload addresses. +* `array`: the compact list represented by one array slot. +* `Output`: exact descriptor words and payload readback, including empty arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +/-- Concrete layout at input length `n` and runtime label-bit budget `Λ`. -/ +structure Layout where + /-- Input length. -/ + n : ℕ + /-- Number of label bits. -/ + digits : ℕ + +namespace Layout + +/-- Number of bit-split triples. -/ +def splits (l : Layout) : ℕ := 2 * l.digits ^ 2 +/-- Total descriptor count, including the two multiplicity triples. -/ +def count (l : Layout) : ℕ := l.splits + 2 +/-- Total compact array count. -/ +def arrays (l : Layout) : ℕ := 3 * l.count +/-- Capacity allows singleton zero even when the original input is empty. -/ +def capacity (l : Layout) : ℕ := l.n + 1 +/-- Descriptor table follows all four radix buffers. -/ +def descriptors (l : Layout) : ℕ := 1 + 4 * l.n +/-- Payload blocks follow the complete descriptor table. -/ +def payloads (l : Layout) : ℕ := l.descriptors + 2 * l.arrays +/-- Pointer for array slot `j`. -/ +def pointer (l : Layout) (j : ℕ) : ℕ := l.payloads + l.capacity * j +/-- One-past-end address of all allocated blocks. -/ +def endpoint (l : Layout) : ℕ := l.pointer l.arrays + +/-- Payload pointers advance by the full reserved capacity. -/ +theorem pointer_succ (l : Layout) (j : ℕ) : l.pointer (j + 1) = l.pointer j + l.capacity := by + simp only [pointer, Nat.mul_add, Nat.mul_one, Nat.add_assoc] + +/-- Payload blocks occur in allocation order. -/ +theorem pointer_mono (l : Layout) {i j : ℕ} (h : i ≤ j) : l.pointer i ≤ l.pointer j := + Nat.add_le_add_left (Nat.mul_le_mul_left _ h) _ + +/-- Every descriptor word precedes every payload block. -/ +theorem descriptor_before (l : Layout) {j : ℕ} (hj : j < l.arrays) (i : ℕ) : + l.descriptors + 2 * j + 2 ≤ l.pointer i := by + dsimp only [pointer, payloads] + omega + +end Layout + +namespace Math + +/-- Selection mode for a physical array slot. -/ +def mode (Λ j : ℕ) : ℕ := + let t := j / 3 + let s := j % 3 + if t < 2 * Λ ^ 2 then s + else if t = 2 * Λ ^ 2 then if s = 0 then 3 else if s = 1 then 6 else 5 + else if s = 0 then 4 else 6 + +/-- Bit coordinates of a physical slot in the split part of the table. -/ +def beta (Λ j : ℕ) : ℕ := (j / 3 / 2) / Λ +@[inherit_doc beta] def beta' (Λ j : ℕ) : ℕ := (j / 3 / 2) % Λ +@[inherit_doc beta] def digit (j : ℕ) : ℕ := j / 3 % 2 + +/-- Compact encoded list for one physical array slot. -/ +def array (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : List ℕ := + if mode Λ j = 6 then [0] + else select U (beta Λ j) (beta' Λ j) (digit j) (mode Λ j) rs + +/-- The two bit coordinates are irrelevant in the three nonsplitting scan modes. -/ +theorem select_extra (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) (hm : 3 ≤ mode) : + select U β β' v mode rs = select U 0 0 0 mode rs := by + unfold select + congr 2 + funext p + unfold keep flag + split <;> first | rfl | omega + +/-- Physical array slots within a split triple follow A, B₀, B₁ order. -/ +theorem array_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t s : ℕ) + (ht : t < 2 * Λ ^ 2) (hs : s < 3) : + array U Λ rs (3 * t + s) = select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) s rs := by + have hd : (3 * t + s) / 3 = t := by omega + have hm : (3 * t + s) % 3 = s := by omega + have hn : s ≠ 6 := by omega + simp [array, mode, hd, hm, ht, hn, beta, beta', digit] + +/-- Read the three array specifications belonging to a descriptor. -/ +def root (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) : List ℕ × List ℕ × List ℕ := + (array U Λ rs (3 * t), array U Λ rs (3 * t + 1), array U Λ rs (3 * t + 2)) + +/-- A descriptor in the split prefix has exactly the requested three compact selections. -/ +theorem root_split (U Λ : ℕ) (rs : List (ℕ × ℕ)) (t : ℕ) (ht : t < 2 * Λ ^ 2) : + root U Λ rs t = + (select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 0 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 1 rs, + select U (t / 2 / Λ) (t / 2 % Λ) (t % 2) 2 rs) := by + have h0 := array_split U Λ rs t 0 ht (by decide) + simp only [Nat.add_zero] at h0 + simp only [root, h0, array_split U Λ rs t 1 ht (by decide), + array_split U Λ rs t 2 ht (by decide)] + +/-- The two final descriptors retain their singleton zeros and exact multiplicity scans. -/ +theorem root_extras (U Λ : ℕ) (rs : List (ℕ × ℕ)) : + root U Λ rs (2 * Λ ^ 2) = + (select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs) ∧ + root U Λ rs (2 * Λ ^ 2 + 1) = (select U 0 0 0 4 rs, [0], [0]) := by + constructor + all_goals simp [root, array, mode, select_extra, Nat.add_div, Nat.add_mod, + show ∀ n, ¬n + 1 < n from fun _ => by omega] + +/-- Every array fits in its `n+1` capacity, including empty-input singleton zeros. -/ +theorem length_array_le (U Λ : ℕ) (rs : List (ℕ × ℕ)) (j : ℕ) : + (array U Λ rs j).length ≤ rs.length + 1 := by + dsimp only [array] + split_ifs + · simp + · exact (length_select_le _ _ _ _ _ _).trans (by omega) + +/-- Every descriptor represents an injective enumeration. -/ +theorem array_nodup (U Λ : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) (j : ℕ) : (array U Λ rs j).Nodup := by + dsimp only [array] + split_ifs + · simp + · exact select_nodup _ _ _ _ _ h + +/-- Each output word is canonical zero, an original input key, or the key of twice an input. -/ +theorem array_values {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + u = 0 ∨ ∃ z ∈ input, u = Signed.encode z ∨ u = Signed.encode (2 * z) := by + unfold array at hu + split_ifs at hu + · exact .inl (by simpa only [List.mem_singleton] using hu) + · unfold select at hu + obtain ⟨p, hp, rfl⟩ := List.mem_map.mp hu + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr + ⟨p, (List.mem_filter.mp hp).1, rfl⟩)) + refine .inr ⟨z, hz, ?_⟩ + rw [← he] + unfold value + split_ifs + · exact .inr (twice_encode z) + · exact .inl rfl + +/-- Every compact output word is a canonical signed encoding. -/ +theorem array_canonical {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (U Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : Signed.encode (Signed.decode u) = u := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, _, rfl | rfl⟩ + · rfl + all_goals rw [Signed.decode_encode] + +/-- Injectivity holds for the represented integer array, as well as its native keys. -/ +theorem array_decoded_nodup {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (h : (rs.map Prod.fst).Nodup) (U Λ j : ℕ) : + ((array U Λ rs j).map Signed.decode).Nodup := by + apply List.Nodup.map_on _ (array_nodup U Λ h j) + intro u hu v hv he + rw [← array_canonical hkeys U Λ j u hu, ← array_canonical hkeys U Λ j v hv, he] + +/-- Every decoded payload integer has the exact reduction's common magnitude bound `2U`. -/ +theorem array_magnitude {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (Λ j u : ℕ) (hu : u ∈ array U Λ rs j) : + |Signed.decode u| ≤ (2 * U : ℕ) := by + rcases array_values hkeys U Λ j u hu with rfl | ⟨z, hz, rfl | rfl⟩ + · simp [Signed.decode] + · rw [Signed.decode_encode] + have := hvalues z hz + omega + · rw [Signed.decode_encode] + have := abs_le.mp (hvalues z hz) + apply abs_le.mpr + constructor <;> omega + +/-- The compact root lists before their native layout is assigned. -/ +def roots (U : ℕ) (rs : List (ℕ × ℕ)) : List (List ℕ × List ℕ × List ℕ) := + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + ((List.range Λ).flatMap fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) ++ + [(select U 0 0 0 3 rs, [0], select U 0 0 0 5 rs), + (select U 0 0 0 4 rs, [0], [0])] + +/-- Decode a compact triple to the finite sets used by the existing reduction. -/ +def decodeRoot (T : List ℕ × List ℕ × List ℕ) : Finset ℤ × Finset ℤ × Finset ℤ := + ((T.1.map Signed.decode).toFinset, (T.2.1.map Signed.decode).toFinset, + (T.2.2.map Signed.decode).toFinset) + +/-- Exact list equality with `ThreeSum.inputs`, preserving root order and every empty array. -/ +theorem roots_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + (roots U rs).map decodeRoot = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + simp only [roots, Cslib.Algorithms.ThreeSum.inputs, List.map_append, List.map_flatMap, + decodeRoot, select_A hkeys hcounts hvalues, + show ∀ β β' v, ((select U β β' v.toNat 1 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v false + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v false, + show ∀ β β' v, ((select U β β' v.toNat 2 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v true + (Finset.univ.image (fun i : Fin input.length => input[i])) + from fun β β' v => select_B hkeys hcounts hvalues β β' v true, + List.map_cons, List.map_nil, List.toFinset_cons, List.toFinset_nil, + Signed.decode, Nat.zero_mod, ↓reduceIte, Nat.zero_div, Int.natCast_zero, + Finset.insert_empty, select_twice hkeys hcounts, select_original hkeys hcounts, + select_zero hkeys hcounts] + +private theorem map_range_mul {α : Type*} (a b : ℕ) (f : ℕ → α) : + (List.range (a * b)).map f = + (List.range a).flatMap (fun i => (List.range b).map fun j => f (i * b + j)) := by + induction a with + | zero => simp + | succ a ih => + rw [Nat.succ_mul, List.range_add, List.map_append, ih, List.range_succ, + List.flatMap_append, List.flatMap_singleton] + simp [List.map_map, Function.comp_def, Nat.add_comm] + +/-- Physical descriptor indices have exactly the nested order of the finite root list. -/ +theorem roots_indexed (U : ℕ) (rs : List (ℕ × ℕ)) : + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = roots U rs := by + dsimp only + let Λ := Cslib.Algorithms.ThreeSum.labelBits (2 * U) + have hΛ : 0 < Λ := by dsimp [Λ, Cslib.Algorithms.ThreeSum.labelBits]; omega + have hlen : 2 * Λ ^ 2 = Λ * (Λ * 2) := by ring + change (List.range (2 * Λ ^ 2 + 2)).map (root U Λ rs) = _ + rw [List.range_add, List.map_append] + have hprefix : (List.range (2 * Λ ^ 2)).map (root U Λ rs) = + (List.range Λ).flatMap (fun β => (List.range Λ).flatMap fun β' => + [false, true].map fun v => + (select U β β' v.toNat 0 rs, select U β β' v.toNat 1 rs, + select U β β' v.toNat 2 rs)) := by + rw [hlen, map_range_mul] + apply List.flatMap_congr + intro β hβ + rw [map_range_mul] + apply List.flatMap_congr + intro β' hβ' + have hb : β < Λ := List.mem_range.mp hβ + have hb' : β' < Λ := List.mem_range.mp hβ' + have hdiv : (β * Λ + β') / Λ = β := by + rw [Nat.add_comm, Nat.add_mul_div_right _ _ hΛ, Nat.div_eq_of_lt hb', Nat.zero_add] + have hmod : (β * Λ + β') % Λ = β' := Nat.mul_add_mod_of_lt hb' + have hlt0 : β * (Λ * 2) + (β' * 2 + 0) < 2 * Λ ^ 2 := by nlinarith + have hlt1 : β * (Λ * 2) + (β' * 2 + 1) < 2 * Λ ^ 2 := by nlinarith + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + root_split _ _ _ _ hlt0, root_split _ _ _ _ hlt1] + have hmul : β * (Λ * 2) = 2 * (β * Λ) := by ring + have hd0 : (β * (Λ * 2) + (β' * 2 + 0)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hd1 : (β * (Λ * 2) + (β' * 2 + 1)) / 2 = β * Λ + β' := by rw [hmul]; omega + have hm0 : (β * (Λ * 2) + (β' * 2 + 0)) % 2 = 0 := by rw [hmul]; omega + have hm1 : (β * (Λ * 2) + (β' * 2 + 1)) % 2 = 1 := by rw [hmul]; omega + simp only [hd0, hd1, hm0, hm1, hdiv, hmod, Bool.toNat_false, Bool.toNat_true] + rw [hprefix] + simp only [show List.range 2 = [0, 1] from rfl, List.map_cons, List.map_nil, + Nat.add_zero, (root_extras U Λ rs).1, + (root_extras U Λ rs).2] + rfl + +end Math + +/-- Exact descriptor and compact payload readback. Unused capacity is unrestricted. -/ +def Output (l : Layout) (U : ℕ) (rs : List (ℕ × ℕ)) (mem : ℕ → ℕ) : Prop := + ∀ j < l.arrays, + mem (l.descriptors + 2 * j) = (Math.array U l.digits rs j).length ∧ + mem (l.descriptors + 2 * j + 1) = l.pointer j ∧ + Block.HoldsList (l.pointer j) (Math.array U l.digits rs j) mem + +/-- Follow one `(length,pointer)` pair in native memory. This is output interpretation, +not an instruction or an assumed computation by the frontend. -/ +def readArray (mem : ℕ → ℕ) (a : ℕ) : List ℕ := + (List.range (mem a)).map fun i => mem (mem (a + 1) + i) + +/-- Follow all six-word descriptors in order and decode their represented sets. -/ +def readRoots (l : Layout) (mem : ℕ → ℕ) : List (Finset ℤ × Finset ℤ × Finset ℤ) := + (List.range l.count).map fun t => + let a := l.descriptors + 6 * t + Math.decodeRoot (readArray mem a, readArray mem (a + 2), readArray mem (a + 4)) + +/-- Exact slot readback includes zero-length arrays and ignores unused capacity. -/ +theorem Output.read_array {l : Layout} {U : ℕ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output l U rs mem) {j : ℕ} (hj : j < l.arrays) : + readArray mem (l.descriptors + 2 * j) = Math.array U l.digits rs j := by + obtain ⟨hl, hp, hm⟩ := h j hj + unfold readArray + rw [hl, hp] + apply List.ext_getElem + · simp + · intro i hi hi' + simpa only [List.getElem_map, List.getElem_range] using hm.get i hi' + +/-- The descriptor table reads back as exactly the existing finite frontend list, in order. -/ +theorem Output.readback {n U : ℕ} {input : List ℤ} {rs : List (ℕ × ℕ)} {mem : ℕ → ℕ} + (h : Output ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ U rs mem) + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) : + readRoots ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ mem = + Cslib.Algorithms.ThreeSum.inputs U (fun i : Fin input.length => input[i]) := by + let l : Layout := ⟨n, Cslib.Algorithms.ThreeSum.labelBits (2 * U)⟩ + have he : readRoots l mem = + ((List.range l.count).map (Math.root U l.digits rs)).map Math.decodeRoot := by + simp only [readRoots, List.map_map, Function.comp_def] + apply List.map_congr_left + intro t ht + have hti : t < l.count := List.mem_range.mp ht + have hs (s : ℕ) (hs : s < 3) : + readArray mem (l.descriptors + 6 * t + 2 * s) = Math.array U l.digits rs (3 * t + s) := by + have hj : 3 * t + s < l.arrays := by dsimp only [Layout.arrays]; omega + have ha : l.descriptors + 6 * t + 2 * s = l.descriptors + 2 * (3 * t + s) := by omega + rw [ha] + exact h.read_array hj + have h0 := hs 0 (by decide) + have h1 := hs 1 (by decide) + have h2 := hs 2 (by decide) + simpa only [Math.root, Nat.mul_zero, Nat.add_zero, Nat.mul_one] using + congrArg Math.decodeRoot (congrArg₂ Prod.mk h0 (congrArg₂ Prod.mk h1 h2)) + rw [he] + have hi := Math.roots_indexed U rs + change (List.range l.count).map (Math.root U l.digits rs) = Math.roots U rs at hi + rw [hi] + exact Math.roots_decoded hkeys hcounts hvalues + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean new file mode 100644 index 0000000000..9e704e6bdb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Model.lean @@ -0,0 +1,282 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ThreeSum.ThreeSets +public import Complexity.Computability.WordRAM.Signed.Encoding +public import Complexity.Computability.WordRAM.ListArray +public import Mathlib.Data.List.Nodup + +/-! +# Finite specifications for the native distinct-position frontend + +The mathematical sets are the existing `ThreeSum.splitA`, `ThreeSum.splitB`, +`ThreeSum.twiceSet` and `ThreeSum.zeroSet`. Native keys remain in unsigned encoding order. +The specifications here describe compact subsequences of the radix key/count arrays. + +## Main definitions + +* `label`: compute the shifted label from a canonical signed key. +* `twice`: the canonical key of twice the represented integer. +* `select`: compact selections from full key/count pairs. + +## Main results + +* `label_encode`: native label arithmetic agrees with the existing mathematical label. +* `twice_encode`: doubling has the required positive sign. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM +namespace Math + +/-- Shift a sign-and-magnitude key into the nonnegative label interval. -/ +def label (U u : ℕ) : ℕ := if u % 2 = 0 then 2 * U + u / 2 else 2 * U - u / 2 + +/-- The canonical key for twice the integer represented by a canonical key. -/ +def twice (u : ℕ) : ℕ := 2 * u - u % 2 + +/-- A mode selects A, B₀, B₁, repeated nonzero values, repeated zero, or all values. +Mode six is reserved for the separately written constant singleton zero. -/ +def flag (a b v mode : ℕ) (p : ℕ × ℕ) : Bool := + match mode with + | 0 => decide (a = v) + | 1 => decide (a ≠ v ∧ b = 0) + | 2 => decide (a ≠ v ∧ b = 1) + | 3 => decide (p.1 ≠ 0 ∧ 2 ≤ p.2) + | 4 => decide (p.1 = 0 ∧ 3 ≤ p.2) + | 5 => true + | _ => false + +/-- Selection from the two native label digits. -/ +def keep (U β β' v mode : ℕ) (p : ℕ × ℕ) : Bool := + flag ((label U p.1 >>> β) % 2) ((label U p.1 >>> β') % 2) v mode p + +/-- Key transformation after selection; only the repeated-nonzero mode doubles values. -/ +def value (mode u : ℕ) : ℕ := if mode = 3 then twice u else u + +/-- Compact output of one native scan. Multiplicities are never truncated. -/ +def select (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : List ℕ := + ((rs.filter (keep U β β' v mode)).map fun p => value mode p.1) + +/-- A single scan writes at most one cell per key/count pair. -/ +theorem length_select_le (U β β' v mode : ℕ) (rs : List (ℕ × ℕ)) : + (select U β β' v mode rs).length ≤ rs.length := by + simpa only [select, List.length_map] using List.length_filter_le (keep U β β' v mode) rs + +/-- Native label arithmetic, including the absence of subtraction underflow. -/ +theorem label_encode {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + label U (Signed.encode z) = Cslib.Algorithms.ThreeSum.lab (2 * U) z := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + by_cases hneg : z < 0 + · have habs : z = -(z.natAbs : ℤ) := by + rw [Int.ofNat_natAbs_of_nonpos (by omega : z ≤ 0)] + omega + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, one_ne_zero, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + · have habs := Int.eq_natAbs_of_nonneg (by omega : 0 ≤ z) + simp only [label, Signed.encode_mod_two, hneg, ↓reduceIte, + Signed.encode_div_two, Cslib.Algorithms.ThreeSum.lab] + omega + +/-- The transient unsigned double is corrected by its low sign bit. -/ +theorem twice_encode (z : ℤ) : twice (Signed.encode z) = Signed.encode (2 * z) := by + have hs : (2 * z < 0) ↔ z < 0 := by omega + rw [twice, Signed.encode_mod_two] + simp only [Signed.encode, Int.natAbs_mul, show (2 : ℤ).natAbs = 2 from rfl, hs] + split_ifs <;> omega + +/-- Label arithmetic and the transient double stay within a common explicit bound. -/ +theorem key_bounds {U : ℕ} {z : ℤ} (hz : |z| ≤ (U : ℤ)) : + Signed.encode z ≤ 2 * U + 1 ∧ label U (Signed.encode z) ≤ 3 * U ∧ + Signed.encode z / 2 ≤ 2 * U ∧ 2 * Signed.encode z ≤ 4 * U + 2 := by + have hn : z.natAbs ≤ U := by + exact_mod_cast (show (z.natAbs : ℤ) ≤ (U : ℤ) by simpa only [Int.natCast_natAbs] using hz) + simp only [label, Signed.encode] + split_ifs <;> omega + +/-- The unsigned transformation is injective, even before restricting to canonical keys. -/ +theorem twice_injective : Function.Injective twice := by + intro a b h + have ha := Nat.mod_lt a (by decide : 0 < 2) + have hb := Nat.mod_lt b (by decide : 0 < 2) + dsimp only [twice] at h + omega + +/-- Every output value transformation preserves injectivity. -/ +theorem value_injective (mode : ℕ) : Function.Injective (value mode) := by + intro a b h + by_cases hm : mode = 3 + · exact twice_injective (by simpa only [value, hm, ↓reduceIte] using h) + · simpa only [value, hm, ↓reduceIte] using h + +/-- Every compact scan enumerates its output without repetitions. -/ +theorem select_nodup (U β β' v mode : ℕ) {rs : List (ℕ × ℕ)} + (h : (rs.map Prod.fst).Nodup) : (select U β β' v mode rs).Nodup := by + have hk : ((rs.filter (keep U β β' v mode)).map Prod.fst).Nodup := + h.sublist ((List.filter_sublist).map _) + simpa only [select, List.map_map, Function.comp_def] using + hk.map (value_injective mode) + +/-- The supplied radix contracts identify every run with its exact input multiplicity. -/ +theorem mem_runs {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (p : ℕ × ℕ) : + p ∈ rs ↔ ∃ z ∈ input, p = (Signed.encode z, input.count z) := by + constructor + · intro hp + obtain ⟨z, hz, he⟩ := List.mem_map.mp ((hkeys p.1).mp (List.mem_map.mpr ⟨p, hp, rfl⟩)) + have hc := hcounts p hp + rw [← he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + exact ⟨z, hz, Prod.ext he.symm hc.symm⟩ + · rintro ⟨z, hz, rfl⟩ + obtain ⟨p, hp, he⟩ := List.mem_map.mp ((hkeys (Signed.encode z)).mpr + (List.mem_map.mpr ⟨z, hz, rfl⟩)) + have hc := hcounts p hp + rw [he, List.count_map_of_injective _ _ Signed.encode_injective] at hc + have hp' : p = (Signed.encode z, input.count z) := Prod.ext he hc.symm + exact hp' ▸ hp + +/-- A compact native selection represents precisely its finite filtered image. -/ +theorem select_decoded {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v mode : ℕ) : + ((select U β β' v mode rs).map Signed.decode).toFinset = + (input.toFinset.filter fun z => keep U β β' v mode + (Signed.encode z, input.count z) = true).image + (fun z => Signed.decode (value mode (Signed.encode z))) := by + ext z + simp only [List.mem_toFinset, List.mem_map, select, List.mem_filter, Finset.mem_image, + Finset.mem_filter, mem_runs hkeys hcounts] + constructor + · rintro ⟨u, ⟨p, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + exact ⟨a, ⟨ha, hkeep⟩, rfl⟩ + · rintro ⟨a, ⟨ha, hkeep⟩, rfl⟩ + exact ⟨_, ⟨_, ⟨⟨a, ha, rfl⟩, hkeep⟩, rfl⟩, rfl⟩ + +/-- List multiplicity is exactly the number of original input positions. -/ +theorem count_positions (input : List ℤ) (z : ℤ) : + (Finset.univ.filter fun i : Fin input.length => input[i] = z).card = input.count z := + Fin.card_filter_univ_eq_vector_get_eq_count z ⟨input, rfl⟩ + +/-- Original input values are precisely the image of its positions. -/ +theorem input_values (input : List ℤ) : + Finset.univ.image (fun i : Fin input.length => input[i]) = input.toFinset := by + ext z + simp only [Finset.mem_image, Finset.mem_univ, true_and, List.mem_toFinset] + constructor + · rintro ⟨i, rfl⟩ + exact List.getElem_mem i.isLt + · intro hz + obtain ⟨i, hi, he⟩ := List.mem_iff_getElem.mp hz + exact ⟨⟨i, hi⟩, he⟩ + +/-- Native division/shift digits agree with the existing Boolean bit operation. -/ +theorem digit_eq (u β : ℕ) (v : Bool) : (u >>> β) % 2 = v.toNat ↔ u.testBit β = v := by + rw [Nat.shiftRight_eq_div_pow, ← Nat.toNat_testBit] + cases u.testBit β <;> cases v <;> decide + +/-- Zero has exactly one canonical key. -/ +theorem encode_eq_zero (z : ℤ) : Signed.encode z = 0 ↔ z = 0 := by + change Signed.encode z = Signed.encode 0 ↔ z = 0 + exact Signed.encode_injective.eq_iff + +/-- Exact bridge from the repeated-nonzero native scan to the existing `twiceSet`. -/ +theorem select_twice {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 3 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.twiceSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, ne_eq, encode_eq_zero, value, ↓reduceIte, + twice_encode, Signed.decode_encode, Cslib.Algorithms.ThreeSum.twiceSet, + input_values, count_positions] + +/-- The original-set scan preserves every distinct input value. -/ +theorem select_original {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 5 rs).map Signed.decode).toFinset = + Finset.univ.image (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts, input_values] + simp [keep, flag, value] + +/-- The zero scan uses the full original multiplicity and threshold three. -/ +theorem select_zero {input : List ℤ} {rs : List (ℕ × ℕ)} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (U β β' v : ℕ) : + ((select U β β' v 4 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.zeroSet (fun i : Fin input.length => input[i]) := by + rw [select_decoded hkeys hcounts] + simp only [keep, flag, decide_eq_true_eq, encode_eq_zero, value, Nat.reduceEqDiff, + ↓reduceIte, Signed.decode_encode, Finset.image_id', Cslib.Algorithms.ThreeSum.zeroSet, + count_positions] + by_cases hc : 3 ≤ input.count 0 + · have hm : (0 : ℤ) ∈ input := List.count_pos_iff.mp (by omega) + ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.mem_singleton] + constructor + · exact fun h => h.2.1 + · rintro rfl + exact ⟨hm, rfl, hc⟩ + · ext z + simp only [hc, ↓reduceIte, Finset.mem_filter, List.mem_toFinset, Finset.notMem_empty, + iff_false, not_and] + intro _ hz + subst z + exact hc + +/-- The A scan represents the exact existing bit split. -/ +theorem select_A {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v : Bool) : + ((select U β β' v.toNat 0 rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitA (2 * U) β v + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + simp only [value, Nat.reduceEqDiff, ↓reduceIte, Signed.decode_encode, Finset.image_id'] + apply Finset.filter_congr + intro z hz + simp only [List.mem_toFinset] at hz + simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz), digit_eq] + +/-- Both B scans represent the exact existing two-bit split, including `β=β'`. -/ +theorem select_B {input : List ℤ} {rs : List (ℕ × ℕ)} {U : ℕ} + (hkeys : ∀ k, k ∈ rs.map Prod.fst ↔ k ∈ input.map Signed.encode) + (hcounts : ∀ p ∈ rs, (input.map Signed.encode).count p.1 = p.2) + (hvalues : ∀ z ∈ input, |z| ≤ (U : ℤ)) (β β' : ℕ) (v w : Bool) : + ((select U β β' v.toNat (if w then 2 else 1) rs).map Signed.decode).toFinset = + Cslib.Algorithms.ThreeSum.splitB (2 * U) β β' v w + (Finset.univ.image (fun i : Fin input.length => input[i])) := by + rw [select_decoded hkeys hcounts, input_values] + cases w + all_goals simp only [Bool.false_eq_true, ↓reduceIte, value, Nat.reduceEqDiff, + Signed.decode_encode, Finset.image_id'] + all_goals apply Finset.filter_congr + all_goals intro z hz + all_goals simp only [List.mem_toFinset] at hz + all_goals simp only [keep, flag, decide_eq_true_eq, label_encode (hvalues z hz)] + all_goals simp only [ne_eq, digit_eq _ _ v] + all_goals cases v <;> cases (Cslib.Algorithms.ThreeSum.lab (2 * U) z).testBit β <;> + simp only [Bool.false_eq_true, Bool.true_eq_false, not_false_eq_true, not_true_eq_false, + true_and, false_and, Bool.not_true, Bool.not_false] + all_goals first | exact digit_eq _ _ false | exact digit_eq _ _ true + +end Math +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean new file mode 100644 index 0000000000..8463c29760 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Program.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Scan +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Layout +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Parameters +public import Complexity.Algorithms.WordRAM.RadixSort.Deduplicate + +/-! +# Initialized-input native distinct-position frontend + +The ABI has 128 registers. Radix uses 0–15; frontend locals use 16–63; the existing parameter +program uses 64–87. Registers 88–90 return descriptor base, descriptor count and heap end. +Each descriptor has six words `(length, pointer)` for its three compact injective arrays. +Each array has capacity `n+1`, including the singleton zeros required when `n=0`. + +## Main definitions + +* `Arrays.program`: native enumeration and descriptor construction. +* `program`: parameters, explicit-bit radix deduplication, and all frontend arrays. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Arrays + +open Select Scan + +/-- Original input length and label-bit budget. -/ +abbrev inputLen : Fin 48 := 27 +@[inherit_doc inputLen] abbrev digits : Fin 48 := 28 +/-- Number of split triples, total triple count and total array count. -/ +abbrev splits : Fin 48 := 29 +@[inherit_doc splits] abbrev triples : Fin 48 := 30 +@[inherit_doc splits] abbrev arrays : Fin 48 := 31 +/-- Capacity per compact array. -/ +abbrev capacity : Fin 48 := 32 +/-- Next payload address. -/ +abbrev heap : Fin 48 := 33 +/-- Descriptor table base. -/ +abbrev descriptors : Fin 48 := 34 +/-- Current array index, current triple and slot. -/ +abbrev current : Fin 48 := 35 +@[inherit_doc current] abbrev triple : Fin 48 := 36 +@[inherit_doc current] abbrev slot : Fin 48 := 37 +/-- Arithmetic temporary and final heap endpoint. -/ +abbrev temp : Fin 48 := 38 +@[inherit_doc temp] abbrev heapEnd : Fin 48 := 39 + +/-- Write the singleton-zero array, including on empty input. -/ +def singleton : Code .standard 48 := ram[ *dst ← zero; out ← one ] + +/-- Compute the exact array selection mode for the two multiplicity triples. -/ +def extra : Code .standard 48 := ram[ + if triple = splits then [ + if slot = zero then [ mode ← three; ⟪Scan.program⟫ ] else [ + if slot = one then [ ⟪singleton⟫ ] else [ mode ← five; ⟪Scan.program⟫ ] ] + ] else [ + if slot = zero then [ mode ← four; ⟪Scan.program⟫ ] else [ ⟪singleton⟫ ] ] ] + +/-- Decode `β`, then `β'`, then `[false,true]`, and scan the indicated one of three arrays. -/ +def splitSetup : Code .standard 48 := ram[ + beta ← triple / two; beta' ← beta % digits; beta ← beta / digits; + digit ← triple % two; mode ← slot ] + +/-- Split setup followed by the complete compacting scan. -/ +def split : Code .standard 48 := .seq splitSetup Scan.program + +/-- Native choice between the split prefix and the two multiplicity triples. -/ +def pick : Code .standard 48 := .branch (.lt triple splits) split extra + +/-- Write a compact array's two descriptor words and advance the allocation cursor. -/ +def writeDescriptor : Code .standard 48 := ram[ + addr ← current + current; addr ← addr + descriptors; *addr ← out; + addr ← addr + one; *addr ← dst; + heap ← heap + capacity; current ← current + one ] + +/-- One complete array and its two descriptor words. -/ +def body : Code .standard 48 := + .seq (ram[ triple ← current / three; slot ← current % three; dst ← heap ]) + (.seq pick writeDescriptor) + +/-- Initialize every constant, layout and cursor, then enumerate every array, including empties. -/ +def setup : Code .standard 48 := ram[ + zero ← #0; one ← #1; two ← #2; three ← #3; four ← #4; five ← #5; + beta ← #0; beta' ← #0; digit ← #0; + capacity ← inputLen + one; + splits ← digits * digits; splits ← splits * two; + triples ← splits + two; arrays ← triples * three; + temp ← arrays * two; heap ← descriptors + temp; + temp ← arrays * capacity; heapEnd ← heap + temp; + current ← #0 ] + +/-- Setup and all array iterations. -/ +def program : Code .standard 48 := .seq setup (.loop (.lt current arrays) body) + +end Arrays + +/-- Embed the 24-register parameter program above both native kernels. -/ +def parameterRegisters (i : Fin 24) : Fin 128 := ⟨64 + i.val, by omega⟩ +/-- Embed the 16-register radix program in the low registers. -/ +def radixRegisters (i : Fin 16) : Fin 128 := ⟨i.val, by omega⟩ +/-- Embed the 48-register frontend program between radix and parameters. -/ +def arrayRegisters (i : Fin 48) : Fin 128 := ⟨16 + i.val, by omega⟩ + +/-- Final descriptor base, descriptor count and one-past-end payload address. -/ +abbrev descriptorBase : Fin 128 := 88 +@[inherit_doc descriptorBase] abbrev descriptorCount : Fin 128 := 89 +@[inherit_doc descriptorBase] abbrev heapEnd : Fin 128 := 90 + +/-- Setup for the lower-level sorter with the magnitude-derived bit bound, never physical width. -/ +def radixSetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let B := parameterRegisters ChanHe.Parameters.digits + let radixLen := radixRegisters RadixSort.len + let radixSrc := radixRegisters RadixSort.src + let radixDst := radixRegisters RadixSort.dst + let radixBits := radixRegisters RadixSort.bits + ram[ + radixLen ← n; radixSrc ← #1; radixDst ← radixLen + radixSrc; radixBits ← B ] + +/-- Transfer actual parameters and radix outputs into the concrete frontend ABI. -/ +def arraySetup : Code .standard 128 := + let n := parameterRegisters ChanHe.Parameters.len + let U := parameterRegisters ChanHe.Parameters.magnitude + let B := parameterRegisters ChanHe.Parameters.digits + let distinct := radixRegisters Deduplicate.out + let len := arrayRegisters Scan.len + let mag := arrayRegisters Select.magnitude + let inputLen := arrayRegisters Arrays.inputLen + let digits := arrayRegisters Arrays.digits + let keys := arrayRegisters Scan.keys + let counts := arrayRegisters Scan.counts + let descriptors := arrayRegisters Arrays.descriptors + let one := arrayRegisters Select.one + ram[ + len ← distinct; mag ← U; inputLen ← n; digits ← B; one ← #1; + keys ← n + n; keys ← keys + one; + counts ← keys + n; descriptors ← counts + n ] + +/-- Return metadata while preserving parameter outputs and full radix key/count arrays. -/ +def finish : Code .standard 128 := + let base := arrayRegisters Arrays.descriptors + let count := arrayRegisters Arrays.triples + let endpoint := arrayRegisters Arrays.heapEnd + ram[ descriptorBase ← base; descriptorCount ← count; heapEnd ← endpoint ] + +/-- Native frontend after the existing complete parameter program. -/ +def construct : Code .standard 128 := + .seq radixSetup (.seq (RadixSort.deduplicateCode.rename radixRegisters) + (.seq arraySetup (.seq (Arrays.program.rename arrayRegisters) finish))) + +/-- One finite native program for each fixed polynomial magnitude exponent. -/ +def program (κ : ℕ) : Code .standard 128 := + .seq ((ChanHe.Parameters.program κ).rename parameterRegisters) construct + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean new file mode 100644 index 0000000000..086209b833 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Scan.lean @@ -0,0 +1,277 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Select + +/-! +# Compact native frontend scans + +Each pass reads both canonical keys and their full multiplicities. It appends only selected +keys to a disjoint destination. The singleton-zero arrays are written separately, so empty +input needs no exceptional semantics. + +## Main definitions + +* `Scan.program`: charged compact selection from the radix output. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan + +open Cslib.WordRAM Select + +/-- Key pointer, count pointer and number of distinct keys. -/ +abbrev keys : Fin 48 := 20 +@[inherit_doc keys] abbrev counts : Fin 48 := 21 +@[inherit_doc keys] abbrev len : Fin 48 := 22 +/-- Output pointer, input cursor and output length. -/ +abbrev dst : Fin 48 := 23 +@[inherit_doc dst] abbrev idx : Fin 48 := 24 +@[inherit_doc dst] abbrev out : Fin 48 := 25 +/-- Address scratch. -/ +abbrev addr : Fin 48 := 26 + +/-- Load one full key/count pair, compute its selection, optionally append, and advance. -/ +def body : Code .standard 48 := + .seq (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) + (.seq Select.program (ram[ + if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ])) + +/-- Reset both cursors and scan all distinct keys. -/ +def program : Code .standard 48 := + .seq (ram[ idx ← #0; out ← #0 ]) (.loop (.lt idx len) body) + +/-- One charged compacting iteration. -/ +theorem step {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hsmall : Fits w 5) + (hkaddr : Fits w (x.regs keys + x.regs idx)) + (hcaddr : Fits w (x.regs counts + x.regs idx)) + (hdaddr : Fits w (x.regs dst + x.regs out)) + (hi : Fits w (x.regs idx + 1)) (ho : Fits w (x.regs out + 1)) + (hkey : Fits w (x.mem (x.regs keys + x.regs idx))) + (habs : x.mem (x.regs keys + x.regs idx) / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.mem (x.regs keys + x.regs idx))) : + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + ∃ y, ExecWithin w body x y 41 ∧ + y.regs idx = x.regs idx + 1 ∧ y.regs out = x.regs out + k.toNat ∧ + y.mem = (if k then Function.update x.mem (x.regs dst + x.regs out) + (Math.value (x.regs mode) p.1) else x.mem) := by + dsimp only + let p := (x.mem (x.regs keys + x.regs idx), x.mem (x.regs counts + x.regs idx)) + let a := (((x.set addr (x.regs keys + x.regs idx)).set key p.1).set + addr (x.regs counts + x.regs idx)).set count p.2 + have ha : ExecWithin w + (ram[ addr ← keys + idx; key ← *addr; addr ← counts + idx; count ← *addr ]) x a 4 := by + ram_eval [a, p, hkaddr, hcaddr, hkey] + obtain ⟨u, hu, hut, huk, hum⟩ := Select.exec a + (by simpa +decide [a] using hz) (by simpa +decide [a] using h1) + (by simpa +decide [a] using h2) (by simpa +decide [a] using h3) + (by simpa +decide [a] using h4) (by simpa +decide [a] using h5) + hkey (by simpa +decide [a, p] using habs) (by simpa +decide [a] using hfit) + hdouble hsmall + have hui : u.regs idx = x.regs idx := by + simpa +decide [a] using hu.regs_eq (i := idx) (by decide) + have huo : u.regs out = x.regs out := by + simpa +decide [a] using hu.regs_eq (i := out) (by decide) + have hud : u.regs dst = x.regs dst := by + simpa +decide [a] using hu.regs_eq (i := dst) (by decide) + have hu1 : u.regs one = 1 := by + simpa +decide [a] using (hu.regs_eq (i := one) (by decide)).trans + (show a.regs one = 1 by simpa +decide [a] using h1) + simp +decide only [a, Data.set_regs, Function.update_apply, ↓reduceIte] at hut huk hum + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) p + have hv : Fits w (Math.value (x.regs mode) p.1) := by + dsimp only [Math.value, Math.twice] + split_ifs + · exact hdouble.of_le (Nat.sub_le _ _) + · exact hkey + have hdo : Fits w (u.regs dst + u.regs out) := by simpa only [hud, huo] using hdaddr + have hii : Fits w (u.regs idx + 1) := by simpa only [hui] using hi + have hoo : Fits w (u.regs out + 1) := by simpa only [huo] using ho + have hvu : Fits w (u.regs key) := by rw [huk]; exact hv + by_cases he : k = true + · have ht : u.regs take = 1 := by rw [hut]; change k.toNat = 1; simp [he] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?posY 6 := by + ram_eval [ht, hu1, hdo, hii, hoo, hvu] + refine ⟨?posY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he] + · simp +decide [Data.set_mem, Data.write_mem, hud, huo, huk, hum, k, p, he] + · have he' : k = false := Bool.eq_false_iff.mpr he + have ht : u.regs take = 0 := by rw [hut]; change k.toNat = 0; simp [he'] + have ht' : ExecWithin w + (ram[ if take = one then [ addr ← dst + out; *addr ← key; out ← out + one ] + else [ addr ← addr ]; idx ← idx + one ]) u ?negY 6 := by + ram_eval [ht, hu1, hii] + refine ⟨?negY, ha.seq (hu.seq ht'), ?_, ?_, ?_⟩ + · simp +decide [Data.set_regs, hui] + · simp +decide [Data.set_regs, huo, k, p, he'] + · simpa only [k, p, he', Bool.false_eq_true, ↓reduceIte, Data.set_mem] using hum + +/-- Complete compact scan, with arbitrary old output/scratch memory and an exact memory frame. +The source key/count blocks precede the output; their contents and all caller registers that +the body does not write are retained. -/ +theorem exec {w : ℕ} (rs : List (ℕ × ℕ)) (x : Data 48) + (hn : x.regs len = rs.length) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hk : Block.HoldsList (x.regs keys) (rs.map Prod.fst) x.mem) + (hc : Block.HoldsList (x.regs counts) (rs.map Prod.snd) x.mem) + (hkey : ∀ p ∈ rs, Fits w p.1) + (habs : ∀ p ∈ rs, p.1 / 2 ≤ 2 * x.regs magnitude) + (hdouble : ∀ p ∈ rs, Fits w (2 * p.1)) + (hfit : Fits w (4 * x.regs magnitude + 2)) (hsmall : Fits w 5) + (hka : Fits w (x.regs keys + rs.length)) + (hca : Fits w (x.regs counts + rs.length)) + (hda : Fits w (x.regs dst + rs.length + 1)) + (hkd : x.regs keys + rs.length ≤ x.regs dst) + (hcd : x.regs counts + rs.length ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (43 * rs.length + 4) ∧ + Block.HoldsList (x.regs dst) + (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs) y.mem ∧ + y.regs out = (Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs).length ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem ∧ + (∀ j, body.Preserves j → y.regs j = x.regs j) := by + let selected (i : ℕ) := Math.select (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (rs.take i) + have hselected (i : ℕ) : (selected i).length ≤ min i rs.length := + (Math.length_select_le _ _ _ _ _ _).trans_eq List.length_take + let I (y : Data 48) : Prop := + (∀ j, body.Preserves j → y.regs j = x.regs j) ∧ + y.regs idx ≤ rs.length ∧ y.regs out = (selected (y.regs idx)).length ∧ + Block.HoldsList (x.regs keys) (rs.map Prod.fst) y.mem ∧ + Block.HoldsList (x.regs counts) (rs.map Prod.snd) y.mem ∧ + Block.HoldsList (x.regs dst) (selected (y.regs idx)) y.mem ∧ + Modifies (Block.mk (x.regs dst) rs.length).footprint x.mem y.mem + have hbody (y : Data 48) (hy : I y) (ht : (Cond.lt idx len).Holds y) : + ∃ z, ExecWithin w body y z 41 ∧ I z ∧ + rs.length - z.regs idx < rs.length - y.regs idx := by + obtain ⟨hr, hle, hout, hkeys, hcounts, hacc, hframe⟩ := hy + have hyl := (hr len (by decide)).trans hn + have hys := hr keys (by decide) + have hyc := hr counts (by decide) + have hyd := hr dst (by decide) + have hyU := hr magnitude (by decide) + have hyb := hr beta (by decide) + have hyb' := hr beta' (by decide) + have hyv := hr digit (by decide) + have hym := hr mode (by decide) + have hi : y.regs idx < rs.length := by simpa only [Cond.holds_lt, hyl] using ht + have hgetk : y.mem (y.regs keys + y.regs idx) = rs[y.regs idx].1 := by + rw [hys] + exact (hkeys.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hgetc : y.mem (y.regs counts + y.regs idx) = rs[y.regs idx].2 := by + rw [hyc] + exact (hcounts.get (y.regs idx) (by simpa using hi)).trans (List.getElem_map _) + have hu : y.regs out ≤ y.regs idx := by + rw [hout]; exact (hselected _).trans (min_le_left _ _) + obtain ⟨z, he, hzi, hzo, hzm⟩ := step y + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) hsmall + (hka.of_le (by rw [hys]; omega)) (hca.of_le (by rw [hyc]; omega)) + (hda.of_le (by rw [hyd]; omega)) (hka.of_le (by omega)) (hda.of_le (by omega)) + (by rw [hgetk]; exact hkey _ (List.getElem_mem hi)) + (by rw [hgetk, hyU]; exact habs _ (List.getElem_mem hi)) + (by simpa only [hyU] using hfit) + (by rw [hgetk]; exact hdouble _ (List.getElem_mem hi)) + dsimp only at hzo hzm + rw [hyU, hyb, hyb', hyv, hym, hgetk, hgetc] at hzo hzm + let k := Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) rs[y.regs idx] + change z.regs out = y.regs out + k.toNat at hzo + change z.mem = (if k then Function.update y.mem (y.regs dst + y.regs out) + (Math.value (x.regs mode) rs[y.regs idx].1) else y.mem) at hzm + have hsuc : selected (y.regs idx + 1) = selected (y.regs idx) ++ + (if k then [Math.value (x.regs mode) rs[y.regs idx].1] else []) := by + dsimp only [selected] + rw [List.take_succ_eq_append_getElem hi] + simp only [Math.select, List.filter_append, List.map_append, List.filter_cons, + List.filter_nil] + split_ifs <;> rfl + have hf (a : ℕ) (hne : a ≠ x.regs dst + y.regs out) : z.mem a = y.mem a := by + rw [hzm, hyd] + split_ifs <;> simp only [Function.update_of_ne hne] + refine ⟨z, he, ?_, by omega⟩ + refine ⟨?_, by omega, ?_, ?_, ?_, ?_, ?_⟩ + · intro j hj + exact (he.regs_eq hj).trans (hr j hj) + · rw [hzi, hsuc, List.length_append, hzo, hout] + cases k <;> simp + · apply hkeys.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · apply hcounts.frame + intro a hlo hhi + apply hf + simp only [List.length_map] at hhi + omega + · rw [hzi, hsuc] + by_cases htake : k = true + · rw [ite_eq_left htake] + have hm : z.mem = Function.update y.mem (x.regs dst + (selected (y.regs idx)).length) + (Math.value (x.regs mode) rs[y.regs idx].1) := by + simpa only [htake, ↓reduceIte, hyd, hout] using hzm + rw [hm] + exact hacc.append_one _ + · have hfalse : k = false := Bool.eq_false_iff.mpr htake + rw [ite_eq_right htake, List.append_nil] + have hm : z.mem = y.mem := by simpa only [hfalse, Bool.false_eq_true, ↓reduceIte] + using hzm + simpa only [hm] using hacc + · intro a hnot + have hne : a ≠ x.regs dst + y.regs out := by + intro ha + apply hnot + apply Block.mem_footprint.mpr + change x.regs dst ≤ a ∧ a < x.regs dst + rs.length + omega + exact (hf a hne).trans (hframe a hnot) + let x₀ := (x.set idx 0).set out 0 + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, hk, hc, ?_, Modifies.refl _ _⟩ + · intro j hj + have hji : j ≠ idx := by rintro rfl; exact (by decide : ¬body.Preserves idx) hj + have hjo : j ≠ out := by rintro rfl; exact (by decide : ¬body.Preserves out) hj + simp only [x₀, Data.set_regs, Function.update_of_ne hji, Function.update_of_ne hjo] + · simp +decide [x₀] + · simp +decide [x₀, selected, Math.select] + · simpa +decide [x₀, selected, Math.select] using Block.holdsList_nil (x.regs dst) x.mem + obtain ⟨y, he, hy, ht⟩ := ExecWithin.loop_invariant I + (fun y => rs.length - y.regs idx) hbody hx₀ + obtain ⟨hr, hle, hout, _, _, hacc, hframe⟩ := hy + have hend : y.regs idx = rs.length := by + have hyl := (hr len (by decide)).trans hn + simp only [Cond.holds_lt, hyl] at ht + omega + have hinit : ExecWithin (B := .standard) w (ram[ idx ← #0; out ← #0 ]) x x₀ 2 := by + ram_eval [x₀] + refine ⟨y, ?_, ?_, ?_, hframe, hr⟩ + · convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, ↓reduceIte, Nat.sub_zero] + omega + · simpa only [hend, selected, List.take_length] using hacc + · simpa only [hend, selected, List.take_length] using hout + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend.Scan diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean new file mode 100644 index 0000000000..7eb8c2d172 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Select.lean @@ -0,0 +1,250 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Model +public import Complexity.Computability.WordRAM.Notation + +/-! +# Native selection of canonical signed keys + +All arithmetic is unsigned. The sign bit and magnitude are decoded explicitly, the label +is formed by addition or subtraction, and its two digits are computed with native shifts and +remainders. The repeated-value case doubles the key and subtracts its original sign bit. + +## Main definitions + +* `Select.program`: concrete selection and transformation instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +open Cslib.WordRAM + +namespace Select + +/-- Magnitude bound, followed by two bit positions, selected digit and scan mode. -/ +abbrev magnitude : Fin 48 := 0 +@[inherit_doc magnitude] abbrev beta : Fin 48 := 1 +@[inherit_doc magnitude] abbrev beta' : Fin 48 := 2 +@[inherit_doc magnitude] abbrev digit : Fin 48 := 3 +@[inherit_doc magnitude] abbrev mode : Fin 48 := 4 +/-- Input key; replaced by its selected output value. -/ +abbrev key : Fin 48 := 5 +/-- Full input multiplicity, read without truncation. -/ +abbrev count : Fin 48 := 6 +/-- Small constants installed by the scan. -/ +abbrev zero : Fin 48 := 7 +@[inherit_doc zero] abbrev one : Fin 48 := 8 +@[inherit_doc zero] abbrev two : Fin 48 := 9 +@[inherit_doc zero] abbrev three : Fin 48 := 10 +@[inherit_doc zero] abbrev four : Fin 48 := 11 +@[inherit_doc zero] abbrev five : Fin 48 := 12 +/-- Scratch registers for decoding and label digits. -/ +abbrev sign : Fin 48 := 13 +@[inherit_doc sign] abbrev label : Fin 48 := 14 +@[inherit_doc sign] abbrev bitA : Fin 48 := 15 +@[inherit_doc sign] abbrev bitB : Fin 48 := 16 +/-- Selection flag, either zero or one. -/ +abbrev take : Fin 48 := 17 +/-- Magnitude temporary. -/ +abbrev absolute : Fin 48 := 18 + +/-- Explicit native sign/magnitude decoding, shifted label and binary digit computation. -/ +def labels : Code .standard 48 := ram[ + sign ← key % two; absolute ← key / two; label ← magnitude + magnitude; + if sign = zero then [ label ← label + absolute ] else [ label ← label - absolute ]; + bitA ← label >>> beta; bitA ← bitA % two; + bitB ← label >>> beta'; bitB ← bitB % two ] + +/-- A no-op used by unsuccessful selections. -/ +def skip : Code .standard 48 := ram[ take ← take ] + +/-- Select one of the six finite scan modes and perform the repeated-value transformation. -/ +def choose : Code .standard 48 := ram[ + take ← #0; + if mode = zero then [ + if bitA = digit then [ take ← one ] else [ ⟪skip⟫ ] + ] else [ if mode = one then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = zero then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = two then [ + if bitA = digit then [ ⟪skip⟫ ] else [ + if bitB = one then [ take ← one ] else [ ⟪skip⟫ ] ] + ] else [ if mode = three then [ + if key = zero then [ ⟪skip⟫ ] else [ + if count < two then [ ⟪skip⟫ ] else [ take ← one ] ]; + key ← key + key; key ← key - sign + ] else [ if mode = four then [ + if key = zero then [ + if count < three then [ ⟪skip⟫ ] else [ take ← one ] + ] else [ ⟪skip⟫ ] + ] else [ if mode = five then [ take ← one ] else [ ⟪skip⟫ ] ] ] ] ] ] ] + +/-- Every decoding, arithmetic, branch and selection instruction is part of this code. -/ +def program : Code .standard 48 := .seq labels choose + +/-- This scalar program never reads the physical word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- Registers below the decoding scratch are preserved by label calculation. -/ +theorem labels_preserves (j : Fin 48) (hj : j.val ≤ 12) : labels.Preserves j := by + have hne (k : Fin 48) (hk : 13 ≤ k.val) : (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [labels, Code.Preserves, Op.dest] + exact ⟨hne sign (by decide), hne absolute (by decide), hne label (by decide), + ⟨hne label (by decide), hne label (by decide)⟩, hne bitA (by decide), + hne bitA (by decide), hne bitB (by decide), hne bitB (by decide)⟩ + +/-- Selection preserves all caller parameters, constants and scan registers. -/ +theorem preserves (j : Fin 48) (hj : (j.val ≤ 12 ∧ j.val ≠ 5) ∨ 19 ≤ j.val) : + program.Preserves j := by + have hne (k : Fin 48) (hk : k.val = 5 ∨ 13 ≤ k.val ∧ k.val ≤ 18) : + (some k : Option (Fin 48)) ≠ some j := by + intro h + have := congrArg Fin.val (Option.some.inj h) + omega + simp only [program, labels, choose, skip, Code.Preserves, Op.bin, Op.dest] + repeat' constructor + all_goals exact hne _ (by decide) + +/-- Decoding and both binary digits cost at most eleven native instructions. -/ +theorem labels_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h2 : x.regs two = 2) + (hkey : Fits w (x.regs key)) + (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) : + ∃ y, ExecWithin w labels x y 11 ∧ + y.regs sign = x.regs key % 2 ∧ + y.regs label = Math.label (x.regs magnitude) (x.regs key) ∧ + y.regs bitA = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta) % 2 ∧ + y.regs bitB = (Math.label (x.regs magnitude) (x.regs key) >>> x.regs beta') % 2 ∧ + y.mem = x.mem := by + have fm := hkey.of_le (Nat.div_le_self _ 2) + have fs := hfit.of_le (show x.regs key % 2 ≤ 4 * x.regs magnitude + 2 by omega) + have fd := hfit.of_le (show x.regs magnitude + x.regs magnitude ≤ + 4 * x.regs magnitude + 2 by omega) + have fa := hfit.of_le (show x.regs magnitude + x.regs magnitude + x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + have fb := hfit.of_le (show x.regs magnitude + x.regs magnitude - x.regs key / 2 ≤ + 4 * x.regs magnitude + 2 by omega) + by_cases he : x.regs key % 2 = 0 + · have fsa := fa.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fa.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fa, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + · have fsa := fb.of_le (Nat.shiftRight_le _ (x.regs beta)) + have fsb := fb.of_le (Nat.shiftRight_le _ (x.regs beta')) + have fra := fsa.of_le (Nat.mod_le _ 2) + have frb := fsb.of_le (Nat.mod_le _ 2) + refine ⟨?_, ?run, ?_, ?_, ?_, ?_, ?_⟩ + case run => ram_eval [labels, hz, h2, hkey, fm, fs, fd, fb, fsa, fsb, fra, frb, he] + all_goals simp +decide [Data.set_regs, Math.label, he, two_mul] + +/-- Selection from already computed digits, with the full repeated-value count. -/ +theorem choose_exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hs : x.regs sign = x.regs key % 2) + (hfit : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w choose x y 20 ∧ + y.regs take = (Math.flag (x.regs bitA) (x.regs bitB) (x.regs digit) + (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + have f1 := hsmall.of_le (show 1 ≤ 5 by decide) + have fd : Fits w (x.regs key + x.regs key) := by simpa only [two_mul] using hfit + have fs : Fits w (x.regs key + x.regs key - x.regs key % 2) := fd.of_le (Nat.sub_le _ _) + rcases (show x.regs mode = 0 ∨ x.regs mode = 1 ∨ x.regs mode = 2 ∨ + x.regs mode = 3 ∨ x.regs mode = 4 ∨ x.regs mode = 5 ∨ 6 ≤ x.regs mode by omega) with + hm | hm | hm | hm | hm | hm | hm + · by_cases ha : x.regs bitA = x.regs digit + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 0 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases ha : x.regs bitA = x.regs digit + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, f1] + all_goals simp +decide [Data.set_regs, hm, ha, Math.value, Math.flag] + · by_cases hb : x.regs bitB = 1 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, ha, hb, f1] + all_goals simp +decide [Data.set_regs, hm, ha, hb, Math.value, Math.flag] + · by_cases hk : x.regs key = 0 + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hs, fd, fs] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice] + · by_cases hc : x.regs count < 2 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, hs, + fd, fs, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, Math.twice, hc, + show (2 ≤ x.regs count) ↔ ¬x.regs count < 2 from by omega, two_mul] + · by_cases hk : x.regs key = 0 + · by_cases hc : x.regs count < 3 + all_goals refine ⟨?_, ?_, ?_, ?_, ?_⟩ + all_goals try ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, hc, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag, hc, + show (3 ≤ x.regs count) ↔ ¬x.regs count < 3 from by omega] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, hk, f1] + all_goals simp +decide [Data.set_regs, hm, hk, Math.value, Math.flag] + · refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hm, f1] + all_goals simp +decide [Data.set_regs, hm, Math.value, Math.flag] + · have hn0 : x.regs mode ≠ 0 := by omega + have hn1 : x.regs mode ≠ 1 := by omega + have hn2 : x.regs mode ≠ 2 := by omega + have hn3 : x.regs mode ≠ 3 := by omega + have hn4 : x.regs mode ≠ 4 := by omega + have hn5 : x.regs mode ≠ 5 := by omega + refine ⟨?_, ?run, ?_, ?_, ?_⟩ + case run => ram_eval [choose, skip, hz, h1, h2, h3, h4, h5, hn0, hn1, hn2, hn3, hn4, hn5] + all_goals simp +decide [Data.set_regs, Math.value, Math.flag, hn3] + +/-- Complete native scalar selection. Its cost includes decoding, both label bits and doubling. -/ +theorem exec {w : ℕ} (x : Data 48) + (hz : x.regs zero = 0) (h1 : x.regs one = 1) (h2 : x.regs two = 2) + (h3 : x.regs three = 3) (h4 : x.regs four = 4) (h5 : x.regs five = 5) + (hkey : Fits w (x.regs key)) (habs : x.regs key / 2 ≤ 2 * x.regs magnitude) + (hfit : Fits w (4 * x.regs magnitude + 2)) + (hdouble : Fits w (2 * x.regs key)) (hsmall : Fits w 5) : + ∃ y, ExecWithin w program x y 31 ∧ + y.regs take = (Math.keep (x.regs magnitude) (x.regs beta) (x.regs beta') + (x.regs digit) (x.regs mode) (x.regs key, x.regs count)).toNat ∧ + y.regs key = Math.value (x.regs mode) (x.regs key) ∧ y.mem = x.mem := by + obtain ⟨u, hu, hus, _, hua, hub, hum⟩ := labels_exec x hz h2 hkey habs hfit + have hr (j : Fin 48) (hj : j.val ≤ 12) : u.regs j = x.regs j := + hu.regs_eq (labels_preserves j hj) + obtain ⟨y, hy, hyt, hyk, hym⟩ := choose_exec u + ((hr zero (by decide)).trans hz) ((hr one (by decide)).trans h1) + ((hr two (by decide)).trans h2) ((hr three (by decide)).trans h3) + ((hr four (by decide)).trans h4) ((hr five (by decide)).trans h5) + (by simpa only [hr key (by decide)] using hus) + (by simpa only [hr key (by decide)] using hdouble) hsmall + refine ⟨y, hu.seq hy, ?_, ?_, hym.trans hum⟩ + · simpa only [Math.keep, hr mode (by decide), hr digit (by decide), hr key (by decide), + hr count (by decide), hua, hub] using hyt + · simpa only [hr mode (by decide), hr key (by decide)] using hyk + +end Select +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean new file mode 100644 index 0000000000..02d4d8a195 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ThreeSum/Frontend/Time.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ThreeSum.Frontend.Bounds + +/-! +# Uniform logarithmic-square native frontend cost + +The bound includes the complete parameter program, explicit-bit radix sorting, full +multiplicities, every compact scan, all descriptor/payload setup and writes, and the final halt. +It is uniform over all admitted sufficiently wide words, including empty input. + +## Main results + +* `cost_bound`: an explicit `Oκ((n+1) clog₂(n+2)²)` instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ThreeSum.Frontend + +/-- Explicit uniform coefficient; deliberately coarse and independent of physical word width. -/ +def costCoefficient (κ : ℕ) : ℕ := 2000 * (20 * κ + 121) ^ 2 + 400 * κ + 4000 + +/-- Concrete derivation from the actual charged instruction formula to logarithmic-square cost. -/ +theorem cost_bound (κ n : ℕ) : + cost κ n ≤ costCoefficient κ * (n + 1) * Nat.clog 2 (n + 2) ^ 2 := by + let L := Nat.clog 2 (n + 2) + let K := 20 * κ + 121 + let Λ := (layout κ n).digits + let A := (layout κ n).arrays + have hL : 1 ≤ L := Nat.clog_pos (by decide) (show 1 < n + 2 by omega) + have hK : 1 ≤ K := by dsimp [K]; omega + have hΛ : Λ ≤ K * L := digits_bound κ n + have hKL : 1 ≤ K * L := by nlinarith + have hKLsq : K * L ≤ (K * L) ^ 2 := by nlinarith + have hAs : A ≤ 12 * (K * L) ^ 2 := by + have hs := Nat.pow_le_pow_left hΛ 2 + dsimp only [A, Layout.arrays, Layout.count, Layout.splits, Λ] + nlinarith + have hRad : RadixSort.deduplicateTime n Λ ≤ (37 * Λ + 43) * (n + 1) := by + dsimp only [RadixSort.deduplicateTime] + nlinarith + have hRcoef : 37 * Λ + 43 ≤ 80 * (K * L) ^ 2 := by nlinarith + have hR := hRad.trans (Nat.mul_le_mul_right (n + 1) hRcoef) + have hA := Nat.mul_le_mul hAs (show 43 * n + 32 ≤ 75 * (n + 1) by omega) + have hParams := ChanHe.Parameters.time_bound κ n + have hsqrt := Nat.sqrt_le_self n + have hLsq : L ≤ L ^ 2 := by nlinarith + have hLN : L ≤ (n + 1) * L ^ 2 := by nlinarith + have hN : n + 1 ≤ (n + 1) * L ^ 2 := by nlinarith + have hC := Nat.mul_le_mul_left (400 * κ + 3000) hLN + change ChanHe.Parameters.time κ n ((n + 2) ^ κ) + RadixSort.deduplicateTime n Λ + + A * (43 * n + 32) + 38 ≤ _ + dsimp only [costCoefficient, K, L] at * + nlinarith + +end Cslib.Algorithms.WordRAM.ThreeSum.Frontend diff --git a/Complexity/Computability/GenericRAM/Signed/Fits.lean b/Complexity/Computability/GenericRAM/Signed/Fits.lean new file mode 100644 index 0000000000..aa069549a0 --- /dev/null +++ b/Complexity/Computability/GenericRAM/Signed/Fits.lean @@ -0,0 +1,39 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.GenericRAM.Signed +import Mathlib.Algebra.Order.Group.Abs + +/-! +# Signed fits from magnitude envelopes + +A representable nonnegative envelope covers every integer whose absolute value is at most it. +The envelope itself must fit: the asymmetric signed range still excludes its positive endpoint. + +## Main results + +* `Fits.of_natAbs_le`: derive signed fitting from a representable natural magnitude envelope. + +This elementary bound is original. +-/ + +@[expose] public section + +namespace Cslib.GenericRAM.Signed + +/-- A signed fit for a nonnegative envelope also covers every integer bounded by it. -/ +theorem Fits.of_natAbs_le {w B : ℕ} (hfit : Fits w (B : ℤ)) + {z : ℤ} (hz : z.natAbs ≤ B) : Fits w z := by + obtain ⟨hw, hlo, hhi⟩ := hfit + have habs := abs_le.mp (show |z| ≤ (B : ℤ) by + rw [← Int.natCast_natAbs] + exact_mod_cast hz) + exact ⟨hw, by omega, by omega⟩ + +end Cslib.GenericRAM.Signed diff --git a/Complexity/Computability/WordRAM/GhostLoop.lean b/Complexity/Computability/WordRAM/GhostLoop.lean new file mode 100644 index 0000000000..8f3b8e7d3c --- /dev/null +++ b/Complexity/Computability/WordRAM/GhostLoop.lean @@ -0,0 +1,59 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Structured + +/-! +# Native loop invariants with explicit proof state + +An invariant may relate the native data to additional mathematical proof state, such as the +represented worklist and emitted output. The decreasing measure then applies directly to that +proof state. It need not be reconstructed by an additional machine program or a choice of +representation from the native data alone. + +## Main results + +* `ExecWithin.loop_invariant_state`: the native loop rule with explicit proof state and a natural + decreasing measure, including all loop-control instructions in the bound. + +This is an original extension of the existing native invariant/measure rule. The body premise +is actual `ExecWithin` execution of the given native code, not a source execution or time oracle. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- Native loop rule carrying explicit mathematical proof state. The body must execute the +actual native code, preserve the relation and strictly decrease the proof-state measure. -/ +theorem ExecWithin.loop_invariant_state {B : Basis} {r w T : ℕ} {σ : Type*} + (I : σ → Data r → Prop) (μ : σ → ℕ) {cond : Cond r} {body : Code B r} + (hbody : ∀ s x, I s x → cond.Holds x → + ∃ s' y, ExecWithin w body x y T ∧ I s' y ∧ μ s' < μ s) + {s : σ} {x : Data r} (hx : I s x) : + ∃ s' y, ExecWithin w (.loop cond body) x y (μ s * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + have aux : ∀ n, ∀ s x, μ s ≤ n → I s x → + ∃ s' y, ExecWithin w (.loop cond body) x y (n * (T + 2) + 2) ∧ + I s' y ∧ ¬cond.Holds y := by + intro n + induction n using Nat.strong_induction_on with + | h n ih => + intro s x hμ hI + by_cases hc : cond.Holds x + · obtain ⟨s₁, y, hy, hIy, hdec⟩ := hbody s x hI hc + obtain ⟨s₂, z, hz, hIz, hexit⟩ := ih (μ s₁) (by omega) s₁ y le_rfl hIy + refine ⟨s₂, z, (ExecWithin.loop_iter hc hy hz).mono ?_, hIz, hexit⟩ + have hm := Nat.mul_le_mul_right (T + 2) (show μ s₁ + 1 ≤ n by omega) + rw [Nat.add_mul, Nat.one_mul] at hm + omega + · exact ⟨s, x, (ExecWithin.loop_exit hc).mono (by omega), hI, hc⟩ + exact aux (μ s) s x le_rfl hx + +end Cslib.WordRAM diff --git a/Complexity/Computability/WordRAM/Signed/Fits.lean b/Complexity/Computability/WordRAM/Signed/Fits.lean new file mode 100644 index 0000000000..72d8b0931d --- /dev/null +++ b/Complexity/Computability/WordRAM/Signed/Fits.lean @@ -0,0 +1,35 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.GenericRAM.Signed.Fits +public import Complexity.Computability.WordRAM.Signed.Encoding + +/-! +# Unsigned envelopes at the signed compiler's execution width + +Native callers use the same nonnegative envelope for encoded signed payloads and unsigned +addresses, counts and loop temporaries. The existing compiler executes at signed width plus two. + +## Main results + +* `unsigned_fits_of_signed`: a representable natural signed envelope fits that physical width. + +This representation bound is original and reuses the existing sign-and-magnitude encoding. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.Signed + +/-- One signed envelope suffices for every unsigned intermediate at the execution width. -/ +theorem unsigned_fits_of_signed {w B : ℕ} (hfit : GenericRAM.Signed.Fits w (B : ℤ)) : + Fits (w + 2) B := + ((encode_fits hfit).mono (by omega)).of_le (by simp [encode]; omega) + +end Cslib.WordRAM.Signed diff --git a/Complexity/Computability/WordRAM/Width.lean b/Complexity/Computability/WordRAM/Width.lean new file mode 100644 index 0000000000..4c96d67331 --- /dev/null +++ b/Complexity/Computability/WordRAM/Width.lean @@ -0,0 +1,102 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Structured + +/-! +# Width-independent native program prefixes + +An execution that halts at one width identifies every successful prefix at a larger width when +the program does not read physical width. These rules apply to both complete native frontends +and programs that compose them. + +## Main results + +* `Code.readsWidth_rename`: register renaming does not change width observation. +* `Runs.prefix_at_smaller_width`: wider prefixes agree with an admitted smaller-width execution. + +These determinism and representation lemmas are original. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- Register renaming never changes whether a structured program observes physical width. -/ +theorem Code.readsWidth_rename {B : Basis} {r r' : ℕ} (f : Fin r → Fin r') + (c : Code B r) : (c.rename f).ReadsWidth ↔ c.ReadsWidth := by + induction c with + | op o => cases o <;> rfl + | seq c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | branch b c d hc hd => simp only [Code.rename, Code.ReadsWidth, hc, hd] + | loop b c hc => exact hc + | rand => rfl + +/-- Every wider prefix is the same smaller-width prefix if that smaller execution halts. -/ +theorem Runs.prefix_at_smaller_width {B : Basis} {w w' r : ℕ} {p : Program B r} + (hp : ¬p.ReadsWidth) (hw : w ≤ w') {s t : State r} {k v total : ℕ} + (hwide : Runs w' p s t k) (hlow : Executes w p s v total) : Runs w p s t k := by + induction hwide using Relation.RelatesInSteps.head_induction_on generalizing total with + | hrefl => exact .refl _ + | @hhead a c k hstep hrest ih => + obtain ⟨z, m, hm, hz, _⟩ := hlow + cases m with + | zero => + have he := hm.zero + subst z + have hh : step w' p a = .halt v := step_halt_mono_width hz + rw [show step w' p a = .next c from hstep] at hh + cases hh + | succ m => + obtain ⟨u, hu, htail⟩ := hm.succ' + have he : u = c := by + have hmono := hu.mono_width hp hw + change step w' p a = .next u at hmono + rw [show step w' p a = .next c from hstep] at hmono + exact Outcome.next.inj hmono.symm + subst u + exact (ih (total := m + 1) ⟨z, m, htail, hz, rfl⟩).head a c t k hu + +/-- A native instruction cannot write memory above the admitted address-word universe. -/ +theorem Op.eval_mem_above {B : Basis} {r w a : ℕ} {o : Op B r} {x y : Data r} + (h : o.eval w x = some y) (hx : x.Bounded w) (ha : 2 ^ w ≤ a) : + y.mem a = x.mem a := by + cases o with + | const => obtain ⟨_, rfl⟩ := Data.put_eq_some.1 h; rfl + | alu => obtain ⟨_, _, _, rfl⟩ := Op.eval_alu_eq_some.1 h; rfl + | copy => cases h; rfl + | load => cases h; rfl + | store d s => + cases h + change Function.update x.mem (x.regs d) (x.regs s) a = x.mem a + exact Function.update_of_ne (by have := hx.1 d; dsimp only [Fits] at this; omega) _ _ + +/-- A complete native step preserves memory beyond the admitted address-word universe. -/ +theorem Next.mem_above {B : Basis} {r w a : ℕ} {p : Program B r} {s t : State r} + (h : Next w p s t) (hs : s.data.Bounded w) (ha : 2 ^ w ≤ a) : + t.data.mem a = s.data.mem a := by + unfold Next WordRAM.step at h + split at h + · cases h + · next o _ => + split at h + · next x hx => cases h; exact Op.eval_mem_above hx hs ha + · cases h + all_goals first | (cases h; rfl) | cases h + +/-- A native run preserves every cell above its admitted address-word universe. -/ +theorem Runs.mem_above {B : Basis} {r w a : ℕ} {p : Program B r} {s t : State r} {n : ℕ} + (h : Runs w p s t n) (hs : s.data.Bounded w) (ha : 2 ^ w ≤ a) : + t.data.mem a = s.data.mem a := by + induction h with + | refl => rfl + | @tail t u n hpre hstep ih => + exact (hstep.mem_above (Runs.bounded hpre hs) ha).trans ih + +end Cslib.WordRAM diff --git a/ComplexityTests/NativeChanHeNode.lean b/ComplexityTests/NativeChanHeNode.lean new file mode 100644 index 0000000000..d6acd15fde --- /dev/null +++ b/ComplexityTests/NativeChanHeNode.lean @@ -0,0 +1,177 @@ +/- +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 square-length client reads the final packed cell from the actual native state. +example : ∃ y, ExecWithin 34 program (input singleton 1 2 1) y 1792 ∧ + Output (layout (input singleton 1 2 1)) singletonValues + (input singleton 1 2 1).mem y.mem ∧ Parameters (input singleton 1 2 1) y ∧ + y.mem ((layout (input singleton 1 2 1)).packed + 7) = + Signed.encode ((node (layout (input singleton 1 2 1)) singletonValues).oneArray 1 7) := by + have hclient : ∃ y, ExecWithin 34 program (input singleton 1 2 1) y 1792 ∧ + Output (layout (input singleton 1 2 1)) singletonValues + (input singleton 1 2 1).mem y.mem ∧ Parameters (input singleton 1 2 1) y := by + apply exec_spec _ _ (by decide) (by decide) (by decide) + · intro j + fin_cases j <;> decide + · intro j i hi + rw [show (layout (input singleton 1 2 1)).U = 1 by decide] + fin_cases j <;> norm_num [singletonValues] + · intro j i hi + have hi0 : i = 0 := by + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi <;> omega + subst i + fin_cases j <;> decide + · intro j i hi k hk _ + fin_cases j <;> norm_num [layout, input, singleton, length, source] at hi hk <;> omega + obtain ⟨y, he, ho, hp⟩ := hclient + exact ⟨y, he, ho, hp, ho.packed_square (m := 1) (by decide) (by decide)⟩ + +end ComplexityTests.NativeChanHeNode diff --git a/ComplexityTests/NativeChanHePreprocessing.lean b/ComplexityTests/NativeChanHePreprocessing.lean new file mode 100644 index 0000000000..36590bab62 --- /dev/null +++ b/ComplexityTests/NativeChanHePreprocessing.lean @@ -0,0 +1,34 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.PreprocessingProgram + +/-! # Executed initialized-input Chan–He preprocessing -/ + +namespace ComplexityTests.NativeChanHePreprocessing + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +-- This successful physical width is smaller than the exported frontend sufficient width. +-- All parameters, the prime bitmap, reservations, descriptor loads and root skips execute. +-- The empty frontend still has twenty roots, rather than bypassing the algorithm. +/-- info: true -/ +#guard_msgs in +#eval decide ((Code.eval 32 256 (Preprocessing.program 0) (Data.init [])).map (fun yn => + [yn.1.regs Traversal.outputCount, yn.1.regs Preprocessing.packedLength, + yn.1.regs Preprocessing.candidateLimit, yn.1.regs Traversal.primeCount, + yn.1.regs Preprocessing.current, yn.1.regs Preprocessing.power, + yn.1.regs Preprocessing.outputCapacity, yn.1.regs Preprocessing.heapFloor, + yn.1.regs Traversal.heap, yn.1.mem 0]) = + some [0, 294912, 192, 43, 20, 9, 180, 575, 575, 0]) + +-- The complete native program faults during actual parameter arithmetic at an invalid width. +/-- info: true -/ +#guard_msgs in +#eval (Code.eval 1 256 (Preprocessing.program 0) (Data.init [])).isNone + +end ComplexityTests.NativeChanHePreprocessing diff --git a/ComplexityTests/NativeChanHePreprocessingContracts.lean b/ComplexityTests/NativeChanHePreprocessingContracts.lean new file mode 100644 index 0000000000..3502b60268 --- /dev/null +++ b/ComplexityTests/NativeChanHePreprocessingContracts.lean @@ -0,0 +1,148 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +-- This test module exports no library declarations. +set_option linter.privateModule false + +/-! +# Admitted whole-program native preprocessing clients + +These clients discharge the width and input promises and retain the actual complete native +execution and memory witness. In particular, the negative two-position case still emits an +instance; it is never replaced by an empty output as a shortcut. +-/ + +namespace ComplexityTests.NativeChanHePreprocessingContracts + +open Cslib.WordRAM +open Cslib.Algorithms.ThreeSum +open Cslib.Algorithms.ThreeSum.ChanHe +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Preprocessing + +private theorem promise {κ : ℕ} (input : List ℤ) + (h : ∀ z ∈ input, |z| ≤ ((input.length + 2) ^ κ : ℕ)) : + ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width κ input.length) (flatProgram κ) + (State.init (input.map Signed.encode)) out.length steps ∧ + Runs (width κ input.length) (flatProgram κ) + (State.init (input.map Signed.encode)) ⟨(program κ).size, y⟩ (steps - 1) ∧ + steps ≤ cost κ input.length ∧ + out.map (fun p : ℕ × Node => p.2.oneArray (2 * (input.length + 2) ^ κ)) = + instances input.length ((input.length + 2) ^ κ) (fun i => input[i]) ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes + (2 * (input.length + 2) ^ κ) + (2 * mPar input.length (2 * (input.length + 2) ^ κ) ^ 2) + (arena κ input.length y.mem).table out y.mem := by + obtain ⟨out, y, steps, he, hr, ht, ho, hm, _, _, _, _⟩ := runs_input input le_rfl h + exact ⟨out, y, steps, he, hr, ht, ho, hm⟩ + +-- Empty input retains the complete initialization/prime/forest execution, with no output. +example : ∃ y steps, + Executes (width 0 0) (flatProgram 0) (State.init []) 0 steps ∧ + Runs (width 0 0) (flatProgram 0) (State.init []) ⟨(program 0).size, y⟩ (steps - 1) ∧ + steps ≤ cost 0 0 := by + obtain ⟨out, y, steps, he, hr, ht, ho, _⟩ := promise (κ := 0) [] (by simp) + have hempty : instances 0 1 (Fin.elim0 : Fin 0 → ℤ) = [] := by decide + have hfun : (fun i : Fin 0 => ([] : List ℤ)[i]) = (Fin.elim0 : Fin 0 → ℤ) := by + funext i + exact Fin.elim0 i + have hlen := congrArg List.length ho + simp only [List.length_nil, Nat.zero_add, pow_zero, hfun, hempty, List.length_map] at hlen + simp only [List.length_nil, List.map_nil] at he hr ht + rw [hlen] at he + exact ⟨y, steps, he, hr, ht⟩ + +-- One negative input also runs the complete program rather than supplying empty descriptors. +example : ∃ steps, Executes (width 0 1) (flatProgram 0) + (State.init [Signed.encode (-1)]) 0 steps ∧ steps ≤ cost 0 1 := by + obtain ⟨out, y, steps, he, _, ht, ho, _⟩ := promise (κ := 0) [-1] (by + intro z hz + simp only [List.mem_singleton] at hz + subst z + decide) + have hempty : instances 1 1 (fun _ : Fin 1 => (-1 : ℤ)) = [] := by decide + have hfun : (fun i : Fin 1 => ([-1] : List ℤ)[i]) = (fun _ : Fin 1 => (-1 : ℤ)) := by + funext i + fin_cases i + rfl + change out.map (fun p : ℕ × Node => p.2.oneArray 2) = + instances 1 1 (fun i : Fin 1 => ([-1] : List ℤ)[i]) at ho + rw [hfun, hempty] at ho + have hlen := congrArg List.length ho + simp only [List.length_map, List.length_nil] at hlen + simp only [List.length_cons, List.length_nil, List.map_cons, List.map_nil] at he ht + rw [hlen] at he + exact ⟨steps, he, ht⟩ + +private def pair : Fin 2 → ℤ := fun _ => 1 + +private def pairNode : Node := + ⟨{2}, {0}, {1}, modulus (Nat.primesLE (mPar 2 2)) (labelBits 2) {2} {0} {1}⟩ + +private theorem pair_node_mem : pairNode ∈ allNodes 2 1 pair := by + apply mem_allNodes_iff.mpr + refine ⟨{2}, {0}, {1}, ?_, ?_⟩ + · have ht : twiceSet pair = {2} := by decide + have hi : Finset.univ.image pair = {1} := by decide + simpa only [ht, hi] using (IsInput.twice (U := 1) (x := pair)) + · change pairNode ∈ nodes (Nat.primesLE (mPar 2 2)) (labelBits 2) 4 {2} {0} {1} + rw [nodes] + simp only [Finset.singleton_ne_empty, false_or, ↓reduceIte, List.mem_cons] + exact Or.inl rfl + +-- Two equal values have no distinct-position 3SUM solution, yet their exact reduction is nonempty. +example : ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width 0 2) (flatProgram 0) (State.init [Signed.encode 1, Signed.encode 1]) + out.length steps ∧ steps ≤ cost 0 2 ∧ out ≠ [] ∧ + out.map (fun p : ℕ × Node => p.2.oneArray 2) = instances 2 1 pair ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes 2 + (2 * mPar 2 2 ^ 2) (arena 0 2 y.mem).table out y.mem ∧ + ¬∃ f ∈ out.map (fun p : ℕ × Node => p.2.oneArray 2), Convolution (8 * mPar 2 2 ^ 2) f := by + obtain ⟨out, y, steps, he, _, ht, ho, hm⟩ := promise (κ := 0) [1, 1] (by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false, or_self] at hz + subst z + decide) + change out.map (fun p : ℕ × Node => p.2.oneArray 2) = instances 2 1 pair at ho + have hmem : pairNode.oneArray 2 ∈ instances 2 1 pair := + List.mem_map.mpr ⟨pairNode, pair_node_mem, rfl⟩ + refine ⟨out, y, steps, he, ht, ?_, ho, hm, ?_⟩ + · intro hnil + rw [← ho, hnil, List.map_nil] at hmem + exact List.not_mem_nil hmem + · rw [ho, ← instances_correct 2 1 (by decide) pair (by unfold pair; decide)] + unfold HasSolution pair + decide + +-- The same program covers a genuine equal-value positive instance and its native memory. +example : ∃ (out : List (ℕ × Node)) (y : Data 128) (steps : ℕ), + Executes (width 1 3) (flatProgram 1) + (State.init ([(-2 : ℤ), 1, 1].map Signed.encode)) out.length steps ∧ + steps ≤ cost 1 3 ∧ + out.map (fun p : ℕ × Node => p.2.oneArray 10) = + instances 3 5 (fun i : Fin 3 => ([(-2 : ℤ), 1, 1] : List ℤ)[i]) ∧ + Cslib.Algorithms.WordRAM.ThreeSum.ChanHe.Traversal.StoredNodes 10 + (2 * mPar 3 10 ^ 2) (arena 1 3 y.mem).table out y.mem ∧ + ∃ f ∈ out.map (fun p : ℕ × Node => p.2.oneArray 10), Convolution (8 * mPar 3 10 ^ 2) f := by + have hv : ∀ z ∈ ([(-2 : ℤ), 1, 1] : List ℤ), |z| ≤ ((3 + 2) ^ 1 : ℕ) := by + intro z hz + simp only [List.mem_cons, List.not_mem_nil, or_false, or_self] at hz + rcases hz with rfl | rfl <;> decide + obtain ⟨out, y, steps, he, _, ht, ho, hm⟩ := promise (κ := 1) [-2, 1, 1] hv + change out.map (fun p : ℕ × Node => p.2.oneArray 10) = + instances 3 5 (fun i : Fin 3 => ([(-2 : ℤ), 1, 1] : List ℤ)[i]) at ho + refine ⟨out, y, steps, he, ht, ho, hm, ?_⟩ + rw [ho] + apply (instances_correct 3 5 (by decide) _ (by decide)).mp + unfold HasSolution + decide + +end ComplexityTests.NativeChanHePreprocessingContracts diff --git a/ComplexityTests/NativeChanHeTraversal.lean b/ComplexityTests/NativeChanHeTraversal.lean new file mode 100644 index 0000000000..1a7781a8ba --- /dev/null +++ b/ComplexityTests/NativeChanHeTraversal.lean @@ -0,0 +1,267 @@ +/- +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 branchingNodes : List Cslib.Algorithms.ThreeSum.ChanHe.Node := + let ν₀ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4, 1}, {0}, {-1}, 4⟩ + let ν₁ : Cslib.Algorithms.ThreeSum.ChanHe.Node := ⟨{0, 4}, {0}, {-1}, 4⟩ + [ν₀, ν₁] + +private instance : DecidableEq Cslib.Algorithms.ThreeSum.ChanHe.Node := fun a b => + decidable_of_iff (a.S₁ = b.S₁ ∧ a.S₂ = b.S₂ ∧ a.S₃ = b.S₃ ∧ a.M = b.M) + (by cases a; cases b; simp only [Cslib.Algorithms.ThreeSum.ChanHe.Node.mk.injEq]) + +-- The same two nodes are computed by the existing finite recursion, in the same order. +example : (Math.Task.mk 2 ![{0, 4, 1}, {0}, {-1}]).output {2} 4 = branchingNodes := by + decide + +private def expectedBranching : List (List ℕ) := + branchingNodes.map fun ν => (List.range 32).map fun i => Signed.encode (ν.oneArray 4 i) + +-- Only the first column has heavy elements. The child changes that descriptor and keeps the +-- other two, then its zero-fuel children and both empty siblings are popped without outputs. +-- 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 0000000000..655224e245 --- /dev/null +++ b/ComplexityTests/WordRAMChanHeModulus.lean @@ -0,0 +1,107 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Modulus + +/-! # Complete native two-prime modulus computations -/ + +namespace ComplexityTests.WordRAMChanHeModulus + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +private def input (n digits : ℕ) (values : ℕ → ℤ) : Data 64 := + ⟨fun i => if i.val = 28 ∨ i.val = 30 ∨ i.val = 32 then n + else if i.val = 29 ∨ i.val = 31 ∨ i.val = 33 then 1 + else if i.val = 34 then 20 else if i.val = 35 then 30 + else if i.val = 40 then 2 else if i.val = 41 then 10 + else if i.val = 42 ∨ i.val = 59 then (if i.val = 42 then 1 else digits) else 71, + fun a => if 1 ≤ a ∧ a < 1 + n then encode (values (a - 1)) + else if 10 ≤ a ∧ a ≤ 12 then (if a = 12 then 1 else 0) else 79⟩ + +-- Each execution includes two complete prime scans and three baseline counting calls. +set_option maxRecDepth 8192 in +/-- Empty sets still compute the exact minimum/default specification, returning `2*2`. -/ +example : (Code.eval 8 6 Modulus.program (input 0 1 (fun i => i))).map + (fun yn => [yn.1.regs Modulus.first, yn.1.regs Modulus.output, + yn.1.regs Modulus.digits, yn.1.mem 12]) = some [2, 4, 1, 1] := by decide + +/-- A target word too small for the very first scalar setup is rejected. -/ +example : (Code.eval 1 1 Modulus.program (input 0 1 (fun i => i))).map + (fun yn => yn.1.regs Modulus.output) = none := by decide + +/-- The native execution contract is admissible at every small array length, including all +fits, the complete bitmap, preserved input and injective enumeration. -/ +example (n : Fin 3) : ∃ y, + ExecWithin 10 Modulus.program (input n 1 (fun i => i)) y (714 * (n : ℕ) + 1111) ∧ + y.regs Modulus.output = 4 := by + let x := input n 1 (fun i => i) + have hn (j : Fin 3) : x.regs (CountCall.length j) = n := by fin_cases j <;> rfl + have hs (j : Fin 3) : x.regs (CountCall.source j) = 1 := by fin_cases j <;> rfl + have hN : (n : ℕ) ≤ 2 := by omega + have hlimit : x.regs Search.limit = 2 := rfl + obtain ⟨y, he, hr, hm, hp, hv⟩ := Modulus.exec (w := 8) x (fun _ i => (i : ℤ)) + (by change 2 ≤ 2; decide) + (by change Cslib.GenericRAM.Signed.Fits 8 (4 : ℤ); decide) + (by fin_cases n <;> decide) (by change 1 = (Nat.primesLE 2).card; decide) + (by change 10 + (2 + 1) ≤ 20; decide) + (by intro q hq; change q ≤ 2 at hq; fin_cases n <;> interval_cases q <;> decide) + (by intro j i hi; rw [hn] at hi; have hi1 : i ≤ 1 := by omega + interval_cases i <;> decide) + (by intro j; rw [hs, hn]; change 1 + (n : ℕ) ≤ 20; omega) + (by intro j; rw [hn]; change 20 + (n : ℕ) ≤ 30; omega) + (by + intro j i hi + rw [hn] at hi + rw [hs] + change (if 1 ≤ 1 + i ∧ 1 + i < 1 + (n : ℕ) then encode ((1 + i - 1 : ℕ) : ℤ) + else if 10 ≤ 1 + i ∧ 1 + i ≤ 12 then (if 1 + i = 12 then 1 else 0) else 79) = _ + simp only [show 1 ≤ 1 + i ∧ 1 + i < 1 + (n : ℕ) by omega, + and_self, ↓reduceIte, show 1 + i - 1 = i by omega]) + (by intro j a _ b _ hab; change (a : ℤ) = (b : ℤ) at hab; exact_mod_cast hab) + have hcost : Modulus.cost x = 714 * (n : ℕ) + 1111 := by + simp only [Modulus.cost, Modulus.searchTime, hn, hlimit] + omega + refine ⟨y, by simpa only [hcost] using he, ?_⟩ + have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.modulus (Nat.primesLE 2) 1 + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) + ((Finset.range (n : ℕ)).image fun i : ℕ => (i : ℤ)) = 4 := by fin_cases n <;> decide + simp only [hn] at hv + exact hv.trans hvalue + +/-- A zero divisor bound and negative colliding values reject the only prime twice. The full +native theorem returns the exact default product one, with admissible input and resource bounds. -/ +example : ∃ y, ExecWithin 10 Modulus.program + (input 2 0 (fun i => if i = 0 then -2 else 0)) y + (Modulus.cost (input 2 0 (fun i => if i = 0 then -2 else 0))) ∧ + y.regs Modulus.first = 1 ∧ y.regs Modulus.output = 1 := by + let values (i : ℕ) : ℤ := if i = 0 then -2 else 0 + let x := input 2 0 values + have hn (j : Fin 3) : x.regs (CountCall.length j) = 2 := by fin_cases j <;> rfl + obtain ⟨y, he, hr, hm, hp, hv⟩ := Modulus.exec (w := 8) x (fun _ => values) + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro q hq; change q ≤ 2 at hq; interval_cases q <;> decide) + (by intro j i hi; rw [hn] at hi; interval_cases i <;> decide) + (by intro j; fin_cases j <;> decide) (by intro j; fin_cases j <;> decide) + (by intro j i hi; rw [hn] at hi; fin_cases j <;> interval_cases i <;> decide) + (by + intro j a ha b hb hab + simp only [Finset.mem_coe, Finset.mem_range, hn] at ha hb + interval_cases a <;> interval_cases b <;> norm_num [values] at *) + refine ⟨y, he, ?_, ?_⟩ + · have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.firstP (Nat.primesLE 2) 0 + ((Finset.range 2).image values) ((Finset.range 2).image values) + ((Finset.range 2).image values) = 1 := by decide + simp only [hn] at hp + exact hp.trans hvalue + · have hvalue : Cslib.Algorithms.ThreeSum.ChanHe.modulus (Nat.primesLE 2) 0 + ((Finset.range 2).image values) ((Finset.range 2).image values) + ((Finset.range 2).image values) = 1 := by decide + simp only [hn] at hv + exact hv.trans hvalue + +end ComplexityTests.WordRAMChanHeModulus diff --git a/ComplexityTests/WordRAMThreeSumFrontend.lean b/ComplexityTests/WordRAMThreeSumFrontend.lean new file mode 100644 index 0000000000..a11266ed81 --- /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 0000000000..b2161e9cfc --- /dev/null +++ b/ComplexityTests/WordRAMThreeSumFrontendContracts.lean @@ -0,0 +1,92 @@ +/- +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) + +-- A checked frontend value fits the signed kernel ABI at the derived physical width. +example : Cslib.GenericRAM.Signed.Fits (4096 - 2) (-8) := + signed_output_fits (κ := 1) (n := 2) (by decide) (by decide) + +-- Complete array construction also accepts arbitrary old output and scratch memory. +example (mem : ℕ → ℕ) : + 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 96a86aba23..1156ce5747 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,10 +438,17 @@ each statement against the numbered result. Organize modules by subject, not cha kept separate; and develop general open-program composition. Use Cook–Levin and GGM as regression clients. Improve configuration views, tape partitions and handoff identities only where several clients benefit, keeping machine transitions out of global simp. -- **Native Chan–He components** ([#1446]): native parameter, collision-counting, prime-table and - collision-tested minimum-search programs have explicit local fit/frame/cost contracts. - Frontend enumeration, heavy extraction, recursive traversal, packed output and global - preprocessing width/time composition remain. +- **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](https://github.com/cslib-community/complexitylib/issues/1512)) executes those components with an + explicit stack, exact preorder output, polynomial local fit/frame contracts and charged + ternary-tree bounds. Complete native preprocessing ([#1446]) composes every root from actual + initialized input, returns the exact finite packed instances and derives positive logarithmic + width, polynomial space/intermediates and an `Oκ(n^1.5 log¹³(n+2))` charged bound. - **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 @@ -450,7 +466,7 @@ each statement against the numbered result. Organize modules by subject, not cha bounds, with the empty zero-depth root handled explicitly. The finite thin-product component ([#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 + needs native tensor/sparse-query programs, native Chan–He/Exact Triangle host composition, the reduction host, the fast Exact Triangle solver and full input/time/width/asymptotic composition, then the improved Section 4 route to exponent 1.9992; size-based width @@ -605,6 +621,5 @@ not planned, not as proved. [#1446]: https://github.com/cslib-community/complexitylib/issues/1446 [#1447]: https://github.com/cslib-community/complexitylib/issues/1447 [#1449]: https://github.com/cslib-community/complexitylib/issues/1449 -[#1459]: https://github.com/cslib-community/complexitylib/issues/1459 [#1524]: https://github.com/cslib-community/complexitylib/issues/1524 [#1572]: https://github.com/cslib-community/complexitylib/issues/1572 diff --git a/UPSTREAM.md b/UPSTREAM.md index 3e4d44738d..09eced4c0e 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -172,16 +172,40 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he theorem connects the actual pre-halt state to the Convolution-3SUM answer and preserves the input. The host's source-width threshold is `(20κ+125)clog₂(N+2)`; signed kernels use target width `w+2`. This remains conditional on the supplied solver's native execution/correctness/frame theorem; - it does not establish a fast solver. Native Chan–He preprocessing, fast Exact Triangle and full + it does not establish a fast solver. Fast Exact Triangle and full input/time/width/asymptotic composition remain in [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution are retained; the native proofs and representation choices are local. Native numeric parameter computation, signed-residue/histogram counting, trial-division prime - tables and collision-tested least-prime scans also use the existing `WordRAM.Code`/`ExecWithin` - APIs. Their explicit local fit, frame and charged-cost contracts do not yet compose the complete - preprocessing from input through frontend, heavy sets, recursion and packed output; the global - logarithmic width and subquadratic preprocessing bound remain in #1446. + 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 every frontend root is proved in + `Algorithms/WordRAM/ThreeSum/ChanHe/Preprocessing.lean`. One finite 128-register program per + fixed magnitude exponent returns the existing exact finite instance list. All fits derive from + `(1300κ+7865) clog₂(n+2)` physical width; every run prefix uses polynomial words/addresses even + at wider admitted widths. Its charged cost is at most + `Kκ (n+2)(sqrt(n+2)+1)(log₂(n+2)+1)^13`, including the final halt. The complete native + preprocessing milestone is #1446; the Exact Triangle solver and full final 3SUM composition + remain #1394. These components do not affect the curated headlines. + + Bounded API follow-ups from the traversal/preprocessing reviews: merge the stage proof modules + `PreprocessingSnapshot`, `PreprocessingPrimes`, `PreprocessingSetup`, `PreprocessingFinish`, + `PreprocessingInvariant`, `PreprocessingStep` and `PreprocessingLoop` with their controller + consumer `PreprocessingExecute`, or use `import all` to make their proof plumbing private. + This includes `Saved`, `ForestCursor`, `snapshot_exec`, `primes_exec`, `setupTraversal_exec`, + `finish_exec`, `execute_next_root` and `forest_body_exec`; keep the initialized-input, readback, + width and resource endpoints available through `Preprocessing`. Separately, move + `modulus_bounds_of_candidates` from `SelectorBounds` beside `Collisions`' `modulus_bounds` and + derive or cross-reference the stronger bound, migrating `TraversalCall` and selector tests. + Derive `Structured`'s `ExecWithin.loop_invariant` as the `Data`-state case of + `GhostLoop`'s `ExecWithin.loop_invariant_state`, preserving the existing copy/residue/padding/ + search/reservation clients. Resolve the current import direction by keeping both rules in one + loop topic module before changing those clients. `Algorithms/ThreeSum/ThreeSets.lean` and `ArrayPacking.lean` provide the generic distinct-position frontend and three-array packing. `Algorithms/ThreeSum/ChanHe/` now adapts the core mathematical @@ -230,8 +254,8 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he both a level set and its complement, and `Finset.piecewise` restricts answers. No source machine, problem-statement universe or solver/time hypothesis is imported. The existing signed and unsigned APIs were reused; no source machine or separate run semantics were copied. - Tensor/sparse query programs, the full reduction host, native Chan–He preprocessing, fast Exact - Triangle and full input/time/width/asymptotic composition remain in + Tensor/sparse query programs, the full reduction host, fast Exact Triangle and full + input/time/width/asymptotic composition remain in [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution are retained; the native proofs and representation choices are local.