diff --git a/Complexity.lean b/Complexity.lean index cfa5796f19..0769327a70 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -32,6 +32,7 @@ public import Complexity.Algorithms.Lean.VanEmdeBoas.Layout public import Complexity.Algorithms.Lean.VanEmdeBoas.Successor public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Alphabets public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Batch +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Full public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Identity public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Layout @@ -79,6 +80,21 @@ public import Complexity.Algorithms.WordRAM.Dijkstra.Statement public import Complexity.Algorithms.WordRAM.DynamicArray public import Complexity.Algorithms.WordRAM.ElementDistinctness public import Complexity.Algorithms.WordRAM.ElementDistinctnessRAM +public import Complexity.Algorithms.WordRAM.ExactTriangle.Catalogue +public import Complexity.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue +public import Complexity.Algorithms.WordRAM.ExactTriangle.Chunks +public import Complexity.Algorithms.WordRAM.ExactTriangle.Classes +public import Complexity.Algorithms.WordRAM.ExactTriangle.InstancePair +public import Complexity.Algorithms.WordRAM.ExactTriangle.Instances +public import Complexity.Algorithms.WordRAM.ExactTriangle.MatrixSpec +public import Complexity.Algorithms.WordRAM.ExactTriangle.Parameters +public import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationBounds +public import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationInput +public import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationSpec +public import Complexity.Algorithms.WordRAM.ExactTriangle.QueryScan +public import Complexity.Algorithms.WordRAM.ExactTriangle.QuerySpec +public import Complexity.Algorithms.WordRAM.ExactTriangle.ResidueClasses +public import Complexity.Algorithms.WordRAM.ExactTriangle.WriteMatrices public import Complexity.Algorithms.WordRAM.FusionTree.Binary public import Complexity.Algorithms.WordRAM.FusionTree.Boundary public import Complexity.Algorithms.WordRAM.FusionTree.Compress @@ -111,6 +127,17 @@ public import Complexity.Algorithms.WordRAM.Karger.FastCut public import Complexity.Algorithms.WordRAM.Karger.Graph public import Complexity.Algorithms.WordRAM.KargerMinCut public import Complexity.Algorithms.WordRAM.KargerStein +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Cell +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Controller +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Copy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Exec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Flat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Forms +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Stage public import Complexity.Algorithms.WordRAM.NatClog public import Complexity.Algorithms.WordRAM.NatLog public import Complexity.Algorithms.WordRAM.NatSqrt @@ -1084,6 +1111,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 @@ -1757,6 +1785,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/MatrixMultiplication/Schonhage/CoordinateTransform.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean new file mode 100644 index 0000000000..eb1392c7db --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/CoordinateTransform.lean @@ -0,0 +1,191 @@ +/- +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.MatrixMultiplication.Schonhage.Full +public import Complexity.Foundations.Algebra.FiniteMagnitude +import Mathlib.Tactic.Ring +import Mathlib.Tactic.Linarith + +/-! +# Shared coordinate transforms in flat arrays + +After `j` coordinates have been transformed, the array has `10^j * 7^k` entries, where +`j + k = L`. Its entry at `(p, r)` sums the input entries with suffix `r`, weighting their +prefixes by the product of the selected coefficients. The next coordinate transform computes +each new entry from seven entries of this shared array. It does not independently enumerate +all input entries for every final leaf. + +## Main definitions + +* `CoordinateTransform.weight`: product of coefficients along the transformed prefix. +* `CoordinateTransform.value`: prefix expansion at a mixed-radix array position. + +## Main results + +* `CoordinateTransform.value_step`: the seven-to-ten shared coordinate update. +* `CoordinateTransform.stageSize_le`: every stage has at most `10^L` entries. + +## References + +An iterative implementation invariant for [AlmanWilliams2026, Section 2.4.1]. The coefficients +are those of the existing encoding API, adapted from Anthropic, PBC's formal source at +`16a5ce41304fcdaea4e0be7c132e1c361dc26da5`. This module defines mathematical arrays and sums, +not a separate machine or an instruction-counting semantics. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform + +open Finset + +/-- Product of `j` coefficients, reading prefix digits from least significant to most +significant. Values at noncanonical indices are harmless; clients use indices below the powers. -/ +def weight (c : ℕ → ℕ → ℤ) : ℕ → ℕ → ℕ → ℤ + | 0, _, _ => 1 + | j + 1, p, v => weight c j (p / 10) (v / 7) * c (p % 10) (v % 7) + +/-- Value at transformed prefix `p` and original suffix `r`. The remaining suffix has `k` +base-seven digits; the original input index is `v * 7^k + r`. -/ +def value (c : ℕ → ℕ → ℤ) (j k : ℕ) (a : ℕ → ℤ) (p r : ℕ) : ℤ := + ∑ v ∈ range (7 ^ j), weight c j p v * a (v * 7 ^ k + r) + +private theorem sum_blocks (m n : ℕ) (f : ℕ → ℤ) : + ∑ i ∈ range (m * n), f i = ∑ q ∈ range m, ∑ s ∈ range n, f (q * n + s) := by + induction m with + | zero => simp + | succ m ih => + rw [Nat.succ_mul, sum_range_add, ih, sum_range_succ] + +/-- At the initial stage no coordinate is transformed, so the entry is the input itself. -/ +theorem value_zero (c : ℕ → ℕ → ℤ) (k : ℕ) (a : ℕ → ℤ) (p r : ℕ) : + value c 0 k a p r = a r := by + simp [value, weight] + +/-- The next array entry uses seven already shared values. This is the exact algebraic +invariant of the mixed-radix native loop. -/ +theorem value_step (c : ℕ → ℕ → ℤ) (j k : ℕ) (a : ℕ → ℤ) (p r : ℕ) + {t : ℕ} (ht : t < 10) : + value c (j + 1) k a (p * 10 + t) r = + ∑ s ∈ range 7, c t s * value c j (k + 1) a p (s * 7 ^ k + r) := by + simp only [value, pow_succ, sum_blocks] + rw [sum_comm] + refine sum_congr rfl fun s hs => ?_ + have hs7 := mem_range.mp hs + simp only [mul_sum] + refine sum_congr rfl fun q _ => ?_ + have hp : (p * 10 + t) / 10 = p := by omega + have hq : (q * 7 + s) / 7 = q := by omega + simp only [weight, hp, hq, Nat.add_mod, Nat.mul_mod_left, Nat.mod_eq_of_lt ht, + Nat.mod_eq_of_lt hs7, zero_add] + have hidx : (q * 7 + s) * 7 ^ k + r = q * (7 ^ k * 7) + (s * 7 ^ k + r) := by ring + rw [hidx] + ring + +/-- Each prefix product has absolute value at most one. -/ +theorem abs_weight_le {c : ℕ → ℕ → ℤ} (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) + (j p v : ℕ) : |weight c j p v| ≤ 1 := by + induction j generalizing p v with + | zero => simp [weight] + | succ j ih => + rw [weight, abs_mul] + exact (mul_le_mul (ih _ _) (hc _ (Nat.mod_lt _ (by decide)) _ + (Nat.mod_lt _ (by decide))) (abs_nonneg _) (by decide : (0 : ℤ) ≤ 1)).trans + (by norm_num) + +/-- Intermediate transformed values are bounded from the original input values. -/ +theorem abs_value_le {c : ℕ → ℕ → ℤ} (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) + (j k p r : ℕ) {a : ℕ → ℤ} {A : ℤ} + (ha : ∀ v < 7 ^ j, |a (v * 7 ^ k + r)| ≤ A) : + |value c j k a p r| ≤ (7 ^ j : ℕ) * A := by + exact (abs_sum_mul_le (range (7 ^ j)) (fun v _ => abs_weight_le hc j p v) + (fun v hv => ha v (mem_range.mp hv))).trans_eq (by simp) + +/-- Input bounds suffice for every shared intermediate value, including all depths and all +prefixes. Only the original input segment is used in this estimate. -/ +theorem abs_value_le_of_input {c : ℕ → ℕ → ℤ} + (hc : ∀ t < 10, ∀ s < 7, |c t s| ≤ 1) {L j k : ℕ} (hjk : j + k = L) + (p r : ℕ) (hr : r < 7 ^ k) {a : ℕ → ℤ} {A : ℤ} + (ha : ∀ i < 7 ^ L, |a i| ≤ A) : |value c j k a p r| ≤ (7 ^ j : ℕ) * A := by + apply abs_value_le hc + intro v hv + have hprod := Nat.mul_le_mul_right (7 ^ k) (Nat.succ_le_of_lt hv) + have hidx : v * 7 ^ k + r < 7 ^ j * 7 ^ k := by nlinarith only [hprod, hr] + rw [← pow_add, hjk] at hidx + exact ha _ hidx + +private theorem index_cons {b L : ℕ} (s : Fin b) (u : Fin L → Fin b) : + (finFunctionFinEquiv (Fin.cons s u) : ℕ) = s.val + b * (finFunctionFinEquiv u : ℕ) := by + simp only [finFunctionFinEquiv_apply, Fin.sum_univ_succ, Fin.cons_zero, Fin.cons_succ, + Fin.val_zero, pow_zero, mul_one, Fin.val_succ, pow_succ, Finset.mul_sum] + congr 1 + apply sum_congr rfl + intro i _ + ring + +/-- Digitwise coefficients as a total flat table. Only the first ten rows and seven columns +are read by the transform. -/ +def table (c : Fin 10 → Fin 7 → ℤ) (t s : ℕ) : ℤ := + c ⟨t % 10, Nat.mod_lt _ (by decide)⟩ ⟨s % 7, Nat.mod_lt _ (by decide)⟩ + +/-- Flat prefix weights are exactly the product of the original coordinate coefficients. +`finFunctionFinEquiv` supplies the existing Mathlib digit numbering, least significant first. -/ +theorem weight_index (c : Fin 10 → Fin 7 → ℤ) {L : ℕ} + (τ : Fin L → Fin 10) (u : Fin L → Fin 7) : + weight (table c) L (finFunctionFinEquiv τ) (finFunctionFinEquiv u) = + ∏ i, c (τ i) (u i) := by + induction L with + | zero => simp [weight] + | succ L ih => + rw [← Fin.cons_self_tail τ, ← Fin.cons_self_tail u, index_cons, index_cons, weight] + have ht : ((τ 0).val + 10 * (finFunctionFinEquiv (Fin.tail τ) : ℕ)) / 10 = + (finFunctionFinEquiv (Fin.tail τ) : ℕ) := by omega + have hu : ((u 0).val + 7 * (finFunctionFinEquiv (Fin.tail u) : ℕ)) / 7 = + (finFunctionFinEquiv (Fin.tail u) : ℕ) := by omega + have htmod : ((τ 0).val + 10 * (finFunctionFinEquiv (Fin.tail τ) : ℕ)) % 10 = + (τ 0).val := by omega + have humod : ((u 0).val + 7 * (finFunctionFinEquiv (Fin.tail u) : ℕ)) % 7 = + (u 0).val := by omega + rw [ht, hu, htmod, humod, ih, Fin.prod_univ_succ] + simp only [table, Nat.mod_eq_of_lt (τ 0).isLt, Nat.mod_eq_of_lt (u 0).isLt, + Fin.cons_zero, Fin.cons_succ] + exact mul_comm _ _ + +/-- At the final stage the flat transform is the tensor encoding sum on digit strings. +The input array is supplied explicitly by its values at Mathlib's numbered string positions. -/ +theorem value_final (c : Fin 10 → Fin 7 → ℤ) (L : ℕ) + (a : (Fin L → Fin 7) → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u, arr (finFunctionFinEquiv u) = a u) (τ : Fin L → Fin 10) : + value (table c) L 0 arr (finFunctionFinEquiv τ) 0 = + ∑ u, a u * ∏ i, c (τ i) (u i) := by + simp only [value, pow_zero, mul_one, add_zero] + rw [← Fin.sum_univ_eq_sum_range] + rw [← (finFunctionFinEquiv (m := 7) (n := L)).sum_comp] + apply sum_congr rfl + intro u _ + rw [weight_index, harr, mul_comm] + +/-- The mixed-radix stage fits into a buffer of `10^(j+k)` entries. -/ +theorem stageSize_le (j k : ℕ) : 10 ^ j * 7 ^ k ≤ 10 ^ (j + k) := by + rw [pow_add] + exact Nat.mul_le_mul_left _ (Nat.pow_le_pow_left (by decide : 7 ≤ 10) k) + +/-- Even charging every one of the `L` stages at the maximum array size gives only +`L * 10^L` cells. This is a combinatorial bound; native loop and arithmetic costs are separate. -/ +theorem total_stageSize_le (L : ℕ) : + ∑ j ∈ range L, 10 ^ (j + 1) * 7 ^ (L - (j + 1)) ≤ L * 10 ^ L := by + calc + _ ≤ ∑ _j ∈ range L, 10 ^ L := by + apply sum_le_sum + intro j hj + have hjL : j + 1 ≤ L := by have := mem_range.mp hj; omega + simpa only [Nat.add_sub_of_le hjL] using stageSize_le (j + 1) (L - (j + 1)) + _ = _ := by simp + +end Cslib.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/Catalogue.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/Catalogue.lean new file mode 100644 index 0000000000..d34b7359c0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/Catalogue.lean @@ -0,0 +1,341 @@ +/- +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.ExactTriangle.ChunkCatalogue +public import Complexity.Algorithms.WordRAM.ExactTriangle.Parameters +public import Complexity.Algorithms.WordRAM.ExactTriangle.ResidueClasses + +/-! +# Computing an ExactTriangle instance catalogue + +The fixed input format is `(D,g,n,p)` followed by the three signed weight matrices. The program +computes residues and query classes, computes the splitting parameters from `D,g,n`, and +concatenates the actual query-chunk descriptors. The caller supplies neither the parameters nor +a class, prefix or descriptor table. Subsequent matrix preparation consumes this catalogue. + +This is native composition of the existing Theorem 17 residue, parameter, class and chunk +kernels. The modulus remains a supplied positive argument; prime selection belongs to the outer +ExactTriangle host. + +## Main definitions + +* `Catalogue.program`: fixed thirty-two-register native catalogue computation. +* `Catalogue.budget`: input-derived numeric bound for arithmetic and allocated addresses. +* `Catalogue.cost`: charged cost before the final halt. +* `Catalogue.Output`: computed parameters, residues, queries, descriptors and memory frame. + +## Main results + +* `Catalogue.exec_at`: actual execution from the fixed input header, exact parameters, residue + and query readback, concatenated descriptors and a combined memory frame. + +## References + +The class/query layout follows the pinned +[Theorem 17 construction](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/Classes.lean). +The native composition and instruction bounds are original. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.Catalogue + +open Cslib.WordRAM Cslib.Algorithms.ExactTriangle + +/-- Computed maximum vertices per piece, preserved by descriptor enumeration. -/ +abbrev pieceLength : Fin 32 := 28 +/-- Computed number of pieces, preserved by descriptor enumeration. -/ +abbrev pieces : Fin 32 := 29 + +/-- Existing parameter computation in the lower register bank. -/ +@[expose] +def parameters : Code .standard 32 := Parameters.program.rename (Fin.castLE (by decide)) + +/-- Internal argument delivery from the fixed `D,g,n,p` header. -/ +@[expose] +def deliverParameters : Code .standard 32 := + let header : Fin 32 := 0 + let one : Fin 32 := 31 + let n : Fin 32 := 6 + let D : Fin 32 := 7 + let g : Fin 32 := 8 + ram[ one ← #1; header ← #1; D ← *header; header ← header + one; + g ← *header; header ← header + one; n ← *header ] + +/-- Internal argument delivery and allocation after the computed query blocks. Copying the +count/query pointers precedes overwriting their old registers with the computed piece data. -/ +@[expose] +def deliverChunks : Code .standard 32 := + let n : Fin 32 := 6 + let cap : Fin 32 := 10 + let len : Fin 32 := 11 + let num : Fin 32 := 12 + let oldCounts : Fin 32 := 28 + let oldQueries : Fin 32 := 29 + let counts := ChunkCatalogue.counts + let queries := ChunkCatalogue.queries + let size := ChunkCatalogue.size + let capacity := ChunkCatalogue.capacity + let modulus := ChunkCatalogue.modulus + let dst := ChunkCatalogue.dst + let header := ChunkCatalogue.addr + ram[ counts ← oldCounts; queries ← oldQueries; size ← n; capacity ← cap; + pieceLength ← len; pieces ← num; header ← #4; modulus ← *header; + dst ← size * size; dst ← dst + dst; dst ← dst * modulus; dst ← dst + queries ] + +/-- All class, parameter and chunk work is performed by actual native instructions. -/ +@[expose] +def program : Code .standard 32 := + .seq (ResidueClasses.programAt 3) (.seq deliverParameters + (.seq parameters (.seq deliverChunks ChunkCatalogue.program))) + +/-- One finite instruction graph, independent of runtime sizes and physical width. -/ +theorem program_fixed : program.size ≤ 320 ∧ ¬program.ReadsWidth := by decide + +/-- A single explicit bound covering the fourth-power square-root input, all ceiling +temporaries and the worst-case descriptor reservation. Signed payloads have a separate bound. -/ +@[expose] +def budget (n D g p : ℕ) : ℕ := + 4 * ((n * n) * (n * n)) + 20 * p * (n * n) + 30 * (n * n) + + 8 * D + 8 * g + 8 * n + 8 * p + 64 + +/-- Every setup, parameter, signed residue, class scan and descriptor instruction is charged; +the final halt is added by the flat caller. -/ +@[expose] +def cost (n D p : ℕ) : ℕ := + 69 * (n * n) + p * (31 * (n * n) + 34) + + 5 * sOf D + 5 * queryCap n D + 85 + +private abbrev bank : Fin 15 → Fin 32 := Fin.castLE (by decide) + +private theorem bank_injective : Function.Injective bank := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 32 => k.val) h) + +private theorem parameters_preserves : ∀ i : Fin 32, 15 ≤ i.val → parameters.Preserves i := by + decide + +private theorem chunks_preserves : ∀ i : Fin 32, + 28 ≤ i.val → ChunkCatalogue.program.Preserves i := by decide + +/-- Exact computed parameters, residue/query arrays, concatenated descriptors and memory +frame. This is the readback established by the actual native catalogue execution. -/ +@[expose] +def Output (n D g p : ℕ) (values : ℕ → ℤ) (initial y : Data 32) : Prop := + let N := n * n + let R := fun q => (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let out := 5 + 6 * N + p + 2 * N * p + y.regs ChunkCatalogue.size = n ∧ y.regs ChunkCatalogue.modulus = p ∧ + y.regs ChunkCatalogue.counts = 5 + 6 * N ∧ + y.regs ChunkCatalogue.queries = 5 + 6 * N + p ∧ + y.regs ChunkCatalogue.dst = out ∧ + y.regs ChunkCatalogue.capacity = queryCap n D ∧ + y.regs pieceLength = pieceSize D g ∧ y.regs pieces = numPieces n D g ∧ + y.regs ChunkCatalogue.written = ChunkCatalogue.before C (queryCap n D) p ∧ + y.regs ChunkCatalogue.heap = out + 3 * y.regs ChunkCatalogue.written ∧ + (∀ q < 3 * N, y.mem (5 + 3 * N + q) = R q) ∧ + (∀ rho < p, y.mem (5 + 6 * N + rho) = C rho) ∧ + (∀ rho < p, ∀ q < N, R q = rho → + y.mem (5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q) = q / n ∧ + y.mem (5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q + 1) = q % n) ∧ + (∀ rho < p, ∀ k < C rho ⌈/⌉ queryCap n D, + y.mem (out + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k)) = rho ∧ + y.mem (out + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 1) = + 5 + 6 * N + p + 2 * rho * N + 2 * (k * queryCap n D) ∧ + y.mem (out + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 2) = + min (queryCap n D) (C rho - k * queryCap n D)) ∧ + (∀ a, a < 5 + 3 * N ∨ out + 3 * y.regs ChunkCatalogue.written ≤ a → + y.mem a = initial.mem a) + +/-- Prepare the actual catalogue at the fixed header, computing both splitting parameters and +every class/chunk. The result contains no supplied layout or execution certificates. The two +input-dependent fit premises cover ordinary signed values and the explicit numeric budget. -/ +theorem exec_at {w : ℕ} (n D g p : ℕ) (values : ℕ → ℤ) (initial : Data 32) + (hDHeader : initial.mem 1 = D) (hgHeader : initial.mem 2 = g) + (hnHeader : initial.mem 3 = n) (hpHeader : initial.mem 4 = p) + (hinput : ∀ q < 3 * (n * n), initial.mem (5 + q) = Signed.encode (values q)) + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : 0 < p) + (hmod : GenericRAM.Signed.Fits w (p : ℤ)) + (hvalues : ∀ q < 3 * (n * n), GenericRAM.Signed.Fits w (values q)) + (hbudget : Fits (w + 2) (budget n D g p)) : + ∃ y, ExecWithin (w + 2) program initial y (cost n D p) ∧ + Output n D g p values initial y := by + let N := n * n + let R (q : ℕ) := (values q % (p : ℤ)).toNat + let C (rho : ℕ) := Nat.count (fun q => R q = rho) N + let countBase := 5 + 6 * N + let queryBase := countBase + p + let out := queryBase + 2 * N * p + change Fits (w + 2) + (4 * (N * N) + 20 * p * N + 30 * N + 8 * D + 8 * g + 8 * n + 8 * p + 64) at hbudget + obtain ⟨a, ha, har, hac, haq, hares, hacounts, hapairs, haframe⟩ := + ResidueClasses.exec_at (w := w) 3 n p values initial hnHeader hpHeader hinput hp hmod + hvalues (hbudget.of_le (by dsimp [ResidueClasses.addressBoundAt, N]; nlinarith only [])) + change a.regs ResidueClasses.counts = countBase at hac + change a.regs ResidueClasses.queries = queryBase at haq + have ha1 : a.mem 1 = D := (haframe 1 (Or.inl (by omega))).trans hDHeader + have ha2 : a.mem 2 = g := (haframe 2 (Or.inl (by omega))).trans hgHeader + have ha3 : a.mem 3 = n := (haframe 3 (Or.inl (by omega))).trans hnHeader + have ha4 : a.mem 4 = p := (haframe 4 (Or.inl (by omega))).trans hpHeader + have h1 : Fits (w + 2) 1 := hbudget.of_le (by omega) + have h2 : Fits (w + 2) (1 + 1) := hbudget.of_le (by omega) + have h3 : Fits (w + 2) (1 + 1 + 1) := hbudget.of_le (by omega) + have h4 : Fits (w + 2) 4 := hbudget.of_le (by omega) + let b := a.set 31 1 |>.set 0 1 |>.set 7 D |>.set 0 2 |>.set 8 g |>.set 0 3 |>.set 6 n + have hb : ExecWithin (w + 2) deliverParameters a b 7 := by + ram_eval [deliverParameters, b, ha1, ha2, ha3, h1, h2, h3, Fin.mk.injEq] + have hb6 : (b.comap bank).regs Parameters.size = n := by + simp +decide [b, bank, Parameters.size] + have hb7 : (b.comap bank).regs Parameters.dimension = D := by + simp +decide [b, bank, Parameters.dimension] + have hb8 : (b.comap bank).regs Parameters.divisor = g := by + simp +decide [b, bank, Parameters.divisor] + have hs : sOf D ≤ D := Nat.sqrt_le_self D + have hpiece : pieceSize D g ≤ D := (pieceSize_le_sOf hg).trans hs + obtain ⟨v, hv, hvs, hvq, hvB, hvH, hvn, -, -, hvm⟩ := Parameters.exec (b.comap bank) + (by rw [hb7]; exact hbudget.of_le (by omega)) + (by rw [hb6]; exact hbudget.of_le (by dsimp [N]; omega)) + (by rw [hb7, hb8]; exact hbudget.of_le (by omega)) + (by rw [hb6, hb7, hb8]; exact hbudget.of_le (by omega)) + let c := v.extend bank b + have hc : ExecWithin (w + 2) parameters b c (5 * sOf D + 5 * queryCap n D + 27) := by + simpa only [parameters, c, hb6, hb7] using hv.rename bank_injective + have hcLow (i : Fin 15) : c.regs (bank i) = v.regs i := by + change Function.extend bank v.regs b.regs (bank i) = _ + exact bank_injective.extend_apply _ _ i + have hc6 : c.regs 6 = n := by + change c.regs (bank Parameters.size) = n + rw [hcLow] + exact hvn.trans hb6 + have hc10 : c.regs 10 = queryCap n D := by + change c.regs (bank Parameters.capacity) = _ + rw [hcLow] + simpa only [hb6, hb7] using hvq + have hc11 : c.regs 11 = pieceSize D g := by + change c.regs (bank Parameters.pieceLength) = _ + rw [hcLow] + simpa only [hb7, hb8] using hvB + have hc12 : c.regs 12 = numPieces n D g := by + change c.regs (bank Parameters.pieces) = _ + rw [hcLow] + simpa only [hb6, hb7, hb8] using hvH + have hc28 : c.regs 28 = countBase := by + have h := hc.regs_eq (parameters_preserves 28 (by decide)) + exact h.trans ((by simp +decide [b] : b.regs 28 = a.regs 28).trans hac) + have hc29 : c.regs 29 = queryBase := by + have h := hc.regs_eq (parameters_preserves 29 (by decide)) + exact h.trans ((by simp +decide [b] : b.regs 29 = a.regs 29).trans haq) + have hcmem : c.mem = a.mem := hvm + let d := c.set 14 countBase |>.set 15 queryBase |>.set 16 n |>.set 19 (queryCap n D) + |>.set pieceLength (pieceSize D g) |>.set pieces (numPieces n D g) + |>.set 25 4 |>.set 17 p |>.set 18 N |>.set 18 (N + N) + |>.set 18 ((N + N) * p) |>.set 18 ((N + N) * p + queryBase) + have hN : Fits (w + 2) (n * n) := hbudget.of_le (by dsimp [N]; omega) + have h2N : Fits (w + 2) (n * n + n * n) := hbudget.of_le (by dsimp [N]; omega) + have h2Np : Fits (w + 2) ((n * n + n * n) * p) := + hbudget.of_le (by dsimp [N]; nlinarith only []) + have hOut : Fits (w + 2) ((n * n + n * n) * p + queryBase) := + hbudget.of_le (by dsimp [N, queryBase, countBase]; nlinarith only []) + have hd : ExecWithin (w + 2) deliverChunks c d 12 := by + have hc4 : c.mem 4 = p := (congrFun hcmem 4).trans ha4 + ram_eval [deliverChunks, d, hc28, hc29, hc6, hc10, hc11, hc12, hc4, + h4, hN, h2N, h2Np, hOut, N, pieceLength, pieces, ChunkCatalogue.counts, + ChunkCatalogue.queries, ChunkCatalogue.size, ChunkCatalogue.capacity, + ChunkCatalogue.modulus, ChunkCatalogue.dst, ChunkCatalogue.addr, Fin.mk.injEq] + have hd14 : d.regs ChunkCatalogue.counts = countBase := by + simp +decide [d, ChunkCatalogue.counts, pieceLength, pieces] + have hd15 : d.regs ChunkCatalogue.queries = queryBase := by + simp +decide [d, ChunkCatalogue.queries, pieceLength, pieces] + have hd16 : d.regs ChunkCatalogue.size = n := by + simp +decide [d, ChunkCatalogue.size, pieceLength, pieces] + have hd17 : d.regs ChunkCatalogue.modulus = p := by + simp +decide [d, ChunkCatalogue.modulus, pieceLength, pieces] + have hd18 : d.regs ChunkCatalogue.dst = out := by + simp +decide [d, ChunkCatalogue.dst, out, pieceLength, pieces, two_mul, Nat.add_comm] + have hd19 : d.regs ChunkCatalogue.capacity = queryCap n D := by + simp +decide [d, ChunkCatalogue.capacity, pieceLength, pieces] + have hdmem : d.mem = a.mem := hcmem + have hC : ∀ rho < p, C rho ≤ N := fun rho _ => Nat.count_le _ + obtain ⟨y, hy, hycount, hyheap, hymeta, hycells, hyframe⟩ := ChunkCatalogue.exec d C + (by + rw [hd17] + intro rho hrho + rw [hd14, hdmem] + exact hacounts rho hrho) + (by rw [hd17, hd16]; exact hC) + (by rw [hd19]; exact one_le_queryCap hD hDn) + (by rw [hd14, hd17, hd18]; dsimp [out, queryBase]; omega) + (by rw [hd15, hd17, hd16, hd18]; dsimp [out, N]; nlinarith only []) + (hbudget.of_le (by + rw [hd18, hd17, hd16, hd14, hd15] + dsimp [out, queryBase, countBase, N] + nlinarith only [])) + rw [hd17, hd16] at hy + rw [hd19, hd17] at hycount + rw [hd18] at hyheap + rw [hd17, hd19, hd18, hd15, hd16] at hycells + rw [hd18] at hyframe + have hymeta' (i : Fin 32) (hi : 14 ≤ i.val) (hi' : i.val < 20) : + y.regs i = d.regs i := hymeta i hi hi' + have hlen : y.regs pieceLength = pieceSize D g := by + rw [hy.regs_eq (chunks_preserves pieceLength (by decide))] + simp +decide [d, pieceLength, pieces] + have hpieces : y.regs pieces = numPieces n D g := by + rw [hy.regs_eq (chunks_preserves pieces (by decide))] + simp +decide [d, pieces] + have hyold (a' : ℕ) (ha' : a' < out) : y.mem a' = a.mem a' := + (hyframe a' (by omega)).trans (congrFun hdmem a') + refine ⟨y, ?_, (hymeta' _ (by decide) (by decide)).trans hd16, + (hymeta' _ (by decide) (by decide)).trans hd17, + (hymeta' _ (by decide) (by decide)).trans hd14, + (hymeta' _ (by decide) (by decide)).trans hd15, + (hymeta' _ (by decide) (by decide)).trans hd18, + (hymeta' _ (by decide) (by decide)).trans hd19, + hlen, hpieces, hycount, hyheap, ?_, ?_, ?_, hycells, ?_⟩ + · convert ha.seq (hb.seq (hc.seq (hd.seq hy))) using 1 + · rfl + · dsimp [cost, N] + ring + · intro q hq + rw [hyold _ (by dsimp [out, queryBase, countBase]; omega)] + exact hares q hq + · intro rho hrho + rw [hyold _ (by dsimp [out, queryBase, countBase]; omega)] + exact hacounts rho hrho + · intro rho hrho q hq hRq + have hcount : Nat.count (fun k => R k = rho) q ≤ q := Nat.count_le _ + have hbound := Nat.mul_le_mul_left (2 * N) (Nat.succ_le_of_lt hrho) + rw [Nat.mul_succ] at hbound + have h0 : 5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q < out := by dsimp [out, queryBase, countBase]; omega + have h1 : 5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q + 1 < out := by + dsimp [out, queryBase, countBase] + omega + rw [hyold _ h0, hyold _ h1] + exact hapairs rho hrho q hq hRq + · intro a' ha' + change a' < 5 + 3 * N ∨ out + 3 * y.regs ChunkCatalogue.written ≤ a' at ha' + have hframe : ¬(out ≤ a' ∧ a' < out + 3 * y.regs ChunkCatalogue.written) := by + rcases ha' with h | h + · have hlt : a' < out := by dsimp [out, queryBase, countBase]; omega + omega + · omega + rw [hyframe a' hframe, hdmem] + apply haframe a' + rcases ha' with h | h + · exact Or.inl h + · apply Or.inr + change out ≤ a' + omega + +end Cslib.Algorithms.WordRAM.ExactTriangle.Catalogue diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/ChunkCatalogue.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/ChunkCatalogue.lean new file mode 100644 index 0000000000..b2fb43a40c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/ChunkCatalogue.lean @@ -0,0 +1,433 @@ +/- +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.ExactTriangle.Chunks +import Mathlib.Algebra.BigOperators.Fin +import Mathlib.Algebra.Order.BigOperators.Group.Finset + +/-! +# Native enumeration of all query chunks + +The controller reads the actual class-count table produced by `Classes.program` and calls the +existing native chunk writer for each class. Descriptors are concatenated without padding; +their query pointers refer to the original disjoint class arrays. No descriptor layout or +prefix table is supplied by the caller. + +This composes the class and chunk loops in the pinned +[Theorem 17 construction](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/Classes.lean). + +## Main definitions + +* `ChunkCatalogue.program`: fixed thirty-two-register descriptor controller. +* `ChunkCatalogue.before`: the mathematical number of descriptors preceding a class. + +## Main results + +* `ChunkCatalogue.exec`: charged enumeration of the actual class counts, exact descriptor + readback and preservation outside the concatenated output. +* `ChunkCatalogue.before_index`: unique class/chunk coordinates for every occupied output slot, + using Mathlib's existing finite sigma enumeration. +* `ChunkCatalogue.before_le`, `ChunkCatalogue.before_add_lt`: descriptor allocation bounds. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue + +open Cslib.WordRAM + +/-- Class-count table pointer. -/ +abbrev counts : Fin 32 := 14 +/-- Base of the class query blocks, each of capacity `2n²` words. -/ +abbrev queries : Fin 32 := 15 +/-- Matrix side length. -/ +abbrev size : Fin 32 := 16 +/-- Number of residue classes. -/ +abbrev modulus : Fin 32 := 17 +/-- Beginning of the contiguous output descriptor array. -/ +abbrev dst : Fin 32 := 18 +/-- Positive maximum pairs per chunk. -/ +abbrev capacity : Fin 32 := 19 +/-- Current class. -/ +abbrev cursor : Fin 32 := 20 +/-- Total number of descriptors written so far. -/ +abbrev written : Fin 32 := 21 +/-- First free descriptor address. -/ +abbrev heap : Fin 32 := 22 +/-- Constant one. -/ +abbrev one : Fin 32 := 23 +/-- Number of words per descriptor. -/ +abbrev three : Fin 32 := 24 +/-- Current count-table address. -/ +abbrev addr : Fin 32 := 25 +/-- Capacity of one class query block, in words. -/ +abbrev stride : Fin 32 := 26 +/-- Descriptor-length temporary. -/ +abbrev temp : Fin 32 := 27 + +private abbrev bank : Fin 14 → Fin 32 := Fin.castLE (by decide) + +private theorem bank_injective : Function.Injective bank := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 32 => k.val) h) + +/-- Query pointer passed to the chunk writer. -/ +abbrev chunkQueries : Fin 32 := 0 +/-- Output pointer passed to the chunk writer. -/ +abbrev chunkDst : Fin 32 := 1 +/-- Loaded class count passed to the chunk writer. -/ +abbrev chunkTotal : Fin 32 := 2 +/-- Chunk capacity passed to the chunk writer. -/ +abbrev chunkCapacity : Fin 32 := 3 +/-- Residue passed to the chunk writer. -/ +abbrev chunkResidue : Fin 32 := 4 +/-- Number of descriptors returned by the chunk writer. -/ +abbrev chunkCount : Fin 32 := 6 + +/-- Call the existing native chunk writer in the lower register bank. -/ +@[expose] +def chunks : Code .standard 32 := Chunks.program.rename (Fin.castLE (by decide)) + +/-- Read the current count and deliver its actual query pointer to the chunk writer. -/ +@[expose] +def deliver : Code .standard 32 := ram[ + addr ← counts + cursor; chunkTotal ← *addr; + chunkQueries ← cursor * stride; chunkQueries ← chunkQueries + queries; + chunkDst ← heap; chunkCapacity ← capacity; chunkResidue ← cursor ] + +/-- Advance the output heap by the number of descriptors actually written. -/ +@[expose] +def advance : Code .standard 32 := ram[ + temp ← chunkCount * three; heap ← heap + temp; + written ← written + chunkCount; cursor ← cursor + one ] + +/-- One class, including empty classes. -/ +@[expose] +def body : Code .standard 32 := .seq deliver (.seq chunks advance) + +/-- Initialize the concatenation cursor and run every residue class. -/ +@[expose] +def program : Code .standard 32 := + .seq (ram[ one ← #1; three ← #3; cursor ← #0; written ← #0; heap ← dst; + stride ← size * size; stride ← stride + stride ]) + (.loop (.lt cursor modulus) body) + +/-- The complete controller is fixed code and does not inspect physical width. -/ +theorem program_fixed : program.size ≤ 80 ∧ ¬program.ReadsWidth := by decide + +/-- Number of actual chunk descriptors for all classes strictly below `rho`. -/ +@[expose] +def before (C : ℕ → ℕ) (cap rho : ℕ) : ℕ := ∑ r ∈ Finset.range rho, C r ⌈/⌉ cap + +private theorem before_zero (C : ℕ → ℕ) (cap : ℕ) : before C cap 0 = 0 := by + simp [before] + +private theorem before_succ (C : ℕ → ℕ) (cap rho : ℕ) : + before C cap (rho + 1) = before C cap rho + C rho ⌈/⌉ cap := by + exact Finset.sum_range_succ _ _ + +/-- Every actual chunk lies strictly within the concatenated descriptor array. -/ +theorem before_add_lt (C : ℕ → ℕ) (cap : ℕ) {rho p k : ℕ} + (hrho : rho < p) (hk : k < C rho ⌈/⌉ cap) : + before C cap rho + k < before C cap p := by + have hsum : before C cap (rho + 1) ≤ before C cap p := by + unfold before + exact Finset.sum_le_sum_of_subset_of_nonneg + (Finset.range_mono (by omega)) (by intro _ _ _; exact Nat.zero_le _) + rw [before_succ] at hsum + omega + +/-- Every occupied descriptor slot names exactly one class and one chunk. This is the existing +`finSigmaFinEquiv`, expressed with the same prefix sum as the native concatenation loop. -/ +theorem before_index (C : ℕ → ℕ) (cap p i : ℕ) (hi : i < before C cap p) : + ∃! idx : (rho : Fin p) × Fin (C rho.val ⌈/⌉ cap), + i = before C cap idx.1.val + idx.2.val := by + have hsum : (∑ rho : Fin p, C rho.val ⌈/⌉ cap) = before C cap p := by + rw [Fin.sum_univ_eq_sum_range (fun rho => C rho ⌈/⌉ cap)] + rfl + let j : Fin (∑ rho : Fin p, C rho.val ⌈/⌉ cap) := ⟨i, by rw [hsum]; exact hi⟩ + have hval (idx : (rho : Fin p) × Fin (C rho.val ⌈/⌉ cap)) : + (finSigmaFinEquiv idx).val = before C cap idx.1.val + idx.2.val := by + rw [finSigmaFinEquiv_apply] + change (∑ r : Fin idx.1.val, C r.val ⌈/⌉ cap) + idx.2.val = _ + rw [Fin.sum_univ_eq_sum_range (fun rho => C rho ⌈/⌉ cap)] + rfl + let idx := finSigmaFinEquiv.symm j + have heq : finSigmaFinEquiv idx = j := finSigmaFinEquiv.apply_symm_apply j + refine ⟨idx, ?_, ?_⟩ + · exact (congrArg Fin.val heq).symm.trans (hval idx) + · intro k hk + apply finSigmaFinEquiv.injective + apply Fin.ext + rw [heq, hval] + exact hk.symm + +/-- At most `N` queries per class give at most `rho * N` occupied descriptors, since every +nonempty chunk contains at least one query. This bounds the actual allocation before matrix +preparation as well as the descriptor controller's own arithmetic. -/ +theorem before_le {C : ℕ → ℕ} {cap p N : ℕ} (hcap : 0 < cap) + (hC : ∀ r < p, C r ≤ N) {rho : ℕ} (hrho : rho ≤ p) : before C cap rho ≤ rho * N := by + apply (Finset.sum_le_sum fun r hr => ?_).trans_eq + (show (∑ _r ∈ Finset.range rho, N) = rho * N by simp) + exact (Nat.ceilDiv_le_iff hcap).mpr + ((hC r ((Finset.mem_range.mp hr).trans_le hrho)).trans + (Nat.le_mul_of_pos_right N hcap)) + +private theorem chunks_preserves : ∀ i : Fin 32, 14 ≤ i.val → chunks.Preserves i := by decide + +private theorem body_preserves : ∀ i : Fin 32, + (14 ≤ i.val ∧ i ≠ cursor ∧ i ≠ written ∧ i ≠ heap ∧ i ≠ addr ∧ i ≠ temp) → + body.Preserves i := by decide + +private theorem exec_body {w : ℕ} (x : Data 32) (C : ℕ) (hread : x.mem + (x.regs counts + x.regs cursor) = C) + (ho : x.regs one = 1) (ht : x.regs three = 3) (hcap : 0 < x.regs capacity) + (hheap : Fits w (x.regs heap + 3 * C + 3)) + (hquery : Fits w (x.regs queries + x.regs cursor * x.regs stride + 2 * C)) + (hcount : Fits w (x.regs counts + x.regs cursor)) + (hcursor : Fits w (x.regs cursor + 1)) + (hwritten : Fits w (x.regs written + C)) : + ∃ y, ExecWithin w body x y (17 * C + 18) ∧ + y.regs cursor = x.regs cursor + 1 ∧ + y.regs written = x.regs written + C ⌈/⌉ x.regs capacity ∧ + y.regs heap = x.regs heap + (C ⌈/⌉ x.regs capacity) * 3 ∧ + (∀ k < C ⌈/⌉ x.regs capacity, + y.mem (x.regs heap + 3 * k) = x.regs cursor ∧ + y.mem (x.regs heap + 3 * k + 1) = + x.regs queries + x.regs cursor * x.regs stride + 2 * (k * x.regs capacity) ∧ + y.mem (x.regs heap + 3 * k + 2) = min (x.regs capacity) (C - k * x.regs capacity)) ∧ + (∀ a, ¬(x.regs heap ≤ a ∧ + a < x.regs heap + (C ⌈/⌉ x.regs capacity) * 3) → y.mem a = x.mem a) := by + let a := x.set addr (x.regs counts + x.regs cursor) |>.set 2 C + |>.set 0 (x.regs cursor * x.regs stride) + |>.set 0 (x.regs cursor * x.regs stride + x.regs queries) + |>.set 1 (x.regs heap) |>.set 3 (x.regs capacity) |>.set 4 (x.regs cursor) + have hq0 : Fits w (x.regs cursor * x.regs stride) := hquery.of_le (by omega) + have hq1 : Fits w (x.regs cursor * x.regs stride + x.regs queries) := + hquery.of_le (by omega) + have hd : ExecWithin w deliver x a 7 := by + ram_eval [deliver, a, hcount, hread, hq0, hq1, counts, cursor, addr, stride, + queries, heap, capacity, chunkTotal, chunkQueries, chunkDst, chunkCapacity, + chunkResidue, Fin.mk.injEq] + have ha (i : Fin 32) (hi : 14 ≤ i.val) (hne : i ≠ addr) : a.regs i = x.regs i := by + have hlow : i ≠ 0 ∧ i ≠ 1 ∧ i ≠ 2 ∧ i ≠ 3 ∧ i ≠ 4 := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ <;> intro h <;> subst i <;> simp at hi + simp only [a, Data.set_regs, Function.update_of_ne hlow.1, + Function.update_of_ne hlow.2.1, Function.update_of_ne hlow.2.2.1, + Function.update_of_ne hlow.2.2.2.1, Function.update_of_ne hlow.2.2.2.2, + Function.update_of_ne hne] + have hCcap : C ⌈/⌉ x.regs capacity ≤ C := + (Nat.ceilDiv_le_iff hcap).mpr (Nat.le_mul_of_pos_right C hcap) + obtain ⟨v, hv, hvc, -, hcells, hframe⟩ := Chunks.exec (a.comap bank) + (by simpa +decide [a, bank, Chunks.capacity] using hcap) + (by simpa +decide [a, bank, Chunks.dst, Chunks.total] using hheap) + (by simpa +decide [a, bank, Chunks.queries, Chunks.total, Nat.add_comm] using hquery) + let b := v.extend bank a + have he : ExecWithin w chunks a b (17 * C + 7) := by + simpa +decide [chunks, b, a, bank, Chunks.total] using hv.rename bank_injective + have hb (i : Fin 32) (hi : 14 ≤ i.val) (hne : i ≠ addr) : b.regs i = x.regs i := + (he.regs_eq (chunks_preserves i hi)).trans (ha i hi hne) + have hc : b.regs 6 = C ⌈/⌉ x.regs capacity := by + change (v.extend bank a).regs (bank Chunks.count) = _ + change Function.extend bank v.regs a.regs (bank Chunks.count) = _ + rw [bank_injective.extend_apply] + simpa +decide [a, bank, Chunks.total, Chunks.capacity] using hvc + let z := b.set temp ((C ⌈/⌉ x.regs capacity) * 3) + |>.set heap (x.regs heap + (C ⌈/⌉ x.regs capacity) * 3) + |>.set written (x.regs written + C ⌈/⌉ x.regs capacity) + |>.set cursor (x.regs cursor + 1) + have hm : Fits w (C ⌈/⌉ x.regs capacity * 3) := hheap.of_le (by omega) + have hh : Fits w (x.regs heap + C ⌈/⌉ x.regs capacity * 3) := hheap.of_le (by omega) + have hw : Fits w (x.regs written + C ⌈/⌉ x.regs capacity) := hwritten.of_le (by omega) + have hf : ExecWithin w advance b z 4 := by + have bho := hb one (by decide) (by decide) + have bht := hb three (by decide) (by decide) + have bhh := hb heap (by decide) (by decide) + have bhw := hb written (by decide) (by decide) + have bhc := hb cursor (by decide) (by decide) + ram_eval [advance, z, hc, bho, bht, bhh, bhw, bhc, ho, ht, hm, hh, hw, hcursor, + temp, three, heap, written, cursor, one, chunkCount, Fin.mk.injEq] + refine ⟨z, (hd.seq (he.seq hf)).mono (by omega), ?_, ?_, ?_, ?_, ?_⟩ + · simp +decide [z, cursor] + · simp +decide [z, written, cursor] + · simp +decide [z, heap, written, cursor] + · simpa +decide [z, b, a, bank, Chunks.dst, Chunks.queries, Chunks.residue, + Chunks.capacity, Chunks.count, Chunks.total, Data.extend, hvc, Nat.add_comm] using hcells + · simpa +decide [z, b, a, bank, Chunks.dst, Chunks.count, Chunks.total, + Chunks.capacity, hvc, Data.extend, Nat.mul_comm] using hframe + +/-- Read the actual count table and concatenate every class's nonempty chunks. The controller +receives no prefix sums. Both source blocks lie before `dst`; all other memory is preserved. +The charged bound excludes halt. -/ +theorem exec {w : ℕ} (x : Data 32) (C : ℕ → ℕ) + (hread : ∀ r < x.regs modulus, x.mem (x.regs counts + r) = C r) + (hC : ∀ r < x.regs modulus, C r ≤ x.regs size * x.regs size) + (hcap : 0 < x.regs capacity) + (hcounts : x.regs counts + x.regs modulus ≤ x.regs dst) + (hqueries : x.regs queries + 2 * x.regs modulus * (x.regs size * x.regs size) ≤ + x.regs dst) + (hfit : Fits w (x.regs dst + 5 * x.regs modulus * (x.regs size * x.regs size) + + x.regs counts + x.regs modulus + x.regs queries + 2 * (x.regs size * x.regs size) + 5)) : + ∃ y, ExecWithin w program x y + (x.regs modulus * (17 * (x.regs size * x.regs size) + 20) + 9) ∧ + y.regs written = before C (x.regs capacity) (x.regs modulus) ∧ + y.regs heap = x.regs dst + 3 * y.regs written ∧ + (∀ i : Fin 32, 14 ≤ i.val → i.val < 20 → y.regs i = x.regs i) ∧ + (∀ r < x.regs modulus, ∀ k < C r ⌈/⌉ x.regs capacity, + y.mem (x.regs dst + 3 * (before C (x.regs capacity) r + k)) = r ∧ + y.mem (x.regs dst + 3 * (before C (x.regs capacity) r + k) + 1) = + x.regs queries + 2 * r * (x.regs size * x.regs size) + + 2 * (k * x.regs capacity) ∧ + y.mem (x.regs dst + 3 * (before C (x.regs capacity) r + k) + 2) = + min (x.regs capacity) (C r - k * x.regs capacity)) ∧ + (∀ a, ¬(x.regs dst ≤ a ∧ a < x.regs dst + 3 * y.regs written) → + y.mem a = x.mem a) := by + let N := x.regs size * x.regs size + let p := x.regs modulus + let Q := x.regs capacity + let d := x.regs dst + let b := x.regs queries + let c := x.regs counts + let P := before C Q + change ∀ r < p, x.mem (c + r) = C r at hread + change ∀ r < p, C r ≤ N at hC + change 0 < Q at hcap + change c + p ≤ d at hcounts + change b + 2 * p * N ≤ d at hqueries + change Fits w (d + 5 * p * N + c + p + b + 2 * N + 5) at hfit + have hP (r : ℕ) (hr : r ≤ p) : P r ≤ r * N := before_le hcap hC hr + let I (y : Data 32) : Prop := + (∀ i : Fin 32, 14 ≤ i.val → i.val < 20 → y.regs i = x.regs i) ∧ + y.regs one = 1 ∧ y.regs three = 3 ∧ y.regs stride = 2 * N ∧ + y.regs cursor ≤ p ∧ y.regs written = P (y.regs cursor) ∧ + y.regs heap = d + 3 * P (y.regs cursor) ∧ + (∀ r < y.regs cursor, ∀ k < C r ⌈/⌉ Q, + y.mem (d + 3 * (P r + k)) = r ∧ + y.mem (d + 3 * (P r + k) + 1) = b + 2 * r * N + 2 * (k * Q) ∧ + y.mem (d + 3 * (P r + k) + 2) = min Q (C r - k * Q)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 3 * P (y.regs cursor)) → y.mem a = x.mem a) + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt cursor modulus).Holds y) : + ∃ z, ExecWithin w body y z (17 * N + 18) ∧ I z ∧ + p - z.regs cursor < p - y.regs cursor := by + obtain ⟨hyr, hyo, hyt, hys, hyi, hyw, hyh, hycells, hyframe⟩ := hy + have hyr' (i : Fin 32) (hi : 14 ≤ i.val ∧ i.val < 20) := hyr i hi.1 hi.2 + have hyc : y.regs counts = c := hyr' counts (by decide) + have hyb : y.regs queries = b := hyr' queries (by decide) + have hyQ : y.regs capacity = Q := hyr' capacity (by decide) + have hyp : y.regs modulus = p := hyr' modulus (by decide) + have hlt : y.regs cursor < p := by simpa only [Cond.holds_lt, hyp] using hc + have hnext : P (y.regs cursor + 1) = P (y.regs cursor) + C (y.regs cursor) ⌈/⌉ Q := + before_succ C Q (y.regs cursor) + have hbefore := hP (y.regs cursor) hyi + have hnew := hC (y.regs cursor) hlt + have hstep : P (y.regs cursor) + C (y.regs cursor) ≤ p * N := by + have := Nat.mul_le_mul_right N (Nat.succ_le_of_lt hlt) + rw [Nat.succ_mul] at this + omega + have hreadY : y.mem (y.regs counts + y.regs cursor) = C (y.regs cursor) := by + rw [hyc, hyframe _ (by omega)] + exact hread _ hlt + obtain ⟨z, he, hzi, hzw, hzh, hzcells, hzframe⟩ := exec_body y (C (y.regs cursor)) + hreadY hyo hyt (by simpa only [hyQ] using hcap) + (hfit.of_le (by rw [hyh]; nlinarith only [hstep])) + (hfit.of_le (by + rw [hyb, hys] + have := Nat.mul_le_mul_right (2 * N) (Nat.succ_le_of_lt hlt) + rw [Nat.succ_mul] at this + nlinarith only [this, hnew])) + (hfit.of_le (by rw [hyc]; omega)) + (hfit.of_le (by omega)) + (hfit.of_le (by rw [hyw]; nlinarith only [hstep])) + rw [hyQ] at hzw hzh hzcells hzframe + have hpres (i : Fin 32) + (hi : 14 ≤ i.val ∧ i ≠ cursor ∧ i ≠ written ∧ i ≠ heap ∧ i ≠ addr ∧ i ≠ temp) : + z.regs i = y.regs i := he.regs_eq (body_preserves i hi) + have hpr (i : Fin 32) (hi : 14 ≤ i.val) (hi' : i.val < 20) : z.regs i = x.regs i := by + apply (hpres i ?_).trans (hyr i hi hi') + refine ⟨hi, ?_, ?_, ?_, ?_, ?_⟩ <;> intro h <;> subst i <;> + simp only [cursor, written, heap, addr, temp] at hi' <;> omega + refine ⟨z, he.mono (by omega), ⟨hpr, + (hpres one (by decide)).trans hyo, (hpres three (by decide)).trans hyt, + (hpres stride (by decide)).trans hys, by omega, ?_, ?_, ?_, ?_⟩, by omega⟩ + · rw [hzw, hyw, hzi, hnext] + · rw [hzh, hyh, hzi, hnext] + omega + · intro r hr k hk + rw [hzi] at hr + by_cases hr' : r = y.regs cursor + · subst r + have h := hzcells k hk + rw [hyh, hyb, hys] at h + simpa only [Nat.mul_add, Nat.add_assoc, Nat.mul_left_comm, Nat.mul_assoc] using h + · have hrlt : r < y.regs cursor := by omega + have hbnd : P r + C r ⌈/⌉ Q ≤ P (y.regs cursor) := by + rw [← before_succ] + exact Finset.sum_le_sum_of_subset_of_nonneg + (Finset.range_mono (by omega)) (by intros; exact Nat.zero_le _) + have h0 : z.mem (d + 3 * (P r + k)) = y.mem (d + 3 * (P r + k)) := + hzframe _ (by rw [hyh]; omega) + have h1 : z.mem (d + 3 * (P r + k) + 1) = y.mem (d + 3 * (P r + k) + 1) := + hzframe _ (by rw [hyh]; omega) + have h2 : z.mem (d + 3 * (P r + k) + 2) = y.mem (d + 3 * (P r + k) + 2) := + hzframe _ (by rw [hyh]; omega) + rw [h0, h1, h2] + exact hycells r hrlt k hk + · intro a ha + rw [hzi, hnext] at ha + rw [hzframe _ (by rw [hyh]; omega), hyframe _ (by omega)] + let a := x.set one 1 |>.set three 3 |>.set cursor 0 |>.set written 0 |>.set heap d + |>.set stride N |>.set stride (2 * N) + have ha : I a := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi hi' + have hne : i ≠ one ∧ i ≠ three ∧ i ≠ cursor ∧ i ≠ written ∧ i ≠ heap ∧ + i ≠ stride := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩ <;> intro h <;> subst i <;> + simp only [one, three, cursor, written, heap, stride] at hi' <;> omega + simp only [a, Data.set_regs, Function.update_of_ne hne.1, + Function.update_of_ne hne.2.1, Function.update_of_ne hne.2.2.1, + Function.update_of_ne hne.2.2.2.1, Function.update_of_ne hne.2.2.2.2.1, + Function.update_of_ne hne.2.2.2.2.2] + · simp +decide [a, one, three, cursor, written, heap, stride] + · simp +decide [a, three, cursor, written, heap, stride] + · simp +decide [a, stride] + · simp +decide [a, cursor, written, heap, stride] + · simp +decide [a, cursor, written, heap, stride, P, before_zero] + · simp +decide [a, cursor, written, heap, stride, P, before_zero] + · simp +decide [a, cursor, written, heap, stride] + · intro k _ + rfl + have hinit : ExecWithin (B := .standard) w + (ram[ one ← #1; three ← #3; cursor ← #0; written ← #0; heap ← dst; + stride ← size * size; stride ← stride + stride ]) x a 7 := by + have h1 : Fits w 1 := hfit.of_le (by omega) + have h3 : Fits w 3 := hfit.of_le (by omega) + have hN : Fits w (x.regs size * x.regs size) := hfit.of_le (by dsimp [N]; omega) + have h2N : Fits w (x.regs size * x.regs size + x.regs size * x.regs size) := + hfit.of_le (by dsimp [N]; omega) + ram_eval [a, d, N, h1, h3, hN, h2N, one, three, cursor, written, heap, stride, size, dst, + Fin.mk.injEq, two_mul] + obtain ⟨y, he, hy, hend⟩ := ExecWithin.loop_invariant I + (fun y => p - y.regs cursor) hbody ha + obtain ⟨hyr, -, -, -, hyi, hyw, hyh, hycells, hyframe⟩ := hy + have hyp : y.regs modulus = p := hyr modulus (by decide) (by decide) + have hdone : y.regs cursor = p := by + simp only [Cond.holds_lt, hyp] at hend + omega + rw [hdone] at hyw hyh hyframe + refine ⟨y, ?_, hyw, by omega, hyr, ?_, ?_⟩ + · exact (hinit.seq he).mono (by + simp +decide only [a, Data.set_regs, Function.update_apply, cursor, written, + heap, stride, ↓reduceIte, Nat.sub_zero] + dsimp [p, N] + nlinarith only []) + · simpa only [hdone] using hycells + · simpa only [hyw] using hyframe + +end Cslib.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/Chunks.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/Chunks.lean new file mode 100644 index 0000000000..c3cab31814 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/Chunks.lean @@ -0,0 +1,281 @@ +/- +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.Computability.WordRAM.Notation +public import Complexity.Foundations.Data.Nat.CeilDiv + +/-! +# Native query-chunk descriptors + +For one computed query class, the program writes consecutive triples `(residue, pair pointer, +pair count)`. It computes the last chunk's shorter length and advances over the actual two-word +coordinate representation. The input query list is preserved. + +This adapts the chunk loop in the pinned +[Theorem 17 chunk builder](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/Chunks.lean). + +## Main definitions + +* `Chunks.program`: fixed fourteen-register descriptor writer. + +## Main results + +* `Chunks.exec`: exact chunk count, descriptor readback and a memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.Chunks + +open Cslib.WordRAM + +/-- Query pair array base. -/ +abbrev queries : Fin 14 := 0 +/-- Descriptor output base. -/ +abbrev dst : Fin 14 := 1 +/-- Actual number of query pairs. -/ +abbrev total : Fin 14 := 2 +/-- Positive maximum pairs per chunk. -/ +abbrev capacity : Fin 14 := 3 +/-- Class residue. -/ +abbrev residue : Fin 14 := 4 +/-- Consumed pair count. -/ +abbrev pos : Fin 14 := 5 +/-- Number of written chunks. -/ +abbrev count : Fin 14 := 6 +/-- Constant one. -/ +abbrev one : Fin 14 := 7 +/-- Number of words per descriptor. -/ +abbrev three : Fin 14 := 8 +/-- Current chunk length. -/ +abbrev length : Fin 14 := 9 +/-- Output address. -/ +abbrev addr : Fin 14 := 10 +/-- Current pair pointer. -/ +abbrev ptr : Fin 14 := 11 + +/-- Compute a nonempty chunk, write its descriptor and advance both counters. -/ +def body : Code .standard 14 := ram[ + length ← total - pos; + if capacity < length then [ length ← capacity ] else [ length ← length ]; + addr ← count * three; addr ← addr + dst; *addr ← residue; addr ← addr + one; + *addr ← ptr; addr ← addr + one; *addr ← length; + ptr ← ptr + length; ptr ← ptr + length; pos ← pos + length; count ← count + one ] + +/-- Initialize all loop state with charged instructions. -/ +def program : Code .standard 14 := + .seq (ram[ one ← #1; three ← #3; pos ← #0; count ← #0; ptr ← queries ]) + (.loop (.lt pos total) body) + +/-- Fixed code and no physical-width reads. -/ +theorem program_fixed : program.size ≤ 48 ∧ ¬program.ReadsWidth := by decide + +private theorem body_preserves : ∀ i : Fin 14, + (i.val < 5 ∨ i = one ∨ i = three) → body.Preserves i := by decide + +private theorem exec_body {w : ℕ} (x : Data 14) (ho : x.regs one = 1) + (hthree : x.regs three = 3) (hp : x.regs pos < x.regs total) + (hc : x.regs count ≤ x.regs pos) + (hptr : x.regs ptr = x.regs queries + 2 * x.regs pos) + (hfit : Fits w (x.regs dst + 3 * x.regs total + 3)) + (hquery : Fits w (x.regs queries + 2 * x.regs total)) : + ∃ y, ExecWithin w body x y 15 ∧ + y.regs pos = x.regs pos + min (x.regs capacity) (x.regs total - x.regs pos) ∧ + y.regs count = x.regs count + 1 ∧ + y.regs ptr = x.regs queries + + 2 * (x.regs pos + min (x.regs capacity) (x.regs total - x.regs pos)) ∧ + y.mem = Function.update (Function.update (Function.update x.mem + (x.regs dst + 3 * x.regs count) (x.regs residue)) + (x.regs dst + 3 * x.regs count + 1) (x.regs queries + 2 * x.regs pos)) + (x.regs dst + 3 * x.regs count + 2) + (min (x.regs capacity) (x.regs total - x.regs pos)) := by + let L := min (x.regs capacity) (x.regs total - x.regs pos) + have hL : L ≤ x.regs total - x.regs pos := min_le_right .. + have hsub : Fits w (x.regs total - x.regs pos) := hfit.of_le (by omega) + have hlen : Fits w L := hsub.of_le hL + have htriple : Fits w (x.regs count * 3) := hfit.of_le (by omega) + have haddr : Fits w (x.regs count * 3 + x.regs dst) := hfit.of_le (by omega) + have haddr1 : Fits w (x.regs count * 3 + x.regs dst + 1) := hfit.of_le (by omega) + have haddr2 : Fits w (x.regs count * 3 + x.regs dst + 1 + 1) := hfit.of_le (by omega) + have hptr1 : Fits w (x.regs ptr + L) := hquery.of_le (by omega) + have hptr2 : Fits w (x.regs ptr + L + L) := hquery.of_le (by omega) + have hpos : Fits w (x.regs pos + L) := hfit.of_le (by omega) + have hcount : Fits w (x.regs count + 1) := hfit.of_le (by omega) + let a := x.set length (x.regs total - x.regs pos) |>.set length L + |>.set addr (x.regs count * 3) |>.set addr (x.regs count * 3 + x.regs dst) + |>.write (x.regs count * 3 + x.regs dst) (x.regs residue) + |>.set addr (x.regs count * 3 + x.regs dst + 1) + |>.write (x.regs count * 3 + x.regs dst + 1) (x.regs ptr) + |>.set addr (x.regs count * 3 + x.regs dst + 1 + 1) + |>.write (x.regs count * 3 + x.regs dst + 1 + 1) L + |>.set ptr (x.regs ptr + L) |>.set ptr (x.regs ptr + L + L) + |>.set pos (x.regs pos + L) |>.set count (x.regs count + 1) + refine ⟨a, ?_, ?_, ?_, ?_, ?_⟩ + · by_cases h : x.regs capacity < x.regs total - x.regs pos + · have he : L = x.regs capacity := min_eq_left h.le + rw [he] at hlen hptr1 hptr2 hpos + ram_eval [body, a, ho, hthree, h, he, hsub, hlen, htriple, haddr, haddr1, haddr2, + hptr1, hptr2, hpos, hcount, length, total, pos, capacity, addr, count, three, + dst, residue, one, ptr, Fin.mk.injEq] + · have he : L = x.regs total - x.regs pos := min_eq_right (by omega) + rw [he] at hlen hptr1 hptr2 hpos + ram_eval [body, a, ho, hthree, h, he, hsub, hlen, htriple, haddr, haddr1, haddr2, + hptr1, hptr2, hpos, hcount, length, total, pos, capacity, addr, count, three, + dst, residue, one, ptr, Fin.mk.injEq] + · change a.regs pos = x.regs pos + L + simp +decide [a, pos, count] + · simp [a] + · change a.regs ptr = x.regs queries + 2 * (x.regs pos + L) + simp +decide only [a, Data.set_regs, Data.write_regs, Function.update_apply, + pos, count, ptr, ↓reduceIte] + simp only [pos, ptr] at hptr + omega + · simp only [a, Data.set_mem, Data.write_mem, hptr] + have haddr' : x.regs count * 3 + x.regs dst = x.regs dst + 3 * x.regs count := by omega + rw [haddr'] + +/-- Write the exact nonempty chunks of one class. Descriptor `k` names the query slice beginning +at pair `k * capacity`, with its final length truncated to the class size. All other memory is +preserved, so placing the query array outside the descriptor block preserves its coordinates. +The final halt is excluded from the `17 total + 7` bound. -/ +theorem exec {w : ℕ} (x : Data 14) (hcap : 0 < x.regs capacity) + (hfit : Fits w (x.regs dst + 3 * x.regs total + 3)) + (hquery : Fits w (x.regs queries + 2 * x.regs total)) : + ∃ y, ExecWithin w program x y (17 * x.regs total + 7) ∧ + y.regs count = x.regs total ⌈/⌉ x.regs capacity ∧ + (∀ i : Fin 14, i.val < 5 → y.regs i = x.regs i) ∧ + (∀ k < y.regs count, + y.mem (x.regs dst + 3 * k) = x.regs residue ∧ + y.mem (x.regs dst + 3 * k + 1) = x.regs queries + 2 * (k * x.regs capacity) ∧ + y.mem (x.regs dst + 3 * k + 2) = + min (x.regs capacity) (x.regs total - k * x.regs capacity)) ∧ + (∀ a, ¬(x.regs dst ≤ a ∧ a < x.regs dst + 3 * y.regs count) → y.mem a = x.mem a) := by + let N := x.regs total + let Q := x.regs capacity + let d := x.regs dst + let b := x.regs queries + change 0 < Q at hcap + let I (y : Data 14) : Prop := + (∀ i : Fin 14, i.val < 5 → y.regs i = x.regs i) ∧ + y.regs one = 1 ∧ y.regs three = 3 ∧ y.regs pos ≤ N ∧ + y.regs pos = min N (y.regs count * Q) ∧ y.regs count ≤ y.regs pos ∧ + (y.regs count = 0 ∨ (y.regs count - 1) * Q < N) ∧ + y.regs ptr = b + 2 * y.regs pos ∧ + (∀ k < y.regs count, + y.mem (d + 3 * k) = x.regs residue ∧ + y.mem (d + 3 * k + 1) = b + 2 * (k * Q) ∧ + y.mem (d + 3 * k + 2) = min Q (N - k * Q)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 3 * y.regs count) → y.mem a = x.mem a) + have hbody (y : Data 14) (hy : I y) (hc : (Cond.lt pos total).Holds y) : + ∃ z, ExecWithin w body y z 15 ∧ I z ∧ N - z.regs pos < N - y.regs pos := by + obtain ⟨hyr, hyo, hyt, hyp, hymin, hyc, hylast, hyptr, hycells, hyframe⟩ := hy + have hyN : y.regs total = N := hyr total (by decide) + have hyQ : y.regs capacity = Q := hyr capacity (by decide) + have hyd : y.regs dst = d := hyr dst (by decide) + have hyb : y.regs queries = b := hyr queries (by decide) + have hyrho : y.regs residue = x.regs residue := hyr residue (by decide) + have hlt : y.regs pos < N := by simpa only [Cond.holds_lt, hyN] using hc + have hprod : y.regs pos = y.regs count * Q := by + by_cases h : y.regs count * Q ≤ N + · simpa only [min_eq_right h] using hymin + · rw [min_eq_left (by omega)] at hymin + omega + have hQ : 0 < Q := hcap + let L := min Q (N - y.regs pos) + have hL : 0 < L := lt_min hQ (by omega) + have hLle : L ≤ N - y.regs pos := min_le_right .. + obtain ⟨z, he, hzp, hzc, hzptr, hzmem⟩ := exec_body y hyo hyt + (by simpa only [hyN] using hlt) hyc (by simpa only [hyb] using hyptr) + (by simpa only [hyd, hyN] using hfit) (by simpa only [hyb, hyN] using hquery) + simp only [hyN, hyQ, hyb, hyd, hyrho] at hzp hzptr hzmem + have hzmin : z.regs pos = min N (z.regs count * Q) := by + rw [hzp, hzc, Nat.add_mul, Nat.one_mul, ← hprod] + omega + have hpres (i : Fin 14) (hi : i.val < 5 ∨ i = one ∨ i = three) : + z.regs i = y.regs i := he.regs_eq (body_preserves i hi) + refine ⟨z, he, ⟨fun i hi => (hpres i (Or.inl hi)).trans (hyr i hi), + (hpres one (by simp)).trans hyo, (hpres three (by simp)).trans hyt, + by omega, hzmin, by omega, Or.inr ?_, by simpa only [hzp] using hzptr, + ?_, ?_⟩, by omega⟩ + · rw [hzc, Nat.add_sub_cancel, ← hprod] + exact hlt + · intro k hk + rw [hzc] at hk + by_cases h : k = y.regs count + · rw [h, hzmem] + refine ⟨?_, ?_, ?_⟩ + · rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega), + Function.update_self] + · rw [Function.update_of_ne (by omega), Function.update_self, hprod] + · rw [Function.update_self, hprod] + · have hk' : k < y.regs count := by omega + obtain ⟨h0, h1, h2⟩ := hycells k hk' + rw [hzmem] + refine ⟨?_, ?_, ?_⟩ <;> + rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega), + Function.update_of_ne (by omega)] + · exact h0 + · exact h1 + · exact h2 + · intro a ha + rw [hzmem, Function.update_of_ne (by omega), Function.update_of_ne (by omega), + Function.update_of_ne (by omega)] + exact hyframe a (by omega) + let x₀ := x.set one 1 |>.set three 3 |>.set pos 0 |>.set count 0 |>.set ptr b + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hne : i ≠ one ∧ i ≠ three ∧ i ≠ pos ∧ i ≠ count ∧ i ≠ ptr := by + refine ⟨?_, ?_, ?_, ?_, ?_⟩ <;> intro h <;> subst i <;> + simp only [one, three, pos, count, ptr] at hi <;> omega + simp only [x₀, Data.set_regs, Function.update_of_ne hne.1, + Function.update_of_ne hne.2.1, Function.update_of_ne hne.2.2.1, + Function.update_of_ne hne.2.2.2.1, Function.update_of_ne hne.2.2.2.2] + · simp +decide [x₀, one, three, pos, count, ptr] + · simp +decide [x₀, three, pos, count, ptr] + · simp +decide [x₀, pos, count, ptr] + · simp +decide [x₀, pos, count, ptr] + · simp +decide [x₀, pos, count, ptr] + · exact Or.inl (by simp +decide [x₀, count, ptr]) + · simp +decide [x₀, ptr, pos, count] + · simp +decide [x₀, ptr, count] + · intro a _ + rfl + have hinit : ExecWithin (B := .standard) w + (ram[ one ← #1; three ← #3; pos ← #0; count ← #0; ptr ← queries ]) x x₀ 5 := by + have h1 : Fits w 1 := hfit.of_le (by omega) + have h3 : Fits w 3 := hfit.of_le (by omega) + ram_eval [x₀, b, h1, h3, one, three, pos, count, ptr, queries, Fin.mk.injEq] + obtain ⟨y, hy, hI, hc⟩ := ExecWithin.loop_invariant I + (fun y => N - y.regs pos) hbody hx₀ + obtain ⟨hyr, -, -, hyp, hymin, hyc, hylast, -, hycells, hyframe⟩ := hI + have hyN : y.regs total = N := hyr total (by decide) + have hend : y.regs pos = N := by + simp only [Cond.holds_lt, hyN] at hc + omega + have hcover : N ≤ y.regs count * Q := by omega + have hcount : y.regs count = N ⌈/⌉ Q := by + apply Nat.le_antisymm + · rcases hylast with h | h + · omega + · have hcov := Nat.le_ceilDiv_mul hcap (a := N) + by_contra hgt + have hm : (N ⌈/⌉ Q) * Q ≤ (y.regs count - 1) * Q := + Nat.mul_le_mul_right Q (by omega) + omega + · exact (Nat.ceilDiv_le_iff hcap).mpr hcover + refine ⟨y, ?_, hcount, hyr, hycells, hyframe⟩ + convert hinit.seq hy using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, pos, count, ptr, + ↓reduceIte] + dsimp [N] + omega + +end Cslib.Algorithms.WordRAM.ExactTriangle.Chunks diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/Classes.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/Classes.lean new file mode 100644 index 0000000000..18d6040f7a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/Classes.lean @@ -0,0 +1,310 @@ +/- +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.ExactTriangle.QueryScan + +/-! +# Native residue-class query lists + +The fixed program calls `QueryScan.program` for each residue, storing the computed class count +and its row-major coordinate pairs. Each class receives a disjoint block of capacity `2n²`. +The cost of scanning once per residue is included; no class table is supplied as input. + +This adapts the class builder in the pinned +[Theorem 17 program](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/Classes.lean). + +## Main definitions + +* `Classes.program`: fixed twenty-register class enumeration. + +## Main results + +* `Classes.exec`: actual execution, class counts, coordinate readback and a memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.Classes + +open Cslib.WordRAM + +/-- Source residue matrix. -/ +abbrev src : Fin 20 := 0 +/-- Query-scanner output pointer. -/ +abbrev dst : Fin 20 := 1 +/-- Matrix side. -/ +abbrev size : Fin 20 := 2 +/-- Query-scanner selected residue. -/ +abbrev residue : Fin 20 := 3 +/-- Query-scanner emitted count. -/ +abbrev count : Fin 20 := 6 +/-- Array of class counts. -/ +abbrev counts : Fin 20 := 12 +/-- Array of disjoint query blocks. -/ +abbrev queries : Fin 20 := 13 +/-- Number of residues to enumerate. -/ +abbrev modulus : Fin 20 := 14 +/-- Current class. -/ +abbrev cursor : Fin 20 := 15 +/-- Constant one. -/ +abbrev one : Fin 20 := 16 +/-- Count address. -/ +abbrev addr : Fin 20 := 17 +/-- Capacity in words of a query block. -/ +abbrev stride : Fin 20 := 18 + +private abbrev scanReg : Fin 12 → Fin 20 := Fin.castLE (by decide) + +private theorem scanReg_injective : Function.Injective scanReg := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 20 => k.val) h) + +/-- Reuse the certified scanner with a disjoint register bank for the outer loop. -/ +def scan : Code .standard 20 := QueryScan.program.rename (Fin.castLE (by decide)) + +/-- Compute the class's output pointer, scan, record its count, and advance. -/ +def body : Code .standard 20 := + .seq (ram[ dst ← cursor * stride; dst ← dst + queries; residue ← cursor ]) + (.seq scan (ram[ addr ← counts + cursor; *addr ← count; cursor ← cursor + one ])) + +/-- The modulus is runtime data; the program itself is independent of it. -/ +def program : Code .standard 20 := + .seq (ram[ one ← #1; cursor ← #0; stride ← size * size; stride ← stride + stride ]) + (.loop (.lt cursor modulus) body) + +/-- Fixed instruction graph, with no physical-width reads. -/ +theorem program_fixed : program.size ≤ 64 ∧ ¬program.ReadsWidth := by decide + +private theorem scan_preserves : ∀ i : Fin 20, 12 ≤ i.val → scan.Preserves i := by decide + +private theorem body_preserves : ∀ i : Fin 20, + (i = src ∨ i = size ∨ i = counts ∨ i = queries ∨ i = modulus ∨ i = one ∨ i = stride) → + body.Preserves i := by decide + +/-- Enumerate every residue class into actual count and coordinate arrays. The source matrix, +count array and query capacity blocks must occur in that order. The final halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 20) (R : ℕ → ℕ) (h1 : Fits w 1) + (hinput : ∀ q < x.regs size * x.regs size, x.mem (x.regs src + q) = R q) + (hsource : x.regs src + x.regs size * x.regs size ≤ x.regs counts) + (hblocks : x.regs counts + x.regs modulus ≤ x.regs queries) + (hcount : Fits w (x.regs counts + x.regs modulus)) + (hquery : Fits w (x.regs queries + 2 * (x.regs size * x.regs size) * x.regs modulus)) + (harea : Fits w (2 * (x.regs size * x.regs size))) : + ∃ y, ExecWithin w program x y + (x.regs modulus * (14 * (x.regs size * x.regs size) + 14) + 6) ∧ + y.regs cursor = x.regs modulus ∧ + (∀ j < x.regs modulus, + y.mem (x.regs counts + j) = Nat.count (fun q => R q = j) + (x.regs size * x.regs size)) ∧ + (∀ j < x.regs modulus, ∀ q < x.regs size * x.regs size, R q = j → + y.mem (x.regs queries + 2 * (x.regs size * x.regs size) * j + + 2 * Nat.count (fun k => R k = j) q) = q / x.regs size ∧ + y.mem (x.regs queries + 2 * (x.regs size * x.regs size) * j + + 2 * Nat.count (fun k => R k = j) q + 1) = q % x.regs size) ∧ + (∀ a, ¬(x.regs counts ≤ a ∧ a < x.regs counts + x.regs modulus) → + ¬(x.regs queries ≤ a ∧ + a < x.regs queries + 2 * (x.regs size * x.regs size) * x.regs modulus) → + y.mem a = x.mem a) := by + let n := x.regs size + let p := x.regs modulus + let b := x.regs counts + let d := x.regs queries + let s := 2 * (n * n) + let C (j q : ℕ) := Nat.count (fun k => R k = j) q + change x.regs src + n * n ≤ b at hsource + change b + p ≤ d at hblocks + change Fits w (b + p) at hcount + change Fits w (d + s * p) at hquery + change Fits w s at harea + let I (y : Data 20) : Prop := + (∀ i, i = src ∨ i = size ∨ i = counts ∨ i = queries ∨ i = modulus → + y.regs i = x.regs i) ∧ + y.regs one = 1 ∧ y.regs stride = s ∧ y.regs cursor ≤ p ∧ + (∀ j < y.regs cursor, y.mem (b + j) = C j (n * n)) ∧ + (∀ j < y.regs cursor, ∀ q < n * n, R q = j → + y.mem (d + s * j + 2 * C j q) = q / n ∧ + y.mem (d + s * j + 2 * C j q + 1) = q % n) ∧ + (∀ a, ¬(b ≤ a ∧ a < b + y.regs cursor) → + ¬(d ≤ a ∧ a < d + s * y.regs cursor) → y.mem a = x.mem a) + have hbody (y : Data 20) (hy : I y) (hc : (Cond.lt cursor modulus).Holds y) : + ∃ z, ExecWithin w body y z (14 * (n * n) + 12) ∧ I z ∧ + p - z.regs cursor < p - y.regs cursor := by + obtain ⟨hyr, hyo, hys, hyc, hycounts, hypairs, hyframe⟩ := hy + have hyn : y.regs size = n := hyr size (by simp) + have hyp : y.regs modulus = p := hyr modulus (by simp) + have hyb : y.regs counts = b := hyr counts (by simp) + have hyd : y.regs queries = d := hyr queries (by simp) + have hysrc : y.regs src = x.regs src := hyr src (by simp) + have hlt : y.regs cursor < p := by simpa only [Cond.holds_lt, hyp] using hc + let j := y.regs cursor + change j < p at hlt + have hmul : s * (j + 1) ≤ s * p := Nat.mul_le_mul_left s hlt + have hstep : s * (j + 1) = s * j + s := Nat.mul_succ s j + have hsj : Fits w (j * s) := hquery.of_le (by rw [Nat.mul_comm j s]; omega) + have hdest : Fits w (j * s + d) := hquery.of_le (by rw [Nat.mul_comm j s]; omega) + let a := y.set dst (j * s) |>.set dst (j * s + d) |>.set residue j + have hstart : ExecWithin w + (ram[ dst ← cursor * stride; dst ← dst + queries; residue ← cursor ]) y a 3 := by + ram_eval [a, j, hys, hyd, hsj, hdest, dst, queries, cursor, residue, stride, Fin.mk.injEq] + have ha (i : Fin 20) (hi : i ≠ dst ∧ i ≠ residue) : a.regs i = y.regs i := by + simp only [a, Data.set_regs, Function.update_of_ne hi.1, Function.update_of_ne hi.2] + have ha_src : (a.comap scanReg).regs QueryScan.src = x.regs src := by + change a.regs src = _ + rw [ha src (by decide), hysrc] + have ha_size : (a.comap scanReg).regs QueryScan.size = n := by + change a.regs size = _ + rw [ha size (by decide), hyn] + have ha_dst : (a.comap scanReg).regs QueryScan.dst = j * s + d := by + simp +decide [a, scanReg, QueryScan.dst, dst, residue] + have ha_residue : (a.comap scanReg).regs QueryScan.residue = j := by + simp +decide [a, scanReg, QueryScan.residue, residue] + have hread : ∀ q < n * n, a.mem (x.regs src + q) = R q := by + intro q hq + change y.mem (x.regs src + q) = _ + rw [hyframe _ (by dsimp [b, n] at *; omega) (by dsimp [d, b, n] at *; omega)] + exact hinput q hq + obtain ⟨v, hv, -, hvc, hvr, hvpairs, hvframe⟩ := QueryScan.exec (a.comap scanReg) R h1 + (by simpa only [ha_src, ha_size, Data.comap_mem] using hread) + (by rw [ha_src, ha_size]; exact hcount.of_le (by omega)) + (by rw [ha_dst, ha_size]; exact hquery.of_le (by rw [Nat.mul_comm j s]; omega)) + (by rw [ha_src, ha_size, ha_dst, Nat.mul_comm j s]; omega) + simp only [ha_dst, ha_residue, ha_size] at hvc hvpairs hvframe + let v' := v.extend scanReg a + have hv' : ExecWithin w scan a v' (14 * (n * n) + 6) := by + simpa only [ha_size, scan, v'] using hv.rename scanReg_injective + have hvreg (i : Fin 20) (hi : 12 ≤ i.val) : v'.regs i = y.regs i := + (hv'.regs_eq (scan_preserves i hi)).trans (ha i (by + constructor <;> intro heq <;> subst i <;> simp_all [dst, residue])) + have hext (i : Fin 12) : v'.regs (scanReg i) = v.regs i := + scanReg_injective.extend_apply _ _ i + have hvsrc : v'.regs src = y.regs src := by + change v'.regs (scanReg QueryScan.src) = _ + rw [hext] + have h := hvr QueryScan.src (by decide) + change v.regs QueryScan.src = a.regs src at h + exact h.trans (ha src (by decide)) + have hvsize : v'.regs size = y.regs size := by + change v'.regs (scanReg QueryScan.size) = _ + rw [hext] + have h := hvr QueryScan.size (by decide) + change v.regs QueryScan.size = a.regs size at h + exact h.trans (ha size (by decide)) + have hvcount : v'.regs count = C j (n * n) := by + change v'.regs (scanReg QueryScan.count) = _ + rw [hext] + exact hvc + have hvj : v'.regs cursor = j := hvreg cursor (by decide) + have hvone : v'.regs one = 1 := (hvreg one (by decide)).trans hyo + have hvb : v'.regs counts = b := (hvreg counts (by decide)).trans hyb + have haddr : Fits w (b + j) := hcount.of_le (by dsimp [b, p] at *; omega) + have hj : Fits w (j + 1) := hcount.of_le (by dsimp [p] at *; omega) + let z := v'.set addr (b + j) |>.write (b + j) (C j (n * n)) |>.set cursor (j + 1) + have hend : ExecWithin w + (ram[ addr ← counts + cursor; *addr ← count; cursor ← cursor + one ]) v' z 3 := by + ram_eval [z, hvb, hvj, hvcount, hvone, haddr, hj, addr, counts, cursor, count, one, + Fin.mk.injEq] + have hpres (i : Fin 20) + (hi : i = src ∨ i = size ∨ i = counts ∨ i = queries ∨ i = modulus ∨ + i = one ∨ i = stride) : z.regs i = y.regs i := + (hstart.seq (hv'.seq hend)).regs_eq (body_preserves i hi) + have hzj : z.regs cursor = j + 1 := by simp [z] + have hmem : z.mem = Function.update v.mem (b + j) (C j (n * n)) := rfl + have hvframe' : ∀ q, ¬(d + s * j ≤ q ∧ q < d + s * (j + 1)) → + v.mem q = y.mem q := by + intro q hq + apply hvframe q + rw [hvc, Nat.mul_comm j s, Nat.add_comm (s * j) d] + have hcle : C j (n * n) ≤ n * n := Nat.count_le _ + change ¬(d + s * j ≤ q ∧ q < d + s * j + 2 * C j (n * n)) + dsimp [s] at * + omega + have hcost : ExecWithin w body y z (14 * (n * n) + 12) := by + convert hstart.seq (hv'.seq hend) using 1 <;> first | rfl | omega + refine ⟨z, hcost, ⟨?_, ?_, ?_, by omega, ?_, ?_, ?_⟩, by omega⟩ + · intro i hi + exact (hpres i (by rcases hi with h | h | h | h | h <;> simp [h])).trans (hyr i hi) + · exact (hpres one (by simp)).trans hyo + · exact (hpres stride (by simp)).trans hys + · intro k hk + rw [hzj] at hk + rw [hmem] + by_cases hkj : k = j + · subst k + exact Function.update_self .. + · rw [Function.update_of_ne (by omega), hvframe' _ (by dsimp [b, d, p] at *; omega)] + exact hycounts k (by omega) + · intro k hk q hq hkey + rw [hzj] at hk + rw [hmem] + have hrank : C k q < n * n := (Nat.count_le _).trans_lt hq + have hcountaddr : b + j < d := by dsimp [b, d, p] at *; omega + constructor + · rw [Function.update_of_ne (by omega)] + by_cases hkj : k = j + · rw [hkj] at hkey ⊢ + simpa only [Nat.mul_comm j s, Nat.add_comm (s * j) d] using (hvpairs q hq hkey).1 + · rw [hvframe' _ (by + have hmul' : s * (k + 1) ≤ s * j := Nat.mul_le_mul_left s (by omega) + have hstep' : s * (k + 1) = s * k + s := Nat.mul_succ s k + dsimp [s] at *; omega)] + exact (hypairs k (by omega) q hq hkey).1 + · rw [Function.update_of_ne (by omega)] + by_cases hkj : k = j + · rw [hkj] at hkey ⊢ + simpa only [Nat.mul_comm j s, Nat.add_comm (s * j) d] using (hvpairs q hq hkey).2 + · rw [hvframe' _ (by + have hmul' : s * (k + 1) ≤ s * j := Nat.mul_le_mul_left s (by omega) + have hstep' : s * (k + 1) = s * k + s := Nat.mul_succ s k + dsimp [s] at *; omega)] + exact (hypairs k (by omega) q hq hkey).2 + · intro q hqb hqd + rw [hzj] at hqb hqd + rw [hmem, Function.update_of_ne (by omega), hvframe' q (by omega)] + exact hyframe q (by omega) (by + change ¬(d ≤ q ∧ q < d + s * j) + have hmul' : s * j ≤ s * (j + 1) := Nat.mul_le_mul_left s (by omega) + omega) + let x₀ := x.set one 1 |>.set cursor 0 |>.set stride (n * n) |>.set stride s + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + rcases hi with rfl | rfl | rfl | rfl | rfl <;> + simp +decide [x₀, src, size, counts, queries, modulus, one, cursor, stride] + · simp +decide [x₀, one, cursor, stride] + · simp [x₀] + · simp +decide [x₀, cursor, stride] + · simp +decide [x₀, cursor, stride] + · simp +decide [x₀, cursor, stride] + · intro q _ _ + rfl + have hinit : ExecWithin w + (ram[ one ← #1; cursor ← #0; stride ← size * size; stride ← stride + stride ]) x x₀ 4 := by + have hnfit : Fits w (n * n) := harea.of_le (by change n * n ≤ 2 * (n * n); omega) + have hdbl : Fits w (n * n + n * n) := by simpa only [s, two_mul] using harea + dsimp [n] at hnfit hdbl + ram_eval [x₀, n, s, two_mul, h1, hnfit, hdbl, one, cursor, stride, size, Fin.mk.injEq] + obtain ⟨y, hy, hI, hc⟩ := ExecWithin.loop_invariant I + (fun y => p - y.regs cursor) hbody hx₀ + obtain ⟨hyr, -, -, hyc, hycounts, hypairs, hyframe⟩ := hI + have hyp : y.regs modulus = p := hyr modulus (by simp) + have hend : y.regs cursor = p := by + simp only [Cond.holds_lt, hyp] at hc + omega + refine ⟨y, ?_, hend, ?_, ?_, ?_⟩ + · convert hinit.seq hy using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, cursor, stride, ↓reduceIte] + dsimp [n, p] + simp only [Nat.add_assoc, Nat.reduceAdd] + omega + · simpa only [hend] using hycounts + · simpa only [hend] using hypairs + · simpa only [hend] using hyframe + +end Cslib.Algorithms.WordRAM.ExactTriangle.Classes diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/InstancePair.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/InstancePair.lean new file mode 100644 index 0000000000..156c84b738 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/InstancePair.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.ExactTriangle.WriteMatrices +import Mathlib.Tactic.FinCases + +/-! +# Native matrix pairs and instance records + +One call writes a six-word record `(rho,piece,queries,count,X,Y)` and the actual raw unsigned +zero/one matrices `X : n×D` and `Y : D×n` immediately after it. Both matrices reuse +`WriteMatrices.program`; all pointer arithmetic, argument delivery and record stores are charged. +The query words remain in the earlier computed catalogue and are shared by reference. + +`setup`, `writeHeader`, `deliverLeft`, `deliverRight` and `matrix` are internal program stages. +They are exposed so that the complete catalogue controller can reuse the same fixed program. + +## Main definitions + +* `InstancePair.program`: fixed thirty-two-register matrix-pair and record writer. +* `InstancePair.leftArgs`, `InstancePair.rightArgs`: existing matrix-writer arguments read + directly from the runtime registers. + +## Main results + +* `InstancePair.exec`: both exact matrices and the real record, with a combined outside frame + and a `40nD+38` charged instruction bound before halt. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.InstancePair + +open Cslib.WordRAM + +/-- Computed A–C residue-array pointer. -/ +abbrev srcLeft : Fin 32 := 17 +/-- Computed B–C residue-array pointer. -/ +abbrev srcRight : Fin 32 := 18 +/-- First word of the instance record. -/ +abbrev base : Fin 32 := 19 +/-- Outer matrix dimension. -/ +abbrev size : Fin 32 := 20 +/-- Padded middle matrix dimension. -/ +abbrev dimension : Fin 32 := 21 +/-- Positive residue modulus. -/ +abbrev modulus : Fin 32 := 22 +/-- First original vertex in the piece. -/ +abbrev first : Fin 32 := 23 +/-- Actual number of vertices in the piece. -/ +abbrev length : Fin 32 := 24 +/-- Query residue. -/ +abbrev rho : Fin 32 := 25 +/-- Actual query-pair pointer. -/ +abbrev queries : Fin 32 := 26 +/-- Actual number of query pairs. -/ +abbrev count : Fin 32 := 27 +/-- Index of the original piece. -/ +abbrev piece : Fin 32 := 28 +/-- Constant one, preserved by both matrix calls. -/ +abbrev one : Fin 32 := 29 +/-- Header cursor. -/ +abbrev addr : Fin 32 := 30 +/-- Number of words in one matrix. -/ +abbrev area : Fin 32 := 31 + +/-- Existing left-matrix arguments, with its output following the six-word record. -/ +@[expose] +def leftArgs (x : Data 32) : WriteMatrices.Args where + src := x.regs srcLeft + dst := x.regs base + 6 + n := x.regs size + D := x.regs dimension + p := x.regs modulus + first := x.regs first + length := x.regs length + rho := x.regs rho + +/-- Existing right-matrix arguments, immediately following the left matrix. -/ +@[expose] +def rightArgs (x : Data 32) : WriteMatrices.Args := + { leftArgs x with + src := x.regs srcRight + dst := x.regs base + 6 + x.regs size * x.regs dimension } + +/-- Compute the left output pointer and matrix area with native arithmetic. -/ +@[expose] +def setup : Code .standard 32 := + let dst : Fin 32 := 1 + ram[ one ← #1; addr ← #6; dst ← base + addr; area ← size * dimension ] + +/-- Write the actual query reference and both computed matrix pointers. -/ +@[expose] +def writeHeader : Code .standard 32 := + let dst : Fin 32 := 1 + let scratch : Fin 32 := 0 + ram[ addr ← base; *addr ← rho; addr ← addr + one; *addr ← piece; + addr ← addr + one; *addr ← queries; addr ← addr + one; *addr ← count; + addr ← addr + one; *addr ← dst; addr ← addr + one; + scratch ← dst + area; *addr ← scratch ] + +/-- Deliver the seven remaining left-matrix arguments; its destination is already computed. -/ +@[expose] +def deliverLeft : Code .standard 32 := + let src : Fin 32 := 0 + let n : Fin 32 := 2 + let D : Fin 32 := 3 + let p : Fin 32 := 4 + let start : Fin 32 := 5 + let len : Fin 32 := 6 + let residue : Fin 32 := 7 + ram[ src ← srcLeft; n ← size; D ← dimension; p ← modulus; + start ← first; len ← length; residue ← rho ] + +/-- The left writer preserves its argument registers, so only the source and output pointer +change for the right call. -/ +@[expose] +def deliverRight : Code .standard 32 := + let src : Fin 32 := 0 + let dst : Fin 32 := 1 + ram[ src ← srcRight; dst ← dst + area ] + +/-- Reuse either native matrix writer in the lower seventeen registers. -/ +@[expose] +def matrix (side : Bool) : Code .standard 32 := + (WriteMatrices.program side).rename (Fin.castLE (by decide)) + +/-- Complete record and matrix-pair construction. -/ +@[expose] +def program : Code .standard 32 := + .seq (.seq setup writeHeader) (.seq deliverLeft (.seq (matrix false) + (.seq deliverRight (matrix true)))) + +/-- Finite code independent of every runtime dimension and physical width. -/ +theorem program_fixed : program.size ≤ 160 ∧ ¬program.ReadsWidth := by decide + +private abbrev bank : Fin 17 → Fin 32 := Fin.castLE (by decide) + +private theorem bank_injective : Function.Injective bank := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 32 => k.val) h) + +private theorem header_preserves : ∀ i : Fin 32, + 17 ≤ i.val → i.val < 29 → (Code.seq setup writeHeader).Preserves i := by decide + +private theorem matrix_preserves (side : Bool) : ∀ i : Fin 32, + 17 ≤ i.val → (matrix side).Preserves i := by cases side <;> decide + +/-- Six raw unsigned record words: residue, piece index, shared query pointer/count, then the +two computed matrix pointers. Matrix entries themselves are raw zero/one words. -/ +@[expose] +def recordWords (x : Data 32) : Fin 6 → ℕ := + ![x.regs rho, x.regs piece, x.regs queries, x.regs count, (leftArgs x).dst, (rightArgs x).dst] + +private theorem exec_header {w : ℕ} (x : Data 32) + (hfit : Fits w (x.regs base + 6 + 2 * (x.regs size * x.regs dimension) + 7)) : + ∃ y, ExecWithin w (.seq setup writeHeader) x y 17 ∧ + y.regs 1 = x.regs base + 6 ∧ y.regs area = x.regs size * x.regs dimension ∧ + (∀ i : Fin 32, 17 ≤ i.val → i.val < 29 → y.regs i = x.regs i) ∧ + (Block.mk (x.regs base) 6).Realizes y.mem (recordWords x) ∧ + (∀ a, ¬(x.regs base ≤ a ∧ a < x.regs base + 6) → y.mem a = x.mem a) := by + let b := x.regs base + let M := x.regs size * x.regs dimension + let a := x.set one 1 |>.set addr 6 |>.set 1 (b + 6) |>.set area M + have h1 : Fits w 1 := hfit.of_le (by omega) + have h6 : Fits w 6 := hfit.of_le (by omega) + have hb6 : Fits w (x.regs base + 6) := hfit.of_le (by omega) + have hM : Fits w (x.regs size * x.regs dimension) := hfit.of_le (by omega) + have hsetup : ExecWithin w setup x a 4 := by + ram_eval [setup, a, b, M, one, addr, base, area, size, dimension, h1, h6, hb6, hM, + Fin.mk.injEq] + let y := a.set addr b |>.write b (x.regs rho) + |>.set addr (b + 1) |>.write (b + 1) (x.regs piece) + |>.set addr (b + 2) |>.write (b + 2) (x.regs queries) + |>.set addr (b + 3) |>.write (b + 3) (x.regs count) + |>.set addr (b + 4) |>.write (b + 4) (b + 6) + |>.set addr (b + 5) |>.set 0 (b + 6 + M) |>.write (b + 5) (b + 6 + M) + have hb1 : Fits w (b + 1) := hfit.of_le (by dsimp [b]; omega) + have hb2 : Fits w (b + 1 + 1) := hfit.of_le (by dsimp [b]; omega) + have hb3 : Fits w (b + 1 + 1 + 1) := hfit.of_le (by dsimp [b]; omega) + have hb4 : Fits w (b + 1 + 1 + 1 + 1) := hfit.of_le (by dsimp [b]; omega) + have hb5 : Fits w (b + 1 + 1 + 1 + 1 + 1) := hfit.of_le (by dsimp [b]; omega) + have hbM : Fits w (b + 6 + M) := hfit.of_le (by dsimp [b, M]; omega) + have hheader : ExecWithin w writeHeader a y 13 := by + ram_eval [writeHeader, y, a, b, M, one, addr, base, area, rho, piece, queries, count, + hb1, hb2, hb3, hb4, hb5, hbM, Fin.mk.injEq] + refine ⟨y, hsetup.seq hheader, ?_, ?_, ?_, ?_, ?_⟩ + · simp +decide [y, a, b, addr, area, one] + · simp +decide [y, a, M, addr, area] + · intro i hi hi' + exact (hsetup.seq hheader).regs_eq (header_preserves i hi hi') + · intro i + fin_cases i <;> + simp [Block.read, recordWords, leftArgs, rightArgs, y, a, b, M] + · intro j hj + simp only [y, a, Data.set_mem, Data.write_mem] + rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega), + Function.update_of_ne (by omega), Function.update_of_ne (by omega), + Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + +/-- Write both actual matrices and their shared-query instance record. The source arrays are +ordinary residue words before the output region. Existing `WriteMatrices.entry` supplies the +semantics; no adjacency array, execution or time contract is an input. Halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 32) (L R : ℕ → ℕ) + (hrho : x.regs rho < x.regs modulus) + (hpiece : x.regs first + x.regs length ≤ x.regs size) + (hL : ∀ i < x.regs size * x.regs size, L i < x.regs modulus) + (hR : ∀ i < x.regs size * x.regs size, R i < x.regs modulus) + (hleft : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcLeft + i) = L i) + (hright : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcRight + i) = R i) + (hsL : x.regs srcLeft + x.regs size * x.regs size ≤ x.regs base) + (hsR : x.regs srcRight + x.regs size * x.regs size ≤ x.regs base) + (hfit : Fits w (x.regs base + 6 + 2 * (x.regs size * x.regs dimension) + + x.regs srcLeft + x.regs srcRight + 2 * (x.regs size * x.regs size) + + 2 * x.regs modulus + 7)) : + ∃ y, ExecWithin w program x y (40 * (x.regs size * x.regs dimension) + 38) ∧ + (Block.mk (x.regs base) 6).Realizes y.mem (recordWords x) ∧ + (Block.mk (leftArgs x).dst (x.regs size * x.regs dimension)).Realizes y.mem + (fun i => WriteMatrices.entry false (leftArgs x) L i) ∧ + (Block.mk (rightArgs x).dst (x.regs size * x.regs dimension)).Realizes y.mem + (fun i => WriteMatrices.entry true (rightArgs x) R i) ∧ + (∀ a, ¬(x.regs base ≤ a ∧ + a < x.regs base + 6 + 2 * (x.regs size * x.regs dimension)) → y.mem a = x.mem a) := by + let M := x.regs size * x.regs dimension + let A := leftArgs x + let B := rightArgs x + obtain ⟨a, ha, ha1, haM, hameta, harecord, haframe⟩ := exec_header x + (hfit.of_le (by omega)) + have har (i : Fin 32) (hi : 17 ≤ i.val ∧ i.val < 29) : a.regs i = x.regs i := + hameta i hi.1 hi.2 + let b := a.set 0 (x.regs srcLeft) |>.set 2 (x.regs size) |>.set 3 (x.regs dimension) + |>.set 4 (x.regs modulus) |>.set 5 (x.regs first) |>.set 6 (x.regs length) + |>.set 7 (x.regs rho) + have hb : ExecWithin w deliverLeft a b 7 := by + have hsrc := har srcLeft (by decide) + have hn := har size (by decide) + have hD := har dimension (by decide) + have hp := har modulus (by decide) + have hfirst := har first (by decide) + have hlen := har length (by decide) + have hr := har rho (by decide) + ram_eval [deliverLeft, b, hsrc, hn, hD, hp, hfirst, hlen, hr, srcLeft, size, + dimension, modulus, first, length, rho, Fin.mk.injEq] + have hbEntry : WriteMatrices.Entry A (b.comap bank) := by + constructor <;> + simp +decide [A, leftArgs, b, bank, WriteMatrices.src, WriteMatrices.dst, + WriteMatrices.size, WriteMatrices.dimension, WriteMatrices.modulus, WriteMatrices.first, + WriteMatrices.length, WriteMatrices.rho, ha1] + obtain ⟨v, hv, hvEntry, -, hvleft, hvframe⟩ := WriteMatrices.exec false A L (b.comap bank) + hbEntry hrho hpiece hL + (by + intro i hi + change a.mem (x.regs srcLeft + i) = L i + rw [haframe _ (by dsimp [A, leftArgs] at hi; omega)] + exact hleft i hi) + (hfit.of_le (by dsimp [A, leftArgs]; omega)) + (hfit.of_le (by dsimp [A, leftArgs]; omega)) + (hfit.of_le (by dsimp [A, leftArgs]; omega)) + (by dsimp [A, leftArgs]; omega) + let c := v.extend bank b + have hc : ExecWithin w (matrix false) b c (20 * M + 6) := by + simpa only [matrix, c, A, leftArgs, M] using hv.rename bank_injective + have hcEntry : WriteMatrices.Entry A (c.comap bank) := by + simpa only [c, Data.comap_extend bank_injective] using hvEntry + have hc1 : c.regs 1 = A.dst := hcEntry.dst + have hcM : c.regs area = M := + (hc.regs_eq (matrix_preserves false area (by decide))).trans (by + simpa +decide [b, area, M] using haM) + have hcRight : c.regs srcRight = x.regs srcRight := + (hc.regs_eq (matrix_preserves false srcRight (by decide))).trans (by + simpa +decide [b, srcRight] using har srcRight (by decide)) + have hcframe (j : ℕ) (hj : ¬(Block.mk A.dst M).Owns j) : c.mem j = a.mem j := + hvframe j hj + let d := c.set 0 (x.regs srcRight) |>.set 1 (A.dst + M) + have hrightDst : Fits w (A.dst + M) := hfit.of_le (by dsimp [A, leftArgs, M]; omega) + have hd : ExecWithin w deliverRight c d 2 := by + ram_eval [deliverRight, d, hcRight, hc1, hcM, hrightDst, srcRight, area, Fin.mk.injEq] + have hdEntry : WriteMatrices.Entry B (d.comap bank) := by + constructor + · simp +decide [d, B, rightArgs, bank, WriteMatrices.src] + · simp +decide [d, B, rightArgs, A, leftArgs, M, bank, WriteMatrices.dst] + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.size] using hcEntry.size + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.dimension] using hcEntry.dimension + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.modulus] using hcEntry.modulus + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.first] using hcEntry.first + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.length] using hcEntry.length + · simpa +decide [d, B, rightArgs, bank, WriteMatrices.rho] using hcEntry.rho + obtain ⟨z, hz, -, -, hzright, hzframe⟩ := WriteMatrices.exec true B R (d.comap bank) + hdEntry hrho hpiece hR + (by + intro i hi + change c.mem (x.regs srcRight + i) = R i + rw [hcframe _ (by + dsimp [Block.Owns, A, leftArgs, M] + dsimp [B, rightArgs, leftArgs] at hi + omega), + haframe _ (by dsimp [B, rightArgs, leftArgs] at hi; omega)] + exact hright i hi) + (hfit.of_le (by dsimp [B, rightArgs, leftArgs]; omega)) + (hfit.of_le (by dsimp [B, rightArgs, leftArgs]; omega)) + (hfit.of_le (by dsimp [B, rightArgs, leftArgs]; omega)) + (by dsimp [B, rightArgs, leftArgs]; omega) + let y := z.extend bank d + have hy : ExecWithin w (matrix true) d y (20 * M + 6) := by + simpa only [matrix, y, B, rightArgs, leftArgs, M] using hz.rename bank_injective + have hyframe (j : ℕ) (hj : ¬(Block.mk B.dst M).Owns j) : y.mem j = c.mem j := + hzframe j hj + refine ⟨y, ?_, ?_, ?_, hzright, ?_⟩ + · convert ha.seq (hb.seq (hc.seq (hd.seq hy))) using 1 + · rfl + · dsimp [M] + omega + · intro i + change y.mem (x.regs base + i.val) = recordWords x i + rw [hyframe _ (by + dsimp [Block.Owns, B, rightArgs, leftArgs] + have hi : i.val < 6 := i.isLt + omega), + hcframe _ (by + dsimp [Block.Owns, A, leftArgs] + have hi : i.val < 6 := i.isLt + omega)] + exact harecord i + · intro i + change y.mem (A.dst + i.val) = WriteMatrices.entry false A L i + rw [hyframe _ (by + dsimp [Block.Owns, B, rightArgs, leftArgs, A] + have hi := i.isLt + dsimp [M] at hi + omega)] + exact hvleft i + · intro j hj + rw [hyframe _ (by dsimp [Block.Owns, B, rightArgs, leftArgs, M]; omega), + hcframe _ (by dsimp [Block.Owns, A, leftArgs, M]; omega), haframe _ (by omega)] + +end Cslib.Algorithms.WordRAM.ExactTriangle.InstancePair diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/Instances.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/Instances.lean new file mode 100644 index 0000000000..cc6b69156a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/Instances.lean @@ -0,0 +1,1161 @@ +/- +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.ExactTriangle.Catalogue +public import Complexity.Algorithms.WordRAM.ExactTriangle.InstancePair +import Mathlib.Tactic.FinCases + +/-! +# Native ExactTriangle instance preparation + +The input header is `(D,g,n,p)`, followed by the three signed weight matrices. The program first +computes the real class/chunk catalogue. It then visits each actual chunk and piece, reads that +chunk's descriptor, computes the piece boundaries and writes its record and two zero/one matrices. +The records share the already computed query arrays. Every visit uses the existing native +`InstancePair.program` rather than assuming a matrix-preparation routine. + +The output records and matrices occupy consecutive variable-size blocks. Register `output` names +the first record, `produced` gives their actual number and `heap` is their final allocation end. +Each block has six record words followed by `2nD` matrix words. This module prepares instances; +it does not choose a prime, evaluate their queried products or decide ExactTriangle. + +## Main definitions + +* `Instances.program`: fixed sixty-four-register initialized instance-preparation program. +* `Instances.visit`: one computed class/chunk/piece visit, including both native matrix calls. +* `Instances.timeBound`: the catalogue cost plus the full native visit and loop budgets, + including the final halt. +* `Instances.Output`: actual catalogue, record, matrix and frame readback. + +## Main results + +* `Instances.exec_at`: complete native execution from the original header and weight arrays. + +`PreparationInput` supplies the initialized flat-program interface and derived width bounds; +`PreparationBounds` proves the preparation time bound in the source's parameter regime. +Code-stage definitions are exposed for kernel-checked interpreter clients. +Proof helpers are private. + +## References + +The matrix geometry follows the pinned +[Theorem 17 matrix construction](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/WriteMatrices.lean). +The native controller and resource proofs are original. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.Instances + +open Cslib.WordRAM Cslib.Algorithms.ExactTriangle + +/-- Outer matrix dimension. -/ +abbrev size : Fin 64 := 32 +/-- Target padded middle dimension. -/ +abbrev dimension : Fin 64 := 33 +/-- Residue modulus. -/ +abbrev modulus : Fin 64 := 34 +/-- Computed maximum vertices per piece. -/ +abbrev pieceLength : Fin 64 := 35 +/-- Computed number of pieces. -/ +abbrev pieces : Fin 64 := 36 +/-- Actual number of nonempty query chunks. -/ +abbrev chunks : Fin 64 := 37 +/-- Computed chunk-descriptor array. -/ +abbrev descriptors : Fin 64 := 38 +/-- First output record. -/ +abbrev output : Fin 64 := 39 +/-- Number of completely written instance blocks. -/ +abbrev produced : Fin 64 := 40 +/-- Actual chunk count times piece count. -/ +abbrev total : Fin 64 := 41 +/-- Next free output record. -/ +abbrev heap : Fin 64 := 42 +/-- Words per record and its two matrices. -/ +abbrev stride : Fin 64 := 43 +/-- Constant one. -/ +abbrev one : Fin 64 := 44 +/-- Words per chunk descriptor. -/ +abbrev three : Fin 64 := 45 +/-- Words per instance record. -/ +abbrev six : Fin 64 := 46 +/-- Words in one matrix. -/ +abbrev area : Fin 64 := 47 +/-- Original A–C residue matrix. -/ +abbrev srcLeft : Fin 64 := 48 +/-- Original B–C residue matrix. -/ +abbrev srcRight : Fin 64 := 49 +/-- Descriptor/header address temporary. -/ +abbrev addr : Fin 64 := 50 +/-- Current chunk ordinal. -/ +abbrev chunkIndex : Fin 64 := 51 +/-- Current piece ordinal. -/ +abbrev pieceIndex : Fin 64 := 52 +/-- Residue loaded from the actual chunk descriptor. -/ +abbrev rho : Fin 64 := 53 +/-- Query pointer loaded from the actual chunk descriptor. -/ +abbrev queries : Fin 64 := 54 +/-- Query count loaded from the actual chunk descriptor. -/ +abbrev queryCount : Fin 64 := 55 +/-- First vertex of the current piece. -/ +abbrev first : Fin 64 := 56 +/-- Actual length of the current piece. -/ +abbrev length : Fin 64 := 57 +/-- Original weight-matrix area temporary. -/ +abbrev temp : Fin 64 := 58 + +/-- Existing catalogue computation in the lower thirty-two registers. -/ +@[expose] +def catalogue : Code .standard 64 := Catalogue.program.rename (Fin.castLE (by decide)) + +/-- Save the actual catalogue outputs and compute the allocation stride and residue bases. -/ +@[expose] +def setup : Code .standard 64 := + let lowSize : Fin 64 := 16 + let lowModulus : Fin 64 := 17 + let lowLength : Fin 64 := 28 + let lowPieces : Fin 64 := 29 + let lowChunks : Fin 64 := 21 + let lowDescriptors : Fin 64 := 18 + let lowOutput : Fin 64 := 22 + ram[ one ← #1; three ← #3; six ← #6; + size ← lowSize; modulus ← lowModulus; pieceLength ← lowLength; pieces ← lowPieces; + chunks ← lowChunks; descriptors ← lowDescriptors; output ← lowOutput; + produced ← #0; heap ← output; total ← chunks * pieces; + addr ← #1; dimension ← *addr; area ← size * dimension; + stride ← area + area; stride ← stride + six; + temp ← size * size; srcLeft ← temp + temp; srcLeft ← srcLeft + temp; + srcRight ← srcLeft + temp; srcLeft ← srcRight + temp; addr ← #5; + srcLeft ← srcLeft + addr; srcRight ← srcRight + addr ] + +/-- Compute the chunk and piece ordinals, then load the real query reference and piece bounds. -/ +@[expose] +def select : Code .standard 64 := ram[ + chunkIndex ← produced / pieces; pieceIndex ← produced % pieces; + addr ← chunkIndex * three; addr ← addr + descriptors; rho ← *addr; + addr ← addr + one; queries ← *addr; addr ← addr + one; queryCount ← *addr; + first ← pieceIndex * pieceLength; length ← size - first; + if pieceLength < length then [ length ← pieceLength ] else [ length ← length ] ] + +/-- Deliver every actual pointer, coordinate and count to the existing instance-pair ABI. -/ +@[expose] +def deliver : Code .standard 64 := + let pairLeft : Fin 64 := 17 + let pairRight : Fin 64 := 18 + let pairBase : Fin 64 := 19 + let pairSize : Fin 64 := 20 + let pairDimension : Fin 64 := 21 + let pairModulus : Fin 64 := 22 + let pairFirst : Fin 64 := 23 + let pairLength : Fin 64 := 24 + let pairRho : Fin 64 := 25 + let pairQueries : Fin 64 := 26 + let pairCount : Fin 64 := 27 + let pairPiece : Fin 64 := 28 + ram[ pairLeft ← srcLeft; pairRight ← srcRight; pairBase ← heap; pairSize ← size; + pairDimension ← dimension; pairModulus ← modulus; pairFirst ← first; pairLength ← length; + pairRho ← rho; pairQueries ← queries; pairCount ← queryCount; pairPiece ← pieceIndex ] + +/-- Existing native record and matrix-pair writer. -/ +@[expose] +def pair : Code .standard 64 := InstancePair.program.rename (Fin.castLE (by decide)) + +/-- Advance only after the complete pair has been written. -/ +@[expose] +def advance : Code .standard 64 := ram[ heap ← heap + stride; produced ← produced + one ] + +/-- One actual instance; every selection, argument-delivery and matrix instruction is native. -/ +@[expose] +def visit : Code .standard 64 := .seq select (.seq deliver (.seq pair advance)) + +/-- Prepare every actual chunk/piece pair after computing the catalogue. -/ +@[expose] +def program : Code .standard 64 := + .seq catalogue (.seq setup (.loop (.lt produced total) visit)) + +/-- The catalogue prefix, twenty-six setup instructions, each `40nD+66` visit and its two loop +instructions, the two loop-exit instructions and the final halt are accounted separately. -/ +@[expose] +def timeBound (n D p K H : ℕ) : ℕ := + Catalogue.cost n D p + K * H * (40 * (n * D) + 68) + 29 + +/-- Original-header bound for all arithmetic and allocated addresses. The conservative maximum +`p*n²` chunk count and `n` pieces cover every actual catalogue entry. Signed weights have their +separate magnitude fit premise; no layout information is supplied by the caller. -/ +@[expose] +def budget (n D g p : ℕ) : ℕ := + let N := n * n + let K := p * N + let out := 5 + 6 * N + p + 2 * N * p + 3 * K + Catalogue.budget n D g p + 2 * out + (K * n) * (6 + 2 * (n * D)) + + 20 * N + 3 * p + 4 * n + 2 * (n * D) + K * n + 64 + +private theorem parameter_bounds {n D g : ℕ} (hD : 1 ≤ D) (hDn : D ≤ n) + (hg : 1 ≤ g) : + 0 < pieceSize D g ∧ pieceSize D g ≤ n ∧ + 0 < numPieces n D g ∧ numPieces n D g ≤ n ∧ + ∀ j < numPieces n D g, j * pieceSize D g ≤ n := by + have hs : 0 < sOf D := by + change 0 < Nat.sqrt D + exact Nat.sqrt_pos.mpr hD + have hc := Nat.le_ceilDiv_mul (a := sOf D) (by omega : 0 < g) + have hB : 0 < pieceSize D g := by + change sOf D ≤ pieceSize D g * g at hc + by_contra h + have : pieceSize D g = 0 := by omega + rw [this, Nat.zero_mul] at hc + omega + have hBn : pieceSize D g ≤ n := + (pieceSize_le_sOf hg).trans ((Nat.sqrt_le_self D).trans hDn) + have hcover : n ≤ numPieces n D g * pieceSize D g := Nat.le_ceilDiv_mul hB + have hH : 0 < numPieces n D g := by + by_contra h + have : numPieces n D g = 0 := by omega + rw [this, Nat.zero_mul] at hcover + omega + have hHn : numPieces n D g ≤ n := + (Nat.ceilDiv_le_iff hB).mpr (Nat.le_mul_of_pos_right n hB) + refine ⟨hB, hBn, hH, hHn, fun j hj => ?_⟩ + have hover : numPieces n D g * pieceSize D g < n + pieceSize D g := + Nat.ceilDiv_mul_lt hB + have hstep := Nat.mul_le_mul_right (pieceSize D g) (Nat.succ_le_of_lt hj) + rw [Nat.succ_mul] at hstep + omega + +/-- One bounded instruction graph with no word-size reads. -/ +theorem program_fixed : program.size ≤ 640 ∧ ¬program.ReadsWidth := by decide + +private theorem select_preserves : ∀ i : Fin 64, i.val < 50 → select.Preserves i := by decide + +private theorem visit_preserves : ∀ i : Fin 64, + (32 ≤ i.val ∧ i.val < 50 ∧ i ≠ heap ∧ i ≠ produced) → visit.Preserves i := by decide + +private theorem prefix_preserves : ∀ i : Fin 64, 32 ≤ i.val → i.val < 50 → + (Code.seq select (.seq deliver pair)).Preserves i := by decide + +private abbrev pairBank : Fin 32 → Fin 64 := Fin.castLE (by decide) + +private theorem budget_covers_setup {n D g p K H : ℕ} + (hK : K ≤ p * (n * n)) (hH : H ≤ n) : + 5 * (n * n) + 2 * (n * D) + K * H + + (5 + 6 * (n * n) + p + 2 * (n * n) * p + 3 * K) + 8 ≤ budget n D g p := by + have hT := Nat.mul_le_mul hK hH + dsimp only [budget] + omega + +private theorem budget_covers_loop {n D g p K H B : ℕ} + (hK : K ≤ p * (n * n)) (hH : H ≤ n) (hHB : H * B ≤ n * n) : + 2 * (5 + 6 * (n * n) + p + 2 * (n * n) * p + 3 * K) + + K * H * (6 + 2 * (n * D)) + 11 * (n * n) + 2 * p + n + H + H * B + K * H + 26 ≤ + budget n D g p := by + have hT := Nat.mul_le_mul hK hH + have hTS := Nat.mul_le_mul_right (6 + 2 * (n * D)) hT + dsimp only [budget] + omega + +private theorem exec_setup {w : ℕ} (x : Data 64) (n D p B H K desc out : ℕ) + (hn : x.regs 16 = n) (hp : x.regs 17 = p) (hB : x.regs 28 = B) + (hH : x.regs 29 = H) (hK : x.regs 21 = K) (hd : x.regs 18 = desc) + (hout : x.regs 22 = out) (hD : x.mem 1 = D) + (hfit : Fits w (5 * (n * n) + 2 * (n * D) + K * H + out + 8)) : + ∃ y, ExecWithin w setup x y 26 ∧ y.mem = x.mem ∧ + y.regs size = n ∧ y.regs dimension = D ∧ y.regs modulus = p ∧ + y.regs pieceLength = B ∧ y.regs pieces = H ∧ y.regs chunks = K ∧ + y.regs descriptors = desc ∧ y.regs output = out ∧ y.regs produced = 0 ∧ + y.regs total = K * H ∧ y.regs heap = out ∧ y.regs stride = 6 + 2 * (n * D) ∧ + y.regs one = 1 ∧ y.regs three = 3 ∧ + y.regs srcLeft = 5 + 5 * (n * n) ∧ y.regs srcRight = 5 + 4 * (n * n) := by + let N := n * n + let M := n * D + let y := x.set one 1 |>.set three 3 |>.set six 6 + |>.set size n |>.set modulus p |>.set pieceLength B |>.set pieces H + |>.set chunks K |>.set descriptors desc |>.set output out + |>.set produced 0 |>.set heap out |>.set total (K * H) + |>.set addr 1 |>.set dimension D |>.set area M + |>.set stride (M + M) |>.set stride (M + M + 6) + |>.set temp N |>.set srcLeft (N + N) |>.set srcLeft (N + N + N) + |>.set srcRight (N + N + N + N) |>.set srcLeft (N + N + N + N + N) + |>.set addr 5 |>.set srcLeft (N + N + N + N + N + 5) + |>.set srcRight (N + N + N + N + 5) + have h0 : Fits w 0 := hfit.of_le (by omega) + have h1 : Fits w 1 := hfit.of_le (by omega) + have h3 : Fits w 3 := hfit.of_le (by omega) + have h5 : Fits w 5 := hfit.of_le (by omega) + have h6 : Fits w 6 := hfit.of_le (by omega) + have hT : Fits w (K * H) := hfit.of_le (by omega) + have hN : Fits w N := hfit.of_le (by dsimp [N]; omega) + have h2N : Fits w (N + N) := hfit.of_le (by dsimp [N]; omega) + have h3N : Fits w (N + N + N) := hfit.of_le (by dsimp [N]; omega) + have h4N : Fits w (N + N + N + N) := hfit.of_le (by dsimp [N]; omega) + have h5N : Fits w (N + N + N + N + N) := hfit.of_le (by dsimp [N]; omega) + have h4N5 : Fits w (N + N + N + N + 5) := hfit.of_le (by dsimp [N]; omega) + have h5N5 : Fits w (N + N + N + N + N + 5) := hfit.of_le (by dsimp [N]; omega) + have hM : Fits w M := hfit.of_le (by dsimp [M]; omega) + have h2M : Fits w (M + M) := hfit.of_le (by dsimp [M]; omega) + have hstride : Fits w (M + M + 6) := hfit.of_le (by dsimp [M]; omega) + refine ⟨y, ?_, rfl, ?_⟩ + · ram_eval [setup, y, hn, hp, hB, hH, hK, hd, hout, hD, h0, h1, h3, h5, h6, + hT, hN, h2N, h3N, h4N, h5N, h4N5, h5N5, hM, h2M, hstride, N, M, + size, dimension, modulus, pieceLength, pieces, chunks, descriptors, output, + produced, heap, total, one, three, six, area, stride, temp, srcLeft, srcRight, addr, + Fin.mk.injEq] + · refine ⟨rfl, rfl, rfl, rfl, rfl, rfl, rfl, rfl, rfl, rfl, rfl, ?_, rfl, rfl, ?_, ?_⟩ + · change M + M + 6 = 6 + 2 * M + omega + · change N + N + N + N + N + 5 = 5 + 5 * N + omega + · change N + N + N + N + 5 = 5 + 4 * N + omega + +private theorem pairBank_injective : Function.Injective pairBank := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 64 => k.val) h) + +-- A specification of the delivered metadata, not a runtime layout input. Its scratch registers +-- are irrelevant: `InstancePair`'s arguments and record words read only registers17–28. +private def pairInput (x : Data 64) : Data 32 where + regs i := match i.val with + | 17 => x.regs srcLeft + | 18 => x.regs srcRight + | 19 => x.regs heap + | 20 => x.regs size + | 21 => x.regs dimension + | 22 => x.regs modulus + | 23 => (x.regs produced % x.regs pieces) * x.regs pieceLength + | 24 => min (x.regs pieceLength) + (x.regs size - (x.regs produced % x.regs pieces) * x.regs pieceLength) + | 25 => x.mem (x.regs produced / x.regs pieces * 3 + x.regs descriptors) + | 26 => x.mem (x.regs produced / x.regs pieces * 3 + x.regs descriptors + 1) + | 27 => x.mem (x.regs produced / x.regs pieces * 3 + x.regs descriptors + 2) + | 28 => x.regs produced % x.regs pieces + | _ => 0 + mem := x.mem + +private def atInstance (x : Data 64) (i : ℕ) : Data 64 := + x.set produced i |>.set heap (x.regs output + i * x.regs stride) + +private def Written (x : Data 64) (L R : ℕ → ℕ) (y : Data 64) (i : ℕ) : Prop := + let B := pairInput (atInstance x i) + let M := x.regs size * x.regs dimension + (Block.mk (x.regs output + i * x.regs stride) 6).Realizes y.mem (InstancePair.recordWords B) ∧ + (Block.mk (InstancePair.leftArgs B).dst M).Realizes y.mem + (fun j => WriteMatrices.entry false (InstancePair.leftArgs B) L j) ∧ + (Block.mk (InstancePair.rightArgs B).dst M).Realizes y.mem + (fun j => WriteMatrices.entry true (InstancePair.rightArgs B) R j) + +private theorem pairInput_regs_congr (x y : Data 64) + (hmeta : ∀ i : Fin 64, 32 ≤ i.val → i.val < 50 → y.regs i = x.regs i) + (hH : 0 < x.regs pieces) (hi : x.regs produced < x.regs chunks * x.regs pieces) + (hdir : x.regs descriptors + 3 * x.regs chunks ≤ x.regs output) + (hmem : ∀ a < x.regs output, y.mem a = x.mem a) : + (pairInput y).regs = (pairInput x).regs := by + have hk : x.regs produced / x.regs pieces < x.regs chunks := + (Nat.div_lt_iff_lt_mul hH).mpr hi + have hm0 := hmem (x.regs produced / x.regs pieces * 3 + x.regs descriptors) (by omega) + have hm1 := hmem (x.regs produced / x.regs pieces * 3 + x.regs descriptors + 1) (by omega) + have hm2 := hmem (x.regs produced / x.regs pieces * 3 + x.regs descriptors + 2) (by omega) + funext i + dsimp only [pairInput] + rw [hmeta srcLeft (by decide) (by decide), hmeta srcRight (by decide) (by decide), + hmeta heap (by decide) (by decide), hmeta size (by decide) (by decide), + hmeta dimension (by decide) (by decide), hmeta modulus (by decide) (by decide), + hmeta produced (by decide) (by decide), hmeta pieces (by decide) (by decide), + hmeta pieceLength (by decide) (by decide), hmeta descriptors (by decide) (by decide), + hm0, hm1, hm2] + +private theorem exec_select {w : ℕ} (x : Data 64) + (ho : x.regs one = 1) (ht : x.regs three = 3) (hH : 0 < x.regs pieces) + (hi : x.regs produced < x.regs chunks * x.regs pieces) + (hfit : Fits w (x.regs descriptors + 3 * x.regs chunks + x.regs size + x.regs pieces + + x.regs pieces * x.regs pieceLength + 3)) : + let k := x.regs produced / x.regs pieces + let j := x.regs produced % x.regs pieces + ∃ y, ExecWithin w select x y 14 ∧ + y.regs chunkIndex = k ∧ y.regs pieceIndex = j ∧ + y.regs rho = x.mem (k * 3 + x.regs descriptors) ∧ + y.regs queries = x.mem (k * 3 + x.regs descriptors + 1) ∧ + y.regs queryCount = x.mem (k * 3 + x.regs descriptors + 2) ∧ + y.regs first = j * x.regs pieceLength ∧ + y.regs length = min (x.regs pieceLength) (x.regs size - j * x.regs pieceLength) ∧ + y.mem = x.mem := by + let k := x.regs produced / x.regs pieces + let j := x.regs produced % x.regs pieces + let f := j * x.regs pieceLength + let a := k * 3 + x.regs descriptors + let l := min (x.regs pieceLength) (x.regs size - f) + have hk : k < x.regs chunks := (Nat.div_lt_iff_lt_mul hH).mpr hi + have hj : j < x.regs pieces := Nat.mod_lt _ hH + have hfirst : f ≤ x.regs pieces * x.regs pieceLength := + Nat.mul_le_mul_right _ hj.le + have hkfit : Fits w k := hfit.of_le (by omega) + have hjfit : Fits w j := hfit.of_le (by omega) + have hk3 : Fits w (k * 3) := hfit.of_le (by omega) + have ha : Fits w (k * 3 + x.regs descriptors) := hfit.of_le (by omega) + have ha1 : Fits w (a + 1) := hfit.of_le (by dsimp [a]; omega) + have ha2 : Fits w (a + 1 + 1) := hfit.of_le (by dsimp [a]; omega) + have hf : Fits w f := hfit.of_le (by omega) + have hsub : Fits w (x.regs size - f) := hfit.of_le (by omega) + have hl : Fits w l := hsub.of_le (min_le_right ..) + let y := x.set chunkIndex k |>.set pieceIndex j |>.set addr (k * 3) + |>.set addr a |>.set rho (x.mem a) |>.set addr (a + 1) + |>.set queries (x.mem (a + 1)) |>.set addr (a + 1 + 1) + |>.set queryCount (x.mem (a + 1 + 1)) |>.set first f + |>.set length (x.regs size - f) |>.set length l + refine ⟨y, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, rfl⟩ + · by_cases h : x.regs pieceLength < x.regs size - f + · have he : l = x.regs pieceLength := min_eq_left h.le + rw [he] at hl + ram_eval [select, y, k, j, f, a, l, he, h, ho, ht, hkfit, hjfit, hk3, ha, + ha1, ha2, hf, hsub, hl, chunkIndex, produced, pieces, pieceIndex, addr, three, + descriptors, rho, one, queries, queryCount, first, pieceLength, size, length, + Fin.mk.injEq] + · have he : l = x.regs size - f := min_eq_right (by omega) + rw [he] at hl + ram_eval [select, y, k, j, f, a, l, he, h, ho, ht, hkfit, hjfit, hk3, ha, + ha1, ha2, hf, hsub, hl, chunkIndex, produced, pieces, pieceIndex, addr, three, + descriptors, rho, one, queries, queryCount, first, pieceLength, size, length, + Fin.mk.injEq] + · simp +decide [y, k, chunkIndex, pieceIndex, addr, rho, queries, queryCount, first, length] + · simp +decide [y, j, pieceIndex, addr, rho, queries, queryCount, first, length] + · simp +decide [y, a, k, rho, addr, queries, queryCount, first, length] + · simp +decide [y, a, k, queries, addr, queryCount, first, length] + · simp +decide [y, a, k, queryCount, first, length, Nat.add_assoc] + · simp +decide [y, f, j, first, length] + · simp +decide [y, l, f, j, length] + +private theorem exec_visit {w : ℕ} (x : Data 64) (L R : ℕ → ℕ) + (ho : x.regs one = 1) (ht : x.regs three = 3) (hH : 0 < x.regs pieces) + (hi : x.regs produced < x.regs chunks * x.regs pieces) + (hstride : x.regs stride = 6 + 2 * (x.regs size * x.regs dimension)) + (hstart : (x.regs produced % x.regs pieces) * x.regs pieceLength ≤ x.regs size) + (hrho : x.mem (x.regs produced / x.regs pieces * 3 + x.regs descriptors) < x.regs modulus) + (hL : ∀ i < x.regs size * x.regs size, L i < x.regs modulus) + (hR : ∀ i < x.regs size * x.regs size, R i < x.regs modulus) + (hleft : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcLeft + i) = L i) + (hright : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcRight + i) = R i) + (hsL : x.regs srcLeft + x.regs size * x.regs size ≤ x.regs heap) + (hsR : x.regs srcRight + x.regs size * x.regs size ≤ x.regs heap) + (hfit : Fits w (x.regs heap + x.regs stride + x.regs srcLeft + x.regs srcRight + + 2 * (x.regs size * x.regs size) + 2 * x.regs modulus + x.regs descriptors + + 3 * x.regs chunks + x.regs size + x.regs pieces + + x.regs pieces * x.regs pieceLength + x.regs produced + 16)) : + let B := pairInput x + let M := x.regs size * x.regs dimension + ∃ y, ExecWithin w visit x y (40 * M + 66) ∧ + y.regs produced = x.regs produced + 1 ∧ y.regs heap = x.regs heap + x.regs stride ∧ + (Block.mk (x.regs heap) 6).Realizes y.mem (InstancePair.recordWords B) ∧ + (Block.mk (InstancePair.leftArgs B).dst M).Realizes y.mem + (fun i => WriteMatrices.entry false (InstancePair.leftArgs B) L i) ∧ + (Block.mk (InstancePair.rightArgs B).dst M).Realizes y.mem + (fun i => WriteMatrices.entry true (InstancePair.rightArgs B) R i) ∧ + (∀ a, ¬(x.regs heap ≤ a ∧ a < x.regs heap + x.regs stride) → y.mem a = x.mem a) := by + let B := pairInput x + let M := x.regs size * x.regs dimension + have Bbase : B.regs InstancePair.base = x.regs heap := rfl + have Bn : B.regs InstancePair.size = x.regs size := rfl + have BD : B.regs InstancePair.dimension = x.regs dimension := rfl + have BsrcL : B.regs InstancePair.srcLeft = x.regs srcLeft := rfl + have BsrcR : B.regs InstancePair.srcRight = x.regs srcRight := rfl + have Bp : B.regs InstancePair.modulus = x.regs modulus := rfl + obtain ⟨a, ha, hak, haj, harho, haq, hacount, hafirst, halen, hamem⟩ := + exec_select x ho ht hH hi (hfit.of_le (by omega)) + have hameta (i : Fin 64) (hi : i.val < 50) : a.regs i = x.regs i := + ha.regs_eq (select_preserves i hi) + let b := a.set 17 (B.regs 17) |>.set 18 (B.regs 18) |>.set 19 (B.regs 19) + |>.set 20 (B.regs 20) |>.set 21 (B.regs 21) |>.set 22 (B.regs 22) + |>.set 23 (B.regs 23) |>.set 24 (B.regs 24) |>.set 25 (B.regs 25) + |>.set 26 (B.regs 26) |>.set 27 (B.regs 27) |>.set 28 (B.regs 28) + have hb : ExecWithin w deliver a b 12 := by + have hsrcL := hameta srcLeft (by decide) + have hsrcR := hameta srcRight (by decide) + have hheap := hameta heap (by decide) + have hn := hameta size (by decide) + have hD := hameta dimension (by decide) + have hp := hameta modulus (by decide) + ram_eval [deliver, b, B, pairInput, hsrcL, hsrcR, hheap, hn, hD, hp, harho, haq, + hacount, hafirst, halen, haj, srcLeft, srcRight, heap, size, dimension, modulus, + first, length, rho, queries, queryCount, pieceIndex, Fin.mk.injEq] + have bregs (i : Fin 32) (hi : 17 ≤ i.val) (hi' : i.val < 29) : + (b.comap pairBank).regs i = B.regs i := by + fin_cases i <;> simp +decide at hi hi' <;> simp +decide [b, pairBank] + have hbLeft : InstancePair.leftArgs (b.comap pairBank) = InstancePair.leftArgs B := by + unfold InstancePair.leftArgs + rw [bregs InstancePair.srcLeft (by decide) (by decide), + bregs InstancePair.base (by decide) (by decide), + bregs InstancePair.size (by decide) (by decide), + bregs InstancePair.dimension (by decide) (by decide), + bregs InstancePair.modulus (by decide) (by decide), + bregs InstancePair.first (by decide) (by decide), + bregs InstancePair.length (by decide) (by decide), + bregs InstancePair.rho (by decide) (by decide)] + have hbRight : InstancePair.rightArgs (b.comap pairBank) = InstancePair.rightArgs B := by + unfold InstancePair.rightArgs + rw [hbLeft, bregs InstancePair.srcRight (by decide) (by decide), + bregs InstancePair.base (by decide) (by decide), + bregs InstancePair.size (by decide) (by decide), + bregs InstancePair.dimension (by decide) (by decide)] + have hbRecord : InstancePair.recordWords (b.comap pairBank) = InstancePair.recordWords B := by + unfold InstancePair.recordWords + rw [hbLeft, hbRight, bregs InstancePair.rho (by decide) (by decide), + bregs InstancePair.piece (by decide) (by decide), + bregs InstancePair.queries (by decide) (by decide), + bregs InstancePair.count (by decide) (by decide)] + have hRho : (b.comap pairBank).regs InstancePair.rho < + (b.comap pairBank).regs InstancePair.modulus := by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide)] + exact hrho + have hPiece : (b.comap pairBank).regs InstancePair.first + + (b.comap pairBank).regs InstancePair.length ≤ (b.comap pairBank).regs InstancePair.size := by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide)] + change (x.regs produced % x.regs pieces) * x.regs pieceLength + + min (x.regs pieceLength) + (x.regs size - (x.regs produced % x.regs pieces) * x.regs pieceLength) ≤ x.regs size + have := min_le_right (x.regs pieceLength) + (x.regs size - (x.regs produced % x.regs pieces) * x.regs pieceLength) + omega + obtain ⟨v, hv, hrecord, hmatL, hmatR, hframe⟩ := InstancePair.exec (b.comap pairBank) L R + hRho hPiece + (by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide)] + exact hL) + (by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide)] + exact hR) + (by + intro i hi + rw [bregs _ (by decide) (by decide)] + change a.mem (x.regs srcLeft + i) = L i + rw [hamem] + exact hleft i hi) + (by + intro i hi + rw [bregs _ (by decide) (by decide)] + change a.mem (x.regs srcRight + i) = R i + rw [hamem] + exact hright i hi) + (by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide)] + exact hsL) + (by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide)] + exact hsR) + (hfit.of_le (by + rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide), bregs _ (by decide) (by decide)] + rw [Bbase, Bn, BD, BsrcL, BsrcR, Bp, hstride] + omega)) + rw [bregs InstancePair.size (by decide) (by decide), Bn, + bregs InstancePair.dimension (by decide) (by decide), BD] at hv + rw [hbRecord, bregs InstancePair.base (by decide) (by decide), Bbase] at hrecord + rw [hbLeft, bregs InstancePair.size (by decide) (by decide), Bn, + bregs InstancePair.dimension (by decide) (by decide), BD] at hmatL + rw [hbRight, bregs InstancePair.size (by decide) (by decide), Bn, + bregs InstancePair.dimension (by decide) (by decide), BD] at hmatR + let c := v.extend pairBank b + have hc : ExecWithin w pair b c (40 * M + 38) := by + simpa only [pair, c, M] using hv.rename pairBank_injective + have hprefix (i : Fin 64) (hi : 32 ≤ i.val) (hi' : i.val < 50) : + c.regs i = x.regs i := (ha.seq (hb.seq hc)).regs_eq (prefix_preserves i hi hi') + let y := c.set heap (x.regs heap + x.regs stride) |>.set produced (x.regs produced + 1) + have hheapFit : Fits w (x.regs heap + x.regs stride) := hfit.of_le (by omega) + have hcountFit : Fits w (x.regs produced + 1) := hfit.of_le (by omega) + have hadvance : ExecWithin w advance c y 2 := by + have hh := hprefix heap (by decide) (by decide) + have hs := hprefix stride (by decide) (by decide) + have hp := hprefix produced (by decide) (by decide) + have h1 := hprefix one (by decide) (by decide) + ram_eval [advance, y, hh, hs, hp, h1, ho, hheapFit, hcountFit, heap, stride, produced, + one, Fin.mk.injEq] + refine ⟨y, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · convert ha.seq (hb.seq (hc.seq hadvance)) using 1 <;> + first | rfl | (dsimp [M]; ring) + · simp [y] + · simp +decide [y, heap, produced] + · exact hrecord + · exact hmatL + · exact hmatR + · intro j hj + change v.mem j = x.mem j + rw [hframe j ?_] + · exact congrFun hamem j + · rw [bregs _ (by decide) (by decide), bregs _ (by decide) (by decide), + bregs _ (by decide) (by decide)] + change ¬(x.regs heap ≤ j ∧ j < x.regs heap + 6 + 2 * (x.regs size * x.regs dimension)) + rw [hstride] at hj + omega + +private theorem exec_loop {w : ℕ} (x : Data 64) (L R : ℕ → ℕ) + (hzero : x.regs produced = 0) (hheap : x.regs heap = x.regs output) + (htotal : x.regs total = x.regs chunks * x.regs pieces) + (ho : x.regs one = 1) (ht : x.regs three = 3) (hH : 0 < x.regs pieces) + (hstride : x.regs stride = 6 + 2 * (x.regs size * x.regs dimension)) + (hstart : ∀ j < x.regs pieces, j * x.regs pieceLength ≤ x.regs size) + (hdir : x.regs descriptors + 3 * x.regs chunks ≤ x.regs output) + (hrho : ∀ k < x.regs chunks, x.mem (k * 3 + x.regs descriptors) < x.regs modulus) + (hL : ∀ i < x.regs size * x.regs size, L i < x.regs modulus) + (hR : ∀ i < x.regs size * x.regs size, R i < x.regs modulus) + (hleft : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcLeft + i) = L i) + (hright : ∀ i < x.regs size * x.regs size, x.mem (x.regs srcRight + i) = R i) + (hsL : x.regs srcLeft + x.regs size * x.regs size ≤ x.regs output) + (hsR : x.regs srcRight + x.regs size * x.regs size ≤ x.regs output) + (hfit : Fits w (x.regs output + x.regs total * x.regs stride + x.regs srcLeft + + x.regs srcRight + 2 * (x.regs size * x.regs size) + 2 * x.regs modulus + + x.regs descriptors + 3 * x.regs chunks + x.regs size + x.regs pieces + + x.regs pieces * x.regs pieceLength + x.regs total + 16)) : + ∃ y, ExecWithin w (.loop (.lt produced total) visit) x y + (x.regs total * (40 * (x.regs size * x.regs dimension) + 68) + 2) ∧ + y.regs produced = x.regs total ∧ + y.regs heap = x.regs output + x.regs total * x.regs stride ∧ + (∀ i < x.regs total, Written x L R y i) ∧ + (∀ a, ¬(x.regs output ≤ a ∧ a < y.regs heap) → y.mem a = x.mem a) := by + let M := x.regs size * x.regs dimension + let T := x.regs total + let I (y : Data 64) : Prop := + (∀ i : Fin 64, 32 ≤ i.val → i.val < 50 → i ≠ produced → i ≠ heap → + y.regs i = x.regs i) ∧ + y.regs produced ≤ T ∧ + y.regs heap = x.regs output + y.regs produced * x.regs stride ∧ + (∀ j < y.regs produced, Written x L R y j) ∧ + (∀ a, ¬(x.regs output ≤ a ∧ a < y.regs heap) → y.mem a = x.mem a) + have hbody (y : Data 64) (hy : I y) (hc : (Cond.lt produced total).Holds y) : + ∃ z, ExecWithin w visit y z (40 * M + 66) ∧ I z ∧ + T - z.regs produced < T - y.regs produced := by + obtain ⟨hyr, hyi, hyheap, hywritten, hyframe⟩ := hy + have hreg (i : Fin 64) + (hi : 32 ≤ i.val ∧ i.val < 50 ∧ i ≠ produced ∧ i ≠ heap) : + y.regs i = x.regs i := hyr i hi.1 hi.2.1 hi.2.2.1 hi.2.2.2 + have hyT := hreg total (by decide) + have hyN := hreg size (by decide) + have hyD := hreg dimension (by decide) + have hyp := hreg modulus (by decide) + have hyH := hreg pieces (by decide) + have hyB := hreg pieceLength (by decide) + have hyK := hreg chunks (by decide) + have hyd := hreg descriptors (by decide) + have hys := hreg stride (by decide) + have hyL := hreg srcLeft (by decide) + have hyR := hreg srcRight (by decide) + have hyo := hreg one (by decide) + have hyt := hreg three (by decide) + have hlt : y.regs produced < T := by simpa only [Cond.holds_lt, hyT] using hc + have hi : y.regs produced < x.regs chunks * x.regs pieces := by + simpa only [T, htotal] using hlt + have hk : y.regs produced / x.regs pieces < x.regs chunks := + (Nat.div_lt_iff_lt_mul hH).mpr hi + have hstep : y.regs produced * x.regs stride + x.regs stride ≤ T * x.regs stride := by + have h := Nat.mul_le_mul_right (x.regs stride) (Nat.succ_le_of_lt hlt) + simpa only [Nat.succ_mul] using h + have hreadonly (a : ℕ) (ha : a < x.regs output) : y.mem a = x.mem a := + hyframe a (by omega) + obtain ⟨z, hz, hzi, hzh, hzrecord, hzL, hzR, hzframe⟩ := exec_visit y L R + (hyo.trans ho) (hyt.trans ht) (by simpa only [hyH] using hH) + (by simpa only [hyK, hyH] using hi) + (by simpa only [hys, hyN, hyD] using hstride) + (by rw [hyH, hyB, hyN]; exact hstart _ (Nat.mod_lt _ hH)) + (by rw [hyH, hyd, hyp, hreadonly _ (by omega)]; exact hrho _ hk) + (by simpa only [hyN, hyp] using hL) + (by simpa only [hyN, hyp] using hR) + (by + intro i hi + rw [hyN] at hi + rw [hyL, hreadonly _ (by omega)] + exact hleft i hi) + (by + intro i hi + rw [hyN] at hi + rw [hyR, hreadonly _ (by omega)] + exact hright i hi) + (by rw [hyL, hyN, hyheap]; omega) + (by rw [hyR, hyN, hyheap]; omega) + (hfit.of_le (by + rw [hyheap, hys, hyL, hyR, hyN, hyp, hyd, hyK, hyH, hyB] + dsimp only [T] at hstep hyi + omega)) + rw [hyN, hyD] at hz + rw [hys] at hzh hzframe + let old := atInstance x (y.regs produced) + have hmeta : ∀ i : Fin 64, 32 ≤ i.val → i.val < 50 → y.regs i = old.regs i := by + intro i hi hi' + by_cases hp : i = produced + · subst i + simp +decide [old, atInstance, produced, heap] + · by_cases hh : i = heap + · subst i + simpa +decide [old, atInstance, produced, heap] using hyheap + · simpa only [old, atInstance, Data.set_regs, Function.update_of_ne hh, + Function.update_of_ne hp] using hyr i hi hi' hp hh + have hinputRegs : (pairInput y).regs = (pairInput old).regs := + pairInput_regs_congr old y hmeta + (by simpa +decide [old, atInstance, pieces, heap, produced] using hH) + (by simpa +decide [old, atInstance, chunks, pieces, heap, produced] using hi) + (by simpa +decide [old, atInstance, descriptors, chunks, output, heap, produced] using hdir) + (by simpa +decide [old, atInstance, output, heap, produced] using hreadonly) + have hargsL : InstancePair.leftArgs (pairInput y) = InstancePair.leftArgs (pairInput old) := by + unfold InstancePair.leftArgs + rw [hinputRegs] + have hargsR : InstancePair.rightArgs (pairInput y) = + InstancePair.rightArgs (pairInput old) := by + unfold InstancePair.rightArgs InstancePair.leftArgs + rw [hinputRegs] + have hrecord : InstancePair.recordWords (pairInput y) = + InstancePair.recordWords (pairInput old) := by + unfold InstancePair.recordWords InstancePair.leftArgs InstancePair.rightArgs + rw [hinputRegs] + rw [hrecord, hyheap] at hzrecord + rw [hargsL, hyN, hyD] at hzL + rw [hargsR, hyN, hyD] at hzR + have hpres (i : Fin 64) + (hi : 32 ≤ i.val ∧ i.val < 50 ∧ i ≠ heap ∧ i ≠ produced) : + z.regs i = y.regs i := hz.regs_eq (visit_preserves i hi) + have hnew : Written x L R z (y.regs produced) := ⟨hzrecord, hzL, hzR⟩ + have hzreadonly (a : ℕ) (ha : a < y.regs heap) : z.mem a = y.mem a := + hzframe a (by omega) + refine ⟨z, hz, ⟨?_, by omega, ?_, ?_, ?_⟩, by omega⟩ + · intro i hi hi' hp hh + exact (hpres i ⟨hi, hi', hh, hp⟩).trans (hyr i hi hi' hp hh) + · rw [hzh, hyheap, hzi, Nat.add_mul, Nat.one_mul] + omega + · intro j hj + rw [hzi] at hj + by_cases hji : j = y.regs produced + · simpa only [hji] using hnew + · have hjlt : j < y.regs produced := by omega + obtain ⟨hjrecord, hjL, hjR⟩ := hywritten j hjlt + have hprev : j * x.regs stride + x.regs stride ≤ y.regs produced * x.regs stride := by + have h := Nat.mul_le_mul_right (x.regs stride) (Nat.succ_le_of_lt hjlt) + simpa only [Nat.succ_mul] using h + refine ⟨?_, ?_, ?_⟩ + · intro a + change z.mem (x.regs output + j * x.regs stride + a.val) = _ + rw [hzreadonly _ (by + have ha : a.val < 6 := a.isLt + rw [hyheap] + omega)] + exact hjrecord a + · intro a + change z.mem ((InstancePair.leftArgs (pairInput (atInstance x j))).dst + a.val) = _ + rw [hzreadonly _ (by + have ha : a.val < M := a.isLt + change x.regs output + j * x.regs stride + 6 + a.val < y.regs heap + rw [hyheap] + dsimp [M] at ha + omega)] + exact hjL a + · intro a + change z.mem ((InstancePair.rightArgs (pairInput (atInstance x j))).dst + a.val) = _ + rw [hzreadonly _ (by + have ha : a.val < M := a.isLt + change x.regs output + j * x.regs stride + 6 + M + a.val < y.regs heap + rw [hyheap] + dsimp [M] at ha ⊢ + omega)] + exact hjR a + · intro a ha + rw [hzh] at ha + rw [hzframe a (by omega), hyframe a (by omega)] + have hx : I x := by + refine ⟨fun _ _ _ _ _ => rfl, ?_, ?_, ?_, fun _ _ => rfl⟩ + · rw [hzero] + exact Nat.zero_le _ + · rw [hzero, Nat.zero_mul, Nat.add_zero] + exact hheap + · intro j hj + rw [hzero] at hj + omega + obtain ⟨y, hy, hI, hend⟩ := ExecWithin.loop_invariant I (fun y => T - y.regs produced) hbody hx + obtain ⟨hmeta, hcount, hheap', hwritten, hframe⟩ := hI + have hyT : y.regs total = T := hmeta total (by decide) (by decide) (by decide) (by decide) + have hfinal : y.regs produced = T := by + simp only [Cond.holds_lt, hyT] at hend + omega + refine ⟨y, ?_, hfinal, ?_, ?_, hframe⟩ + · simpa only [hzero, Nat.sub_zero] using hy + · simpa only [hfinal] using hheap' + · simpa only [hfinal] using hwritten + +private theorem written_readback {n D p B H K desc out i : ℕ} + (b y : Data 64) (L R : ℕ → ℕ) (hH : 0 < H) (hi : i < K * H) + (hmeta : b.regs size = n ∧ b.regs dimension = D ∧ b.regs modulus = p ∧ + b.regs pieceLength = B ∧ b.regs pieces = H ∧ b.regs descriptors = desc ∧ + b.regs output = out ∧ b.regs stride = 6 + 2 * (n * D) ∧ + b.regs srcLeft = 5 + 5 * (n * n) ∧ b.regs srcRight = 5 + 4 * (n * n)) + (hdirBound : desc + 3 * K ≤ out) + (hframe : ∀ q < out, y.mem q = b.mem q) (hwritten : Written b L R y i) : + let base := out + i * (6 + 2 * (n * D)) + let A : WriteMatrices.Args := + ⟨5 + 5 * (n * n), base + 6, n, D, p, i % H * B, min B (n - i % H * B), + y.mem (desc + 3 * (i / H))⟩ + let A' := { A with src := 5 + 4 * (n * n), dst := base + 6 + n * D } + (Block.mk base 6).Realizes y.mem + ![A.rho, i % H, y.mem (desc + 3 * (i / H) + 1), + y.mem (desc + 3 * (i / H) + 2), base + 6, base + 6 + n * D] ∧ + (Block.mk A.dst (n * D)).Realizes y.mem (fun j => WriteMatrices.entry false A L j) ∧ + (Block.mk A'.dst (n * D)).Realizes y.mem (fun j => WriteMatrices.entry true A' R j) := by + obtain ⟨hbn, hbD, hbp, hbB, hbH, hbd, hbo, hbs, hbL, hbR⟩ := hmeta + let N := n * n + let S := 6 + 2 * (n * D) + let base := out + i * S + let A : WriteMatrices.Args := + ⟨5 + 5 * N, base + 6, n, D, p, i % H * B, min B (n - i % H * B), + y.mem (desc + 3 * (i / H))⟩ + let A' := { A with src := 5 + 4 * N, dst := base + 6 + n * D } + have hk : i / H < K := (Nat.div_lt_iff_lt_mul hH).mpr hi + have hdir (t : ℕ) (ht : t < 3) : + b.mem (i / H * 3 + desc + t) = y.mem (desc + 3 * (i / H) + t) := by + have heq : i / H * 3 + desc + t = desc + 3 * (i / H) + t := by omega + rw [heq] + exact (hframe _ (by omega)).symm + have hd0 : b.mem (i / H * 3 + desc) = y.mem (desc + 3 * (i / H)) := by + simpa only [Nat.add_zero] using hdir 0 (by decide) + have hd1 := hdir 1 (by decide) + have hd2 := hdir 2 (by decide) + have hargsL : InstancePair.leftArgs (pairInput (atInstance b i)) = A := by + simp +decide [InstancePair.leftArgs, pairInput, atInstance, A, base, N, S, + hbn, hbD, hbp, hbH, hbB, hbL, hbo, hbs, hbd, hd0, + InstancePair.srcLeft, InstancePair.base, InstancePair.size, InstancePair.dimension, + InstancePair.modulus, InstancePair.first, InstancePair.length, InstancePair.rho, + produced, heap] + have hargsR : InstancePair.rightArgs (pairInput (atInstance b i)) = A' := by + unfold InstancePair.rightArgs + rw [hargsL] + simp +decide [pairInput, atInstance, A', A, base, N, S, + hbn, hbD, hbR, hbo, hbs, InstancePair.srcRight, InstancePair.base, + InstancePair.size, InstancePair.dimension, produced, heap] + have hwords : InstancePair.recordWords (pairInput (atInstance b i)) = + ![A.rho, i % H, y.mem (desc + 3 * (i / H) + 1), + y.mem (desc + 3 * (i / H) + 2), base + 6, base + 6 + n * D] := by + rw [InstancePair.recordWords, hargsL, hargsR] + simp +decide [pairInput, atInstance, A, A', base, hbH, hbd, hd0, hd1, hd2, + InstancePair.rho, InstancePair.piece, InstancePair.queries, InstancePair.count, + produced, heap] + obtain ⟨hr, hL, hR⟩ := hwritten + dsimp only at hr hL hR + rw [hbo, hbs, hwords] at hr + rw [hargsL, hbn, hbD] at hL + rw [hargsR, hbn, hbD] at hR + refine ⟨?_, ?_, ?_⟩ + · exact hr + · exact hL + · exact hR + +/-- Exact computed catalogue, record and raw zero/one matrix readback, with the complete +input/output frame. The matrix predicates reuse the existing writer arguments and entry +semantics. -/ +@[expose] +def Output (n D g p : ℕ) (values : ℕ → ℤ) (initial y : Data 64) : Prop := + let N := n * n + let R := fun q => (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let K := ChunkCatalogue.before C (queryCap n D) p + let H := numPieces n D g + let B := pieceSize D g + let desc := 5 + 6 * N + p + 2 * N * p + let out := desc + 3 * K + y.regs size = n ∧ y.regs dimension = D ∧ y.regs modulus = p ∧ + y.regs output = out ∧ y.regs produced = K * H ∧ + y.regs heap = out + (K * H) * (6 + 2 * (n * D)) ∧ + (∀ q < 3 * N, y.mem (5 + 3 * N + q) = R q) ∧ + (∀ rho < p, y.mem (5 + 6 * N + rho) = C rho) ∧ + (∀ rho < p, ∀ q < N, R q = rho → + y.mem (5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q) = q / n ∧ + y.mem (5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q + 1) = q % n) ∧ + (∀ rho < p, ∀ k < C rho ⌈/⌉ queryCap n D, + y.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k)) = rho ∧ + y.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 1) = + 5 + 6 * N + p + 2 * rho * N + 2 * (k * queryCap n D) ∧ + y.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 2) = + min (queryCap n D) (C rho - k * queryCap n D)) ∧ + (∀ i < K * H, + let base := out + i * (6 + 2 * (n * D)) + let residue := y.mem (desc + 3 * (i / H)) + let A : WriteMatrices.Args := + ⟨5 + 5 * N, base + 6, n, D, p, i % H * B, min B (n - i % H * B), residue⟩ + let A' := { A with src := 5 + 4 * N, dst := base + 6 + n * D } + (Block.mk base 6).Realizes y.mem + ![residue, i % H, y.mem (desc + 3 * (i / H) + 1), + y.mem (desc + 3 * (i / H) + 2), base + 6, base + 6 + n * D] ∧ + (Block.mk A.dst (n * D)).Realizes y.mem + (fun j => WriteMatrices.entry false A (fun q => R (2 * N + q)) j) ∧ + (Block.mk A'.dst (n * D)).Realizes y.mem + (fun j => WriteMatrices.entry true A' (fun q => R (N + q)) j)) ∧ + (∀ a, a < 5 + 3 * N ∨ out + (K * H) * (6 + 2 * (n * D)) ≤ a → + y.mem a = initial.mem a) + +private theorem output_of_loop {n D g p : ℕ} (values : ℕ → ℤ) + (initial : Data 64) (v : Data 32) (b y : Data 64) + (hcat : Catalogue.Output n D g p values (initial.comap pairBank) v) + (hbmem : b.mem = v.mem) + (hmeta : + let N := n * n + let C := fun rho => Nat.count (fun q => (values q % (p : ℤ)).toNat = rho) N + let K := ChunkCatalogue.before C (queryCap n D) p + let H := numPieces n D g + let B := pieceSize D g + let desc := 5 + 6 * N + p + 2 * N * p + let out := desc + 3 * K + b.regs size = n ∧ b.regs dimension = D ∧ b.regs modulus = p ∧ + b.regs pieceLength = B ∧ b.regs pieces = H ∧ b.regs descriptors = desc ∧ + b.regs output = out ∧ b.regs stride = 6 + 2 * (n * D) ∧ + b.regs srcLeft = 5 + 5 * N ∧ b.regs srcRight = 5 + 4 * N) + (hY : y.regs size = n ∧ y.regs dimension = D ∧ y.regs modulus = p ∧ + y.regs output = b.regs output) + (hycount : y.regs produced = + ChunkCatalogue.before (fun rho => Nat.count + (fun q => (values q % (p : ℤ)).toNat = rho) (n * n)) (queryCap n D) p * numPieces n D g) + (hyheap : y.regs heap = b.regs output + y.regs produced * b.regs stride) + (hH : 0 < numPieces n D g) + (hywritten : ∀ i < y.regs produced, + Written b (fun q => (values (2 * (n * n) + q) % (p : ℤ)).toNat) + (fun q => (values (n * n + q) % (p : ℤ)).toNat) y i) + (hyframe : ∀ q, ¬(b.regs output ≤ q ∧ q < y.regs heap) → y.mem q = b.mem q) : + Output n D g p values initial y := by + let N := n * n + let R (q : ℕ) := (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let K := ChunkCatalogue.before C (queryCap n D) p + let H := numPieces n D g + let B := pieceSize D g + let desc := 5 + 6 * N + p + 2 * N * p + let out := desc + 3 * K + let S := 6 + 2 * (n * D) + obtain ⟨hbn, hbD, hbp, hbB, hbH, hbd, hbo, hbs, hbL, hbR⟩ := hmeta + obtain ⟨hyn, hyD, hyp, hyo⟩ := hY + change b.regs pieceLength = B at hbB + change b.regs pieces = H at hbH + change b.regs descriptors = desc at hbd + change b.regs output = out at hbo + change b.regs srcLeft = 5 + 5 * N at hbL + change b.regs srcRight = 5 + 4 * N at hbR + obtain ⟨-, -, -, -, -, -, -, -, hvK, -, hvres, hvcounts, hvpairs, hvdesc, hvframe⟩ := hcat + change v.regs ChunkCatalogue.written = K at hvK + change ∀ q, q < 5 + 3 * N ∨ desc + 3 * v.regs ChunkCatalogue.written ≤ q → + v.mem q = initial.mem q at hvframe + change ∀ q < 3 * N, v.mem (5 + 3 * N + q) = R q at hvres + change ∀ rho < p, v.mem (5 + 6 * N + rho) = C rho at hvcounts + change ∀ rho < p, ∀ q < N, R q = rho → + v.mem (5 + 6 * N + p + 2 * N * rho + 2 * Nat.count (fun k => R k = rho) q) = q / n ∧ + v.mem (5 + 6 * N + p + 2 * N * rho + 2 * Nat.count (fun k => R k = rho) q + 1) = q % n + at hvpairs + change ∀ rho < p, ∀ k < C rho ⌈/⌉ queryCap n D, + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k)) = rho ∧ + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 1) = + 5 + 6 * N + p + 2 * rho * N + 2 * (k * queryCap n D) ∧ + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 2) = + min (queryCap n D) (C rho - k * queryCap n D) at hvdesc + change y.regs produced = K * H at hycount + rw [hbo, hycount, hbs] at hyheap + rw [hycount] at hywritten + have hbefore (q : ℕ) (hq : q < out) : y.mem q = v.mem q := + (hyframe q (by rw [hbo]; omega)).trans (congrFun hbmem q) + refine ⟨hyn, hyD, hyp, hyo.trans hbo, hycount, hyheap, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro q hq + rw [hbefore _ (by dsimp [out, desc]; omega)] + exact hvres q hq + · intro rho hrho + rw [hbefore _ (by dsimp [out, desc]; omega)] + exact hvcounts rho hrho + · intro rho hrho q hq hRq + change q < N at hq + change R q = rho at hRq + have hcount : Nat.count (fun k => R k = rho) q ≤ q := Nat.count_le _ + have hbound := Nat.mul_le_mul_left (2 * N) (Nat.succ_le_of_lt hrho) + rw [Nat.mul_succ] at hbound + have h0 : 5 + 6 * N + p + 2 * N * rho + 2 * Nat.count (fun k => R k = rho) q < out := by + dsimp only [out, desc] + omega + have h1 : 5 + 6 * N + p + 2 * N * rho + + 2 * Nat.count (fun k => R k = rho) q + 1 < out := by + dsimp only [out, desc] + omega + have hread := hvpairs rho hrho q hq hRq + exact ⟨(hbefore _ h0).trans hread.1, (hbefore _ h1).trans hread.2⟩ + · intro rho hrho k hk + change k < C rho ⌈/⌉ queryCap n D at hk + have hindex : ChunkCatalogue.before C (queryCap n D) rho + k < K := + ChunkCatalogue.before_add_lt C (queryCap n D) hrho hk + have h0 : desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) < out := by + dsimp only [out] + omega + have h1 : desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 1 < out := by + dsimp only [out] + omega + have h2 : desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 2 < out := by + dsimp only [out] + omega + have hread := hvdesc rho hrho k hk + exact ⟨(hbefore _ h0).trans hread.1, (hbefore _ h1).trans hread.2.1, + (hbefore _ h2).trans hread.2.2⟩ + · intro i hi + exact written_readback (n := n) (D := D) (p := p) (B := B) (H := H) (K := K) + (desc := desc) (out := out) (i := i) + b y (fun q => R (2 * N + q)) (fun q => R (N + q)) hH hi + ⟨hbn, hbD, hbp, hbB, hbH, hbd, hbo, hbs, hbL, hbR⟩ + (by dsimp only [out]; omega) + (fun q hq => hyframe q (by rw [hbo]; omega)) (hywritten i hi) + · intro q hq + change q < 5 + 3 * N ∨ out + K * H * S ≤ q at hq + rw [hyframe q (by + rw [hbo, hyheap] + change ¬(out ≤ q ∧ q < out + K * H * S) + rcases hq with h | h + · have : 5 + 3 * N ≤ out := by dsimp only [out, desc]; omega + omega + · omega), hbmem] + apply hvframe q + rcases hq with h | h + · exact Or.inl h + · exact Or.inr (by rw [hvK]; omega) + +/-- Execute the complete preparation from the real header and signed matrices. Every output +record refers to the actual computed query chunk, followed by its raw zero/one matrix pair. +The only resource premises concern the original signed values and the explicit numeric budget; +neither a catalogue nor a layout or execution certificate is an input. The final halt is +excluded here and added by the initialized flat-program client. -/ +theorem exec_at {w : ℕ} (n D g p : ℕ) (values : ℕ → ℤ) (initial : Data 64) + (hDHeader : initial.mem 1 = D) (hgHeader : initial.mem 2 = g) + (hnHeader : initial.mem 3 = n) (hpHeader : initial.mem 4 = p) + (hinput : ∀ q < 3 * (n * n), initial.mem (5 + q) = Signed.encode (values q)) + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : 0 < p) + (hmod : GenericRAM.Signed.Fits w (p : ℤ)) + (hvalues : ∀ q < 3 * (n * n), GenericRAM.Signed.Fits w (values q)) + (hbudget : Fits (w + 2) (budget n D g p)) : + let N := n * n + let R := fun q => (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let K := ChunkCatalogue.before C (queryCap n D) p + let H := numPieces n D g + ∃ y, ExecWithin (w + 2) program initial y (timeBound n D p K H - 1) ∧ + Output n D g p values initial y := by + let N := n * n + let R (q : ℕ) := (values q % (p : ℤ)).toNat + let C (rho : ℕ) := Nat.count (fun q => R q = rho) N + let K := ChunkCatalogue.before C (queryCap n D) p + let H := numPieces n D g + let B := pieceSize D g + let desc := 5 + 6 * N + p + 2 * N * p + let out := desc + 3 * K + let S := 6 + 2 * (n * D) + obtain ⟨-, hBn, hH, hHn, hstart⟩ := parameter_bounds hD hDn hg + change B ≤ n at hBn + change 0 < H at hH + change H ≤ n at hHn + change ∀ j < H, j * B ≤ n at hstart + have hK : K ≤ p * N := ChunkCatalogue.before_le (one_le_queryCap hD hDn) + (fun _ _ => Nat.count_le _) le_rfl + have hHB : H * B ≤ N := Nat.mul_le_mul hHn hBn + have hCatalogue : Catalogue.budget n D g p ≤ budget n D g p := by + dsimp only [budget] + omega + obtain ⟨v, hv, hcatOriginal⟩ := Catalogue.exec_at n D g p values + (initial.comap pairBank) hDHeader hgHeader hnHeader hpHeader hinput hD hDn hg hp + hmod hvalues (hbudget.of_le hCatalogue) + have hcatSave := hcatOriginal + obtain ⟨hvn, hvp, -, -, hvd, -, hvB, hvH, hvK, hvheap, + hvres, hvcounts, hvpairs, hvdesc, hvframe⟩ := hcatOriginal + change v.regs ChunkCatalogue.written = K at hvK + change v.regs ChunkCatalogue.heap = desc + 3 * v.regs ChunkCatalogue.written at hvheap + change ∀ q < 3 * N, v.mem (5 + 3 * N + q) = R q at hvres + change ∀ rho < p, v.mem (5 + 6 * N + rho) = C rho at hvcounts + change ∀ rho < p, ∀ q < N, R q = rho → + v.mem (5 + 6 * N + p + 2 * N * rho + 2 * Nat.count (fun k => R k = rho) q) = q / n ∧ + v.mem (5 + 6 * N + p + 2 * N * rho + 2 * Nat.count (fun k => R k = rho) q + 1) = q % n + at hvpairs + change ∀ rho < p, ∀ k < C rho ⌈/⌉ queryCap n D, + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k)) = rho ∧ + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 1) = + 5 + 6 * N + p + 2 * rho * N + 2 * (k * queryCap n D) ∧ + v.mem (desc + 3 * (ChunkCatalogue.before C (queryCap n D) rho + k) + 2) = + min (queryCap n D) (C rho - k * queryCap n D) at hvdesc + let a := v.extend pairBank initial + have ha : ExecWithin (w + 2) catalogue initial a (Catalogue.cost n D p) := + hv.rename pairBank_injective + have haLow (i : Fin 32) : a.regs (pairBank i) = v.regs i := + pairBank_injective.extend_apply _ _ i + have ha16 : a.regs 16 = n := (haLow ChunkCatalogue.size).trans hvn + have ha17 : a.regs 17 = p := (haLow ChunkCatalogue.modulus).trans hvp + have ha28 : a.regs 28 = B := (haLow Catalogue.pieceLength).trans hvB + have ha29 : a.regs 29 = H := (haLow Catalogue.pieces).trans hvH + have ha21 : a.regs 21 = K := (haLow ChunkCatalogue.written).trans hvK + have ha18 : a.regs 18 = desc := (haLow ChunkCatalogue.dst).trans hvd + have ha22 : a.regs 22 = out := by + change a.regs (pairBank ChunkCatalogue.heap) = out + rw [haLow ChunkCatalogue.heap, hvheap, hvK] + have haD : a.mem 1 = D := (hvframe 1 (Or.inl (by omega))).trans hDHeader + obtain ⟨b, hb, hbmem, hbn, hbD, hbp, hbB, hbH, hbK, hbd, hbo, hbzero, hbt, + hbh, hbs, hb1, hb3, hbL, hbR⟩ := exec_setup a n D p B H K desc out + ha16 ha17 ha28 ha29 ha21 ha18 ha22 haD (hbudget.of_le (budget_covers_setup hK hHn)) + change b.regs srcLeft = 5 + 5 * N at hbL + change b.regs srcRight = 5 + 4 * N at hbR + have hres (q : ℕ) : R q < p := Int.natMod_lt (by omega) + have hleft (i : ℕ) (hi : i < N) : b.mem (5 + 5 * N + i) = R (2 * N + i) := by + rw [hbmem] + change v.mem (5 + 5 * N + i) = R (2 * N + i) + convert hvres (2 * N + i) (by omega) using 1 + congr 1 + omega + have hright (i : ℕ) (hi : i < N) : b.mem (5 + 4 * N + i) = R (N + i) := by + rw [hbmem] + change v.mem (5 + 4 * N + i) = R (N + i) + convert hvres (N + i) (by omega) using 1 + congr 1 + omega + obtain ⟨y, hy, hycount, hyheap, hywritten, hyframe⟩ := exec_loop b + (fun q => R (2 * N + q)) (fun q => R (N + q)) hbzero (hbh.trans hbo.symm) + (by rw [hbt, hbK, hbH]) hb1 hb3 (by rw [hbH]; exact hH) + (by rw [hbs, hbn, hbD]) + (by rw [hbH, hbB, hbn]; exact hstart) + (by rw [hbd, hbK, hbo]) + (by + rw [hbK, hbd, hbp] + intro k hk + obtain ⟨idx, hidx, -⟩ := ChunkCatalogue.before_index C (queryCap n D) p k hk + rw [hbmem] + have hr := (hvdesc idx.1.val idx.1.isLt idx.2.val idx.2.isLt).1 + have heq : v.mem (k * 3 + desc) = idx.1.val := by + simpa only [hidx, Nat.mul_comm, Nat.add_comm] using hr + exact heq ▸ idx.1.isLt) + (by rw [hbn, hbp]; exact fun q _ => hres (2 * N + q)) + (by rw [hbn, hbp]; exact fun q _ => hres (N + q)) + (by rw [hbL, hbn]; exact hleft) + (by rw [hbR, hbn]; exact hright) + (by rw [hbL, hbn, hbo]; dsimp [out, desc, N]; omega) + (by rw [hbR, hbn, hbo]; dsimp [out, desc, N]; omega) + (hbudget.of_le (by + rw [hbo, hbt, hbs, hbL, hbR, hbn, hbp, hbd, hbK, hbH, hbB] + change out + K * H * S + (5 + 5 * N) + (5 + 4 * N) + 2 * N + 2 * p + + desc + 3 * K + n + H + H * B + K * H + 16 ≤ _ + calc + _ = 2 * out + K * H * S + 11 * N + 2 * p + n + H + H * B + K * H + 26 := by + dsimp only [out] + ring + _ ≤ _ := budget_covers_loop hK hHn hHB)) + rw [hbn, hbD, hbt] at hy + rw [hbt] at hycount hywritten + rw [hbo, hbt, hbs] at hyheap + have hyreg (i : Fin 64) (hi : 32 ≤ i.val ∧ i.val < 50 ∧ i ≠ heap ∧ i ≠ produced) : + y.regs i = b.regs i := hy.regs_eq (visit_preserves i hi) + refine ⟨y, ?_, ?_⟩ + · apply (ha.seq (hb.seq hy)).mono + change Catalogue.cost n D p + (26 + (K * H * (40 * (n * D) + 68) + 2)) ≤ + Catalogue.cost n D p + K * H * (40 * (n * D) + 68) + 29 - 1 + omega + · apply output_of_loop values initial v b y hcatSave hbmem + ⟨hbn, hbD, hbp, hbB, hbH, hbd, hbo, hbs, hbL, hbR⟩ + ⟨(hyreg size (by decide)).trans hbn, (hyreg dimension (by decide)).trans hbD, + (hyreg modulus (by decide)).trans hbp, hyreg output (by decide)⟩ hycount + (by rw [hbo, hycount, hbs]; exact hyheap) hH + (by rw [hycount]; exact hywritten) hyframe + +end Cslib.Algorithms.WordRAM.ExactTriangle.Instances diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/MatrixSpec.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/MatrixSpec.lean new file mode 100644 index 0000000000..2d7e45dbaf --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/MatrixSpec.lean @@ -0,0 +1,148 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.ExactTriangle.WriteMatrices +public import Complexity.Algorithms.ExactTriangle.Splitting + +/-! +# Modular meaning of the native instance matrices + +The active middle positions use the ordinary flattening `vertex * p + label`. The left and +right zero/one entries test exactly the two modular adjacency relations of `lopInstance`. +The remaining positions are zero. All residues here are Euclidean natural residues of signed +weights, as produced by the existing native signed-residue kernel. + +## Main results + +* `MatrixSpec.left_entry`: left adjacency is `label ≡ weight + rho`. +* `MatrixSpec.right_entry`: right adjacency is `label ≡ -weight`. +* `MatrixSpec.left_padding`, `MatrixSpec.right_padding`: padded middle positions are zero. +* `MatrixSpec.left_lopInstance`, `MatrixSpec.right_lopInstance`: raw matrix ones are exactly + the corresponding edges of the existing finite lopsided instance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.MatrixSpec + +open Cslib.Algorithms.ExactTriangle WriteMatrices + +private theorem eq_mod_iff {p label r : ℕ} (hl : label < p) : + label = r % p ↔ (label : ℤ) ≡ (r : ℤ) [ZMOD (p : ℤ)] := by + rw [Int.natCast_modEq_iff] + change label = r % p ↔ label % p = r % p + rw [Nat.mod_eq_of_lt hl] + +private theorem residue_modEq {p : ℕ} (hp : 0 < p) (w : ℤ) : + ((w.natMod p : ℕ) : ℤ) ≡ w [ZMOD (p : ℤ)] := resFin_modEq (by omega) w + +private theorem left_label {p label rho : ℕ} (hl : label < p) (w : ℤ) : + label = (w.natMod p + rho) % p ↔ + (label : ℤ) ≡ w + (rho : ℤ) [ZMOD (p : ℤ)] := by + rw [eq_mod_iff hl] + have h := (residue_modEq (by omega : 0 < p) w).add (Int.ModEq.refl (rho : ℤ)) + rw [Nat.cast_add] + exact ⟨fun h' => h'.trans h, fun h' => h'.trans h.symm⟩ + +private theorem right_label {p label : ℕ} (hl : label < p) (w : ℤ) : + label = (p - w.natMod p) % p ↔ (label : ℤ) ≡ -w [ZMOD (p : ℤ)] := by + rw [eq_mod_iff hl] + have hr : w.natMod p < p := Int.natMod_lt (by omega) + have hp0 : (p : ℤ) ≡ 0 [ZMOD (p : ℤ)] := Int.modEq_zero_iff_dvd.mpr (dvd_refl _) + have h := hp0.sub (residue_modEq (by omega : 0 < p) w) + rw [Nat.cast_sub hr.le] + simp only [zero_sub] at h + exact ⟨fun h' => h'.trans h, fun h' => h'.trans h.symm⟩ + +private theorem flat_div {d a b : ℕ} (hd : 0 < d) (hb : b < d) : + (a * d + b) / d = a := by + rw [Nat.mul_comm a d, Nat.mul_add_div hd, Nat.div_eq_of_lt hb, Nat.add_zero] + +/-- The native left matrix has exactly the intended modular adjacency at every active middle +vertex. Pointers do not enter this semantic assertion. -/ +theorem left_entry (A : Args) (R : ℕ → ℕ) (a : Fin A.n) (v : Fin A.length) + (label : Fin A.p) (hpad : A.length * A.p ≤ A.D) (w : ℤ) + (hR : R (a.val * A.n + A.first + v.val) = w.natMod A.p) : + entry false A R (a.val * A.D + (v.val * A.p + label.val)) = + if (label.val : ℤ) ≡ w + (A.rho : ℤ) [ZMOD (A.p : ℤ)] then 1 else 0 := by + have hp : 0 < A.p := Nat.pos_of_ne_zero (by intro h; simpa [h] using label.isLt) + have hm : v.val * A.p + label.val < A.D := by + have hv : (v.val + 1) * A.p ≤ A.length * A.p := Nat.mul_le_mul_right A.p v.isLt + rw [Nat.add_mul, Nat.one_mul] at hv + omega + have hd : 0 < A.D := by omega + simp only [entry, Bool.false_eq_true, ↓reduceIte, flat_div hd hm, + Nat.mul_add_mod_of_lt hm, Nat.mul_add_mod_of_lt label.isLt, flat_div hp label.isLt, + v.isLt, hR] + simp only [left_label label.isLt] + +/-- The right matrix uses the negated B–C weight and the transposed residue lookup. -/ +theorem right_entry (A : Args) (R : ℕ → ℕ) (b : Fin A.n) (v : Fin A.length) + (label : Fin A.p) (w : ℤ) + (hR : R (b.val * A.n + A.first + v.val) = w.natMod A.p) : + entry true A R ((v.val * A.p + label.val) * A.n + b.val) = + if (label.val : ℤ) ≡ -w [ZMOD (A.p : ℤ)] then 1 else 0 := by + have hn : 0 < A.n := Nat.pos_of_ne_zero (by intro h; simpa [h] using b.isLt) + have hp : 0 < A.p := Nat.pos_of_ne_zero (by intro h; simpa [h] using label.isLt) + simp only [entry, ↓reduceIte, flat_div hn b.isLt, Nat.mul_add_mod_of_lt b.isLt, + Nat.mul_add_mod_of_lt label.isLt, flat_div hp label.isLt, v.isLt, hR] + simp only [right_label label.isLt] + +/-- A padded middle column is zero even if its input residue would otherwise match. -/ +theorem left_padding (A : Args) (R : ℕ → ℕ) (a : Fin A.n) (middle : Fin A.D) + (hpad : A.length ≤ middle.val / A.p) : + entry false A R (a.val * A.D + middle.val) = 0 := by + simp only [entry, Bool.false_eq_true, ↓reduceIte, + Nat.mul_add_mod_of_lt middle.isLt, show ¬middle.val / A.p < A.length by omega] + +/-- A padded middle row of the right matrix is zero. -/ +theorem right_padding (A : Args) (R : ℕ → ℕ) (b : Fin A.n) (middle : Fin A.D) + (hpad : A.length ≤ middle.val / A.p) : + entry true A R (middle.val * A.n + b.val) = 0 := by + have hn : 0 < A.n := Nat.pos_of_ne_zero (by intro h; simpa [h] using b.isLt) + simp only [entry, ↓reduceIte, flat_div hn b.isLt, + show ¬middle.val / A.p < A.length by omega] + +/-- Active left cells are precisely the adjacency of the existing finite lopsided instance. +The source-array equation relates original weights to their actual Euclidean residues, and +the local-to-original vertex equation identifies the selected piece coordinate. -/ +theorem left_lopInstance (A : Args) (R : ℕ → ℕ) + (wAB wBC wAC : Fin A.n → Fin A.n → ℤ) (g chunk k : ℕ) (rho : Fin A.p) + (hrho : A.rho = rho.val) (a c : Fin A.n) (v : Fin A.length) (label : Fin A.p) + (hpiece : c ∈ piece A.n A.D g k) (hvertex : A.first + v.val = c.val) + (hpad : A.length * A.p ≤ A.D) + (hR : ∀ a c, R (a.val * A.n + c.val) = (wAC a c).natMod A.p) : + entry false A R (a.val * A.D + (v.val * A.p + label.val)) = 1 ↔ + (lopInstance wAB wBC wAC A.D g A.p (rho, chunk, k)).adjA + a (⟨c, hpiece⟩, label) := by + have hload : R (a.val * A.n + A.first + v.val) = (wAC a c).natMod A.p := by + rw [Nat.add_assoc, hvertex] + exact hR a c + rw [left_entry A R a v label hpad (wAC a c) hload, hrho] + simp only [lopInstance] + split_ifs <;> simp_all + +/-- Active right cells are precisely the adjacency of the same finite lopsided instance; +the stored right matrix uses its original B–C row and local middle vertex. -/ +theorem right_lopInstance (A : Args) (R : ℕ → ℕ) + (wAB wBC wAC : Fin A.n → Fin A.n → ℤ) (g chunk k : ℕ) (rho : Fin A.p) + (b c : Fin A.n) (v : Fin A.length) (label : Fin A.p) + (hpiece : c ∈ piece A.n A.D g k) (hvertex : A.first + v.val = c.val) + (hR : ∀ b c, R (b.val * A.n + c.val) = (wBC b c).natMod A.p) : + entry true A R ((v.val * A.p + label.val) * A.n + b.val) = 1 ↔ + (lopInstance wAB wBC wAC A.D g A.p (rho, chunk, k)).adjB + (⟨c, hpiece⟩, label) b := by + have hload : R (b.val * A.n + A.first + v.val) = (wBC b c).natMod A.p := by + rw [Nat.add_assoc, hvertex] + exact hR b c + rw [right_entry A R b v label (wBC b c) hload] + simp only [lopInstance] + split_ifs <;> simp_all + +end Cslib.Algorithms.WordRAM.ExactTriangle.MatrixSpec diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/Parameters.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/Parameters.lean new file mode 100644 index 0000000000..e2830ab187 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/Parameters.lean @@ -0,0 +1,205 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.NatSqrt +public import Complexity.Algorithms.ExactTriangle.Splitting + +/-! +# Native ExactTriangle splitting parameters + +The program computes the existing integer parameters `sOf`, `queryCap`, `pieceSize` and +`numPieces`. Both square roots call the charged native successive-squares program; all products, +quotients and ceilings are native instructions. The modulus is supplied separately by the outer +prime-selection host. + +## Main definitions + +* `Parameters.program`: fixed fifteen-register parameter computation. + +## Main results + +* `Parameters.exec`: exact outputs, explicit word fits, cost and an unchanged-memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.Parameters + +open Cslib.WordRAM Cslib.Algorithms.ExactTriangle + +/-- Square-root input and arithmetic scratch. -/ +abbrev arg : Fin 15 := 0 +/-- Square-root result. -/ +abbrev root : Fin 15 := 1 +/-- Input matrix side. -/ +abbrev size : Fin 15 := 6 +/-- Target middle dimension. -/ +abbrev dimension : Fin 15 := 7 +/-- Piece-size divisor. -/ +abbrev divisor : Fin 15 := 8 +/-- Output floor square root of the middle dimension. -/ +abbrev middleRoot : Fin 15 := 9 +/-- Output query chunk capacity. -/ +abbrev capacity : Fin 15 := 10 +/-- Output maximum piece length. -/ +abbrev pieceLength : Fin 15 := 11 +/-- Output number of pieces. -/ +abbrev pieces : Fin 15 := 12 +/-- Square of the outer dimension. -/ +abbrev area : Fin 15 := 13 +/-- Constant one, outside the square-root register bank. -/ +abbrev one : Fin 15 := 14 + +private abbrev sqrtReg : Fin 6 → Fin 15 := Fin.castLE (by decide) + +private theorem sqrtReg_injective : Function.Injective sqrtReg := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 15 => k.val) h) + +/-- Reused charged integer square root. -/ +def squareRoot : Code .standard 15 := NatSqrt.program.rename (Fin.castLE (by decide)) + +/-- Runtime arithmetic between the square roots. -/ +def between : Code .standard 15 := ram[ + middleRoot ← root; area ← size * size; arg ← area * area; arg ← arg / dimension ] + +/-- Both natural ceilings use the existing `(a+b-1)/b` convention, including zero divisors. -/ +def finish : Code .standard 15 := ram[ + capacity ← root; pieceLength ← middleRoot + divisor; pieceLength ← pieceLength - one; + pieceLength ← pieceLength / divisor; pieces ← size + pieceLength; + pieces ← pieces - one; pieces ← pieces / pieceLength ] + +/-- Fixed code; only the square-root loops depend on the runtime inputs. -/ +def program : Code .standard 15 := + .seq (ram[ one ← #1; arg ← dimension ]) + (.seq squareRoot (.seq between (.seq squareRoot finish))) + +/-- Fixed native code and no physical-width reads. -/ +theorem program_fixed : program.size ≤ 64 ∧ ¬program.ReadsWidth := by decide + +private theorem squareRoot_preserves : ∀ i : Fin 15, + 6 ≤ i.val → squareRoot.Preserves i := by decide + +/-- Compute the finite splitting parameters from actual runtime dimensions. The two explicit +ceiling-addition fits cover their temporary sums, and the fourth-power bound covers the square +formed before dividing by `D`. The final halt is excluded. -/ +theorem exec {w : ℕ} (x : Data 15) + (hD : Fits w (4 * x.regs dimension + 4)) + (hN : Fits w (4 * ((x.regs size * x.regs size) * (x.regs size * x.regs size)) + 4)) + (hB : Fits w (sOf (x.regs dimension) + x.regs divisor)) + (hH : Fits w (x.regs size + pieceSize (x.regs dimension) (x.regs divisor))) : + ∃ y, ExecWithin w program x y + (5 * sOf (x.regs dimension) + 5 * queryCap (x.regs size) (x.regs dimension) + 27) ∧ + y.regs middleRoot = sOf (x.regs dimension) ∧ + y.regs capacity = queryCap (x.regs size) (x.regs dimension) ∧ + y.regs pieceLength = pieceSize (x.regs dimension) (x.regs divisor) ∧ + y.regs pieces = numPieces (x.regs size) (x.regs dimension) (x.regs divisor) ∧ + y.regs size = x.regs size ∧ y.regs dimension = x.regs dimension ∧ + y.regs divisor = x.regs divisor ∧ y.mem = x.mem := by + let n := x.regs size + let D := x.regs dimension + let g := x.regs divisor + let S := sOf D + let Q := queryCap n D + let B := pieceSize D g + let H := numPieces n D g + change Fits w (4 * D + 4) at hD + change Fits w (4 * ((n * n) * (n * n)) + 4) at hN + change Fits w (S + g) at hB + change Fits w (n + B) at hH + have h1 : Fits w 1 := hD.of_le (by omega) + let a := x.set one 1 |>.set arg D + have hstart : ExecWithin (B := .standard) w (ram[ one ← #1; arg ← dimension ]) x a 2 := by + have hfit : Fits w D := hD.of_le (by dsimp [D]; omega) + ram_eval [a, D, h1, hfit, one, arg, dimension, Fin.mk.injEq] + have haD : a.regs arg = D := by simp [a] + obtain ⟨b, hb, hbroot, -, hbm, -⟩ := NatSqrt.exec_rename sqrtReg sqrtReg_injective a + (by change Fits w (4 * a.regs arg + 4); rw [haD]; exact hD) + change b.regs root = S at hbroot + change ExecWithin w squareRoot a b (5 * Nat.sqrt (a.regs arg) + 7) at hb + have hbreg (i : Fin 15) (hi : i = size ∨ i = dimension ∨ i = divisor) : + b.regs i = x.regs i := by + have hhi : 6 ≤ i.val := by rcases hi with rfl | rfl | rfl <;> decide + have h := hb.regs_eq (squareRoot_preserves i hhi) + exact h.trans (by + rcases hi with rfl | rfl | rfl <;> + simp +decide [a, size, dimension, divisor, one, arg]) + have hbsize : b.regs size = n := hbreg size (by simp) + have hbD : b.regs dimension = D := hbreg dimension (by simp) + have hbg : b.regs divisor = g := hbreg divisor (by simp) + have hbone : b.regs one = 1 := by + exact (hb.regs_eq (squareRoot_preserves one (by decide))).trans (by simp +decide [a, one, arg]) + have hnarea : n * n ≤ (n * n) * (n * n) := by + by_cases h : n * n = 0 + · simp [h] + · exact Nat.le_mul_of_pos_right _ (by omega) + have hafit : Fits w (n * n) := hN.of_le (by omega) + have hn4 : Fits w ((n * n) * (n * n)) := hN.of_le (by omega) + have hquot : Fits w ((n * n) * (n * n) / D) := hn4.of_le (Nat.div_le_self ..) + let c := b.set middleRoot S |>.set area (n * n) + |>.set arg ((n * n) * (n * n)) |>.set arg ((n * n) * (n * n) / D) + have hbetween : ExecWithin w between b c 4 := by + ram_eval [between, c, hbroot, hbsize, hbD, hafit, hn4, hquot, + middleRoot, area, arg, root, size, dimension, Fin.mk.injEq] + have hcarg : c.regs arg = (n * n) * (n * n) / D := by simp [c] + obtain ⟨d, hd, hdroot, -, hdm, -⟩ := NatSqrt.exec_rename sqrtReg sqrtReg_injective c (by + change Fits w (4 * c.regs arg + 4) + rw [hcarg] + exact hN.of_le (Nat.add_le_add_right (Nat.mul_le_mul_left 4 (Nat.div_le_self ..)) 4)) + have hdroot' : d.regs root = Q := by + change d.regs root = Nat.sqrt (c.regs arg) at hdroot + rw [hcarg] at hdroot + simpa only [Q, queryCap, pow_two] using hdroot + change ExecWithin w squareRoot c d (5 * Nat.sqrt (c.regs arg) + 7) at hd + have hdreg (i : Fin 15) (hi : i = size ∨ i = dimension ∨ i = divisor ∨ i = one) : + d.regs i = b.regs i := by + have hhi : 6 ≤ i.val := by rcases hi with rfl | rfl | rfl | rfl <;> decide + have h := hd.regs_eq (squareRoot_preserves i hhi) + exact h.trans (by + rcases hi with rfl | rfl | rfl | rfl <;> + simp +decide [c, size, dimension, divisor, one, middleRoot, area, arg]) + have hdn : d.regs size = n := (hdreg size (by simp)).trans hbsize + have hdg : d.regs divisor = g := (hdreg divisor (by simp)).trans hbg + have hdo : d.regs one = 1 := (hdreg one (by simp)).trans hbone + have hdS : d.regs middleRoot = S := by + exact (hd.regs_eq (squareRoot_preserves middleRoot (by decide))).trans (by + simp +decide [c, middleRoot, area, arg]) + let y := d.set capacity Q |>.set pieceLength (S + g) |>.set pieceLength (S + g - 1) + |>.set pieceLength B |>.set pieces (n + B) |>.set pieces (n + B - 1) |>.set pieces H + have hfinish : ExecWithin w finish d y 7 := by + have hsubB : Fits w (S + g - 1) := hB.of_le (Nat.sub_le ..) + have hdivB : Fits w ((S + g - 1) / g) := hsubB.of_le (Nat.div_le_self ..) + have hsubH : Fits w (n + B - 1) := hH.of_le (Nat.sub_le ..) + have hdivH : Fits w ((n + B - 1) / B) := hsubH.of_le (Nat.div_le_self ..) + simp only [B, pieceSize, Nat.ceilDiv_eq_add_pred_div, S, sOf] + at hB hH hsubB hdivB hsubH hdivH + ram_eval [finish, y, hdroot', hdS, hdn, hdg, hdo, B, H, pieceSize, numPieces, + Nat.ceilDiv_eq_add_pred_div, S, sOf, hB, hH, hsubB, hdivB, hsubH, hdivH, + capacity, root, pieceLength, middleRoot, divisor, pieces, size, one, Fin.mk.injEq] + refine ⟨y, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · convert hstart.seq (hb.seq (hbetween.seq (hd.seq hfinish))) using 1 + · rfl + · simp only [haD, hcarg, sOf, queryCap, pow_two] + dsimp [n, D] + omega + · change y.regs middleRoot = S + simp +decide [y, capacity, pieceLength, pieces, middleRoot, hdS] + · change y.regs capacity = Q + simp +decide [y, capacity, pieceLength, pieces] + · change y.regs pieceLength = B + simp +decide [y, capacity, pieceLength, pieces] + · change y.regs pieces = H + simp [y] + · simpa +decide [y, capacity, pieceLength, pieces, size] using hdn + · have h := (hdreg dimension (by simp)).trans hbD + simpa +decide [y, capacity, pieceLength, pieces, dimension] using h + · simpa +decide [y, capacity, pieceLength, pieces, divisor] using hdg + · exact hdm.trans hbm + +end Cslib.Algorithms.WordRAM.ExactTriangle.Parameters diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationBounds.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationBounds.lean new file mode 100644 index 0000000000..64805202cc --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationBounds.lean @@ -0,0 +1,172 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.ExactTriangle.Splitting +public import Complexity.Algorithms.WordRAM.ExactTriangle.Instances +public import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationInput +public import Complexity.Algorithms.WordRAM.ExactTriangle.QuerySpec +import Mathlib.Tactic.Linarith +import Mathlib.Tactic.Ring + +/-! +# Summing the native ExactTriangle preparation costs + +The initialized preparation program computes the catalogue and writes every instance's record +and two matrices within `690n²Dg` charged instructions, including halt, in the source's admissible +parameter regime. The bound uses the existing finite instance count. Prime selection and solving +the prepared instances belong to the outer ExactTriangle host. + +## Main results + +* `PreparationBounds.matrix_writing_total_le`: both native matrix writers over all actual + instances, including an explicit per-instance overhead. +* `PreparationBounds.residue_class_parameter_time_le`: signed residues, class scans and + parameter arithmetic fit the preparation scale in the source's admitted regime. +* `PreparationBounds.catalogue_time_le`: descriptor enumeration, including empty-class costs. +* `PreparationBounds.timeBound_le`: the complete budget summed over the existing actual + finite instance indices. +* `PreparationBounds.runs_input`: actual initialized execution with the complete output + contract and the `690n²Dg` bound. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.PreparationBounds + +open Cslib.Algorithms.ExactTriangle + +/-- Sum the two `20nD+6` matrix-writer bounds over the existing actual instance set. An explicit +additional `c` instructions per instance accounts for caller overhead separately. -/ +theorem matrix_writing_total_le {n D g p : ℕ} (wAB : Fin n → Fin n → ℤ) (c : ℕ) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ∈ Nat.sqrtPrimeWindow D) : + (∑ _ι ∈ instanceIndices wAB D g p, (2 * (20 * (n * D) + 6) + c)) ≤ + (208 + 4 * c) * (n * n) * D * g := by + have hcard : (instanceIndices wAB D g p).card ≤ 4 * n * g := by + exact_mod_cast card_instanceIndices_le wAB hD hDn hg hp + have hpos : 1 ≤ n * D := Nat.mul_pos (by omega) (by omega) + have hc : c ≤ c * (n * D) := Nat.le_mul_of_pos_right c hpos + have h12 : 12 ≤ 12 * (n * D) := by omega + have hstep : 2 * (20 * (n * D) + 6) + c ≤ (52 + c) * (n * D) := by + nlinarith + calc + _ = (instanceIndices wAB D g p).card * (2 * (20 * (n * D) + 6) + c) := by + simp only [Finset.sum_const, smul_eq_mul] + _ ≤ (4 * n * g) * ((52 + c) * (n * D)) := Nat.mul_le_mul hcard hstep + _ = (208 + 4 * c) * (n * n) * D * g := by ring + +private theorem queryCap_le_sq (n D : ℕ) : queryCap n D ≤ n * n := by + calc + queryCap n D ≤ Nat.sqrt ((n ^ 2) ^ 2) := Nat.sqrt_le_sqrt (Nat.div_le_self ..) + _ = n * n := by rw [Nat.sqrt_eq', pow_two] + +/-- The exact signed-residue/class prefix bound and the parameter-kernel bound are charged +separately. Their sum is at most a fixed multiple of `n² D g` when `p ≤ D`; the prime-window +caller supplies that inequality. This includes all per-class scan overhead. -/ +theorem residue_class_parameter_time_le {n D g p : ℕ} + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ≤ D) : + 69 * (n * n) + p * (14 * (n * n) + 14) + 30 + + (5 * sOf D + 5 * queryCap n D + 27) ≤ 164 * (n * n) * D * g := by + have hn : 1 ≤ n := hD.trans hDn + have hN : 1 ≤ n * n := Nat.mul_pos hn hn + have hnN : n ≤ n * n := Nat.le_mul_of_pos_right n hn + have hs : sOf D ≤ n * n := (Nat.sqrt_le_self D).trans (hDn.trans hnN) + have hq := queryCap_le_sq n D + have hscan : p * (14 * (n * n) + 14) ≤ D * (28 * (n * n)) := + Nat.mul_le_mul hp (by omega) + have hbase : n * n ≤ (n * n) * D := Nat.le_mul_of_pos_right _ hD + have hfinal : (n * n) * D ≤ (n * n) * D * g := Nat.le_mul_of_pos_right _ hg + nlinarith + +/-- The actual concatenation loop scans all `p` class counts, but writes only their actual +chunks. Its conservative charged bound still lies on the `n² D g` preparation scale. -/ +theorem catalogue_time_le {n D g p : ℕ} + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ≤ D) : + p * (17 * (n * n) + 20) + 9 ≤ 46 * (n * n) * D * g := by + have hN : 1 ≤ n * n := Nat.mul_pos (hD.trans hDn) (hD.trans hDn) + have hstep : 17 * (n * n) + 20 ≤ 37 * (n * n) := by omega + have hscan := Nat.mul_le_mul hp hstep + have hbase : 1 ≤ (n * n) * D := Nat.mul_pos hN hD + have hfinal : (n * n) * D ≤ (n * n) * D * g := Nat.le_mul_of_pos_right _ hg + nlinarith only [hscan, hbase, hfinal] + +/-- The complete prefix cost includes both argument-delivery stages in addition to residue, +class, parameter and descriptor enumeration. -/ +theorem prefix_time_le {n D g p : ℕ} + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ≤ D) : + Catalogue.cost n D p ≤ 229 * (n * n) * D * g := by + have hprefix := residue_class_parameter_time_le hD hDn hg hp + have hcatalogue := catalogue_time_le hD hDn hg hp + have hbase : 1 ≤ (n * n) * D * g := + Nat.mul_pos (Nat.mul_pos (Nat.mul_pos (hD.trans hDn) (hD.trans hDn)) hD) hg + have heq : Catalogue.cost n D p = + 69 * (n * n) + p * (14 * (n * n) + 14) + 30 + + (5 * sOf D + 5 * queryCap n D + 27) + (p * (17 * (n * n) + 20) + 9) + 19 := by + dsimp [Catalogue.cost] + ring + rw [heq] + nlinarith only [hprefix, hcatalogue, hbase] + +/-- Substitute the existing actual chunk and piece counts into the native budget. The +`690n²Dg` bound includes all catalogue work, selection, record stores, both matrix calls, +argument delivery, loop control and halt. `runs_input` applies this bound to actual execution. -/ +theorem timeBound_le {n D g p : ℕ} (wAB : Fin n → Fin n → ℤ) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ∈ Nat.sqrtPrimeWindow D) : + Instances.timeBound n D p (totalChunks wAB D p) (numPieces n D g) ≤ + 690 * (n * n) * D * g := by + have hpD : p ≤ D := (Nat.le_self_pow two_ne_zero p).trans + ((Nat.mem_sqrtPrimeWindow_iff_sq.mp hp).2.2.le) + have hprefix := prefix_time_le (by omega : 1 ≤ D) hDn hg hpD + have hmat := matrix_writing_total_le wAB 56 hD hDn hg hp + have hvisit : (instanceIndices wAB D g p).card * (40 * (n * D) + 68) ≤ + 432 * (n * n) * D * g := by + simp only [Finset.sum_const, smul_eq_mul] at hmat + convert hmat using 1 + ring + have hbase : 1 ≤ (n * n) * D * g := + Nat.mul_pos (Nat.mul_pos (Nat.mul_pos (by omega) (by omega)) (by omega)) hg + rw [Instances.timeBound, ← card_instanceIndices wAB] + nlinarith only [hprefix, hvisit, hbase] + +/-- The native count in the initialized theorem is the existing finite chunk count. Thus its +actual charged execution budget, not an assumed matrix-write contract, has the preparation +bound in the source's admissible regime. -/ +theorem computed_timeBound_le {n D g p : ℕ} (values : List ℤ) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ∈ Nat.sqrtPrimeWindow D) : + let C := fun rho => Nat.count (fun q => (values.getD q 0 % (p : ℤ)).toNat = rho) (n * n) + Instances.timeBound n D p (ChunkCatalogue.before C (queryCap n D) p) + (numPieces n D g) ≤ 690 * (n * n) * D * g := by + let wAB (a b : Fin n) := values.getD (Cslib.Matrix.pairIndex (a, b)) 0 + have hp0 : p ≠ 0 := (Nat.mem_sqrtPrimeWindow_iff_sq.mp hp).1.ne_zero + dsimp only + rw [QuerySpec.before_eq_totalChunks wAB hp0 + (fun q => (values.getD q 0 % (p : ℤ)).toNat) (by intro q; rfl) D] + exact timeBound_le wAB hD hDn hg hp + +/-- The entire real initialized preparation, including halt, costs at most `690n²Dg` under +the existing finite splitting hypotheses. All output assertions refer to its same pre-halt +state. The supplied prime has not been chosen by this component. -/ +theorem runs_input {n D g p A W : ℕ} (values : List ℤ) + (hlen : values.length = 3 * (n * n)) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : p ∈ Nat.sqrtPrimeWindow D) + (hv : ∀ z ∈ values, z.natAbs ≤ A) (hw : PreparationInput.width n D g p A ≤ W) : + ∃ y t, t ≤ 690 * (n * n) * D * g ∧ + Cslib.WordRAM.Runs W PreparationInput.flatProgram + (Cslib.WordRAM.State.init (PreparationInput.input n D g p values)) + ⟨Instances.program.size, y⟩ (t - 1) ∧ + Cslib.WordRAM.Executes W PreparationInput.flatProgram + (Cslib.WordRAM.State.init (PreparationInput.input n D g p values)) + (y.regs Instances.output) t ∧ + Instances.Output n D g p (fun q => values.getD q 0) + (.init (PreparationInput.input n D g p values)) y ∧ + Cslib.WordRAM.InputFits W (PreparationInput.input n D g p values) ∧ y.Bounded W := by + obtain ⟨y, t, ht, hr, he, hout, hi, hb⟩ := PreparationInput.runs_input values hlen + (by omega) hDn hg (Nat.mem_sqrtPrimeWindow_iff_sq.mp hp).1.pos hv hw + exact ⟨y, t, ht.trans (computed_timeBound_le values hD hDn hg hp), hr, he, hout, hi, hb⟩ + +end Cslib.Algorithms.WordRAM.ExactTriangle.PreparationBounds diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationInput.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationInput.lean new file mode 100644 index 0000000000..876bc8d8da --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationInput.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.ExactTriangle.Instances +public import Complexity.Computability.WordRAM.Width +public import Complexity.Computability.GenericRAM.Signed.Fits +public import Mathlib.Data.Nat.Log + +/-! +# Initialized native ExactTriangle preparation + +The input supplies only four raw scalar headers and three signed weight arrays. One positive +ceiling-logarithm threshold derived from these original parameters covers the signed residue +calls and every arithmetic/address fit of the actual preparation program. The same fixed flat +program executes at every larger physical width, with the complete output read from its actual +pre-halt state and the final halt charged. + +## Main definitions + +* `PreparationInput.input`: original unsigned headers and signed-encoded weights. +* `PreparationInput.width`: sufficient positive physical width from the original parameters. +* `PreparationInput.flatProgram`: fixed native code returning the output-record pointer. + +## Main results + +* `PreparationInput.runs_input`: initialized execution, exact readback, fits and charged halt. +* `PreparationInput.bounded_reachable`: input-derived word and address bounds at every prefix. +* `PreparationInput.width_le_log`, `PreparationInput.wordBound_le_pow`: logarithmic physical + width and polynomial words and address space when the scalar headers and weights are bounded. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.PreparationInput + +open Cslib.WordRAM Cslib.Algorithms.ExactTriangle + +/-- Actual raw `(D,g,n,p)` headers followed by the three signed matrix arrays. -/ +@[expose] +def input (n D g p : ℕ) (values : List ℤ) : List ℕ := + [D, g, n, p] ++ values.map Signed.encode + +/-- Original-input numeric envelope; the preparation budget is an explicit polynomial. -/ +@[expose] +def envelope (n D g p A : ℕ) : ℕ := Instances.budget n D g p + A + p + 2 + +/-- Three extra bits supply both the asymmetric signed range and the native signed compiler's +two-bit extension. This is a positive ceiling-logarithm bound in the original headers/weights. -/ +@[expose] +def width (n D g p A : ℕ) : ℕ := Nat.clog 2 (envelope n D g p A) + 3 + +/-- An explicit polynomial in the original numeric parameters bounds every word and address +at every wider admitted width. It does not grow with the physical width chosen by the caller. -/ +@[expose] +def wordBound (n D g p A : ℕ) : ℕ := 16 * envelope n D g p A + +/-- One fixed 64-register flat program, including the actual halt. -/ +@[expose] +def flatProgram : Program .standard 64 := Instances.program.program Instances.output + +/-- The code length and width independence are uniform over all runtime inputs. -/ +theorem flatProgram_fixed : flatProgram.length ≤ 641 ∧ ¬flatProgram.ReadsWidth := by + refine ⟨?_, Code.not_readsWidth_program Instances.program_fixed.2 _⟩ + simp only [flatProgram, Code.program, List.length_append, Code.length_assemble, + List.length_cons, List.length_nil] + exact Nat.add_le_add_right Instances.program_fixed.1 1 + +/-- The concrete initialized payload is the original weight sequence, with no supplied table. -/ +theorem input_word (n D g p : ℕ) (values : List ℤ) (q : ℕ) : + (Data.init (input n D g p values) : Data 64).mem (5 + q) = + Signed.encode (values.getD q 0) := by + rw [show 5 + q = (4 + q) + 1 by omega, Data.init_mem_succ] + rw [input, List.getD_append_right _ _ _ _ (by simp)] + simp only [List.length_cons, List.length_nil, Nat.reduceAdd, Nat.add_sub_cancel_left] + exact List.getD_map values 0 Signed.encode + +private theorem budget_bounds (n D g p : ℕ) : + 4 + 3 * (n * n) ≤ Instances.budget n D g p ∧ + D ≤ Instances.budget n D g p ∧ g ≤ Instances.budget n D g p ∧ + n ≤ Instances.budget n D g p ∧ p ≤ Instances.budget n D g p := by + dsimp only [Instances.budget, Catalogue.budget] + omega + +private theorem budget_le_pow {n D g p : ℕ} (hn : 1 ≤ n) + (hD : D ≤ n) (hg : g ≤ n) (hp : p ≤ n) : + Instances.budget n D g p ≤ 286 * n ^ 6 := by + have h1 : 1 ≤ n ^ 6 := Nat.one_le_pow _ _ hn + have hn6 : n ≤ n ^ 6 := by + simpa only [pow_one] using Nat.pow_le_pow_right hn (by decide : 1 ≤ 6) + have h2 : n ^ 2 ≤ n ^ 6 := Nat.pow_le_pow_right hn (by decide) + have h3 : n ^ 3 ≤ n ^ 6 := Nat.pow_le_pow_right hn (by decide) + have h4 : n ^ 4 ≤ n ^ 6 := Nat.pow_le_pow_right hn (by decide) + have hhigh : p * D * n ^ 4 ≤ n ^ 6 := by + calc + _ ≤ (n * n) * n ^ 4 := Nat.mul_le_mul_right _ (Nat.mul_le_mul hp hD) + _ = n ^ 6 := by ring + have hpn3 : p * n ^ 3 ≤ n ^ 6 := by + calc + _ ≤ n * n ^ 3 := Nat.mul_le_mul_right _ hp + _ = n ^ 4 := by ring + _ ≤ n ^ 6 := h4 + have hpn2 : p * n ^ 2 ≤ n ^ 6 := by + calc + _ ≤ n * n ^ 2 := Nat.mul_le_mul_right _ hp + _ = n ^ 3 := by ring + _ ≤ n ^ 6 := h3 + have hnD : n * D ≤ n ^ 6 := by + calc + _ ≤ n * n := Nat.mul_le_mul_left _ hD + _ = n ^ 2 := by ring + _ ≤ n ^ 6 := h2 + have hD6 := hD.trans hn6 + have hg6 := hg.trans hn6 + have hp6 := hp.trans hn6 + have heq : Instances.budget n D g p = + 2 * (p * D * n ^ 4) + 4 * n ^ 4 + 7 * (p * n ^ 3) + 30 * (p * n ^ 2) + + 62 * n ^ 2 + 2 * (n * D) + 8 * D + 8 * g + 12 * n + 13 * p + 138 := by + dsimp only [Instances.budget, Catalogue.budget] + ring + rw [heq] + omega + +private theorem envelope_le_pow {n D g p A κ : ℕ} (hn : 1 ≤ n) + (hD : D ≤ n) (hg : g ≤ n) (hp : p ≤ n) (hA : A ≤ (n + 2) ^ κ) : + envelope n D g p A ≤ (n + 2) ^ (κ + 15) := by + let b := n + 2 + have hb : 1 ≤ b := by dsimp only [b]; omega + have hpow : n ^ 6 ≤ b ^ (κ + 6) := + (Nat.pow_le_pow_left (by dsimp only [b]; omega : n ≤ b) 6).trans + (Nat.pow_le_pow_right hb (by omega)) + have hbudget := (budget_le_pow hn hD hg hp).trans (Nat.mul_le_mul_left 286 hpow) + have hAval : A ≤ b ^ (κ + 6) := hA.trans (Nat.pow_le_pow_right hb (by omega)) + have hbpow : b ≤ b ^ (κ + 6) := Nat.le_self_pow (by omega) b + have hpval : p ≤ b ^ (κ + 6) := hp.trans ((by dsimp only [b]; omega : n ≤ b).trans hbpow) + have hunit : 1 ≤ b ^ (κ + 6) := by have := pow_pos (by omega : 0 < b) (κ + 6); omega + have hsum : envelope n D g p A ≤ 290 * b ^ (κ + 6) := by + dsimp only [envelope] + omega + have hcoef : 290 ≤ b ^ 9 := (show 290 ≤ 2 ^ 9 from by decide).trans + (Nat.pow_le_pow_left (by dsimp only [b]; omega : 2 ≤ b) 9) + calc + envelope n D g p A ≤ 290 * b ^ (κ + 6) := hsum + _ ≤ b ^ 9 * b ^ (κ + 6) := Nat.mul_le_mul_right _ hcoef + _ = b ^ (κ + 15) := by rw [← Nat.pow_add]; congr 1; omega + +/-- For polynomially bounded weights and size-bounded scalar headers, the physical threshold +is explicitly logarithmic in the original outer dimension. The bound on `g` is stated because +its raw input word must fit even when it does not change the resulting piece size. -/ +theorem width_le_log {n D g p A κ : ℕ} (hn : 1 ≤ n) + (hD : D ≤ n) (hg : g ≤ n) (hp : p ≤ n) (hA : A ≤ (n + 2) ^ κ) : + width n D g p A ≤ (κ + 18) * Nat.clog 2 (n + 2) := by + have hbase := Nat.le_pow_clog (by decide : 1 < 2) (n + 2) + have hpow := (envelope_le_pow hn hD hg hp hA).trans + (Nat.pow_le_pow_left hbase (κ + 15)) + rw [← Nat.pow_mul] at hpow + have hclog := Nat.clog_le_of_le_pow hpow + have hpos : 0 < Nat.clog 2 (n + 2) := Nat.clog_pos (by decide) (by omega) + dsimp only [width] + nlinarith only [hclog, hpos] + +/-- The same original-size hypotheses turn the every-prefix word/support bound into a +polynomial in the original outer dimension, independent of physical width. -/ +theorem wordBound_le_pow {n D g p A κ : ℕ} (hn : 1 ≤ n) + (hD : D ≤ n) (hg : g ≤ n) (hp : p ≤ n) (hA : A ≤ (n + 2) ^ κ) : + wordBound n D g p A ≤ (n + 2) ^ (κ + 19) := by + have hc : 16 ≤ (n + 2) ^ 4 := (show 16 ≤ 2 ^ 4 from by decide).trans + (Nat.pow_le_pow_left (by omega : 2 ≤ n + 2) 4) + calc + _ ≤ (n + 2) ^ 4 * (n + 2) ^ (κ + 15) := + Nat.mul_le_mul hc (envelope_le_pow hn hD hg hp hA) + _ = (n + 2) ^ (κ + 19) := by rw [← Nat.pow_add]; congr 1; omega + +private theorem fits_at_width (n D g p A : ℕ) : + GenericRAM.Signed.Fits (Nat.clog 2 (envelope n D g p A) + 1) ((A + p + 1 : ℕ) : ℤ) ∧ + Fits (width n D g p A) (Instances.budget n D g p) := by + let V := envelope n D g p A + let k := Nat.clog 2 V + have hpow : V ≤ 2 ^ k := Nat.le_pow_clog (by decide) V + have hmag : A + p + 1 < 2 ^ k := lt_of_lt_of_le + (by dsimp only [V, envelope]; omega) hpow + have hbudget : Instances.budget n D g p < 2 ^ k := lt_of_lt_of_le + (by dsimp only [V, envelope]; omega) hpow + refine ⟨⟨by omega, ?_, ?_⟩, (show Fits k (Instances.budget n D g p) from hbudget).mono + (by dsimp only [width, k, V]; omega)⟩ + · simp only [Nat.add_sub_cancel] + exact (neg_nonpos.mpr (by positivity)).trans (Int.natCast_nonneg _) + · simp only [Nat.add_sub_cancel] + exact_mod_cast hmag + +private theorem input_fits {n D g p A : ℕ} (values : List ℤ) + (hlen : values.length = 3 * (n * n)) (hv : ∀ z ∈ values, z.natAbs ≤ A) : + InputFits (width n D g p A) (input n D g p values) := by + obtain ⟨hmag, hbudget⟩ := fits_at_width n D g p A + obtain ⟨hlenBound, hD, hg, hn, hp⟩ := budget_bounds n D g p + refine ⟨?_, ?_⟩ + · simpa only [input, List.length_append, List.length_cons, List.length_nil, + List.length_map, hlen, Nat.reduceAdd] using hbudget.of_le hlenBound + · intro v hmem + rcases List.mem_append.mp hmem with h | h + · simp only [List.mem_cons, List.not_mem_nil, or_false] at h + rcases h with rfl | rfl | rfl | rfl + · exact hbudget.of_le hD + · exact hbudget.of_le hg + · exact hbudget.of_le hn + · exact hbudget.of_le hp + · obtain ⟨z, hz, rfl⟩ := List.mem_map.mp h + have hs := hmag.of_natAbs_le ((hv z hz).trans (by omega : A ≤ A + p + 1)) + exact (Signed.encode_fits hs).mono (by dsimp only [width]; omega) + +/-- Complete initialized execution at every admitted physical width. The prefix witness and +the output predicate use the same actual pre-halt memory, and the bound includes the halt. -/ +theorem runs_input {n D g p A W : ℕ} (values : List ℤ) + (hlen : values.length = 3 * (n * n)) + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : 0 < p) + (hv : ∀ z ∈ values, z.natAbs ≤ A) (hw : width n D g p A ≤ W) : + let C := fun rho => Nat.count (fun q => (values.getD q 0 % (p : ℤ)).toNat = rho) (n * n) + let K := ChunkCatalogue.before C (queryCap n D) p + ∃ y t, t ≤ Instances.timeBound n D p K (numPieces n D g) ∧ + Runs W flatProgram (State.init (input n D g p values)) ⟨Instances.program.size, y⟩ + (t - 1) ∧ + Executes W flatProgram (State.init (input n D g p values)) (y.regs Instances.output) t ∧ + Instances.Output n D g p (fun q => values.getD q 0) + (.init (input n D g p values)) y ∧ + InputFits W (input n D g p values) ∧ y.Bounded W := by + obtain ⟨hmag, hbudget⟩ := fits_at_width n D g p A + have hmod : GenericRAM.Signed.Fits (Nat.clog 2 (envelope n D g p A) + 1) (p : ℤ) := + hmag.of_natAbs_le (by simp only [Int.natAbs_natCast]; omega) + have hvalues (q : ℕ) (hq : q < 3 * (n * n)) : + GenericRAM.Signed.Fits (Nat.clog 2 (envelope n D g p A) + 1) (values.getD q 0) := by + have hq' : q < values.length := by omega + have hmem : values.getD q 0 ∈ values := by + rw [List.getD_eq_getElem _ _ hq'] + exact List.getElem_mem hq' + exact hmag.of_natAbs_le ((hv _ hmem).trans (by omega : A ≤ A + p + 1)) + obtain ⟨y, hy, hout⟩ := Instances.exec_at n D g p (fun q => values.getD q 0) + (.init (input n D g p values)) rfl rfl rfl rfl (fun q _ => input_word n D g p values q) + hD hDn hg hp hmod hvalues hbudget + have hew := ExecWithin.mono_width Instances.program_fixed.2 hy hw + have hi := (input_fits values hlen hv).mono hw + have hybounded := hew.bounded (Data.Bounded.init hi) + obtain ⟨t, ht, he⟩ := hew + have hr := he.runs (pc := 0) (p := flatProgram) (Program.Contains.self _).append_left + refine ⟨y, t + 1, ?_, ?_, ?_, hout, hi, hybounded⟩ + · let T := Instances.timeBound n D p + (ChunkCatalogue.before + (fun rho => Nat.count (fun q => (values.getD q 0 % (p : ℤ)).toNat = rho) (n * n)) + (queryCap n D) p) (numPieces n D g) + change t ≤ T - 1 at ht + change t + 1 ≤ T + have hpos : 1 ≤ T := by + dsimp only [T, Instances.timeBound] + omega + omega + · simpa only [State.init, Nat.zero_add, Nat.add_sub_cancel] using hr + · simpa only [flatProgram, State.init] using he.executes Instances.output + +private theorem pow_width_lt (n D g p A : ℕ) : + 2 ^ width n D g p A < wordBound n D g p A := by + let V := envelope n D g p A + let k := Nat.clog 2 V + have hV : 1 < V := by dsimp only [V, envelope]; omega + have hkpos : 0 < k := Nat.clog_pos (by decide) hV + have hk : k.pred + 1 = k := Nat.succ_pred_eq_of_pos hkpos + have hpred : 2 ^ k.pred < V := Nat.pow_pred_clog_lt_self (by decide) hV + change 2 ^ (k + 3) < 16 * V + calc + 2 ^ (k + 3) = 2 ^ k * 8 := by rw [Nat.pow_add] + _ = 2 ^ (k.pred + 1) * 8 := congrArg (fun j => 2 ^ j * 8) hk.symm + _ = (2 ^ k.pred * 2) * 8 := by rw [Nat.pow_succ] + _ < 16 * V := by omega + +/-- All actual run prefixes, at every admitted physical width, have bounded words and zero +memory beyond one original-input polynomial. Width independence identifies each such prefix +with the smaller admitted run; the generic native memory-frame theorem supplies the support +bound. No caller-provided invariant or execution certificate is used. -/ +theorem bounded_reachable {n D g p A W : ℕ} (values : List ℤ) + (hlen : values.length = 3 * (n * n)) + (hD : 1 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (hp : 0 < p) + (hv : ∀ z ∈ values, z.natAbs ≤ A) (hw : width n D g p A ≤ W) + (s : State 64) (steps : ℕ) + (hr : Runs W flatProgram (State.init (input n D g p values)) s steps) : + (∀ i, s.data.regs i < wordBound n D g p A) ∧ + (∀ a, s.data.mem a < wordBound n D g p A) ∧ + ∀ a, wordBound n D g p A ≤ a → s.data.mem a = 0 := by + obtain ⟨_, _, _, _, he, _, hi, -⟩ := runs_input values hlen hD hDn hg hp hv le_rfl + have hlow := Runs.prefix_at_smaller_width flatProgram_fixed.2 hw hr he + have hs := Runs.bounded hlow (Data.Bounded.init hi) + have hword := pow_width_lt n D g p A + refine ⟨fun i => (hs.1 i).trans hword, fun a => (hs.2 a).trans hword, ?_⟩ + intro a ha + have habove : 2 ^ width n D g p A ≤ a := hword.le.trans ha + rw [hlow.mem_above (Data.Bounded.init hi) habove] + cases a with + | zero => have := Nat.two_pow_pos (width n D g p A); omega + | succ a => + change (input n D g p values).getD a 0 = 0 + apply List.getD_eq_default + have hlenFit := hi.1 + dsimp only [Fits] at hlenFit + omega + +end Cslib.Algorithms.WordRAM.ExactTriangle.PreparationInput diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationSpec.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationSpec.lean new file mode 100644 index 0000000000..81c19957ab --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/PreparationSpec.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.WordRAM.ExactTriangle.Instances +public import Complexity.Algorithms.WordRAM.ExactTriangle.QuerySpec +public import Complexity.Algorithms.WordRAM.ExactTriangle.MatrixSpec + +/-! +# Exact finite meaning of prepared native instances + +The native catalogue enumerates the existing residue classes, chunks and pieces. These results +identify its actual output count and per-instance records with that finite construction. Matrix +meaning and padding reuse `MatrixSpec` and its existing writer-entry formulas. + +## Main results + +* `PreparationSpec.count_eq_card`: the actual output counter is the finite instance cardinality. +* `PreparationSpec.index_unique`: unique existing finite coordinates for each output record. +* `PreparationSpec.instance_readback`: exact query slices and matrices at every finite index. +-/ + +public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.PreparationSpec + +open Cslib.WordRAM Cslib.Algorithms.ExactTriangle Cslib.Matrix + +/-- The counter of an actual native output is exactly the existing finite instance count. +The weight-array correspondence concerns only the original A–B input, not supplied classes. -/ +theorem count_eq_card {n D g p : ℕ} (values : ℕ → ℤ) (wAB : Fin n → Fin n → ℤ) + (hp : p ≠ 0) (hAB : ∀ a b, values (Cslib.Matrix.pairIndex (a, b)) = wAB a b) + {initial y : Data 64} (h : Instances.Output n D g p values initial y) : + y.regs Instances.produced = (instanceIndices wAB D g p).card := by + obtain ⟨-, -, -, -, hc, -, -, -, -, -, -, -⟩ := h + exact hc.trans (QuerySpec.before_mul_numPieces wAB hp + (fun q => (values q % (p : ℤ)).toNat) (by intro q; rw [hAB]; rfl) D g) + +private theorem pieces_pos {n D g : ℕ} (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) : + 0 < numPieces n D g := by + have hcover : n ≤ numPieces n D g * pieceSize D g := + Nat.le_ceilDiv_mul (pieceSize_pos hD hg) + by_contra h + have hzero : numPieces n D g = 0 := by omega + rw [hzero, Nat.zero_mul] at hcover + omega + +/-- Every occupied native instance slot has exactly one existing finite class/chunk/piece +index. This uses the standard dependent finite-sum enumeration and ordinary division/remainder; +there is no second definition of a residue class or finite instance. -/ +theorem index_unique {n D g p : ℕ} (values : ℕ → ℤ) (wAB : Fin n → Fin n → ℤ) + (hp : p ≠ 0) (hAB : ∀ a b, values (Cslib.Matrix.pairIndex (a, b)) = wAB a b) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) (i : ℕ) : + let C := fun rho => Nat.count (fun q => (values q % (p : ℤ)).toNat = rho) (n * n) + let H := numPieces n D g + i < ChunkCatalogue.before C (queryCap n D) p * H → + ∃! idx : InstanceIndex p, idx ∈ instanceIndices wAB D g p ∧ + i = (ChunkCatalogue.before C (queryCap n D) idx.1.val + idx.2.1) * H + idx.2.2 := by + let C := fun rho => Nat.count (fun q => (values q % (p : ℤ)).toNat = rho) (n * n) + let H := numPieces n D g + have hH : 0 < H := pieces_pos hD hDn hg + have hCount (rho : Fin p) : (residueClass wAB p rho).card = C rho.val := + QuerySpec.card_residueClass wAB hp (fun q => (values q % (p : ℤ)).toNat) + (by intro q; rw [hAB]; rfl) rho + dsimp only + intro hi + change i < ChunkCatalogue.before C (queryCap n D) p * H at hi + obtain ⟨idx, hidx, huniq⟩ := ChunkCatalogue.before_index C (queryCap n D) p (i / H) + ((Nat.div_lt_iff_lt_mul hH).mpr hi) + let out : InstanceIndex p := (idx.1, idx.2.val, i % H) + have hout : out ∈ instanceIndices wAB D g p := by + rw [mem_instanceIndices] + refine ⟨?_, Nat.mod_lt _ hH⟩ + change idx.2.val < numChunks (residueClass wAB p idx.1) (queryCap n D) + rw [numChunks, hCount] + exact idx.2.isLt + refine ⟨out, ⟨hout, ?_⟩, ?_⟩ + · change i = (ChunkCatalogue.before C (queryCap n D) idx.1.val + idx.2.val) * H + i % H + rw [← hidx] + simpa only [Nat.mul_comm] using (Nat.div_add_mod i H).symm + · intro other hother + obtain ⟨hk, hj⟩ := (mem_instanceIndices wAB other).mp hother.1 + have hchunk : other.2.1 < C other.1.val ⌈/⌉ queryCap n D := by + simpa only [numChunks, hCount] using hk + let idx' : (rho : Fin p) × Fin (C rho.val ⌈/⌉ queryCap n D) := + ⟨other.1, ⟨other.2.1, hchunk⟩⟩ + have heq : i = (ChunkCatalogue.before C (queryCap n D) other.1.val + other.2.1) * H + + other.2.2 := hother.2 + have hdiv : i / H = ChunkCatalogue.before C (queryCap n D) other.1.val + other.2.1 := by + rw [heq, Nat.mul_comm _ H, Nat.mul_add_div hH, + Nat.div_eq_of_lt hj, Nat.add_zero] + have hmod : i % H = other.2.2 := by rw [heq, Nat.mul_add_mod_of_lt hj] + have he : idx' = idx := huniq idx' hdiv + exact Prod.ext (congrArg Sigma.fst he) + (Prod.ext (congrArg (fun q => q.2.val) he) hmod.symm) + +/-- Each existing finite instance is stored at its computed native ordinal. Its six-word +record contains the exact residue, piece, existing query-chunk pointer/cardinality and both +matrix pointers. The matrix readback reuses the same entry formulas as `MatrixSpec`, including +their modular adjacency and zero-padding results. -/ +theorem instance_readback {n D g p : ℕ} (values : ℕ → ℤ) (wAB : Fin n → Fin n → ℤ) + (hp : p ≠ 0) (hAB : ∀ a b, values (Cslib.Matrix.pairIndex (a, b)) = wAB a b) + (hD : 16 ≤ D) (hDn : D ≤ n) (hg : 1 ≤ g) + {initial y : Data 64} (h : Instances.Output n D g p values initial y) + (idx : InstanceIndex p) (hidx : idx ∈ instanceIndices wAB D g p) : + let N := n * n + let R := fun q => (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let H := numPieces n D g + let B := pieceSize D g + let K := ChunkCatalogue.before C (queryCap n D) p + let ord := ChunkCatalogue.before C (queryCap n D) idx.1.val + idx.2.1 + let base := 5 + 6 * N + p + 2 * N * p + 3 * K + (ord * H + idx.2.2) * (6 + 2 * (n * D)) + let A : WriteMatrices.Args := + ⟨5 + 5 * N, base + 6, n, D, p, idx.2.2 * B, min B (n - idx.2.2 * B), idx.1.val⟩ + let A' := { A with src := 5 + 4 * N, dst := base + 6 + n * D } + (Block.mk base 6).Realizes y.mem + ![idx.1.val, idx.2.2, 5 + 6 * N + p + 2 * idx.1.val * N + + 2 * (idx.2.1 * queryCap n D), + min (queryCap n D) ((residueClass wAB p idx.1).card - idx.2.1 * queryCap n D), + base + 6, base + 6 + n * D] ∧ + (Block.mk A.dst (n * D)).Realizes y.mem + (fun j => WriteMatrices.entry false A (fun q => R (2 * N + q)) j) ∧ + (Block.mk A'.dst (n * D)).Realizes y.mem + (fun j => WriteMatrices.entry true A' (fun q => R (N + q)) j) := by + let N := n * n + let R := fun q => (values q % (p : ℤ)).toNat + let C := fun rho => Nat.count (fun q => R q = rho) N + let H := numPieces n D g + let K := ChunkCatalogue.before C (queryCap n D) p + let ord := ChunkCatalogue.before C (queryCap n D) idx.1.val + idx.2.1 + let i := ord * H + idx.2.2 + let desc := 5 + 6 * N + p + 2 * N * p + have hH : 0 < H := pieces_pos hD hDn hg + obtain ⟨hk, hj⟩ := (mem_instanceIndices wAB idx).mp hidx + have hCount : (residueClass wAB p idx.1).card = C idx.1.val := + QuerySpec.card_residueClass wAB hp R + (by intro q; dsimp only [R]; rw [hAB]; rfl) idx.1 + have hchunk : idx.2.1 < C idx.1.val ⌈/⌉ queryCap n D := by + simpa only [numChunks, hCount] using hk + have hord : ord < K := ChunkCatalogue.before_add_lt C (queryCap n D) idx.1.isLt hchunk + have hbound := Nat.mul_le_mul_right H (Nat.succ_le_of_lt hord) + rw [Nat.succ_mul] at hbound + have hi : i < K * H := by dsimp only [i]; omega + have hdiv : i / H = ord := by + dsimp only [i] + rw [Nat.mul_comm ord H, Nat.mul_add_div hH, Nat.div_eq_of_lt hj, Nat.add_zero] + have hmod : i % H = idx.2.2 := by dsimp only [i]; exact Nat.mul_add_mod_of_lt hj + obtain ⟨-, -, -, -, -, -, -, -, -, hdesc, hblocks, -⟩ := h + have hd := hdesc idx.1.val idx.1.isLt idx.2.1 hchunk + have hresidue : y.mem (desc + 3 * ord) = idx.1.val := hd.1 + have hquery : y.mem (desc + 3 * ord + 1) = + 5 + 6 * N + p + 2 * idx.1.val * N + 2 * (idx.2.1 * queryCap n D) := hd.2.1 + have hcount : y.mem (desc + 3 * ord + 2) = + min (queryCap n D) ((residueClass wAB p idx.1).card - idx.2.1 * queryCap n D) := by + rw [hCount] + exact hd.2.2 + obtain ⟨hr, hL, hR⟩ := hblocks i hi + dsimp only at hr hL hR + rw [hdiv, hmod, hresidue, hquery, hcount] at hr + rw [hdiv, hmod, hresidue] at hL hR + exact ⟨hr, hL, hR⟩ + +end Cslib.Algorithms.WordRAM.ExactTriangle.PreparationSpec diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/QueryScan.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/QueryScan.lean new file mode 100644 index 0000000000..a5b66127de --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/QueryScan.lean @@ -0,0 +1,263 @@ +/- +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.Computability.WordRAM.Notation +public import Mathlib.Data.Nat.Count + +/-! +# Native row-major residue-class queries + +The program scans a square residue matrix and writes the row and column of each entry equal to +the runtime query residue. Its output order is the row-major order used by the existing finite +chunk construction. Prefix ranks use Mathlib's `Nat.count`. + +This directly adapts the query-class construction in the pinned +[Theorem 17 class builder](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/Classes.lean). +The per-class scan is charged explicitly; composing all residue classes multiplies its bound by +the modulus. No class-membership or query-list table is supplied to the program. + +## Main definitions + +* `QueryScan.program`: the fixed twelve-register query scan. + +## Main results + +* `QueryScan.exec`: actual scan execution, exact ranks and the output-prefix frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.QueryScan + +open Cslib.WordRAM + +/-- Square residue input pointer. -/ +abbrev src : Fin 12 := 0 +/-- Output pointer for consecutive row/column pairs. -/ +abbrev dst : Fin 12 := 1 +/-- Matrix side. -/ +abbrev size : Fin 12 := 2 +/-- Selected residue. -/ +abbrev residue : Fin 12 := 3 +/-- Flat input cursor. -/ +abbrev idx : Fin 12 := 4 +/-- Input area. -/ +abbrev area : Fin 12 := 5 +/-- Number of emitted pairs. -/ +abbrev count : Fin 12 := 6 +/-- Address temporary. -/ +abbrev addr : Fin 12 := 7 +/-- Loaded residue. -/ +abbrev key : Fin 12 := 8 +/-- Constant one. -/ +abbrev one : Fin 12 := 9 +/-- Output row. -/ +abbrev row : Fin 12 := 10 +/-- Output column. -/ +abbrev col : Fin 12 := 11 + +/-- Every successful match computes both coordinates and stores their two words. -/ +def body : Code .standard 12 := ram[ + addr ← src + idx; key ← *addr; + if key = residue then [ + row ← idx / size; col ← idx % size; addr ← count + count; addr ← addr + dst; + *addr ← row; addr ← addr + one; *addr ← col; count ← count + one + ] else [ count ← count ]; + idx ← idx + one ] + +/-- Constants, counters and matrix area are initialized by actual instructions. -/ +def program : Code .standard 12 := + .seq (ram[ one ← #1; idx ← #0; count ← #0; area ← size * size ]) + (.loop (.lt idx area) body) + +/-- The query scanner has a fixed instruction graph and no physical-width reads. -/ +theorem program_fixed : program.size ≤ 32 ∧ ¬program.ReadsWidth := by decide + +private theorem body_preserves : ∀ i : Fin 12, + (i.val < 4 ∨ i = area ∨ i = one) → body.Preserves i := by decide + +private theorem exec_body {w : ℕ} (x : Data 12) (R : ℕ → ℕ) + (ho : x.regs one = 1) (hq : x.regs idx < x.regs size * x.regs size) + (hc : x.regs count ≤ x.regs idx) + (hinput : x.mem (x.regs src + x.regs idx) = R (x.regs idx)) + (hsrc : Fits w (x.regs src + x.regs size * x.regs size)) + (hdst : Fits w (x.regs dst + 2 * (x.regs size * x.regs size))) : + ∃ y, ExecWithin w body x y 12 ∧ y.regs idx = x.regs idx + 1 ∧ + y.regs count = x.regs count + (if R (x.regs idx) = x.regs residue then 1 else 0) ∧ + y.mem = (if R (x.regs idx) = x.regs residue then + Function.update (Function.update x.mem (x.regs dst + 2 * x.regs count) + (x.regs idx / x.regs size)) (x.regs dst + 2 * x.regs count + 1) + (x.regs idx % x.regs size) else x.mem) := by + let q := x.regs idx + let c := x.regs count + let n := x.regs size + let d := x.regs dst + have hread : Fits w (x.regs src + q) := hsrc.of_le (by dsimp [q]; omega) + have hqfit : Fits w q := hsrc.of_le (by dsimp [q]; omega) + have hdiv : Fits w (q / n) := hqfit.of_le (Nat.div_le_self ..) + have hmod : Fits w (q % n) := hqfit.of_le (Nat.mod_le ..) + have hdouble : Fits w (c + c) := hdst.of_le (by dsimp [c] at *; omega) + have hdest : Fits w (c + c + d) := hdst.of_le (by dsimp [c, d] at *; omega) + have hnext : Fits w (c + c + d + 1) := hdst.of_le (by dsimp [c, d] at *; omega) + have hcount : Fits w (c + 1) := hdst.of_le (by dsimp [c] at *; omega) + have hidx : Fits w (q + 1) := hsrc.of_le (by dsimp [q]; omega) + let a := x.set addr (x.regs src + q) |>.set key (R q) + let b := if R q = x.regs residue then + a.set row (q / n) |>.set col (q % n) |>.set addr (c + c) + |>.set addr (c + c + d) |>.write (c + c + d) (q / n) + |>.set addr (c + c + d + 1) |>.write (c + c + d + 1) (q % n) |>.set count (c + 1) + else a.set count c + let y := b.set idx (q + 1) + refine ⟨y, ?_, ?_, ?_, ?_⟩ + · by_cases h : R q = x.regs residue <;> + ram_eval [body, a, b, y, q, c, n, d, ho, h, hinput, hread, hdiv, hmod, hdouble, + hdest, hnext, hcount, hidx, src, dst, size, residue, idx, count, addr, key, one, + row, col, Fin.mk.injEq] + · simp [y, q] + · by_cases h : R q = x.regs residue <;> + simp +decide [y, b, a, q, c, h, count, idx, addr, key, row, col] + · by_cases h : R q = x.regs residue + · change R (x.regs idx) = x.regs residue at h + simp only [y, b, a, q, n, c, d, h, ↓reduceIte, Data.set_mem, Data.write_mem] + congr 2 <;> omega + · change ¬R (x.regs idx) = x.regs residue at h + simp only [y, b, a, q, h, ↓reduceIte, Data.set_mem] + +/-- Scan an actual residue matrix, preserving the input and all cells outside the emitted pair +prefix. Each selected flat position is stored at its exact row-major rank, counted by `Nat.count`. +The final halt is excluded from the `14n²+6` instruction bound. -/ +theorem exec {w : ℕ} (x : Data 12) (R : ℕ → ℕ) (h1 : Fits w 1) + (hinput : ∀ i < x.regs size * x.regs size, x.mem (x.regs src + i) = R i) + (hsrc : Fits w (x.regs src + x.regs size * x.regs size)) + (hdst : Fits w (x.regs dst + 2 * (x.regs size * x.regs size))) + (hdisj : x.regs src + x.regs size * x.regs size ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (14 * (x.regs size * x.regs size) + 6) ∧ + y.regs idx = x.regs size * x.regs size ∧ + y.regs count = Nat.count (fun j => R j = x.regs residue) (x.regs size * x.regs size) ∧ + (∀ i : Fin 12, i.val < 4 → y.regs i = x.regs i) ∧ + (∀ q < x.regs size * x.regs size, R q = x.regs residue → + y.mem (x.regs dst + 2 * Nat.count (fun j => R j = x.regs residue) q) = q / x.regs size ∧ + y.mem (x.regs dst + 2 * Nat.count (fun j => R j = x.regs residue) q + 1) = + q % x.regs size) ∧ + (∀ a, ¬(x.regs dst ≤ a ∧ a < x.regs dst + 2 * y.regs count) → y.mem a = x.mem a) := by + let C := Nat.count (fun j => R j = x.regs residue) + let n := x.regs size + let d := x.regs dst + let I (y : Data 12) : Prop := + (∀ i : Fin 12, i.val < 4 → y.regs i = x.regs i) ∧ + y.regs area = n * n ∧ y.regs one = 1 ∧ y.regs idx ≤ n * n ∧ + y.regs count = C (y.regs idx) ∧ + (∀ q < y.regs idx, R q = x.regs residue → + y.mem (d + 2 * C q) = q / n ∧ y.mem (d + 2 * C q + 1) = q % n) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 2 * C (y.regs idx)) → y.mem a = x.mem a) + have hbody (y : Data 12) (hy : I y) (hcond : (Cond.lt idx area).Holds y) : + ∃ z, ExecWithin w body y z 12 ∧ I z ∧ n * n - z.regs idx < n * n - y.regs idx := by + obtain ⟨hyr, hya, hyo, hyi, hyc, hycells, hyout⟩ := hy + have hyn : y.regs size = n := hyr size (by decide) + have hyd : y.regs dst = d := hyr dst (by decide) + have hys : y.regs src = x.regs src := hyr src (by decide) + have hyρ : y.regs residue = x.regs residue := hyr residue (by decide) + have hlt : y.regs idx < n * n := by simpa only [Cond.holds_lt, hya] using hcond + have hread : y.mem (y.regs src + y.regs idx) = R (y.regs idx) := by + rw [hys, hyout _ (by dsimp [d, n] at *; omega)] + exact hinput _ hlt + have hcle : y.regs count ≤ y.regs idx := by rw [hyc]; exact Nat.count_le _ + obtain ⟨z, he, hzi, hzc, hzm⟩ := exec_body y R hyo (by simpa only [hyn] using hlt) + hcle hread (by simpa only [hys, hyn] using hsrc) + (by simpa only [hyd, hyn] using hdst) + have hsucc : C (y.regs idx + 1) = + C (y.regs idx) + if R (y.regs idx) = x.regs residue then 1 else 0 := Nat.count_succ _ _ + have hzc' : z.regs count = C (z.regs idx) := by + rw [hzi] + simpa only [hyc, hyρ, hsucc] using hzc + have hpres (i : Fin 12) (hi : i.val < 4 ∨ i = area ∨ i = one) : + z.regs i = y.regs i := he.regs_eq (body_preserves i hi) + simp only [hyd, hyn, hyc, hyρ] at hzm + refine ⟨z, he, ⟨fun i hi => (hpres i (Or.inl hi)).trans (hyr i hi), + (hpres area (by simp)).trans hya, (hpres one (by simp)).trans hyo, + by omega, hzc', ?_, ?_⟩, by omega⟩ + · intro j hj hkey + by_cases h : R (y.regs idx) = x.regs residue + · simp only [h, ↓reduceIte] at hzm + by_cases hji : j = y.regs idx + · subst j + rw [hzm] + constructor + · rw [Function.update_of_ne (by omega), Function.update_self] + · exact Function.update_self .. + · have hjlt : j < y.regs idx := by omega + have hrank : C j < C (y.regs idx) := Nat.count_strict_mono hkey hjlt + obtain ⟨hrow, hcol⟩ := hycells j hjlt hkey + rw [hzm] + constructor + · rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact hrow + · rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact hcol + · simp only [h, ↓reduceIte] at hzm + rw [hzm] + have hne : j ≠ y.regs idx := by + intro hj + exact h (hj ▸ hkey) + exact hycells j (by omega) hkey + · intro a ha + by_cases h : R (y.regs idx) = x.regs residue + · simp only [h, ↓reduceIte] at hzm + have hcstep : C (z.regs idx) = C (y.regs idx) + 1 := by + simp only [hzi, hsucc, h, ↓reduceIte] + rw [hzm, Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact hyout a (by omega) + · simp only [h, ↓reduceIte] at hzm + have hcstep : C (z.regs idx) = C (y.regs idx) := by + simp only [hzi, hsucc, h, ↓reduceIte, Nat.add_zero] + rw [hzm] + exact hyout a (by omega) + have harea : Fits w (n * n) := hsrc.of_le (by dsimp [n]; omega) + let x₀ := x.set one 1 |>.set idx 0 |>.set count 0 |>.set area (n * n) + have hx₀ : I x₀ := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have ho : i ≠ one := by intro h; subst i; change 9 < 4 at hi; omega + have hi' : i ≠ idx := by intro h; subst i; change 4 < 4 at hi; omega + have hc : i ≠ count := by intro h; subst i; change 6 < 4 at hi; omega + have ha : i ≠ area := by intro h; subst i; change 5 < 4 at hi; omega + simp only [x₀, Data.set_regs, Function.update_of_ne ho, Function.update_of_ne hi', + Function.update_of_ne hc, Function.update_of_ne ha] + · simp [x₀] + · simp +decide [x₀, one, idx, count, area] + · simp +decide [x₀, idx, count, area] + · simp +decide [x₀, idx, count, area, C] + · intro j hj + simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, count, area, + ↓reduceIte] at hj + omega + · intro a _ + rfl + have hinit : ExecWithin w (ram[ one ← #1; idx ← #0; count ← #0; area ← size * size ]) + x x₀ 4 := by + ram_eval [x₀, n, h1, harea, one, idx, count, area, size, Fin.mk.injEq] + obtain ⟨y, hy, hI, hc⟩ := ExecWithin.loop_invariant I + (fun y => n * n - y.regs idx) hbody hx₀ + obtain ⟨hr, ha, -, hi, hcount, hcells, hout⟩ := hI + have hend : y.regs idx = n * n := by + simp only [Cond.holds_lt, ha] at hc + omega + refine ⟨y, ?_, hend, by simpa only [hend] using hcount, hr, ?_, ?_⟩ + · convert hinit.seq hy using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, count, area, + ↓reduceIte] + dsimp [n] + omega + · simpa only [hend] using hcells + · intro a ha + apply hout a + simpa only [← hcount] using ha + +end Cslib.Algorithms.WordRAM.ExactTriangle.QueryScan diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/QuerySpec.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/QuerySpec.lean new file mode 100644 index 0000000000..0e30a03e47 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/QuerySpec.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.ExactTriangle.Classes +public import Complexity.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue +public import Complexity.Algorithms.ExactTriangle.Splitting +public import Mathlib.Data.Nat.Nth + +/-! +# Native query ranks and the finite ExactTriangle chunks + +The native scanner uses `Nat.count` on flat positions. The finite splitting construction uses +`Matrix.rankIn` on pairs. These lemmas identify their counts and ranks through the existing +row-major indexing; they introduce no alternative notion of a residue class or chunk. + +## Main results + +* `QuerySpec.card_residueClass`: the native count is the finite class cardinality. +* `QuerySpec.rank_residueClass`: native prefix rank is the existing finite rank. +* `QuerySpec.mem_chunkOf`: consecutive native query slices are the existing chunks. +* `QuerySpec.query_readback`: every occupied native query slot names exactly one class member. +* `QuerySpec.chunk_readback`: each local chunk slot names its unique existing finite member. +* `QuerySpec.before_eq_totalChunks`: the native catalogue count is the existing finite count. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.QuerySpec + +open Cslib.Matrix Cslib.Algorithms.ExactTriangle + +private theorem pairIndex_eq (n : ℕ) (q : Fin n × Fin n) : + pairIndex q = (finProdFinEquiv q).val := by + simp [pairIndex, finProdFinEquiv, Nat.add_comm, Nat.mul_comm] + +private theorem pairIndex_lt {n : ℕ} (q : Fin n × Fin n) : pairIndex q < n * n := by + rw [pairIndex_eq] + exact (finProdFinEquiv q).isLt + +private theorem card_of_mem_pairIndex {n N : ℕ} (hN : N ≤ n * n) + (S : Finset (Fin n × Fin n)) (P : ℕ → Prop) [DecidablePred P] + (hS : ∀ q, q ∈ S ↔ pairIndex q < N ∧ P (pairIndex q)) : + S.card = Nat.count P N := by + rw [Nat.count_eq_card_filter_range] + refine Finset.card_bij (fun q _ => pairIndex q) ?_ ?_ ?_ + · intro q hq + exact Finset.mem_filter.mpr ⟨Finset.mem_range.mpr ((hS q).mp hq).1, ((hS q).mp hq).2⟩ + · intro q _ r _ h + exact pairIndex_injective h + · intro k hk + obtain ⟨hkN, hkP⟩ := Finset.mem_filter.mp hk + have hklt : k < n * n := (Finset.mem_range.mp hkN).trans_le hN + let q := finProdFinEquiv.symm (⟨k, hklt⟩ : Fin (n * n)) + have hq : pairIndex q = k := by + rw [pairIndex_eq] + exact congrArg Fin.val (finProdFinEquiv.apply_symm_apply ⟨k, hklt⟩) + have hqS : q ∈ S := (hS q).mpr (by + rw [hq] + exact ⟨Finset.mem_range.mp hkN, hkP⟩) + exact ⟨q, hqS, hq⟩ + +variable {n p : ℕ} (wAB : Fin n → Fin n → ℤ) (hp : p ≠ 0) (R : ℕ → ℕ) + (hR : ∀ q : Fin n × Fin n, R (pairIndex q) = (resFin hp (wAB q.1 q.2)).val) + +include hp hR + +private theorem mem_class (rho : Fin p) (q : Fin n × Fin n) : + q ∈ residueClass wAB p rho ↔ R (pairIndex q) = rho.val := by + rw [mem_residueClass wAB hp, hR q] + exact ⟨fun h => congrArg Fin.val h.symm, fun h => (Fin.ext h).symm⟩ + +/-- The count emitted by the native class scanner equals the existing finite class size. -/ +theorem card_residueClass (rho : Fin p) : + (residueClass wAB p rho).card = Nat.count (fun q => R q = rho.val) (n * n) := + card_of_mem_pairIndex le_rfl _ _ fun q => by + rw [mem_class wAB hp R hR] + exact ⟨fun h => ⟨pairIndex_lt q, h⟩, And.right⟩ + +/-- The concatenation controller's final counter is exactly the existing total chunk count. +The native table uses `Nat.count`; `card_residueClass` identifies each summand. -/ +theorem before_eq_totalChunks (D : ℕ) : + ChunkCatalogue.before (fun rho => Nat.count (fun q => R q = rho) (n * n)) + (queryCap n D) p = totalChunks wAB D p := by + rw [totalChunks, ChunkCatalogue.before, ← Fin.sum_univ_eq_sum_range] + refine Finset.sum_congr rfl fun rho _ => ?_ + rw [numChunks, card_residueClass wAB hp R hR] + +/-- Iterating every actual catalogue entry over every piece creates exactly the existing +finite instance set's cardinality. This is the multiplier used by the native matrix caller. -/ +theorem before_mul_numPieces (D g : ℕ) : + ChunkCatalogue.before (fun rho => Nat.count (fun q => R q = rho) (n * n)) + (queryCap n D) p * numPieces n D g = (instanceIndices wAB D g p).card := by + rw [before_eq_totalChunks wAB hp R hR, card_instanceIndices] + +/-- Prefix rank agrees with the existing row-major rank used to split query chunks. -/ +theorem rank_residueClass (rho : Fin p) (q : Fin n × Fin n) : + rankIn (residueClass wAB p rho) q = + Nat.count (fun j => R j = rho.val) (pairIndex q) := + card_of_mem_pairIndex (pairIndex_lt q).le _ _ fun r => by + simp only [Finset.mem_filter, mem_class wAB hp R hR] + exact and_comm + +/-- Dividing the actual prefix rank by the query capacity gives precisely `chunkOf`. -/ +theorem mem_chunkOf (D : ℕ) (rho : Fin p) (j : ℕ) (q : Fin n × Fin n) : + q ∈ chunkOf wAB D p rho j ↔ R (pairIndex q) = rho.val ∧ + Nat.count (fun k => R k = rho.val) (pairIndex q) / queryCap n D = j := by + simp only [chunkOf, chunk, Finset.mem_filter, mem_class wAB hp R hR, + rank_residueClass wAB hp R hR] + +/-- Every occupied native query slot names exactly one member of the existing residue class. +The readback premise is precisely the rank-indexed conclusion of `Classes.exec`; in particular, +this rules out unspecified or duplicate pairs below the computed count. -/ +theorem query_readback (rho : Fin p) (mem : ℕ → ℕ) (base : ℕ) + (hread : ∀ q < n * n, R q = rho.val → + mem (base + 2 * Nat.count (fun j => R j = rho.val) q) = q / n ∧ + mem (base + 2 * Nat.count (fun j => R j = rho.val) q + 1) = q % n) + (k : ℕ) (hk : k < (residueClass wAB p rho).card) : + ∃! q : Fin n × Fin n, q ∈ residueClass wAB p rho ∧ + rankIn (residueClass wAB p rho) q = k ∧ + mem (base + 2 * k) = q.1.val ∧ mem (base + 2 * k + 1) = q.2.val := by + rw [card_residueClass wAB hp R hR] at hk + let P (q : ℕ) := R q = rho.val + let t := Nat.nth P k + have ht : t < n * n := Nat.nth_lt_of_lt_count hk + have hfinite (hf : (Set.ofPred P).Finite) : k < hf.toFinset.card := + hk.trans_le (Nat.count_le_card hf (n * n)) + have htP : P t := Nat.nth_mem k hfinite + have hcount : Nat.count P t = k := Nat.count_nth hfinite + let q := finProdFinEquiv.symm (⟨t, ht⟩ : Fin (n * n)) + have hqindex : pairIndex q = t := by + rw [pairIndex_eq] + exact congrArg Fin.val (finProdFinEquiv.apply_symm_apply ⟨t, ht⟩) + have hq : q ∈ residueClass wAB p rho := (mem_class wAB hp R hR rho q).mpr (by + rw [hqindex] + exact htP) + have hrank : rankIn (residueClass wAB p rho) q = k := by + rw [rank_residueClass wAB hp R hR, hqindex] + exact hcount + have hcoords := hread t ht htP + change mem (base + 2 * Nat.count P t) = t / n ∧ + mem (base + 2 * Nat.count P t + 1) = t % n at hcoords + rw [hcount] at hcoords + have hn : 0 < n := Nat.pos_of_ne_zero (by intro h; simpa [h] using q.1.isLt) + have hrow : t / n = q.1.val := by + rw [← hqindex, pairIndex, Nat.mul_comm q.1.val n, Nat.mul_add_div hn, + Nat.div_eq_of_lt q.2.isLt, Nat.add_zero] + have hcol : t % n = q.2.val := by + rw [← hqindex, pairIndex, Nat.mul_add_mod_of_lt q.2.isLt] + refine ⟨q, ⟨hq, hrank, hcoords.1.trans hrow, hcoords.2.trans hcol⟩, ?_⟩ + intro r hr + exact rankIn_injOn _ hr.1 hq (hr.2.1.trans hrank.symm) + +/-- Every occupied position of a native chunk names exactly one member of the existing finite +chunk. The local position is translated to its actual global class rank; the scanner's readback +premise is unchanged, so no list or ordering certificate is assumed. -/ +theorem chunk_readback (D : ℕ) (rho : Fin p) (mem : ℕ → ℕ) (base : ℕ) + (hread : ∀ q < n * n, R q = rho.val → + mem (base + 2 * Nat.count (fun j => R j = rho.val) q) = q / n ∧ + mem (base + 2 * Nat.count (fun j => R j = rho.val) q + 1) = q % n) + (hcap : 0 < queryCap n D) (k : ℕ) + (hk : k < numChunks (residueClass wAB p rho) (queryCap n D)) + (r : ℕ) (hr : r < min (queryCap n D) + ((residueClass wAB p rho).card - k * queryCap n D)) : + ∃! q : Fin n × Fin n, q ∈ chunkOf wAB D p rho k ∧ + rankIn (residueClass wAB p rho) q = k * queryCap n D + r ∧ + mem (base + 2 * (k * queryCap n D + r)) = q.1.val ∧ + mem (base + 2 * (k * queryCap n D + r) + 1) = q.2.val := by + have hkcard : k * queryCap n D < (residueClass wAB p rho).card := by + by_contra h + have hle : (residueClass wAB p rho).card ≤ k * queryCap n D := by omega + have hceil := (Nat.ceilDiv_le_iff hcap).mpr hle + change k < (residueClass wAB p rho).card ⌈/⌉ queryCap n D at hk + omega + have hrCap : r < queryCap n D := hr.trans_le (min_le_left _ _) + have hrRem := hr.trans_le (min_le_right _ _) + have hglobal : k * queryCap n D + r < (residueClass wAB p rho).card := by omega + obtain ⟨q, hq, huniq⟩ := query_readback wAB hp R hR rho mem base hread _ hglobal + have hdiv : (k * queryCap n D + r) / queryCap n D = k := by + rw [Nat.mul_comm k (queryCap n D), Nat.mul_add_div hcap, + Nat.div_eq_of_lt hrCap, Nat.add_zero] + have hchunk : q ∈ chunkOf wAB D p rho k := by + rw [mem_chunkOf wAB hp R hR] + refine ⟨(mem_class wAB hp R hR rho q).mp hq.1, ?_⟩ + rw [← rank_residueClass wAB hp R hR, hq.2.1, hdiv] + refine ⟨q, ⟨hchunk, hq.2⟩, ?_⟩ + intro q' hq' + apply huniq q' + exact ⟨(mem_class wAB hp R hR rho q').mpr + ((mem_chunkOf wAB hp R hR D rho k q').mp hq'.1).1, hq'.2⟩ + +end Cslib.Algorithms.WordRAM.ExactTriangle.QuerySpec diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/ResidueClasses.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/ResidueClasses.lean new file mode 100644 index 0000000000..266ed93bf2 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/ResidueClasses.lean @@ -0,0 +1,359 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.ExactTriangle.Classes +public import Complexity.Algorithms.WordRAM.ThreeSum.ChanHe.Residues +public import Complexity.Computability.WordRAM.Compile +public import Mathlib.Data.List.GetD + +/-! +# Initialized signed weights to native residue classes + +The flat input is an unsigned `(n,p)` header followed by the signed encodings of the three +`n×n` weight matrices. Native arithmetic allocates a residue array, class counts and disjoint +query blocks. The existing signed residue kernel processes every weight before the class scanner +reads the A–B residues. Neither residues nor class/layout tables are input certificates. + +## Main definitions + +* `ResidueClasses.input`: the mixed unsigned-header/signed-weight encoding. +* `ResidueClasses.programAt`: the same preparation at a fixed caller header offset. +* `ResidueClasses.program`: fixed thirty-two-register native preparation. +* `ResidueClasses.addressBound`: explicit arithmetic and allocation bound. + +## Main results + +* `ResidueClasses.exec_at`: callable execution from a fixed caller header. +* `ResidueClasses.exec_input`: initialized execution, residues, class counts, queries and frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.ResidueClasses + +open Cslib.WordRAM + +open Cslib.Algorithms.WordRAM.ThreeSum.ChanHe + +/-- Header cursor and class-scanner source register. -/ +abbrev temp : Fin 32 := 0 +/-- Class-scanner side register. -/ +abbrev scanSize : Fin 32 := 2 +/-- Signed-residue source register. -/ +abbrev weightSrc : Fin 32 := 9 +/-- Signed-residue destination register. -/ +abbrev weightDst : Fin 32 := 10 +/-- Signed-residue length register. -/ +abbrev weightLen : Fin 32 := 11 +/-- Signed modulus and subsequent class-count pointer register. -/ +abbrev encodedMod : Fin 32 := 12 +/-- Class-scanner query pointer register. -/ +abbrev scanQueries : Fin 32 := 13 +/-- Class-scanner modulus register. -/ +abbrev scanModulus : Fin 32 := 14 + +/-- Unsigned side length. -/ +abbrev size : Fin 32 := 24 +/-- Unsigned positive modulus. -/ +abbrev modulus : Fin 32 := 25 +/-- Start of the signed weights. -/ +abbrev weights : Fin 32 := 26 +/-- Start of all three unsigned residue matrices. -/ +abbrev residues : Fin 32 := 27 +/-- Start of the computed class-count array. -/ +abbrev counts : Fin 32 := 28 +/-- Start of the class query blocks. -/ +abbrev queries : Fin 32 := 29 +/-- Area of one weight matrix. -/ +abbrev area : Fin 32 := 30 +/-- Constant one, preserved by both callees. -/ +abbrev one : Fin 32 := 31 + +/-- Header followed by the three signed weight matrices in A–B, B–C, A–C order. -/ +def input (n p : ℕ) (values : List ℤ) : List ℕ := [n, p] ++ values.map Signed.encode + +/-- A fitting value covering every allocation endpoint and temporary for a caller header. +The adjacent header cells contain `n,p`, followed by the three signed matrices. -/ +def addressBoundAt (header n p : ℕ) : ℕ := + header + 2 + 6 * (n * n) + p + 2 * (n * n) * p + 4 + +/-- The standard input places its `n,p` header immediately after the machine's length cell. -/ +def addressBound (n p : ℕ) : ℕ := addressBoundAt 1 n p + +private abbrev bank (r : ℕ) (hr : r ≤ 24) : Fin r → Fin 32 := Fin.castLE (by omega) + +private theorem bank_injective (r : ℕ) (hr : r ≤ 24) : (bank r hr).Injective := by + intro i j h + exact Fin.ext (congrArg (fun k : Fin 32 => k.val) h) + +/-- The existing signed residue conversion, including unsigned decoding of each remainder. -/ +def reduce : Code .standard 32 := Residues.program.rename (Fin.castLE (by decide)) + +/-- Reuse the complete native class scanner. -/ +def classify : Code .standard 32 := Classes.program.rename (Fin.castLE (by decide)) + +/-- Read a fixed caller header and compute all array bases, lengths and the encoded modulus. +The offset is a code parameter; the complete ET caller uses one fixed input format. -/ +def setupAt (header : ℕ) : Code .standard 32 := ram[ + one ← #1; temp ← #header; size ← *temp; temp ← temp + one; modulus ← *temp; + weights ← #(header + 2); area ← size * size; weightLen ← area + area; + weightLen ← weightLen + area; + weightSrc ← weights; weightDst ← weights + weightLen; residues ← weightDst; + counts ← residues + weightLen; queries ← counts + modulus; encodedMod ← modulus + modulus ] + +/-- Read the standard initialized input header. -/ +def setup : Code .standard 32 := setupAt 1 + +/-- Deliver the five actual scanner arguments after residue conversion. -/ +def deliver : Code .standard 32 := ram[ + temp ← residues; scanSize ← size; encodedMod ← counts; + scanQueries ← queries; scanModulus ← modulus ] + +/-- Native setup, reduction and enumeration for a fixed caller header. -/ +def programAt (header : ℕ) : Code .standard 32 := + .seq (setupAt header) (.seq reduce (.seq deliver classify)) + +/-- All setup, reduction and enumeration is native code. -/ +def program : Code .standard 32 := programAt 1 + +/-- Changing the fixed header offset changes constants but not the instruction graph. -/ +theorem programAt_fixed (header : ℕ) : + (programAt header).size ≤ 160 ∧ ¬(programAt header).ReadsWidth := by + change (programAt 0).size ≤ 160 ∧ ¬(programAt 0).ReadsWidth + decide + +/-- Fixed instruction graph, independent of dimensions and physical width. -/ +theorem program_fixed : program.size ≤ 160 ∧ ¬program.ReadsWidth := by decide + +private theorem reduce_preserves : ∀ i : Fin 32, 24 ≤ i.val → reduce.Preserves i := by decide + +private theorem classify_preserves : ∀ i : Fin 32, 24 ≤ i.val → classify.Preserves i := by decide + +private theorem input_weight (n p : ℕ) (values : List ℤ) (q : ℕ) : + (Data.init (input n p values) : Data 32).mem (3 + q) = Signed.encode (values.getD q 0) := by + rw [show 3 + q = (q + 2) + 1 by omega, Data.init_mem_succ] + simp only [input, List.getD_eq_getElem?_getD, List.getElem?_cons_succ, + List.cons_append, List.nil_append, List.getElem?_map] + simpa only [List.getD_eq_getElem?_getD, List.getElem?_map, + Signed.encode, Int.natAbs_zero, lt_self_iff_false, + ↓reduceIte, Nat.mul_zero, Nat.add_zero] using (List.getD_map values (n := q) Signed.encode + (d := (0 : ℤ))) + +/-- Callable residue/class preparation from a concrete caller header and signed matrix words. +The fixed offset selects the adjacent `n,p` cells; no initialized-memory shape is assumed outside +that payload. The cost and output frame include every setup, signed reduction and class scan. -/ +theorem exec_at {w : ℕ} (header n p : ℕ) (values : ℕ → ℤ) (initial : Data 32) + (hnHeader : initial.mem header = n) (hpHeader : initial.mem (header + 1) = p) + (hinput : ∀ q < 3 * (n * n), + initial.mem (header + 2 + q) = Signed.encode (values q)) + (hp : 0 < p) (hmod : GenericRAM.Signed.Fits w (p : ℤ)) + (hvalues : ∀ q < 3 * (n * n), GenericRAM.Signed.Fits w (values q)) + (haddress : Fits (w + 2) (addressBoundAt header n p)) : + let base := header + 2 + ∃ y, ExecWithin (w + 2) (programAt header) initial y + (69 * (n * n) + p * (14 * (n * n) + 14) + 30) ∧ + y.regs residues = base + 3 * (n * n) ∧ y.regs counts = base + 6 * (n * n) ∧ + y.regs queries = base + 6 * (n * n) + p ∧ + (∀ q < 3 * (n * n), y.mem (base + 3 * (n * n) + q) = + (values q % (p : ℤ)).toNat) ∧ + (∀ j < p, y.mem (base + 6 * (n * n) + j) = + Nat.count (fun q => (values q % (p : ℤ)).toNat = j) (n * n)) ∧ + (∀ j < p, ∀ q < n * n, (values q % (p : ℤ)).toNat = j → + y.mem (base + 6 * (n * n) + p + 2 * (n * n) * j + + 2 * Nat.count (fun k => (values k % (p : ℤ)).toNat = j) q) = q / n ∧ + y.mem (base + 6 * (n * n) + p + 2 * (n * n) * j + + 2 * Nat.count (fun k => (values k % (p : ℤ)).toNat = j) q + 1) = q % n) ∧ + (∀ a, a < base + 3 * (n * n) ∨ + base + 6 * (n * n) + p + 2 * (n * n) * p ≤ a → + y.mem a = initial.mem a) := by + let N := n * n + let base := header + 2 + let R (q : ℕ) := (values q % (p : ℤ)).toNat + have h1 : Fits (w + 2) 1 := haddress.of_le (by simp only [addressBoundAt]; omega) + have hhead : Fits (w + 2) header := haddress.of_le + (by simp only [addressBoundAt]; omega) + have hhead1 : Fits (w + 2) (header + 1) := haddress.of_le + (by simp only [addressBoundAt]; omega) + have hbase : Fits (w + 2) base := haddress.of_le + (by simp only [addressBoundAt]; dsimp only [base]; omega) + have hN : Fits (w + 2) N := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have h2N : Fits (w + 2) (N + N) := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have h3N : Fits (w + 2) (N + N + N) := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have hres : Fits (w + 2) (base + (N + N + N)) := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have hcounts : Fits (w + 2) (base + (N + N + N) + (N + N + N)) := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have hqueries : Fits (w + 2) (base + (N + N + N) + (N + N + N) + p) := haddress.of_le + (by simp only [addressBoundAt]; dsimp [N, base]; omega) + have hmodfit : Fits (w + 2) (p + p) := by + have h := (Signed.encode_fits hmod).mono (show w + 1 ≤ w + 2 by omega) + simpa [Signed.encode, two_mul, show ¬(p : ℤ) < 0 by omega] using h + have hsizeFit : Fits (w + 2) n := hN.of_le + (by simpa only [N, pow_two] using Nat.le_self_pow two_ne_zero n) + have hpFit : Fits (w + 2) p := hmodfit.of_le (by omega) + let a := initial.set one 1 |>.set 0 header |>.set size n |>.set 0 (header + 1) |>.set modulus p + |>.set weights base |>.set area N |>.set 11 (N + N) |>.set 11 (N + N + N) + |>.set 9 base |>.set 10 (base + (N + N + N)) |>.set residues (base + (N + N + N)) + |>.set counts (base + (N + N + N) + (N + N + N)) + |>.set queries (base + (N + N + N) + (N + N + N) + p) |>.set 12 (p + p) + have hsetup : ExecWithin (w + 2) (setupAt header) initial a 15 := by + dsimp [N, base] at hN h2N h3N hres hcounts hqueries + ram_eval [setupAt, a, N, base, h1, hhead, hhead1, hbase, hN, h2N, h3N, hres, + hcounts, hqueries, hmodfit, hsizeFit, hpFit, + one, size, modulus, weights, area, residues, counts, queries, + temp, weightLen, weightSrc, weightDst, encodedMod, hnHeader, hpHeader, Fin.mk.injEq] + have ha9 : (a.comap (bank 16 (by decide))).regs Residues.src = base := by + simp +decide [a, bank, Residues.src, one, size, modulus, weights, area, residues, + counts, queries] + have ha10 : (a.comap (bank 16 (by decide))).regs Residues.dst = base + 3 * N := by + simp +decide [a, bank, Residues.dst, one, size, modulus, weights, area, residues, + counts, queries] + omega + have ha11 : (a.comap (bank 16 (by decide))).regs Residues.len = 3 * N := by + simp +decide [a, bank, Residues.len, one, size, modulus, weights, area, residues, + counts, queries] + omega + have ha12 : (a.comap (bank 16 (by decide))).regs Residues.modulus = Signed.encode (p : ℤ) := by + simp +decide [a, bank, Residues.modulus, one, size, modulus, weights, area, residues, + counts, queries, Signed.encode, two_mul, show ¬(p : ℤ) < 0 by omega] + obtain ⟨v, hv, hvm, -⟩ := Residues.exec (a.comap (bank 16 (by decide))) + (fun q => values q) hp hmod ha12 + (by intro q hq; apply hvalues q; simpa only [ha11, N] using hq) + (by rw [ha9, ha11]; exact haddress.of_le (by dsimp [addressBoundAt, N, base]; omega)) + (by rw [ha10, ha11]; exact haddress.of_le (by dsimp [addressBoundAt, N, base]; omega)) + (by rw [ha9, ha10, ha11]) + (by intro q hq; rw [ha9]; exact hinput q (by simpa only [ha11, N] using hq)) + let v' := v.extend (bank 16 (by decide)) a + have hv' : ExecWithin (w + 2) reduce a v' (69 * N + 4) := by + simpa only [reduce, v', ha11, ← Nat.mul_assoc, Nat.reduceMul] using + hv.rename (bank_injective 16 (by decide)) + have hvreg (i : Fin 32) (hi : 24 ≤ i.val) : v'.regs i = a.regs i := + hv'.regs_eq (reduce_preserves i hi) + have hvn : v'.regs size = n := by simpa +decide [a, size, modulus, weights, area, + residues, counts, queries, one] using hvreg size (by decide) + have hvp : v'.regs modulus = p := by simpa +decide [a, size, modulus, weights, area, + residues, counts, queries, one] using hvreg modulus (by decide) + have hvr : v'.regs residues = base + 3 * N := by + have h := hvreg residues (by decide) + simp +decide only [a, Data.set_regs, Function.update_apply, residues, counts, queries, + ↓reduceIte] at h + change v'.regs 27 = base + 3 * N + omega + have hvc : v'.regs counts = base + 6 * N := by + have h := hvreg counts (by decide) + simp +decide only [a, Data.set_regs, Function.update_apply, counts, queries, ↓reduceIte] at h + change v'.regs 28 = base + 6 * N + omega + have hvq : v'.regs queries = base + 6 * N + p := by + have h := hvreg queries (by decide) + simp +decide only [a, Data.set_regs, Function.update_apply, queries, ↓reduceIte] at h + change v'.regs 29 = base + 6 * N + p + omega + let b := v'.set 0 (base + 3 * N) |>.set 2 n |>.set 12 (base + 6 * N) + |>.set 13 (base + 6 * N + p) |>.set 14 p + have hdeliver : ExecWithin (w + 2) deliver v' b 5 := by + ram_eval [deliver, b, hvn, hvp, hvr, hvc, hvq, residues, size, counts, queries, + modulus, Fin.mk.injEq] + have hb0 : (b.comap (bank 20 (by decide))).regs Classes.src = base + 3 * N := by + simp +decide [b, bank, Classes.src] + have hb2 : (b.comap (bank 20 (by decide))).regs Classes.size = n := by + simp +decide [b, bank, Classes.size] + have hb12 : (b.comap (bank 20 (by decide))).regs Classes.counts = base + 6 * N := by + simp +decide [b, bank, Classes.counts] + have hb13 : (b.comap (bank 20 (by decide))).regs Classes.queries = base + 6 * N + p := by + simp +decide [b, bank, Classes.queries] + have hb14 : (b.comap (bank 20 (by decide))).regs Classes.modulus = p := by + simp +decide [b, bank, Classes.modulus] + have hresmem (q : ℕ) (hq : q < 3 * N) : v.mem (base + 3 * N + q) = R q := by + rw [hvm, ha10, ha11] + simp only [Residues.memory] + split_ifs with h + swap + · omega + rw [show base + 3 * N + q - (base + 3 * N) = q by omega] + obtain ⟨z, hz, -, hzcounts, hzpairs, hzframe⟩ := Classes.exec + (b.comap (bank 20 (by decide))) R h1 + (by intro q hq; rw [hb0]; exact hresmem q (by rw [hb2] at hq; dsimp [N, base] at *; omega)) + (by rw [hb0, hb2, hb12]; dsimp [N, base]; omega) + (by rw [hb12, hb14, hb13]) + (by rw [hb12, hb14]; exact haddress.of_le (by dsimp [addressBoundAt, N, base]; omega)) + (by rw [hb13, hb2, hb14]; exact haddress.of_le (by dsimp [addressBoundAt, N, base]; omega)) + (haddress.of_le (by rw [hb2]; dsimp [addressBoundAt, N, base]; omega)) + let y := z.extend (bank 20 (by decide)) b + have hz' : ExecWithin (w + 2) classify b y (p * (14 * N + 14) + 6) := by + simpa only [hb2, hb14, N, classify, y] using hz.rename (bank_injective 20 (by decide)) + have hyreg (i : Fin 32) (hi : 24 ≤ i.val) : y.regs i = v'.regs i := by + have h := hz'.regs_eq (classify_preserves i hi) + exact h.trans (by + have h0 : i ≠ 0 := by intro heq; subst i; change 24 ≤ 0 at hi; omega + have h2 : i ≠ 2 := by intro heq; subst i; change 24 ≤ 2 at hi; omega + have h12 : i ≠ 12 := by intro heq; subst i; change 24 ≤ 12 at hi; omega + have h13 : i ≠ 13 := by intro heq; subst i; change 24 ≤ 13 at hi; omega + have h14 : i ≠ 14 := by intro heq; subst i; change 24 ≤ 14 at hi; omega + simp only [b, Data.set_regs, Function.update_of_ne h0, Function.update_of_ne h2, + Function.update_of_ne h12, Function.update_of_ne h13, Function.update_of_ne h14]) + simp only [hb2, hb12, hb13, hb14] at hzcounts hzpairs hzframe + refine ⟨y, ?_, (hyreg residues (by decide)).trans hvr, + (hyreg counts (by decide)).trans hvc, (hyreg queries (by decide)).trans hvq, + ?_, hzcounts, hzpairs, ?_⟩ + · convert hsetup.seq (hv'.seq (hdeliver.seq hz')) using 1 + · rfl + · dsimp [N, base] + omega + · intro q hq + change z.mem (base + 3 * N + q) = R q + rw [hzframe _ (by omega) (by omega)] + exact hresmem q hq + · intro a ha + change z.mem a = initial.mem a + rw [hzframe a (by rcases ha with h | h <;> omega) (by + rcases ha with h | h + · omega + · exact not_lt_of_ge h ∘ And.right)] + change v.mem a = initial.mem a + rw [hvm, ha10, ha11] + simp only [Residues.memory, show ¬(base + 3 * N ≤ a ∧ a < base + 3 * N + 3 * N) by + rcases ha with h | h <;> omega, ↓reduceIte] + rfl + +/-- Read the input header, compute all three residue matrices and enumerate the A–B queries. +The signed width is `w`, and execution uses the existing signed compiler's `w+2` unsigned width. +The cost includes every header, reduction, argument-delivery and class-scan instruction; the +final halt is excluded. This subroutine does not choose a prime or solve an instance. -/ +theorem exec_input {w : ℕ} (n p : ℕ) (values : List ℤ) + (hlen : values.length = 3 * (n * n)) (hp : 0 < p) + (hmod : GenericRAM.Signed.Fits w (p : ℤ)) + (hvalues : ∀ q < values.length, GenericRAM.Signed.Fits w (values.getD q 0)) + (haddress : Fits (w + 2) (addressBound n p)) : + ∃ y, ExecWithin (w + 2) program (.init (input n p values)) y + (69 * (n * n) + p * (14 * (n * n) + 14) + 30) ∧ + y.regs residues = 3 + 3 * (n * n) ∧ y.regs counts = 3 + 6 * (n * n) ∧ + y.regs queries = 3 + 6 * (n * n) + p ∧ + (∀ q < 3 * (n * n), y.mem (3 + 3 * (n * n) + q) = + (values.getD q 0 % (p : ℤ)).toNat) ∧ + (∀ j < p, y.mem (3 + 6 * (n * n) + j) = + Nat.count (fun q => (values.getD q 0 % (p : ℤ)).toNat = j) (n * n)) ∧ + (∀ j < p, ∀ q < n * n, (values.getD q 0 % (p : ℤ)).toNat = j → + y.mem (3 + 6 * (n * n) + p + 2 * (n * n) * j + + 2 * Nat.count (fun k => (values.getD k 0 % (p : ℤ)).toNat = j) q) = q / n ∧ + y.mem (3 + 6 * (n * n) + p + 2 * (n * n) * j + + 2 * Nat.count (fun k => (values.getD k 0 % (p : ℤ)).toNat = j) q + 1) = q % n) ∧ + (∀ a, a < 3 + 3 * (n * n) ∨ + 3 + 6 * (n * n) + p + 2 * (n * n) * p ≤ a → + y.mem a = (Data.init (input n p values) : Data 32).mem a) := by + have h := exec_at (w := w) 1 n p (fun q => values.getD q 0) (.init (input n p values)) + (by simp [input, Data.init]) (by simp [input, Data.init]) + (fun q _ => input_weight n p values q) hp hmod + (by intro q hq; exact hvalues q (by simpa only [hlen] using hq)) haddress + simpa only [program, Nat.reduceAdd] using h + +end Cslib.Algorithms.WordRAM.ExactTriangle.ResidueClasses diff --git a/Complexity/Algorithms/WordRAM/ExactTriangle/WriteMatrices.lean b/Complexity/Algorithms/WordRAM/ExactTriangle/WriteMatrices.lean new file mode 100644 index 0000000000..6ee0235e5d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/ExactTriangle/WriteMatrices.lean @@ -0,0 +1,428 @@ +/- +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.Computability.WordRAM.Allocation +public import Complexity.Computability.WordRAM.Notation + +/-! +# Native zero/one matrices for an Exact Triangle instance + +The input contains ordinary unsigned residues. Each output cell computes its row, middle vertex +and label through charged division/remainder instructions. Cells outside the selected piece are +zero; all other cells test the required modular adjacency. The same seventeen registers serve +the left `n×D` and right `D×n` matrices, with no supplied indexing table. + +The X/Y output entries are raw unsigned words `0` and `1`. A consumer using canonical +`Signed.encode` matrix entries must double each entry while copying it into that input and +charge the conversion instruction. Scalar headers, pointers and query coordinates remain raw +unsigned words. + +This directly adapts the adjacency formulas in the pinned +[Theorem 17 instance builder](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec3/Theorem17/Instances/WriteMatrices.lean). +The native implementation visits each output cell once; the source clears and then marks cells. + +## Main definitions + +* `WriteMatrices.Args`: matrix dimensions, residue/piece parameters and memory blocks. +* `WriteMatrices.entry`: the zero/one value at a flat output position. +* `WriteMatrices.program`: fixed native left/right matrix builders. + +## Main results + +* `WriteMatrices.exec`: complete charged execution and the output/input frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.ExactTriangle.WriteMatrices + +open Cslib.WordRAM + +/-- Runtime arguments of one matrix-building call. -/ +structure Args where + /-- Residue matrix base. -/ + src : ℕ + /-- Output matrix base. -/ + dst : ℕ + /-- Number of vertices in each outer part. -/ + n : ℕ + /-- Padded middle dimension. -/ + D : ℕ + /-- Positive modulus. -/ + p : ℕ + /-- First vertex of the selected piece. -/ + first : ℕ + /-- Number of vertices in the piece. -/ + length : ℕ + /-- A–B query residue. -/ + rho : ℕ + +/-- Raw unsigned zero/one row-major adjacency. A true side chooses the right `D×n` matrix; +false chooses the left `n×D` matrix. The source residue matrix has shape `n×n`. A signed-matrix +consumer encodes these entries by doubling them. -/ +def entry (rightSide : Bool) (A : Args) (R : ℕ → ℕ) (q : ℕ) : ℕ := + let line := if rightSide then q % A.n else q / A.D + let middle := if rightSide then q / A.n else q % A.D + let label := middle % A.p + let vertex := middle / A.p + if vertex < A.length then + let r := R (line * A.n + A.first + vertex) + let wanted := if rightSide then (A.p - r) % A.p else (r + A.rho) % A.p + if label = wanted then 1 else 0 + else 0 + +/-- Residue input pointer. -/ +abbrev src : Fin 17 := 0 +/-- Output matrix pointer. -/ +abbrev dst : Fin 17 := 1 +/-- Outer dimension. -/ +abbrev size : Fin 17 := 2 +/-- Middle dimension. -/ +abbrev dimension : Fin 17 := 3 +/-- Modulus. -/ +abbrev modulus : Fin 17 := 4 +/-- First vertex of the piece. -/ +abbrev first : Fin 17 := 5 +/-- Piece length. -/ +abbrev length : Fin 17 := 6 +/-- Query residue. -/ +abbrev rho : Fin 17 := 7 +/-- Flat output cursor. -/ +abbrev idx : Fin 17 := 8 +/-- Number of output cells. -/ +abbrev area : Fin 17 := 9 +/-- Selected source row. -/ +abbrev line : Fin 17 := 10 +/-- Selected vertex within the piece. -/ +abbrev vertex : Fin 17 := 11 +/-- Middle label. -/ +abbrev label : Fin 17 := 12 +/-- Address temporary. -/ +abbrev addr : Fin 17 := 13 +/-- Residue and output value temporary. -/ +abbrev value : Fin 17 := 14 +/-- Constant zero. -/ +abbrev zero : Fin 17 := 15 +/-- Constant one. -/ +abbrev one : Fin 17 := 16 + +/-- The eight runtime arguments occupy registers 0–7. -/ +structure Entry (A : Args) (x : Data 17) : Prop where + /-- Source pointer. -/ + src : x.regs src = A.src + /-- Destination pointer. -/ + dst : x.regs dst = A.dst + /-- Outer dimension. -/ + size : x.regs size = A.n + /-- Middle dimension. -/ + dimension : x.regs dimension = A.D + /-- Modulus. -/ + modulus : x.regs modulus = A.p + /-- First vertex. -/ + first : x.regs first = A.first + /-- Piece length. -/ + length : x.regs length = A.length + /-- Query residue. -/ + rho : x.regs rho = A.rho + +/-- Compute the source row and decode the middle label using actual arithmetic instructions. -/ +def indices (rightSide : Bool) : Code .standard 17 := + if rightSide then ram[ + line ← idx % size; vertex ← idx / size; label ← vertex % modulus; + vertex ← vertex / modulus ] + else ram[ + line ← idx / dimension; vertex ← idx % dimension; label ← vertex % modulus; + vertex ← vertex / modulus ] + +/-- Read the selected residue and form its required label. -/ +def readLabel (rightSide : Bool) : Code .standard 17 := + .seq (ram[ addr ← line * size; addr ← addr + first; addr ← addr + vertex; + addr ← src + addr; value ← *addr ]) + (if rightSide then ram[ value ← modulus - value; value ← value % modulus ] + else ram[ value ← value + rho; value ← value % modulus ]) + +/-- Write one zero/one cell and increment the flat cursor. -/ +def body (rightSide : Bool) : Code .standard 17 := + .seq (indices rightSide) + (.seq (.branch (.lt vertex length) + (.seq (readLabel rightSide) + (.branch (.eq label value) (.op (.copy value one)) (.op (.copy value zero)))) + (.op (.copy value zero))) + (ram[ addr ← dst + idx; *addr ← value; idx ← idx + one ])) + +/-- The complete builder initializes constants and visits all output positions once. -/ +def program (rightSide : Bool) : Code .standard 17 := + .seq (ram[ zero ← #0; one ← #1; idx ← #0; area ← size * dimension ]) + (.loop (.lt idx area) (body rightSide)) + +/-- The instruction graph has fixed size and never reads the physical word width. -/ +theorem program_fixed (rightSide : Bool) : + (program rightSide).size ≤ 64 ∧ ¬(program rightSide).ReadsWidth := by + cases rightSide <;> decide + +private theorem entry_of_regs {A : Args} {x y : Data 17} (hx : Entry A x) + (h : ∀ i : Fin 17, i.val < 8 → y.regs i = x.regs i) : Entry A y := + ⟨(h src (by decide)).trans hx.src, (h dst (by decide)).trans hx.dst, + (h size (by decide)).trans hx.size, (h dimension (by decide)).trans hx.dimension, + (h modulus (by decide)).trans hx.modulus, (h first (by decide)).trans hx.first, + (h length (by decide)).trans hx.length, (h rho (by decide)).trans hx.rho⟩ + +private theorem exec_indices {w q : ℕ} (side : Bool) (A : Args) (x : Data 17) + (hx : Entry A x) (hq : x.regs idx = q) (hfit : Fits w q) : + ∃ y, ExecWithin w (indices side) x y 4 ∧ Entry A y ∧ + y.regs idx = q ∧ y.regs line = (if side then q % A.n else q / A.D) ∧ + y.regs vertex = (if side then q / A.n else q % A.D) / A.p ∧ + y.regs label = (if side then q / A.n else q % A.D) % A.p ∧ + y.regs one = x.regs one ∧ y.regs zero = x.regs zero ∧ y.mem = x.mem := by + have hdn : Fits w (q / A.n) := hfit.of_le (Nat.div_le_self ..) + have hmn : Fits w (q % A.n) := hfit.of_le (Nat.mod_le ..) + have hdd : Fits w (q / A.D) := hfit.of_le (Nat.div_le_self ..) + have hmd : Fits w (q % A.D) := hfit.of_le (Nat.mod_le ..) + have hdnp : Fits w (q / A.n / A.p) := hdn.of_le (Nat.div_le_self ..) + have hmnp : Fits w (q / A.n % A.p) := hdn.of_le (Nat.mod_le ..) + have hmdp : Fits w (q % A.D / A.p) := hmd.of_le (Nat.div_le_self ..) + have hmmp : Fits w (q % A.D % A.p) := hmd.of_le (Nat.mod_le ..) + let rowValue := if side then q % A.n else q / A.D + let middleValue := if side then q / A.n else q % A.D + let y := x.set line rowValue |>.set vertex middleValue + |>.set label (middleValue % A.p) |>.set vertex (middleValue / A.p) + refine ⟨y, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, rfl⟩ + · cases side <;> + ram_eval [indices, y, rowValue, middleValue, hq, hx.size, hx.dimension, hx.modulus, + hdn, hmn, hdd, hmd, hdnp, hmnp, hmdp, hmmp, line, vertex, label, idx, size, + dimension, modulus, Fin.mk.injEq] + · apply entry_of_regs hx + intro i hi + have hl : i ≠ line := by intro h; subst i; change 10 < 8 at hi; omega + have hv : i ≠ vertex := by intro h; subst i; change 11 < 8 at hi; omega + have hb : i ≠ label := by intro h; subst i; change 12 < 8 at hi; omega + simp only [y, Data.set_regs, Function.update_of_ne hl, Function.update_of_ne hv, + Function.update_of_ne hb] + · simp +decide [y, idx, line, vertex, label, hq] + · simp +decide [y, rowValue, line, vertex, label] + · simp [y, middleValue] + · simp +decide [y, middleValue, line, vertex, label] + · simp +decide [y, one, line, vertex, label] + · simp +decide [y, zero, line, vertex, label] + +private theorem exec_readLabel {w row v : ℕ} (side : Bool) (A : Args) (R : ℕ → ℕ) + (x : Data 17) (hx : Entry A x) + (hrow : x.regs line = row) (hv : x.regs vertex = v) + (hpoint : row * A.n + A.first + v < A.n * A.n) + (hR : ∀ i < A.n * A.n, R i < A.p) + (hinput : ∀ i < A.n * A.n, x.mem (A.src + i) = R i) + (hs : Fits w (A.src + A.n * A.n)) (hp : Fits w (2 * A.p)) (hrho : A.rho < A.p) : + ∃ y, ExecWithin w (readLabel side) x y 7 ∧ + y.regs value = (if side then (A.p - R (row * A.n + A.first + v)) % A.p + else (R (row * A.n + A.first + v) + A.rho) % A.p) ∧ + (∀ i, i ≠ addr → i ≠ value → y.regs i = x.regs i) ∧ y.mem = x.mem := by + let pos := row * A.n + A.first + v + have hpos : pos < A.n * A.n := hpoint + have hkey := hR pos hpos + have hr := hinput pos hpos + have hmul : Fits w (row * A.n) := hs.of_le (by dsimp [pos] at hpos; omega) + have hfirst : Fits w (row * A.n + A.first) := hs.of_le (by dsimp [pos] at hpos; omega) + have hindex : Fits w pos := hs.of_le (by omega) + have haddr : Fits w (A.src + pos) := hs.of_le (by omega) + have hsub : Fits w (A.p - R pos) := hp.of_le (by omega) + have hadd : Fits w (R pos + A.rho) := hp.of_le (by omega) + have hmodsub : Fits w ((A.p - R pos) % A.p) := hsub.of_le (Nat.mod_le ..) + have hmodadd : Fits w ((R pos + A.rho) % A.p) := hadd.of_le (Nat.mod_le ..) + let z := x.set addr (row * A.n) |>.set addr (row * A.n + A.first) + |>.set addr pos |>.set addr (A.src + pos) |>.set value (R pos) + let t := if side then A.p - R pos else R pos + A.rho + let y := z.set value t |>.set value (t % A.p) + refine ⟨y, ?_, ?_, ?_, rfl⟩ + · cases side <;> + ram_eval [readLabel, z, t, y, hrow, hv, hx.size, hx.first, hx.src, hx.modulus, hx.rho, + hmul, hfirst, hindex, haddr, hsub, hadd, hmodsub, hmodadd, hr, pos, line, vertex, + size, first, src, modulus, rho, addr, value, Fin.mk.injEq] + · cases side <;> simp [y, t, pos] + · intro i hi hv + simp only [y, z, Data.set_regs, Function.update_of_ne hi, Function.update_of_ne hv] + +private theorem body_preserves (side : Bool) : ∀ i : Fin 17, + (i.val < 8 ∨ i = area ∨ i = one ∨ i = zero) → (body side).Preserves i := by + cases side <;> decide + +private theorem exec_body {w q : ℕ} (side : Bool) (A : Args) (R : ℕ → ℕ) + (x : Data 17) (hx : Entry A x) (hq : x.regs idx = q) + (ho : x.regs one = 1) (hz : x.regs zero = 0) (hlt : q < A.n * A.D) + (hrho : A.rho < A.p) (hpiece : A.first + A.length ≤ A.n) + (hR : ∀ i < A.n * A.n, R i < A.p) + (hinput : ∀ i < A.n * A.n, x.mem (A.src + i) = R i) + (hsrc : Fits w (A.src + A.n * A.n)) (hdst : Fits w (A.dst + A.n * A.D)) + (hmod : Fits w (2 * A.p)) : + ∃ y, ExecWithin w (body side) x y 18 ∧ y.regs idx = q + 1 ∧ + y.mem = Function.update x.mem (A.dst + q) (entry side A R q) := by + have hqfit : Fits w q := hdst.of_le (by omega) + obtain ⟨b, hb, hba, hbi, hbl, hbv, hbb, hbo, hbz, hbm⟩ := + exec_indices side A x hx hq hqfit + let row := if side then q % A.n else q / A.D + let mid := if side then q / A.n else q % A.D + let v := mid / A.p + let σ := mid % A.p + let gate : Code .standard 17 := .branch (.lt vertex length) + (.seq (readLabel side) + (.branch (.eq label value) (.op (.copy value one)) (.op (.copy value zero)))) + (.op (.copy value zero)) + have hgate : ∃ z, ExecWithin w gate b z 11 ∧ z.regs value = entry side A R q ∧ + z.regs idx = q ∧ z.regs one = 1 ∧ z.mem = x.mem := by + by_cases hv : v < A.length + · have hn : 0 < A.n := by + by_contra h + have hn : A.n = 0 := by omega + simp only [hn, Nat.zero_mul] at hlt + omega + have hD : 0 < A.D := by + by_contra h + have hD : A.D = 0 := by omega + simp only [hD, Nat.mul_zero] at hlt + omega + have hrow : row < A.n := by + cases side + · exact (Nat.div_lt_iff_lt_mul hD).mpr hlt + · exact Nat.mod_lt _ hn + have hpoint : row * A.n + A.first + v < A.n * A.n := by + have hb := Nat.mul_le_mul_right A.n (Nat.succ_le_of_lt hrow) + rw [Nat.succ_mul] at hb + omega + obtain ⟨c, hc, hcv, hcr, hcm⟩ := exec_readLabel side A R b hba hbl hbv hpoint hR + (by simpa only [hbm] using hinput) hsrc hmod hrho + let wanted := if side then (A.p - R (row * A.n + A.first + v)) % A.p + else (R (row * A.n + A.first + v) + A.rho) % A.p + have hcl : c.regs label = σ := (hcr label (by decide) (by decide)).trans hbb + have hci : c.regs idx = q := (hcr idx (by decide) (by decide)).trans hbi + have hco : c.regs one = 1 := (hcr one (by decide) (by decide)).trans (hbo.trans ho) + have hcz : c.regs zero = 0 := (hcr zero (by decide) (by decide)).trans (hbz.trans hz) + let z := c.set value (if σ = wanted then 1 else 0) + have hchoose : ExecWithin (B := .standard) w + (.branch (.eq label value) (.op (.copy value one)) (.op (.copy value zero))) c z 3 := by + by_cases h : σ = wanted <;> + ram_eval [z, hcl, hcv, hco, hcz, h, wanted, row, v, mid, σ, label, value, one, + zero, Fin.mk.injEq] + have hcond : (Cond.lt vertex length).Holds b := by + simpa only [Cond.holds_lt, hbv, hba.length] using hv + refine ⟨z, ?_, ?_, ?_, ?_, hcm.trans hbm⟩ + · exact (ExecWithin.branch_pos hcond (hc.seq hchoose)).mono (by omega) + · simp only [z, Data.set_regs, Function.update_self, entry, row, mid, v, σ, hv, + ↓reduceIte, wanted] + · simpa +decide [z, idx, value] using hci + · simpa +decide [z, one, value] using hco + · have hcond : ¬(Cond.lt vertex length).Holds b := by + simpa only [Cond.holds_lt, hbv, hba.length] using hv + refine ⟨b.set value 0, ?_, ?_, ?_, ?_, hbm⟩ + · have he : ExecWithin w gate b (b.set value 0) 3 := ExecWithin.branch_neg hcond + (show ExecWithin w (.op (.copy value zero)) b (b.set value 0) 1 from by + simpa only [hbz, hz] using (ExecWithin.copy (x := b) (d := value) (s := zero))) + exact he.mono (by omega) + · simp only [Data.set_regs, Function.update_self, entry, mid, v, hv, ↓reduceIte] + · simpa +decide [idx, value] using hbi + · simpa +decide [one, value] using hbo.trans ho + obtain ⟨z, hzg, hzv, hzi, hzo, hzm⟩ := hgate + have hzd : z.regs dst = A.dst := + (hzg.regs_eq (by cases side <;> decide)).trans hba.dst + have haddr : Fits w (A.dst + q) := hdst.of_le (by omega) + have hinc : Fits w (q + 1) := hdst.of_le (by omega) + let y := (z.set addr (A.dst + q)).write (A.dst + q) (entry side A R q) + |>.set idx (q + 1) + have htail : ExecWithin w + (ram[ addr ← dst + idx; *addr ← value; idx ← idx + one ]) z y 3 := by + ram_eval [y, hzd, hzi, hzv, hzo, haddr, hinc, addr, dst, idx, value, one, Fin.mk.injEq] + refine ⟨y, ?_, by simp [y], ?_⟩ + · simpa only [body, gate, Nat.reduceAdd] using hb.seq (hzg.seq htail) + · change Function.update z.mem (A.dst + q) (entry side A R q) = _ + rw [hzm] + +/-- Construct every entry of a left or right instance matrix in `20nD+6` native instructions, +excluding the halt. The source is an actual residue array below the output. All cells outside +the output block and all eight arguments are preserved. No adjacency table is supplied. -/ +theorem exec {w : ℕ} (side : Bool) (A : Args) (R : ℕ → ℕ) (x : Data 17) + (hx : Entry A x) (hrho : A.rho < A.p) (hpiece : A.first + A.length ≤ A.n) + (hR : ∀ i < A.n * A.n, R i < A.p) + (hinput : ∀ i < A.n * A.n, x.mem (A.src + i) = R i) + (hsrc : Fits w (A.src + A.n * A.n)) (hdst : Fits w (A.dst + A.n * A.D)) + (hmod : Fits w (2 * A.p)) (hdisj : A.src + A.n * A.n ≤ A.dst) : + ∃ y, ExecWithin w (program side) x y (20 * (A.n * A.D) + 6) ∧ Entry A y ∧ + y.regs idx = A.n * A.D ∧ + (Block.mk A.dst (A.n * A.D)).Realizes y.mem (fun i => entry side A R i) ∧ + (∀ a, ¬(Block.mk A.dst (A.n * A.D)).Owns a → y.mem a = x.mem a) := by + let I (y : Data 17) : Prop := Entry A y ∧ y.regs area = A.n * A.D ∧ + y.regs one = 1 ∧ y.regs zero = 0 ∧ y.regs idx ≤ A.n * A.D ∧ + (∀ j < y.regs idx, y.mem (A.dst + j) = entry side A R j) ∧ + (∀ a, ¬(A.dst ≤ a ∧ a < A.dst + y.regs idx) → y.mem a = x.mem a) + have hbody (y : Data 17) (hy : I y) (hc : (Cond.lt idx area).Holds y) : + ∃ z, ExecWithin w (body side) y z 18 ∧ I z ∧ + A.n * A.D - z.regs idx < A.n * A.D - y.regs idx := by + obtain ⟨hyp, hya, hyo, hyz, hyi, hym, hyout⟩ := hy + have hlt : y.regs idx < A.n * A.D := by + simpa only [Cond.holds_lt, hya] using hc + have hyinput : ∀ i < A.n * A.n, y.mem (A.src + i) = R i := by + intro i hi + rw [hyout _ (by omega)] + exact hinput i hi + obtain ⟨z, he, hzi, hzm⟩ := exec_body side A R y hyp rfl hyo hyz hlt + hrho hpiece hR hyinput hsrc hdst hmod + have hpres (i : Fin 17) (hi : i.val < 8 ∨ i = area ∨ i = one ∨ i = zero) : + z.regs i = y.regs i := he.regs_eq (body_preserves side i hi) + refine ⟨z, he, ⟨entry_of_regs hyp (fun i hi => hpres i (Or.inl hi)), + (hpres area (by simp)).trans hya, (hpres one (by simp)).trans hyo, + (hpres zero (by simp)).trans hyz, by omega, ?_, ?_⟩, by omega⟩ + · intro j hj + rw [hzm] + by_cases h : j = y.regs idx + · subst j + exact Function.update_self .. + · rw [Function.update_of_ne (by omega)] + exact hym j (by omega) + · intro a ha + rw [hzm, Function.update_of_ne (by omega)] + exact hyout a (by omega) + have h1 : Fits w 1 := hmod.of_le (by omega) + have ha : Fits w (A.n * A.D) := hdst.of_le (by omega) + let x₀ := x.set zero 0 |>.set one 1 |>.set idx 0 |>.set area (A.n * A.D) + have hx₀ : I x₀ := by + refine ⟨entry_of_regs hx ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + have hz : i ≠ zero := by intro h; subst i; change 15 < 8 at hi; omega + have ho : i ≠ one := by intro h; subst i; change 16 < 8 at hi; omega + have hn : i ≠ idx := by intro h; subst i; change 8 < 8 at hi; omega + have ha : i ≠ area := by intro h; subst i; change 9 < 8 at hi; omega + simp only [x₀, Data.set_regs, Function.update_of_ne hz, Function.update_of_ne ho, + Function.update_of_ne hn, Function.update_of_ne ha] + · simp [x₀] + · simp +decide [x₀, one, idx, area] + · simp +decide [x₀, zero, one, idx, area] + · simp +decide [x₀, idx, area] + · intro j hj + simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, area, ↓reduceIte] at hj + omega + · intro a _ + rfl + have hinit : ExecWithin w + (ram[ zero ← #0; one ← #1; idx ← #0; area ← size * dimension ]) x x₀ 4 := by + ram_eval [x₀, hx.size, hx.dimension, ha, h1, zero, one, idx, area, size, dimension, + Fin.mk.injEq] + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => A.n * A.D - y.regs idx) hbody hx₀ + obtain ⟨hyp, hya, -, -, hyi, hym, hyout⟩ := hy + have hend : y.regs idx = A.n * A.D := by + simp only [Cond.holds_lt, hya] at hc + omega + refine ⟨y, ?_, hyp, hend, ?_, ?_⟩ + · convert hinit.seq he using 1 + · rfl + · simp +decide only [x₀, Data.set_regs, Function.update_apply, idx, area, ↓reduceIte] + omega + · intro i + exact hym i (by simpa only [hend] using i.isLt) + · intro a ha + exact hyout a (by simpa only [hend, Block.Owns] using ha) + +end Cslib.Algorithms.WordRAM.ExactTriangle.WriteMatrices diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean new file mode 100644 index 0000000000..160e70cd78 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Cell.lean @@ -0,0 +1,129 @@ +/- +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.MatrixMultiplication.Schonhage.Forms +import Mathlib.Tactic.Ring + +/-! +# A native mixed-radix encoding cell + +## Main results + +* `Encoding.exec_cell`: charged index arithmetic, coefficient dispatch, signed linear form, + output store and counter increment for one cell of a shared coordinate transform. + +## References + +The linear forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The registers modified by a cell, apart from the nine signed-arithmetic scratch registers. -/ +def CellLocal (i : Fin 32) : Prop := + i = cell ∨ i = block ∨ i = term ∨ i = suffix ∨ i = address ∨ + i = accumulator ∨ i = group ∨ i = temporary + +instance (i : Fin 32) : Decidable (CellLocal i) := + inferInstanceAs (Decidable (i = cell ∨ i = block ∨ i = term ∨ i = suffix ∨ + i = address ∨ i = accumulator ∨ i = group ∨ i = temporary)) + +private theorem cell_preserves (side : Bool) (i : Fin 32) : + 9 ≤ i.val → ¬CellLocal i → (cellBody side).Preserves i := by + decide +kernel +revert + +/-- One output entry, with all index arithmetic and the counter increment charged. A single +ordinary explicit natural-number bound covers the addresses and index products; no fit +condition is imposed on an unspecified intermediate machine state. -/ +theorem exec_cell {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d i : ℕ) (hP : 0 < P) (hnpos : 0 < n) (hi : i < 10 * P * n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hcell : x.regs cell = i) + (hn : x.regs stride = n) (hseven : x.regs seven = 7) + (hten : x.regs ten = 10) (hone : x.regs one = 1) + (v : Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ a, |v a| ≤ B) + (hmem : ∀ a, x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) : + ∃ y, ExecWithin (w + 2) (cellBody side) x y 205 ∧ + y.mem = Function.update x.mem (d + i) + (encode (∑ a, coefficients side ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ a * v a)) ∧ + y.regs cell = i + 1 ∧ + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) := by + let p := i / (10 * n) + let t : Fin 10 := ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ + let r := i % n + -- Keep nonlinear arithmetic focused on index bounds, rather than the execution context. + have hp : p < P := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + nlinarith only [hi] + have hr : r < n := Nat.mod_lt _ hnpos + have hn1 : 1 ≤ n := hnpos + have hPn : n ≤ P * n := Nat.le_mul_of_pos_left n hP + have hmul : p * n < P * n := Nat.mul_lt_mul_of_pos_right hp hnpos + have h10n : Fits (w + 2) (10 * n) := haddr.of_le (by nlinarith only [hPn]) + have hpfit : Fits (w + 2) p := haddr.of_le (by nlinarith only [hp, hn1]) + have hdiv : i / n ≤ i := Nat.div_le_self _ _ + have hdivfit : Fits (w + 2) (i / n) := haddr.of_le (by nlinarith only [hdiv, hi]) + have htfit : Fits (w + 2) t.val := haddr.of_le (by have := t.isLt; omega) + have hrfit : Fits (w + 2) r := haddr.of_le (by nlinarith only [hr, hPn]) + have hp7 : Fits (w + 2) (p * 7) := haddr.of_le (by nlinarith only [hp, hn1]) + have hp7n : Fits (w + 2) (p * 7 * n) := haddr.of_le (by nlinarith only [hmul]) + have hp7nr : Fits (w + 2) (p * 7 * n + r) := + haddr.of_le (by nlinarith only [hmul, hr, hPn]) + have hgroup : Fits (w + 2) (s + (p * 7 * n + r)) := + haddr.of_le (by nlinarith only [hmul, hr, hPn]) + let z := ((((((((x.set temporary (10 * n)).set block p).set term (i / n)).set term t.val).set + suffix r).set group (p * 7)).set group (p * 7 * n)).set group (p * 7 * n + r)).set + group (s + (p * 7 * n + r)) + have hz : ExecWithin (w + 2) cellSetup x z 9 := by + ram_eval [cellSetup, z, p, t, r, hsrc, hcell, hn, hseven, hten, + h10n, hpfit, hdivfit, htfit, hrfit, hp7, hp7n, hp7nr, hgroup, + temporary, block, term, suffix, group, src, cell, stride, seven, ten, Fin.mk.injEq] + have hzterm : z.regs term = t.val := by + simp [z, temporary, block, term, suffix, group] + have hzgroup : z.regs group = s + (p * 7 * n + r) := by simp [z] + have hzn : z.regs stride = n := by + simpa [z, temporary, block, term, suffix, group, stride, Fin.mk.injEq] using hn + have hzmem : ∀ a, z.mem (s + (p * 7 * n + r) + a.val * n) = encode (v a) := by + intro a + have hidx : s + (p * 7 * n + r) + a.val * n = s + (p * 7 + a.val) * n + r := by ring + simpa only [z, Data.set_mem, hidx] using hmem a + have hspan : Fits (w + 2) (s + (p * 7 * n + r) + 7 * n) := + haddr.of_le (by nlinarith only [hmul, hr, hPn]) + obtain ⟨u, hu, huacc, humem, hureg⟩ := exec_dispatch hw side (List.finRange 10) t + (List.mem_finRange _) z B (s + (p * 7 * n + r)) n v hzterm hzgroup hzn hzmem + hB hv hval hspan (haddr.of_le (by omega)) + have zucell : u.regs cell = i := by + rw [hureg cell (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, cell, Fin.mk.injEq] using hcell + have zudst : u.regs dst = d := by + rw [hureg dst (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, dst, Fin.mk.injEq] using hdst + have zuone : u.regs one = 1 := by + rw [hureg one (by decide) (by decide) (by decide) (by decide)] + simpa [z, temporary, block, term, suffix, group, one, Fin.mk.injEq] using hone + have hdfit : Fits (w + 2) (d + i) := haddr.of_le (by nlinarith only [hi]) + have hinc : Fits (w + 2) (i + 1) := haddr.of_le (by nlinarith only [hi]) + let y := ((u.set address (d + i)).write (d + i) (u.regs accumulator)).set cell (i + 1) + have hy : ExecWithin (w + 2) cellFinish u y 3 := by + ram_eval [cellFinish, y, zucell, zudst, zuone, hdfit, hinc, + address, cell, accumulator, dst, one, Fin.mk.injEq] + have he : ExecWithin (w + 2) (cellBody side) x y 205 := by + simpa only [cellBody, List.length_finRange] using (hz.seq (hu.seq hy)).mono + (by decide : 9 + ((3 * 10 + 163) + 3) ≤ 205) + refine ⟨y, he, ?_, ?_, fun a ha hl => he.regs_eq (cell_preserves side a ha hl)⟩ + · simp only [y, Data.set_mem, Data.write_mem, humem, z, huacc] + rfl + · simp [y] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean new file mode 100644 index 0000000000..04eabe5c36 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Controller.lean @@ -0,0 +1,250 @@ +/- +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.MatrixMultiplication.Schonhage.Header +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Copy +import Mathlib.Tactic.Ring + +/-! +# Alternating buffers for the shared native encoder + +## Main definitions + +* `Encoding.destination`, `Encoding.source`: the alternating buffer addresses. +* `Encoding.Progress`: the mixed-radix array and frame invariant after a stage. + +## References + +The shared encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- Destination of the next coordinate transform; always one of the two fixed buffers. -/ +def destination (L : ℕ) : ℕ → ℕ + | 0 => outputBase L + | j + 1 => if destination L j = outputBase L then scratchBase L else outputBase L + +/-- Source of the next coordinate transform; initially the preserved input segment. -/ +def source (L : ℕ) : ℕ → ℕ + | 0 => 2 + | j + 1 => destination L j + +/-- A destination is either the fixed output block or the scratch block. -/ +theorem destination_cases (L j : ℕ) : + destination L j = outputBase L ∨ destination L j = scratchBase L := by + cases j with + | zero => exact Or.inl rfl + | succ j => unfold destination; split <;> simp + +/-- After the initial transform, the source is also one of the two buffers. -/ +theorem source_cases {L j : ℕ} (hj : 0 < j) : + source L j = outputBase L ∨ source L j = scratchBase L := by + cases j with + | zero => omega + | succ j => exact destination_cases L j + +/-- The two alternating buffer addresses differ at every stage. -/ +theorem source_ne_destination (L j : ℕ) : source L j ≠ destination L j := by + have hpos : 0 < 10 ^ L := by positivity + have h7pos : 0 < 7 ^ L := by positivity + cases j with + | zero => simp only [source, destination, outputBase]; omega + | succ j => + rcases destination_cases L j with h | h + · simp only [source, destination, h, ↓reduceIte] + unfold scratchBase + omega + · have hne : scratchBase L ≠ outputBase L := by unfold scratchBase; omega + simp only [source, destination, h, hne, ↓reduceIte] + exact hne + +/-- Both current addresses are bounded by the end of the second buffer. -/ +theorem buffer_address_le (L j : ℕ) : + source L j ≤ 2 + 2 * 10 ^ L ∧ destination L j ≤ 2 + 2 * 10 ^ L := by + have h7 := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + have hd := destination_cases L j + have hs : source L j = 2 ∨ source L j = outputBase L ∨ source L j = scratchBase L := by + cases j with + | zero => exact Or.inl rfl + | succ j => exact Or.inr (destination_cases L j) + simp only [scratchBase, outputBase] at hs hd + constructor <;> omega + +/-- Arrays stored at consecutive buffers are disjoint, in either order. The initial input +ends exactly at the first output address. -/ +theorem buffers_separated {L j k N : ℕ} (hjk : j + k = L) + (hN : N ≤ 10 ^ L) : + source L j + 10 ^ j * 7 ^ k ≤ destination L j ∨ + destination L j + N ≤ source L j := by + have hsize : 10 ^ j * 7 ^ k ≤ 10 ^ L := by simpa only [hjk] using stageSize_le j k + cases j with + | zero => + left + simp only [Nat.zero_add] at hjk + simp [source, destination, outputBase, hjk] + | succ j => + have hs := source_cases (L := L) (j := j + 1) (by omega) + have hd := destination_cases L (j + 1) + have hne := source_ne_destination L (j + 1) + unfold scratchBase at hs hd + omega + +/-- The shared-array invariant at depth `j`. All fields describe ordinary registers or +mathematical memory values. Execution, timing and width are proved separately. -/ +structure Progress (side : Bool) (L j : ℕ) (arr : ℕ → ℤ) (initial : ℕ → ℕ) + (y : Data 32) : Prop where + /-- Fixed parameters retained across coordinate stages. -/ + fixed : y.regs depth = L ∧ y.regs capacity = 10 ^ L ∧ + y.regs output = outputBase L ∧ y.regs scratch = scratchBase L ∧ + y.regs one = 1 ∧ y.regs seven = 7 ∧ y.regs ten = 10 + /-- Number of transformed coordinates. -/ + stage : y.regs Encoding.stage = j + /-- Number of transformed prefixes. -/ + prefixes : y.regs Encoding.prefixes = 10 ^ j + /-- Original suffix length in the current source. -/ + stride : y.regs Encoding.stride = 7 ^ (L - j) + /-- Current source address. -/ + src : y.regs Encoding.src = source L j + /-- Current destination address. -/ + dst : y.regs Encoding.dst = destination L j + /-- The source holds exactly the shared prefix sums. -/ + values : ∀ p < 10 ^ j, ∀ r < 7 ^ (L - j), + y.mem (source L j + p * 7 ^ (L - j) + r) = + encode (value (table (coefficients side)) j (L - j) arr p r) + /-- Input, headers and all cells outside both work buffers are preserved. -/ + frame : ∀ a, ¬(outputBase L ≤ a ∧ a < scratchBase L + 10 ^ L) → y.mem a = initial a + +/-- The actual header initialization establishes the zero-stage invariant from the original +flat input segment. No scratch values or preinitialized coefficient tables are required. -/ +theorem progress_zero (side : Bool) (L : ℕ) (arr : ℕ → ℤ) (x y : Data 32) + (hy : SetupPost L x y) (hinput : ∀ i < 7 ^ L, x.mem (2 + i) = encode (arr i)) : + Progress side L 0 arr x.mem y := by + refine ⟨⟨hy.depth, hy.capacity, hy.output, hy.scratch, hy.one, hy.seven, hy.ten⟩, + hy.stage, hy.prefixes, ?_, hy.src, hy.dst, ?_, ?_⟩ + · simpa only [Nat.sub_zero] using hy.stride + · intro p hp r hr + have hp0 : p = 0 := by simpa only [pow_zero] using Nat.eq_zero_of_le_zero (by omega : p ≤ 0) + subst p + rw [hy.mem, value_zero] + simpa only [source, Nat.sub_zero, Nat.zero_mul, Nat.add_zero] using hinput r hr + · intro a _ + rw [hy.mem] + +/-- One proved native stage advances the mixed-radix invariant. The signed magnitude bound +comes from the original input and the number of coordinates already transformed. -/ +theorem progress_step {w L j : ℕ} (hw : 0 < w) (hj : j < L) (side : Bool) + (arr : ℕ → ℤ) (initial : ℕ → ℕ) (x : Data 32) (hx : Progress side L j arr initial x) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (stageBody side) x y (207 * 10 ^ L + 12) ∧ + Progress side L (j + 1) arr initial y := by + let k := L - (j + 1) + let n := 7 ^ k + let P := 10 ^ j + have hn : 0 < n := by dsimp only [n]; positivity + have hP : 0 < P := by dsimp only [P]; positivity + have hk : L - j = k + 1 := by omega + have hjk : j + (k + 1) = L := by omega + have hjk' : j + 1 + k = L := by omega + have hstride : 7 ^ (L - j) = 7 * n := by rw [hk, pow_succ'] + have hN : 10 * P * n ≤ 10 ^ L := by + have h := stageSize_le (j + 1) k + simpa only [hjk', pow_succ', P, n] using h + have hcoeff : ∀ t < 10, ∀ s < 7, |table (coefficients side) t s| ≤ 1 := by + intro t _ s _ + unfold table coefficients + cases side + · exact abs_phi_le_one _ _ + · exact abs_psi_le_one _ _ + let v (i : ℕ) (a : Fin 7) : ℤ := value (table (coefficients side)) j (k + 1) arr + (i / (10 * n)) (a.val * n + i % n) + let B : ℤ := (7 ^ j : ℕ) * A + have hB : 0 ≤ B := mul_nonneg (by positivity) hA + have hsuffix (i : ℕ) (a : Fin 7) : a.val * n + i % n < 7 ^ (k + 1) := by + have hm := Nat.mul_le_mul_right n (Nat.succ_le_of_lt a.isLt) + have hr := Nat.mod_lt i hn + rw [pow_succ'] + change a.val * n + i % n < 7 * n + nlinarith only [hm, hr] + have hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B := by + intro i _ a + exact abs_value_le_of_input hcoeff hjk _ _ (hsuffix i a) ha + have hload : ∀ i < 10 * P * n, ∀ a, + x.mem (source L j + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a) := by + intro i hi a + have hp : i / (10 * n) < 10 ^ j := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + dsimp only [P] at hi + nlinarith only [hi] + have hidx : source L j + (i / (10 * n) * 7 + a.val) * n + i % n = + source L j + i / (10 * n) * 7 ^ (L - j) + (a.val * n + i % n) := by + rw [hstride] + ring + rw [hidx, hx.values _ hp _ (by rw [hk]; exact hsuffix i a)] + rw [hk] + have hvalue : 7 * B < (2 : ℤ) ^ (w - 1) := by + have hpow : 7 ^ (j + 1) ≤ 7 ^ L := Nat.pow_le_pow_right (by decide) (by omega) + have hpowZ : ((7 ^ (j + 1) : ℕ) : ℤ) ≤ (7 ^ L : ℕ) := by exact_mod_cast hpow + have hprod := mul_le_mul_of_nonneg_right hpowZ hA + simpa only [Nat.pow_succ', Nat.cast_mul, Nat.cast_ofNat, mul_assoc, B] using + hprod.trans_lt hmag + have hbound := buffer_address_le L j + have hcap : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + have hnative : Fits (w + 2) (source L j + destination L j + 64 + 64 * P * n) := + haddr.of_le (by unfold addressBound; nlinarith only [hbound.1, hbound.2, hN, hcap]) + have hseparate : source L j + 7 * P * n ≤ destination L j ∨ + destination L j + 10 * P * n ≤ source L j := by + have h := buffers_separated (k := L - j) (by omega : j + (L - j) = L) hN + simpa only [hstride, P, Nat.mul_assoc, Nat.mul_comm, Nat.mul_left_comm] using h + obtain ⟨y, he, hm, hf, hsrc, hdst, hstr, hp, hstage, hreg⟩ := exec_stageBody hw side x + P n (source L j) (destination L j) j (outputBase L) (scratchBase L) hP hn hx.src hx.dst + hx.stage (by simpa only [hstride] using hx.stride) hx.prefixes hx.fixed.2.2.2.2.2.1 + hx.fixed.2.2.2.2.2.2 hx.fixed.2.2.2.2.1 hx.fixed.2.2.1 hx.fixed.2.2.2.1 v B hB hv hload + hvalue hnative (haddr.of_le (by unfold addressBound; omega)) hseparate + refine ⟨y, he.mono (by omega), ?_⟩ + have hr (a : Fin 32) (ha : 9 ≤ a.val) (hl : ¬StageLocal a) : y.regs a = x.regs a := hreg a ha hl + refine ⟨?_, hstage, ?_, ?_, hsrc, hdst, ?_, ?_⟩ + · rcases hx.fixed with ⟨hd, hc, ho, hs, h1, h7, h10⟩ + exact ⟨(hr depth (by decide) (by decide)).trans hd, + (hr capacity (by decide) (by decide)).trans hc, + (hr output (by decide) (by decide)).trans ho, + (hr scratch (by decide) (by decide)).trans hs, + (hr one (by decide) (by decide)).trans h1, + (hr seven (by decide) (by decide)).trans h7, + (hr ten (by decide) (by decide)).trans h10⟩ + · simpa only [P, pow_succ'] using hp + · exact hstr + · intro p hpp r hrr + have hrr' : r < n := hrr + have hpp' : p < 10 * P := by simpa only [pow_succ', P] using hpp + have hprod := Nat.mul_le_mul_right n (Nat.succ_le_of_lt hpp') + have hi : p * n + r < 10 * P * n := by nlinarith only [hprod, hrr'] + have hdiv : (p * n + r) / n = p := by + rw [Nat.mul_comm p n, Nat.mul_add_div hn, Nat.div_eq_of_lt hrr', Nat.add_zero] + have hmod : (p * n + r) % n = r := Nat.mul_add_mod_of_lt hrr' + have hvout := hm (p * n + r) hi + rw [show stageResult side n v (p * n + r) = value (table (coefficients side)) + (j + 1) k arr ((p * n + r) / n) ((p * n + r) % n) from stageResult_value side j k _ arr, + hdiv, hmod] at hvout + simpa only [source, n, k, Nat.add_assoc] using hvout + · intro a haout + rw [hf a ?_] + · exact hx.frame a haout + · rcases destination_cases L j with hd | hd <;> rw [hd] <;> unfold scratchBase at * <;> omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean new file mode 100644 index 0000000000..8ada703386 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Copy.lean @@ -0,0 +1,48 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.MatrixMultiplication.Schonhage.Encoding + +/-! +# Copying the final encoding to its fixed output block + +## Main results + +* `Encoding.exec_copyOut`: reuse the native array copy, including equal blocks and both + possible disjoint address orders, with its charged `7n+4` bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM + +/-- The final copy uses seven instructions per output word plus four instructions of setup +and loop termination. Equal source/output blocks are permitted, so odd and even depths use +the same fixed program. All other registers retain their values. -/ +theorem exec_copyOut {w : ℕ} (x : Data 32) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs capacity)) + (hd : Fits w (x.regs output + x.regs capacity)) + (hdisj : x.regs src + x.regs capacity ≤ x.regs output ∨ + x.regs output + x.regs capacity ≤ x.regs src ∨ x.regs src = x.regs output) : + ∃ y, ExecWithin w copyOut x y (7 * x.regs capacity + 4) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (x.regs src) (x.regs output) (x.regs capacity) ∧ + (∀ i, i ≠ cell → i ≠ address → i ≠ accumulator → i ≠ one → y.regs i = x.regs i) := by + obtain ⟨z, hz, hm, _, _, _⟩ := ArrayCopy.exec_of_separated_or_eq (x.comap copyRegisters) + h1 hs hd hdisj + have he := hz.rename copyRegisters_injective + refine ⟨z.extend copyRegisters x, he, hm, ?_⟩ + intro i hc ha hv ho + apply he.regs_eq + have hp : ∀ i : Fin 32, i ≠ cell → i ≠ address → i ≠ accumulator → i ≠ one → + copyOut.Preserves i := by decide + exact hp i hc ha hv ho + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean new file mode 100644 index 0000000000..10afd12bbb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Encoding.lean @@ -0,0 +1,244 @@ +/- +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.MatrixMultiplication.Schonhage.Full +public import Complexity.Computability.WordRAM.Signed.Arithmetic +public import Complexity.Computability.WordRAM.Copy + +/-! +# Native shared tensor encoding + +The two fixed programs use unsigned addresses and counters, and sign-and-magnitude encoded +integer entries. Address 1 holds the depth; the `7^L` encoded entries begin at address 2. +The ordinary `State.init` length header is at address 0. Output and scratch are consecutive +blocks of `10^L` cells immediately after the input. Each coordinate transform reads the +previous stage and writes the other buffer. No initialization of unread scratch is needed. +A final charged copy puts the result in the first buffer, also at depth zero. + +The coordinate transform is a new iterative implementation of the linear forms in +[AlmanWilliams2026, Section 2.4.1]. The coefficients and mathematical encoding are selectively +adapted from Anthropic's formal-math revision `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +[`Step`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/Step.lean) +and [`Recursion`](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/Recursion.lean). The source's +recursive machine and execution semantics are not used. + +## Main definitions + +* `Encoding.program`: fixed native code for either coefficient table. +* `Encoding.input`: concrete input for `State.init`, including the depth header. +* `Encoding.outputBase`, `Encoding.scratchBase`: disjoint output and scratch blocks. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Seven variable digits: the three outer variables, then the four inner variables. -/ +def leftAt (s : Fin 7) : LeftVar := + if h : s.val < 3 then .x ⟨s.val, h⟩ + else .p ⟨(s.val - 3) / 2, by omega⟩ ⟨(s.val - 3) % 2, Nat.mod_lt _ (by decide)⟩ + +/-- Right-variable digits use the same order as the left-variable digits. -/ +def rightAt (s : Fin 7) : RightVar := + if h : s.val < 3 then .y ⟨s.val, h⟩ + else .q ⟨(s.val - 3) / 2, by omega⟩ ⟨(s.val - 3) % 2, Nat.mod_lt _ (by decide)⟩ + +/-- Row-major order on the nine `P` terms, followed by `P0`. -/ +def termAt (t : Fin 10) : Term := + if h : t.val < 9 then .P ⟨t.val / 3, by omega⟩ ⟨t.val % 3, Nat.mod_lt _ (by decide)⟩ + else .P0 + +/-- Explicit numbering of the seven left variables. -/ +def leftDigits : Fin 7 ≃ LeftVar where + toFun := leftAt + invFun + | .x i => ⟨i.val, by omega⟩ + | .p i j => ⟨3 + 2 * i.val + j.val, by omega⟩ + left_inv := by decide + right_inv := by decide + +/-- Explicit numbering of the seven right variables. -/ +def rightDigits : Fin 7 ≃ RightVar where + toFun := rightAt + invFun + | .y j => ⟨j.val, by omega⟩ + | .q i j => ⟨3 + 2 * i.val + j.val, by omega⟩ + left_inv := by decide + right_inv := by decide + +/-- Explicit numbering of all ten terms. -/ +def termDigits : Fin 10 ≃ Term where + toFun := termAt + invFun + | .P i j => ⟨3 * i.val + j.val, by omega⟩ + | .P0 => 9 + left_inv := by decide + right_inv := by decide + +/-- The left and right coefficient tables, with digits made explicit. -/ +def coefficients (rightSide : Bool) (t : Fin 10) (s : Fin 7) : ℤ := + if rightSide then psi (termAt t) (rightAt s) else phi (termAt t) (leftAt s) + +/-- Every actual coefficient is zero, one or minus one. -/ +theorem coefficients_values (side : Bool) (t : Fin 10) (s : Fin 7) : + coefficients side t s = 0 ∨ coefficients side t s = 1 ∨ coefficients side t s = -1 := by + decide +revert + +/-- The input block ends where the output block begins. -/ +def outputBase (L : ℕ) : ℕ := 2 + 7 ^ L + +/-- The scratch block immediately follows the entire output block. -/ +def scratchBase (L : ℕ) : ℕ := outputBase L + 10 ^ L + +/-- Concrete input: depth header followed by the signed input entries in flat order. +The caller encodes each integer with `Signed.encode` before passing the list to `State.init`. +The native unsigned length header is installed by `State.init` itself. -/ +def input (L : ℕ) (a : Fin (7 ^ L) → ℤ) : List ℕ := + L :: List.ofFn (fun i => encode (a i)) + +/-- The concrete left-array input, using Mathlib's base-seven string numbering. -/ +def inputL {L : ℕ} (a : LeftStr L → ℤ) : List ℕ := + input L fun i => a fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ) + +/-- The concrete right-array input, in exactly the same flat memory layout. -/ +def inputR {L : ℕ} (a : RightStr L → ℤ) : List ℕ := + input L fun i => a fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ) + +/-- All thirty-two registers are fixed independently of depth and width. Registers 0–8 +are precisely the existing signed-arithmetic workspace. -/ +abbrev depth : Fin 32 := 9 +@[inherit_doc depth] abbrev inputSize : Fin 32 := 10 +@[inherit_doc depth] abbrev capacity : Fin 32 := 11 +@[inherit_doc depth] abbrev src : Fin 32 := 12 +@[inherit_doc depth] abbrev dst : Fin 32 := 13 +@[inherit_doc depth] abbrev output : Fin 32 := 14 +@[inherit_doc depth] abbrev scratch : Fin 32 := 15 +@[inherit_doc depth] abbrev stride : Fin 32 := 16 +@[inherit_doc depth] abbrev prefixes : Fin 32 := 17 +@[inherit_doc depth] abbrev stage : Fin 32 := 18 +@[inherit_doc depth] abbrev cell : Fin 32 := 19 +@[inherit_doc depth] abbrev block : Fin 32 := 20 +@[inherit_doc depth] abbrev term : Fin 32 := 21 +@[inherit_doc depth] abbrev suffix : Fin 32 := 22 +@[inherit_doc depth] abbrev address : Fin 32 := 23 +@[inherit_doc depth] abbrev accumulator : Fin 32 := 24 +@[inherit_doc depth] abbrev one : Fin 32 := 25 +@[inherit_doc depth] abbrev seven : Fin 32 := 26 +@[inherit_doc depth] abbrev ten : Fin 32 := 27 +@[inherit_doc depth] abbrev group : Fin 32 := 28 +@[inherit_doc depth] abbrev temporary : Fin 32 := 29 +@[inherit_doc depth] abbrev stageSize : Fin 32 := 30 + +/-- One coefficient in `{0, 1, -1}` times one array entry. Zero coefficients cause no load. +Nonzero coefficients use the proved native signed addition or subtraction block. -/ +def summand (s : Fin 7) (c : ℤ) : Code .standard 32 := + if c = 0 then ram[ accumulator ← accumulator ] + else .seq (ram[ + temporary ← #s.val; + address ← temporary * stride; address ← group + address; + right ← *address; left ← accumulator ]) + (.seq (if c = 1 then add else subtract) (ram[ accumulator ← result ])) + +/-- A finite unrolling of the seven coefficients. This recursion constructs code; it does +not recurse on the input depth. -/ +def sumCode : List (Fin 7 × ℤ) → Code .standard 32 + | [] => ram[ accumulator ← accumulator ] + | (s, c) :: rest => .seq (summand s c) (sumCode rest) + +/-- One linear form, including accumulator initialization. -/ +def form (side : Bool) (t : Fin 10) : Code .standard 32 := + .seq (ram[ accumulator ← #0 ]) + (sumCode (List.ofFn fun s => (s, coefficients side t s))) + +/-- A fixed finite dispatch on the ten possible term digits. -/ +def dispatch (side : Bool) : List (Fin 10) → Code .standard 32 + | [] => ram[ accumulator ← #0 ] + | t :: rest => ram[ + temporary ← #t.val; + if term = temporary then [ ⟪form side t⟫ ] else [ ⟪dispatch side rest⟫ ] ] + +/-- Decode one flat output index into its mixed-radix coordinates and source group. -/ +def cellSetup : Code .standard 32 := ram[ + temporary ← ten * stride; + block ← cell / temporary; + term ← cell / stride; term ← term % ten; + suffix ← cell % stride; + group ← block * seven; group ← group * stride; + group ← group + suffix; group ← src + group ] + +/-- Store the computed entry and advance the flat index. -/ +def cellFinish : Code .standard 32 := ram[ + address ← dst + cell; *address ← accumulator; + cell ← cell + one ] + +/-- One output cell. The flat index is `(prefix * 10 + term) * stride + suffix`; +its seven source addresses are `(prefix * 7 + s) * stride + suffix`. -/ +def cellBody (side : Bool) : Code .standard 32 := + .seq cellSetup (.seq (dispatch side (List.finRange 10)) cellFinish) + +/-- Divide the suffix stride and compute the next stage size. -/ +def stageHead : Code .standard 32 := ram[ + stride ← stride / seven; + stageSize ← prefixes * ten; stageSize ← stageSize * stride; + cell ← #0 ] + +/-- Alternate the two buffers and advance the prefix and stage counters. -/ +def stageTail : Code .standard 32 := ram[ + src ← dst; + if dst = output then [ dst ← scratch ] else [ dst ← output ]; + prefixes ← prefixes * ten; stage ← stage + one ] + +/-- Transform the next coordinate, then alternate the output buffers. -/ +def stageBody (side : Bool) : Code .standard 32 := + .seq stageHead (.seq (.loop (.lt cell stageSize) (cellBody side)) stageTail) + +/-- Load the actual depth header and initialize the power counters. -/ +def setupHead : Code .standard 32 := ram[ + one ← #1; seven ← #7; ten ← #10; + address ← #1; depth ← *address; + inputSize ← #1; capacity ← #1; stage ← #0 ] + +/-- One multiplication by each radix and one counter increment. -/ +def powerBody : Code .standard 32 := ram[ + inputSize ← inputSize * seven; capacity ← capacity * ten; stage ← stage + one ] + +/-- Compute the concrete buffer addresses from the powers and initialize the first stage. -/ +def setupTail : Code .standard 32 := ram[ + temporary ← #2; output ← temporary + inputSize; scratch ← output + capacity; + src ← #2; dst ← output; stride ← inputSize; prefixes ← #1; stage ← #0 ] + +/-- Read the depth and compute both powers in a charged native loop. -/ +def setup : Code .standard 32 := + .seq setupHead (.seq (.loop (.lt stage depth) powerBody) setupTail) + +/-- Reuse the existing seven-register array copy inside the encoder's register file. -/ +def copyRegisters : Fin 7 → Fin 32 := ![src, output, capacity, cell, address, accumulator, one] + +/-- The seven copy registers are distinct. -/ +theorem copyRegisters_injective : Function.Injective copyRegisters := by decide + +/-- A charged copy into the fixed output block, permitting disjoint blocks in either order +or identical source and destination. -/ +def copyOut : Code .standard 32 := ArrayCopy.program.rename copyRegisters + +/-- The complete fixed native encoder, excluding its final halt. The returned `output` +register identifies the first output cell; the `capacity` register holds its length. -/ +def program (side : Bool) : Code .standard 32 := ram[ + ⟪setup⟫; + while stage < depth do [ ⟪stageBody side⟫ ]; + ⟪copyOut⟫ ] + +/-- The code performs no word-width queries. -/ +theorem program_readsWidth (side : Bool) : ¬(program side).ReadsWidth := by + cases side <;> decide + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean new file mode 100644 index 0000000000..1d80c56e63 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Exec.lean @@ -0,0 +1,136 @@ +/- +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.MatrixMultiplication.Schonhage.Controller +import Mathlib.Tactic.Ring + +/-! +# Complete charged execution of the shared encoder + +## Main results + +* `Encoding.exec_coordinates`: all shared coordinate transforms, with charged loop control. +* `Encoding.exec`: complete initialization, transforms and fixed-output copy with an explicit + `240 * (L+1) * 10^L` native instruction bound. + +The code is directly over the standard unsigned WordRAM. Its signed add/subtract blocks use +two extra target bits and respectively at most fifteen/seventeen actual instructions. There +is no whole-program signed compiler in this construction and no uncharged simulation premise. + +## References + +The encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- Explicit total instruction bound, including header preparation, all index arithmetic, +stage and cell loop control, and the final copy. A final halt costs one additional instruction. -/ +def timeBound (L : ℕ) : ℕ := 240 * (L + 1) * 10 ^ L + +/-- Every coordinate stage is executed natively. Charging every stage at the maximum buffer +size gives `L * (207 * 10^L + 14) + 2` instructions, including both outer loop controls. -/ +theorem exec_coordinates {w L : ℕ} (hw : 0 < w) (side : Bool) (arr : ℕ → ℤ) + (initial : ℕ → ℕ) (x : Data 32) (hx : Progress side L 0 arr initial x) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (.loop (.lt stage depth) (stageBody side)) x y + (L * (207 * 10 ^ L + 14) + 2) ∧ Progress side L L arr initial y := by + let I (y : Data 32) : Prop := ∃ j, j ≤ L ∧ Progress side L j arr initial y + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt stage depth).Holds y) : + ∃ z, ExecWithin (w + 2) (stageBody side) y z (207 * 10 ^ L + 12) ∧ I z ∧ + L - z.regs stage < L - y.regs stage := by + obtain ⟨j, hj, hprogress⟩ := hy + have hjL : j < L := by simpa only [Cond.holds_lt, hprogress.stage, hprogress.fixed.1] using hc + obtain ⟨z, he, hz⟩ := progress_step hw hjL side arr initial y hprogress A hA ha hmag haddr + exact ⟨z, he, ⟨j + 1, by omega, hz⟩, by rw [hprogress.stage, hz.stage]; omega⟩ + obtain ⟨y, he, ⟨j, hj, hy⟩, hc⟩ := ExecWithin.loop_invariant I + (fun y => L - y.regs stage) hbody ⟨0, Nat.zero_le _, hx⟩ + have hjL : j = L := by + simp only [Cond.holds_lt, hy.stage, hy.fixed.1] at hc + omega + subst j + refine ⟨y, ?_, hy⟩ + simpa only [hx.stage, Nat.sub_zero, Nat.add_assoc] using he + +/-- The encoder runs from a concrete flat memory layout and preserves every cell outside +its output/scratch region. Only the original input magnitude and explicit address bounds are +assumed; every intermediate signed arithmetic fit is derived in the coordinate-stage proof. -/ +theorem exec {w L : ℕ} (hw : 0 < w) (side : Bool) (arr : ℕ → ℤ) (x : Data 32) + (hheader : x.mem 1 = L) (hinput : ∀ i < 7 ^ L, x.mem (2 + i) = encode (arr i)) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i < 7 ^ L, |arr i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + ∃ y, ExecWithin (w + 2) (program side) x y (timeBound L) ∧ + (∀ p < 10 ^ L, y.mem (outputBase L + p) = + encode (value (table (coefficients side)) L 0 arr p 0)) ∧ + (∀ a, ¬(outputBase L ≤ a ∧ a < scratchBase L + 10 ^ L) → y.mem a = x.mem a) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + obtain ⟨z, hz, hsetup⟩ := exec_setup L x hheader haddr + have hzero := progress_zero side L arr x z hsetup hinput + obtain ⟨u, hu, hp⟩ := exec_coordinates hw side arr x.mem z hzero A hA ha hmag haddr + have hsource := hp.src + have hout := hp.fixed.2.2.1 + have hcap := hp.fixed.2.1 + have hcap1 : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + have hsfit : Fits (w + 2) (u.regs src + u.regs capacity) := by + rw [hsource, hcap] + apply haddr.of_le + have hb := (buffer_address_le L L).1 + unfold addressBound + omega + have hdfit : Fits (w + 2) (u.regs output + u.regs capacity) := by + rw [hout, hcap] + apply haddr.of_le + have h7 := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound outputBase + omega + have hdisj : u.regs src + u.regs capacity ≤ u.regs output ∨ + u.regs output + u.regs capacity ≤ u.regs src ∨ u.regs src = u.regs output := by + rw [hsource, hout, hcap] + by_cases hL : L = 0 + · rcases hL with rfl + left + decide + · rcases source_cases (L := L) (j := L) (by omega) with h | h + · exact Or.inr (Or.inr h) + · right + left + rw [h] + rfl + obtain ⟨y, hy, hymem, hyr⟩ := exec_copyOut u (haddr.of_le (by unfold addressBound; omega)) + hsfit hdfit hdisj + have he : ExecWithin (w + 2) (program side) x y (timeBound L) := by + have hwhole := hz.seq (hu.seq hy) + apply hwhole.mono + rw [hcap] + unfold timeBound + have hLcap : L ≤ L * 10 ^ L := by nlinarith only [Nat.mul_le_mul_left L hcap1] + nlinarith only [hcap1, hLcap] + refine ⟨y, he, ?_, ?_, ?_, ?_⟩ + · intro p hpp + rw [hymem, hsource, hout, hcap, ArrayCopy.copyPrefix_read hpp] + have h := hp.values p hpp 0 (by simp) + simpa only [Nat.sub_self, pow_zero, Nat.mul_one, Nat.add_zero] using h + · intro a haout + rw [hymem, hsource, hout, hcap, ArrayCopy.copyPrefix_outside ?_] + · exact hp.frame a haout + · unfold scratchBase at haout + omega + · exact (hyr output (by decide) (by decide) (by decide) (by decide)).trans hout + · exact (hyr capacity (by decide) (by decide) (by decide) (by decide)).trans hcap + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.lean new file mode 100644 index 0000000000..9ced15aa52 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Flat.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.MatrixMultiplication.Schonhage.Input + +/-! +# Flat standard-WordRAM clients for the complete encoders + +## Main results + +* `Encoding.runs_left`, `Encoding.runs_right`: actual flat runs from `State.init`, the complete + output memory block, preserved outside frame, and a charged final halt. +* `Encoding.program_length_le`: one finite code-size bound independent of depth and width. + +The final halt returns the output block's address. Its length is `10^L` and its cells contain +the canonical signed encodings of the public mathematical values. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +set_option maxRecDepth 4096 in +-- Kernel reduction flattens the finite instruction list, including each signed arithmetic block. +/-- A single code-size bound for both fixed encoders, including the final halt. -/ +theorem program_length_le (side : Bool) : ((program side).program output).length ≤ 4096 := by + cases side <;> decide + +/-- Every one of the `10^L` flat output cells corresponds to exactly one original leaf. -/ +theorem leafIndex_bijective (L : ℕ) : Function.Bijective (leafIndex (L := L)) := by + have h : Function.Bijective (fun τ : Leaf L => fun i => termDigits.symm (τ i)) := + (Equiv.piCongrRight fun _ : Fin L => termDigits.symm).bijective + change Function.Bijective (fun τ : Leaf L => + finFunctionFinEquiv (fun i => termDigits.symm (τ i))) + exact finFunctionFinEquiv.bijective.comp h + +/-- Actual flat native left-encoding execution. The prefix exposes every output memory cell; +the same run then halts at its fixed final instruction, charging one more instruction. -/ +theorem runs_left {w L : ℕ} (hw : 0 < w) (a : LeftStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputL a) ∧ + ∃ y n, n ≤ timeBound L ∧ + Runs (w + 2) ((program false).program output) (State.init (inputL a)) + ⟨(program false).size, y⟩ n ∧ + Executes (w + 2) ((program false).program output) (State.init (inputL a)) + (outputBase L) (n + 1) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingL a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputL a) : State 32).data.mem b) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, _, hv, hf, ho, _⟩ := exec_left hw a A hA ha hmag haddr + refine ⟨hi, y, n, hn, ?_, ?_, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := (program false).program output) (Program.Contains.self _).append_left + · simpa only [ho, State.init] using he.executes output + +/-- Actual flat native right-encoding execution, with all output cells and the same charged +standard-WordRAM calling convention and final halt. -/ +theorem runs_right {w L : ℕ} (hw : 0 < w) (a : RightStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputR a) ∧ + ∃ y n, n ≤ timeBound L ∧ + Runs (w + 2) ((program true).program output) (State.init (inputR a)) + ⟨(program true).size, y⟩ n ∧ + Executes (w + 2) ((program true).program output) (State.init (inputR a)) + (outputBase L) (n + 1) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingR a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputR a) : State 32).data.mem b) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, _, hv, hf, ho, _⟩ := exec_right hw a A hA ha hmag haddr + refine ⟨hi, y, n, hn, ?_, ?_, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := (program true).program output) (Program.Contains.self _).append_left + · simpa only [ho, State.init] using he.executes output + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean new file mode 100644 index 0000000000..33ed8e8815 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Forms.lean @@ -0,0 +1,235 @@ +/- +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.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Foundations.Algebra.FiniteMagnitude +import Mathlib.Tactic.Ring + +/-! +# Native execution of the encoding forms + +The form code reads seven strided entries and uses only the existing native signed arithmetic +blocks. These results account for those blocks' actual instructions and their scratch registers. + +## Main results + +* `Encoding.exec_summand`: a coefficient in `{0, 1, -1}` takes at most 23 instructions. + +## References + +The linear forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Registers outside the signed workspace and the form's three local registers. -/ +def FormFrame (x y : Data 32) : Prop := + y.mem = x.mem ∧ ∀ i, 9 ≤ i.val → i ≠ address → i ≠ accumulator → i ≠ temporary → + y.regs i = x.regs i + +/-- Form frames compose without changing any memory cell. -/ +theorem FormFrame.trans {x y z : Data 32} (hxy : FormFrame x y) (hyz : FormFrame y z) : + FormFrame x z := + ⟨hyz.1.trans hxy.1, fun i hi ha hc ht => + (hyz.2 i hi ha hc ht).trans (hxy.2 i hi ha hc ht)⟩ + +private theorem fits_of_abs_lt {w : ℕ} (hw : 0 < w) {a : ℤ} + (ha : |a| < (2 : ℤ) ^ (w - 1)) : GenericRAM.Signed.Fits w a := by + have h := abs_lt.mp ha + exact ⟨hw, h.1.le, h.2⟩ + +/-- A single coefficient is executed by real loads and signed arithmetic. The seven address +computations fit whenever the one-past-end address fits. The two operands must fit the signed +source range; the sum itself may use the two extra target bits supplied by the arithmetic block. -/ +theorem exec_summand {w : ℕ} (x : Data 32) (s : Fin 7) (c a v : ℤ) (base n : ℕ) + (hc : c = 0 ∨ c = 1 ∨ c = -1) + (hacc : x.regs accumulator = encode a) (hbase : x.regs group = base) + (hn : x.regs stride = n) (hmem : x.mem (base + s.val * n) = encode v) + (ha : GenericRAM.Signed.Fits w a) (hv : GenericRAM.Signed.Fits w v) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (summand s c) x y 23 ∧ + y.regs accumulator = encode (a + c * v) ∧ FormFrame x y := by + rcases hc with rfl | rfl | rfl + · refine ⟨x.set accumulator (x.regs accumulator), (ExecWithin.copy).mono (by decide), ?_, ?_⟩ + · simp [hacc] + · refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [Data.set_regs, Function.update_of_ne hi] + all_goals + have hs : Fits (w + 2) s.val := hseven.of_le (by omega) + have hmul : Fits (w + 2) (s.val * n) := haddr.of_le (by nlinarith [s.isLt]) + have hsum : Fits (w + 2) (base + s.val * n) := haddr.of_le (by nlinarith [s.isLt]) + let z := ((((x.set temporary s.val).set address (s.val * n)).set address + (base + s.val * n)).set right (encode v)).set left (encode a) + have hz : ExecWithin (w + 2) + (ram[ temporary ← #s.val; address ← temporary * stride; + address ← group + address; right ← *address; left ← accumulator ]) x z 5 := by + ram_eval [z, hs, hmul, hsum, hn, hbase, hacc, hmem, + temporary, address, group, stride, right, left, accumulator, Fin.mk.injEq] + have hza : z.regs left = encode a := by simp [z] + have hzv : z.regs right = encode v := by + simp [z, left, right] + have hzframe : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i hi hia hic hit + have hil : i ≠ left := by intro h; subst i; simp at hi + have hir : i ≠ right := by intro h; subst i; simp at hi + simp only [z, Data.set_regs, Function.update_of_ne hil, Function.update_of_ne hir, + Function.update_of_ne hia, Function.update_of_ne hit] + · obtain ⟨y, he, hyr, hym, hyreg⟩ := exec_add ha hv z hza hzv + refine ⟨y.set accumulator (y.regs result), ?_, ?_, ?_⟩ + · simpa only [summand, show ¬(1 : ℤ) = 0 by decide, ↓reduceIte] using + (hz.seq (he.seq (ExecWithin.copy (d := accumulator) (s := result)))).mono + (by decide : 5 + (15 + 1) ≤ 23) + · simp [GenericRAM.Signed.Arithmetic.eval, hyr] + · refine ⟨hym.trans hzframe.1, ?_⟩ + intro i hi hia hic hit + simpa only [Data.set_regs, Function.update_of_ne hic] using + (hyreg i (Or.inr hi)).trans (hzframe.2 i hi hia hic hit) + · obtain ⟨y, he, hyr, hym, hyreg⟩ := exec_subtract ha hv z hza hzv + refine ⟨y.set accumulator (y.regs result), ?_, ?_, ?_⟩ + · simpa only [summand, show ¬(-1 : ℤ) = 0 by decide, + show ¬(-1 : ℤ) = 1 by decide, ↓reduceIte] using + (hz.seq (he.seq (ExecWithin.copy (d := accumulator) (s := result)))).mono + (by decide : 5 + (17 + 1) ≤ 23) + · simp [GenericRAM.Signed.Arithmetic.eval, hyr, sub_eq_add_neg] + · refine ⟨hym.trans hzframe.1, ?_⟩ + intro i hi hia hic hit + simpa only [Data.set_regs, Function.update_of_ne hic] using + (hyreg i (Or.inr hi)).trans (hzframe.2 i hi hia hic hit) + +/-- Execute any finite list of the fixed digit/coefficients. Partial sums are bounded by +`|a| + length * B`; the theorem derives each arithmetic operand's fit from that inequality. -/ +theorem exec_sumCode {w : ℕ} (hw : 0 < w) (pairs : List (Fin 7 × ℤ)) + (x : Data 32) (a B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hc : ∀ sc ∈ pairs, sc.2 = 0 ∨ sc.2 = 1 ∨ sc.2 = -1) + (hacc : x.regs accumulator = encode a) (hbase : x.regs group = base) + (hn : x.regs stride = n) (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfitB : B < (2 : ℤ) ^ (w - 1)) + (hfit : |a| + pairs.length * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (sumCode pairs) x y (23 * pairs.length + 1) ∧ + y.regs accumulator = encode (a + (pairs.map fun sc => sc.2 * v sc.1).sum) ∧ + FormFrame x y := by + induction pairs generalizing x a with + | nil => + refine ⟨x.set accumulator (x.regs accumulator), ExecWithin.copy, ?_, ?_⟩ + · simpa using hacc + · refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [Data.set_regs, Function.update_of_ne hi] + | cons sc rest ih => + obtain ⟨s, c⟩ := sc + have hc0 := hc (s, c) (by simp) + have hcv : |c * v s| ≤ B := by + rcases hc0 with rfl | rfl | rfl + · simpa using hB + · simpa using hv s + · simpa using hv s + have ha : GenericRAM.Signed.Fits w a := + fits_of_abs_lt hw (by have := abs_nonneg a; simp only [List.length_cons, + Nat.cast_add, Nat.cast_one] at hfit; nlinarith [Nat.cast_nonneg (α := ℤ) rest.length]) + have hvs := fits_of_abs_lt hw ((hv s).trans_lt hfitB) + obtain ⟨y, he, hyacc, hyframe⟩ := exec_summand x s c a (v s) base n hc0 hacc + hbase hn (hmem s) ha hvs haddr hseven + have hybase : y.regs group = base := (hyframe.2 group (by decide) (by decide) + (by decide) (by decide)).trans hbase + have hyn : y.regs stride = n := (hyframe.2 stride (by decide) (by decide) + (by decide) (by decide)).trans hn + have hfit' : |a + c * v s| + rest.length * B < (2 : ℤ) ^ (w - 1) := by + have ht := abs_add_le a (c * v s) + simp only [List.length_cons, Nat.cast_add, Nat.cast_one] at hfit + nlinarith + obtain ⟨z, hz, hzacc, hzframe⟩ := ih y (a + c * v s) + (fun sc hsc => hc sc (by simp [hsc])) hyacc hybase hyn + (fun s => by rw [hyframe.1]; exact hmem s) hfit' + refine ⟨z, ?_, ?_, hyframe.trans hzframe⟩ + · simpa only [sumCode, List.length_cons, Nat.mul_add, Nat.mul_one, + Nat.add_assoc, Nat.add_comm, Nat.add_left_comm] using he.seq hz + · simpa only [List.map_cons, List.sum_cons, add_assoc] using hzacc + +/-- A complete coefficient row, including initialization, costs at most 163 instructions. +All seven entries have absolute value at most `B`, so every partial sum has magnitude at +most `7B`; this ordinary explicit width condition is sufficient for every signed operation. -/ +theorem exec_form {w : ℕ} (hw : 0 < w) (side : Bool) (t : Fin 10) + (x : Data 32) (B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hbase : x.regs group = base) (hn : x.regs stride = n) + (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfit : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hseven : Fits (w + 2) 7) : + ∃ y, ExecWithin (w + 2) (form side t) x y 163 ∧ + y.regs accumulator = encode (∑ s, coefficients side t s * v s) ∧ FormFrame x y := by + let z := x.set accumulator 0 + have hz : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i _ _ hi _ + simp only [z, Data.set_regs, Function.update_of_ne hi] + obtain ⟨y, he, hacc, hf⟩ := exec_sumCode hw + (List.ofFn fun s => (s, coefficients side t s)) z 0 B base n v + (by intro sc hsc; obtain ⟨s, rfl⟩ := List.mem_ofFn.mp hsc; exact coefficients_values side t s) + (by simp [z, encode]) + ((hz.2 group (by decide) (by decide) (by decide) (by decide)).trans hbase) + ((hz.2 stride (by decide) (by decide) (by decide) (by decide)).trans hn) + (by simpa only [z, Data.set_mem] using hmem) hB hv (by linarith) + (by simpa using hfit) haddr hseven + refine ⟨y, ?_, ?_, hz.trans hf⟩ + · simpa only [form, List.length_ofFn] using + (ExecWithin.const (fits_zero (w := w + 2))).seq he + · simpa only [List.map_ofFn, List.sum_ofFn, Function.comp_apply, zero_add] using hacc + +/-- The finite term dispatch costs at most three instructions per inspected row in addition +to the row's 163-instruction bound. All ten forms share the same native register file. -/ +theorem exec_dispatch {w : ℕ} (hw : 0 < w) (side : Bool) (rows : List (Fin 10)) + (t : Fin 10) (ht : t ∈ rows) (x : Data 32) (B : ℤ) (base n : ℕ) (v : Fin 7 → ℤ) + (hterm : x.regs term = t.val) (hbase : x.regs group = base) (hn : x.regs stride = n) + (hmem : ∀ s, x.mem (base + s.val * n) = encode (v s)) + (hB : 0 ≤ B) (hv : ∀ s, |v s| ≤ B) (hfit : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (base + 7 * n)) (hten : Fits (w + 2) 10) : + ∃ y, ExecWithin (w + 2) (dispatch side rows) x y (3 * rows.length + 163) ∧ + y.regs accumulator = encode (∑ s, coefficients side t s * v s) ∧ FormFrame x y := by + induction rows generalizing x with + | nil => simp at ht + | cons q rest ih => + let z := x.set temporary q.val + have hz : ExecWithin (B := .standard) (w + 2) (ram[ temporary ← #q.val ]) x z 1 := + ExecWithin.const (hten.of_le (by omega)) + have hzf : FormFrame x z := by + refine ⟨rfl, ?_⟩ + intro i _ _ _ hi + simp only [z, Data.set_regs, Function.update_of_ne hi] + have hzt : z.regs term = t.val := by + simpa [z, term, temporary, Fin.mk.injEq] using hterm + have hzb : z.regs group = base := by + simpa [z, group, temporary, Fin.mk.injEq] using hbase + have hzn : z.regs stride = n := by + simpa [z, stride, temporary, Fin.mk.injEq] using hn + have hzm : ∀ s, z.mem (base + s.val * n) = encode (v s) := hmem + by_cases hqt : t = q + · subst t + obtain ⟨y, he, hacc, hf⟩ := exec_form hw side q z B base n v hzb hzn hzm hB hv + hfit haddr (hten.of_le (by decide)) + refine ⟨y, ?_, hacc, hzf.trans hf⟩ + apply (hz.seq (ExecWithin.branch_pos ?_ he)).mono + · simp only [List.length_cons] + omega + · simp only [Cond.holds_eq, hzt, z, Data.set_regs, Function.update_self] + · have htrest : t ∈ rest := (List.mem_cons.mp ht).resolve_left hqt + obtain ⟨y, he, hacc, hf⟩ := ih htrest z hzt hzb hzn hzm + refine ⟨y, ?_, hacc, hzf.trans hf⟩ + apply (hz.seq (ExecWithin.branch_neg ?_ he)).mono + · simp only [List.length_cons] + omega + · simp only [Cond.holds_eq, hzt, z, Data.set_regs, Function.update_self] + exact fun h => hqt (Fin.ext h) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean new file mode 100644 index 0000000000..5beb51aafe --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Header.lean @@ -0,0 +1,174 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.MatrixMultiplication.Schonhage.Encoding + +/-! +# Charged initialization from the actual depth header + +## Main results + +* `Encoding.exec_setup`: load the header and compute both powers, buffer addresses and + loop parameters within `5L+18` native instructions, preserving every memory cell. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM + +/-- An explicit bound for the encoder's unsigned addresses, index products and counters. -/ +def addressBound (L : ℕ) : ℕ := L + 64 + 64 * 10 ^ L + +/-- Registers and memory after header initialization. This is only a postcondition; it +contains no execution, resource or intermediate-fit assumption. -/ +structure SetupPost (L : ℕ) (x y : Data 32) : Prop where + /-- Memory is unchanged. -/ + mem : y.mem = x.mem + /-- Depth loaded from memory address one. -/ + depth : y.regs Encoding.depth = L + /-- Computed input size. -/ + inputSize : y.regs Encoding.inputSize = 7 ^ L + /-- Computed buffer capacity. -/ + capacity : y.regs Encoding.capacity = 10 ^ L + /-- First output address. -/ + output : y.regs Encoding.output = outputBase L + /-- First scratch address. -/ + scratch : y.regs Encoding.scratch = scratchBase L + /-- Original input base. -/ + src : y.regs Encoding.src = 2 + /-- First destination buffer. -/ + dst : y.regs Encoding.dst = outputBase L + /-- First source suffix length. -/ + stride : y.regs Encoding.stride = 7 ^ L + /-- The empty transformed prefix. -/ + prefixes : y.regs Encoding.prefixes = 1 + /-- No coordinates transformed yet. -/ + stage : y.regs Encoding.stage = 0 + /-- Counter increment. -/ + one : y.regs Encoding.one = 1 + /-- Input radix. -/ + seven : y.regs Encoding.seven = 7 + /-- Output radix. -/ + ten : y.regs Encoding.ten = 10 + +/-- Header loads, both power computations, address arithmetic and counter initialization +are included. The only fit premise is the explicit bound on ordinary natural numbers. -/ +theorem exec_setup {w : ℕ} (L : ℕ) (x : Data 32) (hheader : x.mem 1 = L) + (haddr : Fits w (addressBound L)) : + ∃ y, ExecWithin w setup x y (5 * L + 18) ∧ SetupPost L x y := by + have h1 : Fits w 1 := haddr.of_le (by unfold addressBound; omega) + have h2 : Fits w 2 := haddr.of_le (by unfold addressBound; omega) + have h7 : Fits w 7 := haddr.of_le (by unfold addressBound; omega) + have h10 : Fits w 10 := haddr.of_le (by unfold addressBound; omega) + have hpow (b j : ℕ) (hb : b ≤ 10) (hj : j ≤ L) : Fits w (b ^ j) := by + apply haddr.of_le + have hbj := Nat.pow_le_pow_left hb j + have hjL := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hj + unfold addressBound + omega + let I (y : Data 32) : Prop := + y.mem = x.mem ∧ y.regs depth = L ∧ y.regs inputSize = 7 ^ y.regs stage ∧ + y.regs capacity = 10 ^ y.regs stage ∧ y.regs stage ≤ L ∧ + y.regs one = 1 ∧ y.regs seven = 7 ∧ y.regs ten = 10 + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt stage depth).Holds y) : + ∃ z, ExecWithin w powerBody y z 3 ∧ I z ∧ L - z.regs stage < L - y.regs stage := by + obtain ⟨hm, hd, hi, hcap, hj, ho, hs, ht⟩ := hy + have hjL : y.regs stage < L := by simpa only [Cond.holds_lt, hd] using hc + have hpi : Fits w (y.regs inputSize * y.regs seven) := by + rw [hi, hs, ← pow_succ] + exact hpow 7 _ (by decide) (by omega) + have hpc : Fits w (y.regs capacity * y.regs ten) := by + rw [hcap, ht, ← pow_succ] + exact hpow 10 _ (by decide) (by omega) + have hjfit : Fits w (y.regs stage + y.regs one) := by + rw [ho] + exact haddr.of_le (by unfold addressBound; omega) + have hpi' : Fits w (y.regs inputSize * 7) := by simpa only [hs] using hpi + have hpc' : Fits w (y.regs capacity * 10) := by simpa only [ht] using hpc + have hjfit' : Fits w (y.regs stage + 1) := by simpa only [ho] using hjfit + let z := ((y.set inputSize (y.regs inputSize * 7)).set capacity + (y.regs capacity * 10)).set stage (y.regs stage + 1) + have he : ExecWithin w powerBody y z 3 := by + ram_eval [powerBody, z, ho, hs, ht, hpi', hpc', hjfit', + inputSize, capacity, stage, one, seven, ten, Fin.mk.injEq] + refine ⟨z, he, ?_, ?_⟩ + · refine ⟨hm, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [z, depth, inputSize, capacity, stage, Function.update_apply] using hd + · simp +decide [z, inputSize, capacity, stage, pow_succ, hi] + · simp +decide [z, inputSize, capacity, stage, pow_succ, hcap] + · simpa [z] using Nat.succ_le_of_lt hjL + · simpa +decide [z, one, inputSize, capacity, stage, Function.update_apply] using ho + · simpa +decide [z, seven, inputSize, capacity, stage, Function.update_apply] using hs + · simpa +decide [z, ten, inputSize, capacity, stage, Function.update_apply] using ht + · simp only [z, Data.set_regs, Function.update_self] + omega + let x0 := (((((((x.set one 1).set seven 7).set ten 10).set address 1).set depth L).set + inputSize 1).set capacity 1).set stage 0 + have hx0 : I x0 := by + simp +decide [I, x0, depth, inputSize, capacity, stage, one, seven, ten, address] + have hxrun : ExecWithin w setupHead x x0 8 := by + ram_eval [setupHead, x0, hheader, h1, h7, h10, fits_zero, + one, seven, ten, address, depth, inputSize, capacity, stage, Fin.mk.injEq] + obtain ⟨u, hu, hi, hc⟩ := ExecWithin.loop_invariant I (fun y => L - y.regs stage) hbody hx0 + obtain ⟨hm, hd, hin, hcap, hj, ho, hs, ht⟩ := hi + have hend : u.regs stage = L := by + simp only [Cond.holds_lt, hd] at hc + omega + rw [hend] at hin hcap + have hout : Fits w (2 + 7 ^ L) := haddr.of_le (by + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega) + have hscratch : Fits w (2 + 7 ^ L + 10 ^ L) := haddr.of_le (by + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega) + let y := (((((((u.set temporary 2).set output (outputBase L)).set scratch (scratchBase L)).set + src 2).set dst (outputBase L)).set stride (7 ^ L)).set prefixes 1).set stage 0 + have hyrun : ExecWithin w setupTail u y 8 := by + ram_eval [setupTail, y, outputBase, scratchBase, hin, hcap, h2, h1, hout, hscratch, + temporary, output, scratch, src, dst, stride, prefixes, stage, inputSize, capacity, + Fin.mk.injEq] + refine ⟨y, ?_, ?_⟩ + · have hcost : 8 + ((L - x0.regs stage) * (3 + 2) + 2 + 8) ≤ 5 * L + 18 := by + simp only [x0, Data.set_regs, Function.update_self, Nat.sub_zero] + omega + exact (hxrun.seq (hu.seq hyrun)).mono hcost + · constructor + all_goals simp +decide [y, outputBase, scratchBase, temporary, output, scratch, src, dst, + stride, prefixes, stage, depth, inputSize, capacity, one, seven, ten, + hm, hd, hin, hcap, ho, hs, ht] + +/-- The actual flat input fits the target machine. Each signed input value uses the existing +canonical encoding; the depth and the ordinary unsigned length header are bounded separately. -/ +theorem input_fits {w L : ℕ} (hw : 0 < w) (a : Fin (7 ^ L) → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ i, |a i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : InputFits (w + 2) (input L a) := by + have hpow : (1 : ℤ) ≤ (7 ^ L : ℕ) := by + exact_mod_cast Nat.one_le_pow L 7 (by decide) + have hAfit : A < (2 : ℤ) ^ (w - 1) := by nlinarith + refine ⟨?_, ?_⟩ + · simp only [input, List.length_cons, List.length_ofFn] + apply haddr.of_le + have := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold addressBound + omega + · intro v hv + rcases List.mem_cons.mp hv with rfl | hv + · exact haddr.of_le (by unfold addressBound; omega) + · obtain ⟨i, rfl⟩ := List.mem_ofFn.mp hv + have hi : GenericRAM.Signed.Fits w (a i) := by + have h := abs_lt.mp ((ha i).trans_lt hAfit) + exact ⟨hw, h.1.le, h.2⟩ + exact (Cslib.WordRAM.Signed.encode_fits hi).mono (by omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean new file mode 100644 index 0000000000..727d0f96f8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Input.lean @@ -0,0 +1,127 @@ +/- +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.MatrixMultiplication.Schonhage.Exec +import Mathlib.Data.List.GetD + +/-! +# Actual initialized-input clients for the shared encoders + +## Main results + +* `Encoding.exec_input`: flat signed array, concrete depth header and actual `State.init`. +* `Encoding.exec_left`, `Encoding.exec_right`: all public tensor encodings, at every depth. + +The two programs and their thirty-two registers are independent of depth and word width. +Inputs are signed words encoded with `WordRAM.Signed.encode`; the depth and the machine's +length header are ordinary unsigned words. All input and reachable data words fit the target +width. Output and scratch are disjoint concrete blocks immediately after the input. + +## References + +The encodings are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +private theorem getD_ofFn {N : ℕ} (f : Fin N → ℤ) (i : Fin N) : + (List.ofFn f).getD i.val 0 = f i := by + rw [List.getD_eq_getElem _ _ (by simp)] + simp + +/-- The complete native encoder starts from the ordinary `State.init` convention with a +real depth header and encoded input words. It returns all flat shared-transform values, keeps +the input and every outside cell, and leaves only target-sized words in memory and registers. -/ +theorem exec_input {w L : ℕ} (hw : 0 < w) (side : Bool) (a : Fin (7 ^ L) → ℤ) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ i, |a i| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (input L a) ∧ + ∃ y, ExecWithin (w + 2) (program side) (State.init (input L a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ p < 10 ^ L, y.mem (outputBase L + p) = encode + (value (table (coefficients side)) L 0 (fun i => (List.ofFn a).getD i 0) p 0)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (input L a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + have hi := input_fits hw a A hA ha hmag haddr + let x : Data 32 := (State.init (input L a)).data + have hheader : x.mem 1 = L := by + simp [x, State.init, Data.init, input] + have hinput (i : ℕ) (hi : i < 7 ^ L) : + x.mem (2 + i) = encode ((List.ofFn a).getD i 0) := by + have hidx : 2 + i = (i + 1) + 1 := by omega + change (Data.init (input L a) : Data 32).mem (2 + i) = _ + rw [hidx, Data.init_mem_succ] + simp only [input, List.getD_cons_succ] + rw [List.getD_eq_getElem _ _ (by simpa only [List.length_ofFn] using hi)] + simpa using congrArg encode (getD_ofFn a ⟨i, hi⟩).symm + have ha' (i : ℕ) (hi : i < 7 ^ L) : |(List.ofFn a).getD i 0| ≤ A := by + rw [getD_ofFn a ⟨i, hi⟩] + exact ha _ + obtain ⟨y, he, hv, hf, ho, hc⟩ := exec hw side (fun i => (List.ofFn a).getD i 0) x + hheader hinput A hA ha' hmag haddr + exact ⟨hi, y, he, he.bounded (Data.Bounded.init hi), hv, hf, ho, hc⟩ + +/-- Complete left encoder on arbitrary signed arrays indexed by the original seven-symbol +strings. Every one of the `10^L` leaves receives the unchanged public `encodingL` value. -/ +theorem exec_left {w L : ℕ} (hw : 0 < w) (a : LeftStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputL a) ∧ + ∃ y, ExecWithin (w + 2) (program false) (State.init (inputL a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingL a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputL a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + let f : Fin (7 ^ L) → ℤ := fun i => a (fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ)) + obtain ⟨hi, y, he, hb, hv, hf, ho, hc⟩ := exec_input hw false f A hA (fun _ => ha _) hmag haddr + refine ⟨hi, y, he, hb, ?_, hf, ho, hc⟩ + intro τ + have harr (u : Fin L → Fin 7) : (List.ofFn f).getD (finFunctionFinEquiv u).val 0 = + a (fun i => leftAt (u i)) := by + rw [getD_ofFn] + simp only [f, Equiv.symm_apply_apply] + have h := hv (leafIndex τ) (leafIndex τ).isLt + rw [value_final_left a _ harr τ] at h + exact h + +/-- Complete right encoder on arbitrary signed arrays with the original `encodingR` API. -/ +theorem exec_right {w L : ℕ} (hw : 0 < w) (a : RightStr L → ℤ) (A : ℤ) + (hA : 0 ≤ A) (ha : ∀ u, |a u| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L)) : + InputFits (w + 2) (inputR a) ∧ + ∃ y, ExecWithin (w + 2) (program true) (State.init (inputR a)).data y (timeBound L) ∧ + y.Bounded (w + 2) ∧ + (∀ τ, y.mem (outputBase L + (leafIndex τ).val) = encode (encodingR a τ)) ∧ + (∀ b, ¬(outputBase L ≤ b ∧ b < scratchBase L + 10 ^ L) → + y.mem b = (State.init (inputR a) : State 32).data.mem b) ∧ + y.regs output = outputBase L ∧ y.regs capacity = 10 ^ L := by + let f : Fin (7 ^ L) → ℤ := fun i => a (fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ)) + obtain ⟨hi, y, he, hb, hv, hf, ho, hc⟩ := exec_input hw true f A hA (fun _ => ha _) hmag haddr + refine ⟨hi, y, he, hb, ?_, hf, ho, hc⟩ + intro τ + have harr (u : Fin L → Fin 7) : (List.ofFn f).getD (finFunctionFinEquiv u).val 0 = + a (fun i => rightAt (u i)) := by + rw [getD_ofFn] + simp only [f, Equiv.symm_apply_apply] + have h := hv (leafIndex τ) (leafIndex τ).isLt + rw [value_final_right a _ harr τ] at h + exact h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean new file mode 100644 index 0000000000..a22fed2332 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Meaning.lean @@ -0,0 +1,96 @@ +/- +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.MatrixMultiplication.Schonhage.Encoding +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Stage +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.CoordinateTransform + +/-! +# Agreement of the flat tensor transform with the public encodings + +The digit numbering is supplied by Mathlib's `finFunctionFinEquiv`. The explicit alphabet +numberings contain exactly the original seven variables and ten terms. The final mixed-radix +transform is `encodingL` or `encodingR` for every depth, including zero. + +## Main results + +* `Encoding.value_final_left`, `Encoding.value_final_right`: unchanged public encoding semantics. + +## References + +The original encodings and coefficients are from [AlmanWilliams2026, Section 2.4.1], selectively +adapted from Anthropic, PBC's formal source; see `Encoding` for the exact pin and paths. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open CoordinateTransform + +/-- The numbered output position of a term string. -/ +def leafIndex {L : ℕ} (τ : Leaf L) : Fin (10 ^ L) := + finFunctionFinEquiv fun i => termDigits.symm (τ i) + +private theorem value_final_alphabet {α : Type} [Fintype α] (e : Fin 7 ≃ α) + (c : Term → α → ℤ) {L : ℕ} (a : (Fin L → α) → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => e (u i))) + (τ : Leaf L) : + value (table (fun t s => c (termAt t) (e s))) L 0 arr (leafIndex τ) 0 = + encodeWith c τ a := by + have h := value_final (fun t s => c (termAt t) (e s)) L + (fun u => a (fun i => e (u i))) arr harr (fun i => termDigits.symm (τ i)) + change value (table (fun t s => c (termAt t) (e s))) L 0 arr + (finFunctionFinEquiv fun i => termDigits.symm (τ i)) 0 = _ + rw [h] + unfold encodeWith + refine Fintype.sum_equiv (Equiv.piCongrRight fun _ : Fin L => e) _ _ ?_ + intro u + congr 1 + apply Finset.prod_congr rfl + intro i _ + change c (termDigits (termDigits.symm (τ i))) (e (u i)) = c (τ i) (e (u i)) + rw [Equiv.apply_symm_apply] + +/-- The flat shared left transform equals the existing `encodingL` on every leaf. -/ +theorem value_final_left {L : ℕ} (a : LeftStr L → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => leftAt (u i))) + (τ : Leaf L) : + value (table (coefficients false)) L 0 arr (leafIndex τ) 0 = encodingL a τ := + value_final_alphabet leftDigits phi a arr harr τ + +/-- The flat shared right transform equals the existing `encodingR` on every leaf. -/ +theorem value_final_right {L : ℕ} (a : RightStr L → ℤ) (arr : ℕ → ℤ) + (harr : ∀ u : Fin L → Fin 7, arr (finFunctionFinEquiv u) = a (fun i => rightAt (u i))) + (τ : Leaf L) : + value (table (coefficients true)) L 0 arr (leafIndex τ) 0 = encodingR a τ := + value_final_alphabet rightDigits psi a arr harr τ + +/-- The native stage's row-wise specification is the next shared-prefix value. This includes +the quotient/remainder decomposition of its actual flat loop counter. -/ +theorem stageResult_value (side : Bool) (j k i : ℕ) (arr : ℕ → ℤ) : + stageResult side (7 ^ k) + (fun i a => value (table (coefficients side)) j (k + 1) arr + (i / (10 * 7 ^ k)) (a.val * 7 ^ k + i % 7 ^ k)) i = + value (table (coefficients side)) (j + 1) k arr (i / 7 ^ k) (i % 7 ^ k) := by + have hdiv : i / 7 ^ k / 10 = i / (10 * 7 ^ k) := by + rw [Nat.div_div_eq_div_mul, Nat.mul_comm (7 ^ k) 10] + have hprefix : i / (10 * 7 ^ k) * 10 + i / 7 ^ k % 10 = i / 7 ^ k := by + simpa only [hdiv, Nat.mul_comm] using Nat.div_add_mod (i / 7 ^ k) 10 + have he := value_step (table (coefficients side)) j k arr (i / (10 * 7 ^ k)) + (i % 7 ^ k) (Nat.mod_lt (i / 7 ^ k) (by decide : 0 < 10)) + rw [hprefix] at he + unfold stageResult + rw [he, ← Fin.sum_univ_eq_sum_range] + apply Finset.sum_congr rfl + intro a _ + simp only [table, Nat.mod_mod, Nat.mod_eq_of_lt a.isLt] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean new file mode 100644 index 0000000000..85c382fd0b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Stage.lean @@ -0,0 +1,203 @@ +/- +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.MatrixMultiplication.Schonhage.Cell + +/-! +# Charged shared-coordinate array loop + +## Main results + +* `Encoding.exec_cells`: all cells in one shared coordinate transform, preserving the input + and every memory cell outside the destination block. + +## References + +The forms are from [AlmanWilliams2026, Section 2.4.1]; see `Encoding` for provenance. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The linear-form value at flat output position `i` of a stage with suffix stride `n`. -/ +def stageResult (side : Bool) (n : ℕ) (v : ℕ → Fin 7 → ℤ) (i : ℕ) : ℤ := + ∑ a, coefficients side ⟨i / n % 10, Nat.mod_lt _ (by decide)⟩ a * v i a + +/-- The complete cell loop has at most `207 * (10 * P * n) + 2` instructions. The seven +source entries for each output are shared values from the previous array, not separately +enumerated original input terms. Disjointness is permitted in either address order. -/ +theorem exec_cells {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d : ℕ) (hP : 0 < P) (hnpos : 0 < n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hcell : x.regs cell = 0) + (hn : x.regs stride = n) (hseven : x.regs seven = 7) + (hten : x.regs ten = 10) (hone : x.regs one = 1) + (hsize : x.regs stageSize = 10 * P * n) + (v : ℕ → Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B) + (hmem : ∀ i < 10 * P * n, ∀ a, + x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) + (hdisj : s + 7 * P * n ≤ d ∨ d + 10 * P * n ≤ s) : + ∃ y, ExecWithin (w + 2) (.loop (.lt cell stageSize) (cellBody side)) x y + (207 * (10 * P * n) + 2) ∧ + (∀ i < 10 * P * n, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 10 * P * n) → y.mem a = x.mem a) ∧ + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) := by + let N := 10 * P * n + let I (y : Data 32) : Prop := + (∀ a, 9 ≤ a.val → ¬CellLocal a → y.regs a = x.regs a) ∧ y.regs cell ≤ N ∧ + (∀ i < y.regs cell, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + y.regs cell) → y.mem a = x.mem a) + have hbody (y : Data 32) (hy : I y) (hc : (Cond.lt cell stageSize).Holds y) : + ∃ z, ExecWithin (w + 2) (cellBody side) y z 205 ∧ I z ∧ + N - z.regs cell < N - y.regs cell := by + obtain ⟨hr, hiN, hm, hf⟩ := hy + have hys : y.regs src = s := (hr src (by decide) (by decide)).trans hsrc + have hyd : y.regs dst = d := (hr dst (by decide) (by decide)).trans hdst + have hyn : y.regs stride = n := (hr stride (by decide) (by decide)).trans hn + have hy7 : y.regs seven = 7 := (hr seven (by decide) (by decide)).trans hseven + have hy10 : y.regs ten = 10 := (hr ten (by decide) (by decide)).trans hten + have hy1 : y.regs one = 1 := (hr one (by decide) (by decide)).trans hone + have hysz : y.regs stageSize = N := (hr stageSize (by decide) (by decide)).trans hsize + have hi : y.regs cell < N := by simpa only [Cond.holds_lt, hysz] using hc + have hyinput (a : Fin 7) : + y.mem (s + (y.regs cell / (10 * n) * 7 + a.val) * n + y.regs cell % n) = + encode (v (y.regs cell) a) := by + have hp : y.regs cell / (10 * n) < P := by + apply (Nat.div_lt_iff_lt_mul (by positivity : 0 < 10 * n)).mpr + dsimp only [N] at hi + nlinarith + have hmod := Nat.mod_lt (y.regs cell) hnpos + have hprefix : y.regs cell / (10 * n) * 7 + a.val < P * 7 := by omega + have hproduct := Nat.mul_le_mul_right n (Nat.succ_le_of_lt hprefix) + have hoff : (y.regs cell / (10 * n) * 7 + a.val) * n + y.regs cell % n < + P * 7 * n := by nlinarith only [hproduct, hmod] + rw [Nat.mul_comm P 7] at hoff + rw [hf _ ?_] + · exact hmem _ hi a + · rcases hdisj with h | h <;> dsimp only [N] at hiN <;> omega + obtain ⟨z, he, hzmem, hzcell, hzreg⟩ := exec_cell hw side y P n s d (y.regs cell) + hP hnpos hi hys hyd rfl hyn hy7 hy10 hy1 (v (y.regs cell)) B hB (hv _ hi) + hyinput hval haddr + refine ⟨z, he, ?_, by rw [hzcell]; omega⟩ + refine ⟨fun a ha hl => (hzreg a ha hl).trans (hr a ha hl), by omega, ?_, ?_⟩ + · intro i hzi + rw [hzcell] at hzi + by_cases hic : i = y.regs cell + · subst i + simp only [hzmem, Function.update_self, stageResult] + · have hin : i < y.regs cell := by omega + rw [hzmem, Function.update_of_ne (by omega : d + i ≠ d + y.regs cell)] + exact hm i hin + · intro a ha + rw [hzcell] at ha + have hne : a ≠ d + y.regs cell := by omega + rw [hzmem, Function.update_of_ne hne] + exact hf a (by omega) + have hx : I x := by + refine ⟨fun _ _ _ => rfl, by simp [hcell, N], ?_, ?_⟩ + · simp only [hcell, Nat.not_lt_zero, false_implies, implies_true] + · intro a _ + rfl + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs cell) hbody hx + obtain ⟨hr, hi, hm, hf⟩ := hy + have hysz : y.regs stageSize = N := (hr stageSize (by decide) (by decide)).trans hsize + have hend : y.regs cell = N := by + simp only [Cond.holds_lt, hysz] at hc + omega + refine ⟨y, ?_, ?_, ?_, hr⟩ + · simpa only [hcell, N, Nat.sub_zero, Nat.add_comm, Nat.mul_comm] using he + · simpa only [hend] using hm + · simpa only [hend] using hf + +/-- Registers changed by the stage controller or its cell loop. -/ +def StageLocal (i : Fin 32) : Prop := + CellLocal i ∨ i = stride ∨ i = stageSize ∨ i = src ∨ i = dst ∨ i = prefixes ∨ i = stage + +instance (i : Fin 32) : Decidable (StageLocal i) := + inferInstanceAs (Decidable (CellLocal i ∨ i = stride ∨ i = stageSize ∨ i = src ∨ + i = dst ∨ i = prefixes ∨ i = stage)) + +private theorem stage_preserves (side : Bool) (i : Fin 32) : + 9 ≤ i.val → ¬StageLocal i → (stageBody side).Preserves i := by + decide +kernel +revert + +/-- A complete coordinate stage, including stride division, stage-size arithmetic, both +cell-loop control instructions per entry, buffer alternation and the next-stage counters. -/ +theorem exec_stageBody {w : ℕ} (hw : 0 < w) (side : Bool) (x : Data 32) + (P n s d j o q : ℕ) (hP : 0 < P) (hnpos : 0 < n) + (hsrc : x.regs src = s) (hdst : x.regs dst = d) (hj : x.regs stage = j) + (hn : x.regs stride = 7 * n) (hp : x.regs prefixes = P) + (hseven : x.regs seven = 7) (hten : x.regs ten = 10) (hone : x.regs one = 1) + (hout : x.regs output = o) (hscratch : x.regs scratch = q) + (v : ℕ → Fin 7 → ℤ) (B : ℤ) (hB : 0 ≤ B) (hv : ∀ i < 10 * P * n, ∀ a, |v i a| ≤ B) + (hmem : ∀ i < 10 * P * n, ∀ a, + x.mem (s + (i / (10 * n) * 7 + a.val) * n + i % n) = encode (v i a)) + (hval : 7 * B < (2 : ℤ) ^ (w - 1)) + (haddr : Fits (w + 2) (s + d + 64 + 64 * P * n)) (hjfit : Fits (w + 2) (j + 1)) + (hdisj : s + 7 * P * n ≤ d ∨ d + 10 * P * n ≤ s) : + ∃ y, ExecWithin (w + 2) (stageBody side) x y (207 * (10 * P * n) + 12) ∧ + (∀ i < 10 * P * n, y.mem (d + i) = encode (stageResult side n v i)) ∧ + (∀ a, ¬(d ≤ a ∧ a < d + 10 * P * n) → y.mem a = x.mem a) ∧ + y.regs src = d ∧ y.regs dst = (if d = o then q else o) ∧ + y.regs stride = n ∧ y.regs prefixes = 10 * P ∧ y.regs stage = j + 1 ∧ + (∀ a, 9 ≤ a.val → ¬StageLocal a → y.regs a = x.regs a) := by + have hPn : n ≤ P * n := by nlinarith + have h10P : Fits (w + 2) (P * 10) := haddr.of_le (by nlinarith) + have hN : Fits (w + 2) (P * 10 * n) := haddr.of_le (by nlinarith) + have hnfit : Fits (w + 2) n := haddr.of_le (by nlinarith) + have hdiv : 7 * n / 7 = n := by omega + let z := (((x.set stride n).set stageSize (P * 10)).set stageSize (P * 10 * n)).set cell 0 + have hz : ExecWithin (w + 2) stageHead x z 4 := by + ram_eval [stageHead, z, hn, hseven, hp, hten, hdiv, hnfit, h10P, hN, + stride, prefixes, ten, seven, stageSize, cell, Fin.mk.injEq] + have hzr (a : Fin 32) (ha : a ≠ stride ∧ a ≠ stageSize ∧ a ≠ cell) : + z.regs a = x.regs a := by + simp only [z, Data.set_regs, Function.update_of_ne ha.1, Function.update_of_ne ha.2.1, + Function.update_of_ne ha.2.2] + obtain ⟨u, hu, hum, huf, hur⟩ := exec_cells hw side z P n s d hP hnpos + ((hzr src (by decide)).trans hsrc) ((hzr dst (by decide)).trans hdst) + (by simp [z]) (by simp [z, stride, stageSize, cell]) + ((hzr seven (by decide)).trans hseven) ((hzr ten (by decide)).trans hten) + ((hzr one (by decide)).trans hone) + (by simp [z, stageSize, cell, Nat.mul_comm]) v B hB hv hmem hval haddr hdisj + have ur (a : Fin 32) (ha : 9 ≤ a.val) (hl : ¬CellLocal a) + (hzl : a ≠ stride ∧ a ≠ stageSize ∧ a ≠ cell) : u.regs a = x.regs a := + (hur a ha hl).trans (hzr a hzl) + have us : u.regs dst = d := (ur dst (by decide) (by decide) (by decide)).trans hdst + have uo : u.regs output = o := (ur output (by decide) (by decide) (by decide)).trans hout + have uq : u.regs scratch = q := (ur scratch (by decide) (by decide) (by decide)).trans hscratch + have up : u.regs prefixes = P := (ur prefixes (by decide) (by decide) (by decide)).trans hp + have uj : u.regs stage = j := (ur stage (by decide) (by decide) (by decide)).trans hj + have ut : u.regs ten = 10 := (ur ten (by decide) (by decide) (by decide)).trans hten + have u1 : u.regs one = 1 := (ur one (by decide) (by decide) (by decide)).trans hone + have un : u.regs stride = n := by + rw [hur stride (by decide) (by decide)] + simp [z, stride, stageSize, cell] + let y := (((u.set src d).set dst (if d = o then q else o)).set prefixes (P * 10)).set + stage (j + 1) + have hy : ExecWithin (w + 2) stageTail u y 6 := by + by_cases hdo : d = o + all_goals ram_eval [stageTail, y, us, uo, uq, up, uj, ut, u1, hdo, h10P, hjfit, + src, dst, output, scratch, prefixes, stage, ten, one, Fin.mk.injEq] + have he : ExecWithin (w + 2) (stageBody side) x y (207 * (10 * P * n) + 12) := + (hz.seq (hu.seq hy)).mono (by omega) + refine ⟨y, he, hum, huf, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simp [y, src, dst, prefixes, stage] + · simp [y, dst, prefixes, stage] + · simpa [y, src, dst, prefixes, stage, stride] using un + · simp [y, prefixes, stage, Nat.mul_comm] + · simp [y] + · intro a ha hl + exact he.regs_eq (stage_preserves side a ha hl) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding 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/Copy.lean b/Complexity/Computability/WordRAM/Copy.lean index 62e6600b36..1652067c00 100644 --- a/Complexity/Computability/WordRAM/Copy.lean +++ b/Complexity/Computability/WordRAM/Copy.lean @@ -1,7 +1,7 @@ /- -Copyright (c) 2026 Samuel Schlesinger. All rights reserved. +Copyright (c) 2026 Samuel Schlesinger, Erik Demaine. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. -Authors: Samuel Schlesinger +Authors: Samuel Schlesinger, Erik Demaine -/ module @@ -10,10 +10,10 @@ module public import Complexity.Computability.WordRAM.Allocation /-! -# Copying disjoint arrays on the word RAM +# Copying arrays on the word RAM The seven-register copy routine charges every address calculation, load, store, increment, -and loop test. Its source precedes its destination, as in a bump-allocated growing array. +and loop test. Source and destination may be disjoint in either order or equal. It is a fixed program: neither the length nor either address is baked into the code. ## Main definitions @@ -29,6 +29,8 @@ It is a fixed program: neither the length nor either address is baked into the c * `Cslib.WordRAM.ArrayCopy.exec`: copying `n` words within `7 * n + 4` instructions, preserving the source and all cells outside the destination. +* `Cslib.WordRAM.ArrayCopy.exec_of_separated_or_eq`: the same native program also supports + disjoint blocks in the reverse address order or identical source and destination. ## References @@ -39,7 +41,7 @@ It is a fixed program: neither the length nor either address is baked into the c namespace Cslib.WordRAM.ArrayCopy -/-- The memory after copying a prefix into a disjoint destination block. -/ +/-- The memory after copying a prefix into a destination block. -/ def copyPrefix (mem : ℕ → ℕ) (src dst k : ℕ) : ℕ → ℕ := fun a => if dst ≤ a ∧ a < dst + k then mem (src + (a - dst)) else mem a @@ -94,10 +96,11 @@ def program : Code .standard 7 := ram[ idx ← #0; one ← #1; while idx < len d /-- Low-level structured copying charges register setup and all loop instructions, excluding the final halt. The one-past-end addresses must fit a word. Boundedness of the input memory and registers is a separate premise when using `ExecWithin.bounded` to obtain bounded-word execution. -/ -theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) +theorem exec_of_separated_or_eq {w : ℕ} (x : Data 7) (h1 : Fits w 1) (hs : Fits w (x.regs src + x.regs len)) (hd : Fits w (x.regs dst + x.regs len)) - (hdisj : x.regs src + x.regs len ≤ x.regs dst) : + (hdisj : x.regs src + x.regs len ≤ x.regs dst ∨ + x.regs dst + x.regs len ≤ x.regs src ∨ x.regs src = x.regs dst) : ∃ y, ExecWithin w program x y (7 * x.regs len + 4) ∧ y.mem = copyPrefix x.mem (x.regs src) (x.regs dst) (x.regs len) ∧ y.regs src = x.regs src ∧ y.regs dst = x.regs dst ∧ y.regs len = x.regs len := by @@ -118,7 +121,7 @@ theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) have hread : y.mem (y.regs src + y.regs idx) = x.mem (x.regs src + y.regs idx) := by rw [hym, hys] apply copyPrefix_outside - omega + rcases hdisj with h | h | h <;> omega refine ⟨?z, ?run, ?inv, ?decrease⟩ case run => ram_eval [body, hsrc, hdst, hinc, hyo] case inv => @@ -150,6 +153,16 @@ theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) omega · simpa [hend] using hym +/-- Forward copying between disjoint arrays, retaining the original calling convention. -/ +theorem exec {w : ℕ} (x : Data 7) (h1 : Fits w 1) + (hs : Fits w (x.regs src + x.regs len)) + (hd : Fits w (x.regs dst + x.regs len)) + (hdisj : x.regs src + x.regs len ≤ x.regs dst) : + ∃ y, ExecWithin w program x y (7 * x.regs len + 4) ∧ + y.mem = copyPrefix x.mem (x.regs src) (x.regs dst) (x.regs len) ∧ + y.regs src = x.regs src ∧ y.regs dst = x.regs dst ∧ y.regs len = x.regs len := + exec_of_separated_or_eq x h1 hs hd (Or.inl hdisj) + end Cslib.WordRAM.ArrayCopy end 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/SchonhageEncoding.lean b/ComplexityTests/SchonhageEncoding.lean new file mode 100644 index 0000000000..6f9d10937a --- /dev/null +++ b/ComplexityTests/SchonhageEncoding.lean @@ -0,0 +1,142 @@ +/- +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 +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Flat + +-- Full-program regressions intentionally evaluate the native interpreter at elaboration time. +set_option linter.hashCommand false + +/-! +# Executed native shared-encoding regressions + +These clients start with the actual unsigned input convention. The interpreter charges every +native arithmetic block, branch, loop and store. They compare observable memory after execution. +-/ + +namespace ComplexityTests.SchonhageEncoding + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding + +private def observe (side : Bool) (w L fuel : ℕ) (a : Fin (7 ^ L) → ℤ) : + Option ((List ℤ × Bool) × ℕ) := + ((program side).eval w fuel (State.init (input L a)).data).map fun (y, time) => + (((List.range (10 ^ L)).map fun i => decode (y.mem (outputBase L + i)), + decide (y.mem 0 = (input L a).length ∧ y.mem 1 = L ∧ + (∀ i : Fin (7 ^ L), y.mem (2 + i.val) = encode (a i)) ∧ + y.mem (scratchBase L + 10 ^ L) = 0)), time) + +-- Kernel reduction follows the finite form dispatch and all actual signed arithmetic blocks. +-- Its nested code and loop reductions exceed the default elaborator recursion depth. +set_option maxRecDepth 16384 in +example : observe false 8 0 2 (fun _ => -3) = some (([-3], true), 31) := by + decide +kernel + +-- These checks run the actual interpreter as regression tests; the general execution +-- theorems are kernel checked separately. VM evaluation avoids quadratic kernel reduction +-- through thousands of functional register updates in each concrete full-program test. +#guard (observe false 12 1 16 (fun i => (i.val : ℤ) + 1)).map Prod.fst == + some ([5, 6, 1, 8, 9, 2, -7, -9, 3, -6], true) + +#guard (observe true 12 1 16 (fun i => (i.val : ℤ) + 1)).map Prod.fst == + some ([5, 7, -6, 7, 9, -10, 1, 2, 3, 6], true) + +#guard (observe false 12 1 16 (fun i => -((i.val : ℤ) + 1))).map Prod.fst == + some ([-5, -6, -1, -8, -9, -2, 7, 9, -3, 6], true) + +-- At width four, the computed scratch address 19 overflows; width five is the first working +-- width for this zero-valued depth-one client. Every output and the adjacent frame are checked. +#guard observe false 4 1 16 (fun _ => 0) == none +#guard (observe false 5 1 16 (fun _ => 0)).map Prod.fst == + some (List.replicate 10 0, true) + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +private def signedLeft (u : LeftStr 2) : ℤ := + if u 0 = .x 0 then -2 else if u 1 = .p 1 0 then 3 else 1 + +private def signedRight (u : RightStr 2) : ℤ := + if u 0 = .q 0 1 then -3 else if u 1 = .y 2 then 2 else 0 + +private def leafAt (i : Fin (10 ^ 2)) : Leaf 2 := + fun ℓ => termAt (finFunctionFinEquiv.symm i ℓ) + +-- Output addresses can be calculated by ordinary executable finite alphabet numberings. +#guard (leafIndex (fun _ : Fin 2 => Term.P0)).val == 99 +#guard (leafIndex (fun _ : Fin 2 => Term.P 0 0)).val == 0 +#guard (leftDigits.symm (.p 1 1)).val == 6 + +-- Two stages alternate buffers and then copy backward from scratch to output. The expected +-- values use the unchanged public mathematical encodings, including every one of the 100 leaves. +#guard (observe false 16 2 128 (fun i => signedLeft + (fun ℓ => leftAt (finFunctionFinEquiv.symm i ℓ)))).map Prod.fst == + some (List.ofFn (fun i => encodingL signedLeft (leafAt i)), true) + +#guard (observe true 16 2 128 (fun i => signedRight + (fun ℓ => rightAt (finFunctionFinEquiv.symm i ℓ)))).map Prod.fst == + some (List.ofFn (fun i => encodingR signedRight (leafAt i)), true) + +private def copyObserve (s d : ℕ) : Option (List ℕ × Bool × ℕ) := + let x : Data 7 := ⟨![s, d, 3, 0, 0, 0, 0], + fun a => if s ≤ a ∧ a < s + 3 then a - s + 1 else 99⟩ + (ArrayCopy.program.eval 8 4 x).map fun (y, time) => + (List.ofFn (fun i : Fin 3 => y.mem (d + i.val)), + decide ((∀ i : Fin 3, y.mem (s + i.val) = i.val + 1) ∧ + y.mem 0 = 99 ∧ y.mem 12 = 99), time) + +-- Execute the generalized helper in both disjoint orders and with equal blocks. The old +-- forward-copy theorem remains available; these also check nonzero words outside the frame. +#guard copyObserve 4 8 == some ([1, 2, 3], true, 25) +#guard copyObserve 8 4 == some ([1, 2, 3], true, 25) +#guard copyObserve 4 4 == some ([1, 2, 3], true, 25) + +-- Nonzero garbage immediately beyond the two work buffers survives the complete encoder. +#guard ((program false).eval 16 128 + ((State.init (input 2 (fun _ => -1)) : State 32).data.write + (scratchBase 2 + 10 ^ 2) 91)).map + (fun (y, _) => y.mem (scratchBase 2 + 10 ^ 2)) == some 91 + +#guard observe true 8 0 2 (fun _ => -4) == some (([-4], true), 31) + +/-- The explicit width condition itself has a checked boundary. -/ +example : ¬Fits 9 (addressBound 1) ∧ Fits 10 (addressBound 1) := by decide + +/-- A kernel-checked zero-depth flat execution with a negative value, derived from the +general theorem rather than from VM evaluation. All calling/width premises are discharged. -/ +example : ∃ y : Data 32, ∃ n, n ≤ 240 ∧ + Runs 8 ((program false).program output) + (State.init (inputL (fun _ : LeftStr 0 => -3))) ⟨(program false).size, y⟩ n ∧ + Executes 8 ((program false).program output) + (State.init (inputL (fun _ : LeftStr 0 => -3))) 3 (n + 1) ∧ y.mem 3 = 7 := by + obtain ⟨_, y, n, hn, hr, he, hv, _⟩ := runs_left (w := 6) (by decide) + (fun _ : LeftStr 0 => -3) 3 (by decide) (by intro u; decide) (by decide) (by decide) + refine ⟨y, n, ?_, hr, he, ?_⟩ + · simpa only [timeBound, pow_zero, Nat.reduceAdd, Nat.mul_one] using hn + · have hidx : (leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by + have := (leafIndex (Fin.elim0 : Leaf 0)).isLt + norm_num at this ⊢ + have h := hv (Fin.elim0 : Leaf 0) + have hval : encodingL (fun _ : LeftStr 0 => -3) Fin.elim0 = -3 := + encodeWith_zero phi _ _ + have henc : encode (-3) = 7 := by decide + simpa only [outputBase, pow_zero, hidx, hval, henc, Nat.reduceAdd] using h + +/-- A nonvacuous two-stage client of the complete flat execution theorem. The checked signed +array has a zero `P0,P0` leaf through cancellation and also many nonzero leaves in the VM test. -/ +example : ∃ y : Data 32, ∃ n, n ≤ 72000 ∧ + Runs 18 ((program false).program output) (State.init (inputL signedLeft)) + ⟨(program false).size, y⟩ n ∧ + Executes 18 ((program false).program output) (State.init (inputL signedLeft)) 51 (n + 1) ∧ + y.mem (outputBase 2 + (leafIndex (fun _ : Fin 2 => Term.P0)).val) = 0 := by + obtain ⟨_, y, n, hn, hr, he, hv, _⟩ := runs_left (w := 16) (by decide) + signedLeft 3 (by decide) (by decide +kernel) (by decide) (by decide) + refine ⟨y, n, ?_, hr, he, ?_⟩ + · norm_num [timeBound] at hn ⊢ + exact hn + · have hmath : encodingL signedLeft (fun _ : Fin 2 => Term.P0) = 0 := by decide +kernel + simpa only [hmath, show encode 0 = 0 from rfl] using hv (fun _ : Fin 2 => Term.P0) + +end ComplexityTests.SchonhageEncoding diff --git a/ComplexityTests/WordRAMExactTriangleChunks.lean b/ComplexityTests/WordRAMExactTriangleChunks.lean new file mode 100644 index 0000000000..43c919392b --- /dev/null +++ b/ComplexityTests/WordRAMExactTriangleChunks.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 +-/ + +import Complexity.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue +import Complexity.Algorithms.WordRAM.ExactTriangle.Chunks +import Complexity.Algorithms.WordRAM.ExactTriangle.Parameters +import Complexity.Computability.WordRAM.Compile + +/-! # Native ET chunk-boundary and parameter clients -/ + +namespace ComplexityTests.WordRAMExactTriangleChunks + +open Cslib.WordRAM + +namespace Chunk + +open Cslib.Algorithms.WordRAM.ExactTriangle.Chunks + +private def boot : Code .standard 14 := ram[ + one ← #1; addr ← #1; queries ← *addr; addr ← addr + one; dst ← *addr; + addr ← addr + one; total ← *addr; addr ← addr + one; capacity ← *addr; + addr ← addr + one; residue ← *addr ] + +private def input (n cap rho : ℕ) : List ℕ := + [6, 6 + 2 * n, n, cap, rho] ++ List.range (2 * n) ++ List.replicate (3 * n + 3) 88 ++ [99] + +private def code : Code .standard 14 := .seq boot program + +private def observe (w n cap rho : ℕ) : Option (List (ℕ × ℕ × ℕ) × ℕ × ℕ) := + (code.eval w (n + 1) (.init (input n cap rho))).map fun yn => + ((List.range (yn.1.regs count)).map (fun k => + (yn.1.mem (6 + 2 * n + 3 * k), yn.1.mem (6 + 2 * n + 3 * k + 1), + yn.1.mem (6 + 2 * n + 3 * k + 2))), + yn.1.mem (6 + 5 * n + 3), yn.1.mem 6) + +example : observe 8 5 2 7 = some ([(7, 6, 2), (7, 10, 2), (7, 14, 1)], 99, 0) := by decide + +example : observe 8 4 2 7 = some ([(7, 6, 2), (7, 10, 2)], 99, 0) := by decide + +example : observe 8 2 1 3 = some ([(3, 6, 1), (3, 8, 1)], 99, 0) := by decide + +example : observe 8 0 3 7 = some ([], 99, 88) := by decide + +/-- Insufficient address width faults in the real descriptor writer. -/ +example : observe 4 5 2 7 = none := by decide + +private def raw : List ℕ := input 5 2 7 + +/-- The complete descriptor theorem is instantiated at a shortened last chunk and connected +to the initialized compiled execution. The source pair words and outside sentinel are retained. -/ +example : ∃ y t, t ≤ 104 ∧ + Runs 8 (code.program count) (State.init raw) ⟨code.size, y⟩ (t - 1) ∧ + Executes 8 (code.program count) (State.init raw) 3 t ∧ + y.mem 22 = 7 ∧ y.mem 23 = 14 ∧ y.mem 24 = 1 ∧ y.mem 6 = 0 ∧ y.mem 34 = 99 := by + obtain ⟨b, hb, hregs, hmem⟩ : ∃ b, + ExecWithin 8 boot (.init raw) b 11 ∧ + (b.regs queries = 6 ∧ b.regs dst = 16 ∧ b.regs total = 5 ∧ + b.regs capacity = 2 ∧ b.regs residue = 7) ∧ + b.mem = (Data.init raw : Data 14).mem := by + refine ⟨?b, ?run, ?args, ?memory⟩ + case run => ram_eval [boot, raw, input, Data.init, Fits] + case args => decide + case memory => rfl + obtain ⟨hq, hd, hn, hc, hrho⟩ := hregs + obtain ⟨y, hy, hycount, -, hcells, hframe⟩ := exec (w := 8) b + (by rw [hc]; decide) (by rw [hd, hn]; decide) (by rw [hq, hn]; decide) + have hcount : y.regs count = 3 := by simpa [hn, hc, Nat.ceilDiv_eq_add_pred_div] using hycount + have hlast := hcells 2 (by rw [hcount]; decide) + obtain ⟨t, ht, he⟩ := hb.seq hy + have hr := he.runs (pc := 0) (p := code.program count) + (Program.Contains.self _).append_left + have ht' : t ≤ 103 := by simpa only [hn] using ht + refine ⟨y, t + 1, by omega, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [code, State.init, Nat.zero_add, Nat.add_sub_cancel] using hr + · simpa only [code, State.init, hcount] using he.executes count + · simpa [hd, hrho] using hlast.1 + · simpa [hd, hq, hc] using hlast.2.1 + · simpa [hd, hn, hc] using hlast.2.2 + · exact (hframe 6 (by rw [hd, hcount]; decide)).trans (congrFun hmem 6) + · exact (hframe 34 (by rw [hd, hcount]; decide)).trans (congrFun hmem 34) + +end Chunk + +namespace Catalogue + +open Cslib.Algorithms.WordRAM.ExactTriangle.ChunkCatalogue + +private def boot : Code .standard 32 := ram[ + one ← #1; addr ← #1; counts ← *addr; addr ← addr + one; queries ← *addr; + addr ← addr + one; size ← *addr; addr ← addr + one; modulus ← *addr; + addr ← addr + one; dst ← *addr; addr ← addr + one; capacity ← *addr ] + +private def input : List ℕ := + [7, 10, 2, 3, 35, 2] ++ [0, 3, 4] ++ List.range 24 ++ [99] ++ + List.replicate 12 88 ++ [77] + +private def code : Code .standard 32 := .seq boot program + +private def observe : Option (List (ℕ × ℕ × ℕ) × List ℕ) := + (code.eval 8 8 (.init input)).map fun yn => + ((List.range (yn.1.regs written)).map (fun k => + (yn.1.mem (35 + 3 * k), yn.1.mem (35 + 3 * k + 1), yn.1.mem (35 + 3 * k + 2))), + [yn.1.regs heap, yn.1.mem 7, yn.1.mem 8, yn.1.mem 9, yn.1.mem 10, + yn.1.mem 34, yn.1.mem 47]) + +/-- Empty classes consume no descriptor slots; the odd class has a shortened last chunk. +The count table, query words and both outside sentinels survive. -/ +example : observe = some + ([(1, 18, 2), (1, 22, 1), (2, 26, 2), (2, 30, 2)], [47, 0, 3, 4, 0, 99, 77]) := by + decide + +private def classSize (r : ℕ) : ℕ := if r = 1 then 3 else if r = 2 then 4 else 0 + +/-- The same four actual descriptors follow from the general theorem and an initialized +compiled run, with all count, separation and width premises discharged. -/ +example : ∃ y t, t ≤ 287 ∧ + Runs 8 (code.program written) (State.init input) ⟨code.size, y⟩ (t - 1) ∧ + Executes 8 (code.program written) (State.init input) 4 t ∧ + y.mem 35 = 1 ∧ y.mem 39 = 22 ∧ y.mem 40 = 1 ∧ y.mem 45 = 30 ∧ + y.mem 47 = 77 ∧ y.mem 10 = 0 := by + obtain ⟨b, hb, hregs, hmem⟩ : ∃ b, + ExecWithin 8 boot (.init input) b 13 ∧ + (b.regs counts = 7 ∧ b.regs queries = 10 ∧ b.regs size = 2 ∧ + b.regs modulus = 3 ∧ b.regs dst = 35 ∧ b.regs capacity = 2) ∧ + b.mem = (Data.init input : Data 32).mem := by + refine ⟨?b, ?run, ?args, ?memory⟩ + case run => ram_eval [boot, input, Data.init, Fits] + case args => decide + case memory => rfl + obtain ⟨hcount, hq, hn, hp, hd, hcap⟩ := hregs + obtain ⟨y, hy, hycount, hyheap, -, hcells, hframe⟩ := exec (w := 8) b classSize + (by + intro r hr + rw [hp] at hr + rw [hcount, hmem] + interval_cases r <;> decide) + (by + intro r hr + rw [hp] at hr + rw [hn] + interval_cases r <;> decide) + (by rw [hcap]; decide) (by rw [hcount, hp, hd]; decide) + (by rw [hq, hp, hn, hd]; decide) + (by rw [hd, hp, hn, hcount, hq]; decide) + have hwritten : y.regs written = 4 := by + simpa [hcap, hp, before, classSize, Finset.sum_range_succ, + Nat.ceilDiv_eq_add_pred_div] using hycount + have first := hcells 1 (by rw [hp]; decide) 0 (by rw [hcap]; decide) + have second := hcells 1 (by rw [hp]; decide) 1 (by rw [hcap]; decide) + have last := hcells 2 (by rw [hp]; decide) 1 (by rw [hcap]; decide) + obtain ⟨t, ht, he⟩ := hb.seq hy + have hr := he.runs (pc := 0) (p := code.program written) + (Program.Contains.self _).append_left + have ht' : t ≤ 286 := by simpa only [hp, hn] using ht + refine ⟨y, t + 1, by omega, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [code, State.init, Nat.zero_add, Nat.add_sub_cancel] using hr + · simpa only [code, State.init, hwritten] using he.executes written + · simpa [hd, hcap, before, classSize, Finset.sum_range_succ, + Nat.ceilDiv_eq_add_pred_div] using first.1 + · simpa [hd, hq, hn, hcap, before, classSize, Finset.sum_range_succ, + Nat.ceilDiv_eq_add_pred_div] using second.2.1 + · simpa [hd, hcap, before, classSize, Finset.sum_range_succ, + Nat.ceilDiv_eq_add_pred_div] using second.2.2 + · simpa [hd, hq, hn, hcap, before, classSize, Finset.sum_range_succ, + Nat.ceilDiv_eq_add_pred_div] using last.2.1 + · exact (hframe 47 (by rw [hd, hwritten]; decide)).trans (congrFun hmem 47) + · exact (hframe 10 (by rw [hd, hwritten]; decide)).trans (congrFun hmem 10) + +end Catalogue + +namespace Params + +open Cslib.Algorithms.WordRAM.ExactTriangle.Parameters +open Cslib.Algorithms.ExactTriangle + +private def boot : Code .standard 15 := ram[ + one ← #1; arg ← #1; size ← *arg; arg ← arg + one; dimension ← *arg; + arg ← arg + one; divisor ← *arg ] + +private def code : Code .standard 15 := .seq boot program + +private def observe (w n D g : ℕ) : Option (ℕ × ℕ × ℕ × ℕ) := + (code.eval w (n * n + D + 2) (.init [n, D, g])).map fun yn => + (yn.1.regs middleRoot, yn.1.regs capacity, yn.1.regs pieceLength, yn.1.regs pieces) + +example : observe 10 3 4 1 = some (2, 4, 2, 2) := by decide + +example : observe 10 3 4 3 = some (2, 4, 1, 3) := by decide + +example : observe 10 0 0 0 = some (0, 0, 0, 0) := by decide + +example : observe 2 3 4 1 = none := by decide + +private theorem parameters_values : + sOf 4 = 2 ∧ queryCap 3 4 = 4 ∧ pieceSize 4 1 = 2 ∧ numPieces 3 4 1 = 2 := by + have h4 : Nat.sqrt 4 = 2 := by + have hlow : 2 ≤ Nat.sqrt 4 := Nat.le_sqrt.mpr (by decide) + have hhigh : Nat.sqrt 4 < 3 := Nat.sqrt_lt.mpr (by decide) + omega + have h20 : Nat.sqrt 20 = 4 := by + have hlow : 4 ≤ Nat.sqrt 20 := Nat.le_sqrt.mpr (by decide) + have hhigh : Nat.sqrt 20 < 5 := Nat.sqrt_lt.mpr (by decide) + omega + simp only [sOf, queryCap, pieceSize, numPieces, Nat.ceilDiv_eq_add_pred_div] + norm_num [h4, h20] + +/-- The exported sufficient fits are satisfied, and all four computed parameters come from the +same initialized compiled run; parameter computation preserves every input cell. -/ +example : ∃ y t, t ≤ 65 ∧ + Runs 10 (code.program pieces) (State.init [3, 4, 1]) ⟨code.size, y⟩ (t - 1) ∧ + Executes 10 (code.program pieces) (State.init [3, 4, 1]) 2 t ∧ + y.regs middleRoot = 2 ∧ y.regs capacity = 4 ∧ y.regs pieceLength = 2 ∧ + y.regs pieces = 2 ∧ y.mem = (Data.init [3, 4, 1] : Data 15).mem := by + obtain ⟨b, hb, hregs, hmem⟩ : ∃ b, + ExecWithin 10 boot (.init [3, 4, 1]) b 7 ∧ + (b.regs size = 3 ∧ b.regs dimension = 4 ∧ b.regs divisor = 1) ∧ + b.mem = (Data.init [3, 4, 1] : Data 15).mem := by + refine ⟨?b, ?run, ?args, ?memory⟩ + case run => ram_eval [boot, Data.init, Fits] + case args => decide + case memory => rfl + obtain ⟨hn, hD, hg⟩ := hregs + obtain ⟨hS, hQ, hPiece, hPieces⟩ := parameters_values + obtain ⟨y, hy, hs, hq, hB, hH, -, -, -, hym⟩ := exec (w := 10) b + (by rw [hD]; decide) (by rw [hn]; decide) + (by rw [hD, hg, hS]; decide) (by rw [hn, hD, hg, hPiece]; decide) + have hyH : y.regs pieces = 2 := by + simpa only [hn, hD, hg, hPieces] using hH + obtain ⟨t, ht, he⟩ := hb.seq hy + have hr := he.runs (pc := 0) (p := code.program pieces) + (Program.Contains.self _).append_left + have ht' : t ≤ 64 := by simpa only [hn, hD, hS, hQ] using ht + refine ⟨y, t + 1, by omega, ?_, ?_, ?_, ?_, ?_, hyH, hym.trans hmem⟩ + · simpa only [code, State.init, Nat.zero_add, Nat.add_sub_cancel] using hr + · simpa only [code, State.init, hyH] using he.executes pieces + · simpa only [hD, hS] using hs + · simpa only [hn, hD, hQ] using hq + · simpa only [hD, hg, hPiece] using hB + +end Params + +end ComplexityTests.WordRAMExactTriangleChunks diff --git a/ComplexityTests/WordRAMExactTriangleInstance.lean b/ComplexityTests/WordRAMExactTriangleInstance.lean new file mode 100644 index 0000000000..0460e504ef --- /dev/null +++ b/ComplexityTests/WordRAMExactTriangleInstance.lean @@ -0,0 +1,274 @@ +/- +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.ExactTriangle.InstancePair +import Complexity.Algorithms.WordRAM.ExactTriangle.Instances +import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationInput +import Complexity.Algorithms.WordRAM.ExactTriangle.PreparationBounds + +/-! # Native ET instance-pair records and matrix padding -/ + +namespace ComplexityTests.WordRAMExactTriangleInstance + +open Cslib.WordRAM Cslib.Algorithms.WordRAM.ExactTriangle.InstancePair + +private def leftValues : List ℕ := [0, 1, 1, 0] +private def rightValues : List ℕ := [0, 0, 1, 1] + +private def initial : Data 32 where + regs i := match i.val with + | 17 => 5 | 18 => 9 | 19 => 30 | 20 => 2 | 21 => 5 | 22 => 2 + | 23 => 0 | 24 => 2 | 25 => 1 | 26 => 13 | 27 => 3 | 28 => 1 | _ => 0 + mem j := if 5 ≤ j ∧ j < 9 then leftValues.getD (j - 5) 0 + else if 9 ≤ j ∧ j < 13 then rightValues.getD (j - 9) 0 + else if j = 29 then 99 else if j = 56 then 77 else if j = 13 then 41 else 88 + +/-- The native pair writer overwrites nonzero output garbage, writes both computed matrix +pointers, pads the fifth middle coordinate with zero, and preserves query/source sentinels. -/ +example : (program.eval 8 10 initial).map (fun (y, _) => + ((List.range 6).map (fun i => y.mem (30 + i)), + (List.range 10).map (fun i => y.mem (36 + i)), + (List.range 10).map (fun i => y.mem (46 + i)), [y.mem 13, y.mem 29, y.mem 56])) = + some ([1, 1, 13, 3, 36, 46], [0, 1, 1, 0, 0, 1, 0, 0, 1, 0], + [1, 0, 0, 1, 1, 0, 0, 1, 0, 0], [41, 99, 77]) := by decide +kernel + +/-- Fully admitted general execution produces the real record and both concrete matrix +corners/padded positions in the same memory witness. -/ +example : ∃ y, ExecWithin 8 program initial y 438 ∧ + y.mem 30 = 1 ∧ y.mem 32 = 13 ∧ y.mem 34 = 36 ∧ y.mem 35 = 46 ∧ + y.mem 37 = 1 ∧ y.mem 40 = 0 ∧ y.mem 46 = 1 ∧ y.mem 55 = 0 ∧ + y.mem 13 = 41 ∧ y.mem 29 = 99 ∧ y.mem 56 = 77 := by + have hleft : ∀ i : Fin 4, + initial.mem (5 + i.val) = leftValues.getD i.val 0 := by decide + have hright : ∀ i : Fin 4, + initial.mem (9 + i.val) = rightValues.getD i.val 0 := by decide + obtain ⟨y, hy, hrecord, hL, hR, hf⟩ := exec (w := 8) initial + (fun i => leftValues.getD i 0) (fun i => rightValues.getD i 0) + (by decide) (by decide) + (by + intro i hi + have hv : ∀ j : Fin 4, leftValues.getD j.val 0 < 2 := by decide + exact hv ⟨i, hi⟩) + (by + intro i hi + have hv : ∀ j : Fin 4, rightValues.getD j.val 0 < 2 := by decide + exact hv ⟨i, hi⟩) + (by intro i hi; exact hleft ⟨i, hi⟩) + (by intro i hi; exact hright ⟨i, hi⟩) + (by decide) (by decide) (by decide) + have hr0 := hrecord 0 + have hr2 := hrecord 2 + have hr4 := hrecord 4 + have hr5 := hrecord 5 + have hl1 := hL ⟨1, by decide⟩ + have hl4 := hL ⟨4, by decide⟩ + have hb0 := hR ⟨0, by decide⟩ + have hb9 := hR ⟨9, by decide⟩ + refine ⟨y, hy, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact hr0 + · exact hr2 + · exact hr4 + · exact hr5 + · exact hl1 + · exact hl4 + · exact hb0 + · exact hb9 + · exact (hf 13 (by decide)).trans (by decide) + · exact (hf 29 (by decide)).trans (by decide) + · exact (hf 56 (by decide)).trans (by decide) + +end ComplexityTests.WordRAMExactTriangleInstance + +namespace ComplexityTests.WordRAMExactTriangleInstances + +open Cslib.WordRAM Cslib.Algorithms.WordRAM.ExactTriangle +open Cslib.Algorithms.WordRAM.ExactTriangle.Instances + +private def input : List ℕ := [1, 1, 1, 2] ++ ([-1, 0, 1] : List ℤ).map Signed.encode + +-- Unfolding all composed kernels, their nested loops and the outer visit exceeds the default +-- expression recursion depth. Kernel reduction checks the actual initialized interpreter. +set_option maxRecDepth 4096 in +example : (program.eval 10 4 (.init input)).map (fun (y, _) => + ([y.regs output, y.regs produced, y.regs heap], + (List.range 8).map (fun i => y.mem (20 + i)), [y.mem 5, y.mem 17, y.mem 28])) = + some ([20, 1, 28], [1, 0, 15, 1, 26, 27, 1, 1], [3, 1, 0]) := by decide +kernel + +-- Even with no visits, unfolding the composed initialization exceeds the default recursion depth. +set_option maxRecDepth 4096 in +example : (program.eval 10 4 (.init [0, 1, 0, 2])).map (fun (y, _) => + [y.regs output, y.regs produced, y.regs heap, + y.mem 0, y.mem 1, y.mem 2, y.mem 3, y.mem 4]) = + some [7, 0, 7, 4, 0, 1, 0, 2] := by decide +kernel + +/-- The real input words fit, but the output addresses require more than four bits. -/ +example : InputFits 4 input ∧ program.eval 4 4 (.init input) = none := by decide +kernel + +/-- The complete public initialized theorem is inhabited for signed weights. Both the actual +prefix/halt and concrete allocation/input readback use the same returned state. -/ +example : ∃ y t, t ≤ 431 ∧ + Runs (PreparationInput.width 1 1 1 2 1) PreparationInput.flatProgram + (State.init input) ⟨program.size, y⟩ (t - 1) ∧ + Executes (PreparationInput.width 1 1 1 2 1) PreparationInput.flatProgram + (State.init input) 20 t ∧ + y.regs produced = 1 ∧ y.regs heap = 28 ∧ y.mem 5 = 3 ∧ y.mem 28 = 0 := by + let values : List ℤ := [-1, 0, 1] + obtain ⟨y, t, ht, hr, he, hout, -, -⟩ := PreparationInput.runs_input + (n := 1) (D := 1) (g := 1) (p := 2) (A := 1) values + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro z hz; simp only [values, List.mem_cons, List.not_mem_nil, or_false] at hz + rcases hz with rfl | rfl | rfl <;> decide) le_rfl + let C := fun rho => Nat.count (fun q => (values.getD q 0 % (2 : ℤ)).toNat = rho) 1 + have hK : ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 = 1 := by + decide + have hH : Cslib.Algorithms.ExactTriangle.numPieces 1 1 1 = 1 := by decide + have hcost : Instances.timeBound 1 1 2 1 1 = 431 := by decide + change t ≤ Instances.timeBound 1 1 2 + (ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2) + (Cslib.Algorithms.ExactTriangle.numPieces 1 1 1) at ht + rw [hK, hH, hcost] at ht + obtain ⟨-, -, -, hout, hcount, hheap, -, -, -, -, -, hframe⟩ := hout + change y.regs output = 17 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 at hout + change y.regs produced = ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 * + Cslib.Algorithms.ExactTriangle.numPieces 1 1 1 at hcount + change y.regs heap = 17 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 + + ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 * + Cslib.Algorithms.ExactTriangle.numPieces 1 1 1 * 8 at hheap + have ho : y.regs output = 20 := by simpa only [hK, Nat.reduceAdd, Nat.reduceMul] using hout + have hc : y.regs produced = 1 := by + simpa only [hK, hH, Nat.reduceMul] using hcount + have hh : y.regs heap = 28 := by + simpa only [hK, hH, Nat.reduceAdd, Nat.reduceMul] using hheap + refine ⟨y, t, ht, hr, ?_, hc, hh, ?_, ?_⟩ + · simpa only [ho, input, values, PreparationInput.input] using he + · exact hframe 5 (Or.inl (by decide)) + · apply hframe 28 + apply Or.inr + change 17 + 3 * ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 + + ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 1 1) 2 * + Cslib.Algorithms.ExactTriangle.numPieces 1 1 1 * 8 ≤ 28 + rw [hK, hH] + +private def sourceValues : List ℤ := List.replicate 768 0 + +set_option maxRecDepth 4096 in +-- This source-admissible client installs 768 concrete weights; type checking its actual +-- initialized state and flattened matrix readback exceeds the default expression depth. +/-- A source-admissible `n=D=16`, `p=3` instance checks the quantitative theorem, actual first +record, active adjacency cells, padded cells and complete allocation end. No execution premise +is supplied, and no interpreter reduction over the large composed run is needed. -/ +example : ∃ y t, t ≤ 2826240 ∧ + Runs (PreparationInput.width 16 16 1 3 0) PreparationInput.flatProgram + (State.init (PreparationInput.input 16 16 1 3 sourceValues)) ⟨program.size, y⟩ (t - 1) ∧ + Executes (PreparationInput.width 16 16 1 3 0) PreparationInput.flatProgram + (State.init (PreparationInput.input 16 16 1 3 sourceValues)) 3092 t ∧ + y.regs produced = 16 ∧ y.regs heap = 11380 ∧ + y.mem 3094 = 1544 ∧ y.mem 3095 = 64 ∧ + y.mem 3098 = 1 ∧ y.mem 3110 = 0 ∧ y.mem 3354 = 1 ∧ y.mem 3546 = 0 := by + have hprime : 3 ∈ Nat.sqrtPrimeWindow 16 := + Nat.mem_sqrtPrimeWindow_iff_sq.mpr + ⟨by decide, by decide, by decide⟩ + obtain ⟨y, t, ht, hr, he, hout, -, -⟩ := PreparationBounds.runs_input + (n := 16) (D := 16) (g := 1) (p := 3) (A := 0) sourceValues + (by simp only [sourceValues, List.length_replicate]) + (by decide) (by decide) (by decide) hprime + (by intro z hz; have : z = 0 := List.mem_replicate.mp hz |>.2; subst z; decide) le_rfl + have hR (q : ℕ) : (sourceValues.getD q 0 % (3 : ℤ)).toNat = 0 := by + change ((List.replicate 768 (0 : ℤ))[q]?.getD 0 % (3 : ℤ)).toNat = 0 + rw [List.getElem?_getD_replicate_default_eq] + rfl + let C := fun rho => Nat.count (fun q => (sourceValues.getD q 0 % (3 : ℤ)).toNat = rho) 256 + have hc0 : C 0 = 256 := Nat.count_of_forall fun q _ => hR q + have hc1 : C 1 = 0 := Nat.count_of_forall_not fun q _ => by rw [hR]; decide + have hc2 : C 2 = 0 := Nat.count_of_forall_not fun q _ => by rw [hR]; decide + have hQ : Cslib.Algorithms.ExactTriangle.queryCap 16 16 = 64 := by + have hl : 64 ≤ Nat.sqrt 4096 := Nat.le_sqrt.mpr (by decide) + have hu : Nat.sqrt 4096 < 65 := Nat.sqrt_lt.mpr (by decide) + have hs : Nat.sqrt 4096 = 64 := by omega + simpa only [Cslib.Algorithms.ExactTriangle.queryCap, Nat.reducePow, Nat.reduceDiv] using hs + have hS : Cslib.Algorithms.ExactTriangle.sOf 16 = 4 := by + change Nat.sqrt 16 = 4 + have hl : 4 ≤ Nat.sqrt 16 := Nat.le_sqrt.mpr (by decide) + have hu : Nat.sqrt 16 < 5 := Nat.sqrt_lt.mpr (by decide) + omega + have hB : Cslib.Algorithms.ExactTriangle.pieceSize 16 1 = 4 := by + simp only [Cslib.Algorithms.ExactTriangle.pieceSize, hS, ceilDiv_one] + have hH : Cslib.Algorithms.ExactTriangle.numPieces 16 16 1 = 4 := by + norm_num [Cslib.Algorithms.ExactTriangle.numPieces, hB, Nat.ceilDiv_eq_add_pred_div] + have hK : ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 = 4 := by + simp only [ChunkCatalogue.before, Finset.sum_range_succ, Finset.sum_range_zero, + hc0, hc1, hc2, hQ, Nat.ceilDiv_eq_add_pred_div] + obtain ⟨-, -, -, ho, hc, hh, -, -, -, hdesc, hblocks, -⟩ := hout + change y.regs output = 3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 at ho + change y.regs produced = ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 * + Cslib.Algorithms.ExactTriangle.numPieces 16 16 1 at hc + change y.regs heap = 3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + + ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 * + Cslib.Algorithms.ExactTriangle.numPieces 16 16 1 * 518 at hh + have hout : y.regs output = 3092 := by simpa only [hK, Nat.reduceAdd, Nat.reduceMul] using ho + have hcount : y.regs produced = 16 := by + simpa only [hK, hH, Nat.reduceMul] using hc + have hheap : y.regs heap = 11380 := by + simpa only [hK, hH, Nat.reduceAdd, Nat.reduceMul] using hh + have hd := hdesc 0 (by decide) 0 (by change 0 < C 0 ⌈/⌉ _; rw [hc0, hQ]; decide) + have hpref : ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 0 = 0 := by + simp [ChunkCatalogue.before] + have hd0 : y.mem 3080 = 0 := by + have hd' := hd.1 + change y.mem (3080 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 0 + 0)) = 0 at hd' + simpa only [hpref, Nat.reduceAdd, Nat.reduceMul] using hd' + obtain ⟨hrec, hleft, hright⟩ := hblocks 0 + (by change 0 < ChunkCatalogue.before C _ 3 * _; rw [hK, hH]; decide) + have hr2 := hrec 2 + have hr3 := hrec 3 + have hl0 := hleft ⟨0, by change 0 < 256; decide⟩ + have hl12 := hleft ⟨12, by change 12 < 256; decide⟩ + have hb0 := hright ⟨0, by change 0 < 256; decide⟩ + have hb192 := hright ⟨192, by change 192 < 256; decide⟩ + have hd1 := hd.2.1 + simp only [Nat.zero_mul, Nat.add_zero] at hd1 + change y.mem (3080 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 0 + 0) + 1) = 1544 at hd1 + rw [hpref] at hd1 + have hd2 := hd.2.2 + simp only [Nat.zero_mul] at hd2 + change y.mem (3080 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 0 + 0) + 2) = + min (Cslib.Algorithms.ExactTriangle.queryCap 16 16) (C 0 - 0) at hd2 + rw [hpref, hQ, hc0] at hd2 + simp only [hH, Nat.zero_div, Nat.zero_mod, Nat.zero_mul, Nat.add_zero] + at hr2 hr3 hl0 hl12 hb0 hb192 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 2) = y.mem 3081 at hr2 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 3) = y.mem 3082 at hr3 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 6) = _ at hl0 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 6 + 12) = _ at hl12 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 6 + 256) = _ at hb0 + change y.mem (3080 + 3 * ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 16 16) 3 + 6 + 256 + 192) = _ at hb192 + rw [hK] at hr2 hr3 hl0 hl12 hb0 hb192 + norm_num only [hH, hB, hR, hd0, WriteMatrices.entry] at hl0 hl12 hb0 hb192 + refine ⟨y, t, ht, hr, ?_, hcount, hheap, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [hout] using he + · exact hr2.trans hd1 + · exact hr3.trans hd2 + · exact hl0 + · exact hl12 + · exact hb0 + · exact hb192 + +end ComplexityTests.WordRAMExactTriangleInstances diff --git a/ComplexityTests/WordRAMExactTriangleMatrices.lean b/ComplexityTests/WordRAMExactTriangleMatrices.lean new file mode 100644 index 0000000000..ebad67bb39 --- /dev/null +++ b/ComplexityTests/WordRAMExactTriangleMatrices.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 +-/ + +import Complexity.Algorithms.WordRAM.ExactTriangle.WriteMatrices +import Complexity.Computability.WordRAM.Compile + +/-! # Native ET instance-matrix clients + +The input to these local matrix-builder clients is an unsigned residue matrix. The complete ET +frontend must first produce those residues from signed weights and compute the instance catalogue. +-/ + +namespace ComplexityTests.WordRAMExactTriangleMatrices + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ExactTriangle.WriteMatrices + +private def boot : Code .standard 17 := ram[ + one ← #1; idx ← #1; src ← *idx; idx ← idx + one; dst ← *idx; idx ← idx + one; + size ← *idx; idx ← idx + one; dimension ← *idx; idx ← idx + one; + modulus ← *idx; idx ← idx + one; first ← *idx; idx ← idx + one; + length ← *idx; idx ← idx + one; rho ← *idx ] + +private def input (n D p c len r : ℕ) (R : List ℕ) : List ℕ := + [9, 9 + R.length, n, D, p, c, len, r] ++ R ++ List.replicate (n * D) 77 ++ [99] + +private def code (side : Bool) : Code .standard 17 := .seq boot (program side) + +private def observe (side : Bool) (w n D p c len r : ℕ) (R : List ℕ) : + Option (List ℕ × ℕ × ℕ) := + ((code side).eval w (n * D + 1) (.init (input n D p c len r R))).map fun yn => + ((List.range (n * D)).map fun i => yn.1.mem (9 + R.length + i), + yn.1.mem (9 + R.length + n * D), yn.1.regs idx) + +/-- Two source rows, all three labels, and an extra padded middle position. -/ +example : observe false 8 2 7 3 0 2 1 [0, 1, 2, 0] = + some ([0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0], 99, 14) := by decide + +/-- The right matrix uses the negated residue and the transposed input indices. -/ +example : observe true 8 2 7 3 0 2 1 [0, 1, 2, 0] = + some ([1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0], 99, 14) := by decide + +/-- A shifted one-vertex piece reads the second column of the residue input. -/ +example : observe false 8 2 4 3 1 1 2 [0, 1, 2, 0] = + some ([1, 0, 0, 0, 0, 0, 1, 0], 99, 8) := by decide + +example : observe true 8 2 4 3 1 1 2 [0, 1, 2, 0] = + some ([0, 1, 0, 0, 1, 0, 0, 0], 99, 8) := by decide + +/-- An empty outer part performs no stores and keeps the adjacent sentinel. -/ +example : observe false 8 0 5 3 0 0 1 [] = some ([], 99, 0) := by decide + +/-- A physical width too small for the matrix's addresses faults during actual execution. -/ +example : observe true 4 2 7 3 0 2 1 [0, 1, 2, 0] = none := by decide + +private def leftArgs : Args := ⟨9, 13, 2, 7, 3, 0, 2, 1⟩ +private def residues (i : ℕ) : ℕ := [0, 1, 2, 0].getD i 0 +private def leftInput : List ℕ := input 2 7 3 0 2 1 [0, 1, 2, 0] +private def leftOutput : List ℕ := [0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0] + +/-- The quantitative theorem applies after a charged native header load. The witness is tied +to the actual initialized compiled run, all fourteen expected entries and the outside sentinel. -/ +example : ∃ y t, t ≤ 304 ∧ + Runs 8 ((code false).program dst) (State.init leftInput) ⟨(code false).size, y⟩ (t - 1) ∧ + Executes 8 ((code false).program dst) (State.init leftInput) 13 t ∧ + (Block.mk 13 14).Realizes y.mem (fun i => leftOutput.getD i 0) ∧ + y.mem 27 = 99 ∧ y.Bounded 8 := by + obtain ⟨b, hb, hba, hbm⟩ : ∃ b, + ExecWithin 8 boot (.init leftInput) b 17 ∧ Entry leftArgs b ∧ + b.mem = (Data.init leftInput : Data 17).mem := by + refine ⟨?b, ?run, ?args, ?memory⟩ + case run => ram_eval [boot, leftInput, input, Data.init, Fits] + case args => constructor <;> decide + case memory => rfl + obtain ⟨y, hy, hya, -, hmatrix, hframe⟩ := exec (w := 8) false leftArgs residues b hba + (by decide) (by decide) + (by + intro i hi + have h : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by change i < 4 at hi; omega + rcases h with rfl | rfl | rfl | rfl <;> decide) + (by + intro i hi + rw [hbm] + have h : i = 0 ∨ i = 1 ∨ i = 2 ∨ i = 3 := by change i < 4 at hi; omega + rcases h with rfl | rfl | rfl | rfl <;> decide) + (by decide) (by decide) (by decide) (by decide) + obtain ⟨t, ht, he⟩ := hb.seq hy + have hr := he.runs (pc := 0) (p := (code false).program dst) + (Program.Contains.self _).append_left + refine ⟨y, t + 1, by exact Nat.add_le_add_right ht 1, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [Nat.add_sub_cancel, Nat.zero_add, code, State.init] using hr + · simpa only [code, State.init, hya.dst, leftArgs] using he.executes dst + · have hv : ∀ i : Fin 14, entry false leftArgs residues i = leftOutput.getD i 0 := by decide + intro i + exact (hmatrix i).trans (hv i) + · exact (hframe 27 (by change ¬(13 ≤ 27 ∧ 27 < 27); omega)).trans (congrFun hbm 27) + · exact hr.bounded_init (by decide : InputFits 8 leftInput) + +end ComplexityTests.WordRAMExactTriangleMatrices diff --git a/ComplexityTests/WordRAMExactTriangleQueries.lean b/ComplexityTests/WordRAMExactTriangleQueries.lean new file mode 100644 index 0000000000..95bf2fadf9 --- /dev/null +++ b/ComplexityTests/WordRAMExactTriangleQueries.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 +-/ + +import Complexity.Algorithms.WordRAM.ExactTriangle.Classes +import Complexity.Computability.WordRAM.Compile + +/-! # Initialized native residue-class clients + +These clients execute the actual header loader and class scanner. Empty classes, duplicate +residues, row-major ordering, unused output capacity and an outside sentinel are observable. +-/ + +namespace ComplexityTests.WordRAMExactTriangleQueries + +open Cslib.WordRAM +open Cslib.Algorithms.WordRAM.ExactTriangle.Classes + +private def boot : Code .standard 20 := ram[ + one ← #1; addr ← #1; src ← *addr; addr ← addr + one; size ← *addr; + addr ← addr + one; counts ← *addr; addr ← addr + one; queries ← *addr; + addr ← addr + one; modulus ← *addr ] + +private def input (n p : ℕ) (R : List ℕ) : List ℕ := + [6, n, 6 + R.length, 6 + R.length + p, p] ++ R ++ List.replicate p 77 ++ + List.replicate (2 * (n * n) * p) 88 ++ [99] + +private def code : Code .standard 20 := .seq boot program + +private def observe (w n p : ℕ) (R : List ℕ) : + Option (List (ℕ × List (ℕ × ℕ)) × ℕ × ℕ) := + (code.eval w (p + 1) (.init (input n p R))).map fun yn => + ((List.range p).map (fun j => + (yn.1.mem (6 + R.length + j), + (List.range (yn.1.mem (6 + R.length + j))).map fun k => + (yn.1.mem (6 + R.length + p + 2 * (n * n) * j + 2 * k), + yn.1.mem (6 + R.length + p + 2 * (n * n) * j + 2 * k + 1)))), + yn.1.mem (6 + R.length + p + 2 * (n * n) * p), yn.1.mem 6) + +example : observe 8 2 4 [0, 1, 2, 0] = + some ([(2, [(0, 0), (1, 1)]), (1, [(0, 1)]), (1, [(1, 0)]), (0, [])], 99, 0) := by + decide + +example : observe 8 0 3 [] = some ([(0, []), (0, []), (0, [])], 99, 0) := by decide + +example : observe 8 2 0 [0, 1, 2, 0] = some ([], 99, 0) := by decide + +example : observe 4 2 4 [0, 1, 2, 0] = none := by decide + +private def raw : List ℕ := input 2 4 [0, 1, 2, 0] +private def residues (q : ℕ) : ℕ := [0, 1, 2, 0].getD q 0 + +/-- Every theorem premise is discharged for the same initialized native run. Both matching +positions in class zero are connected to its computed count, and adjacent capacity stays intact. -/ +example : ∃ y t, t ≤ 298 ∧ + Runs 8 (code.program counts) (State.init raw) ⟨code.size, y⟩ (t - 1) ∧ + Executes 8 (code.program counts) (State.init raw) 10 t ∧ + y.mem 10 = 2 ∧ y.mem 13 = 0 ∧ + y.mem 14 = 0 ∧ y.mem 15 = 0 ∧ y.mem 16 = 1 ∧ y.mem 17 = 1 ∧ + y.mem 46 = 99 ∧ y.mem 6 = 0 ∧ y.Bounded 8 := by + obtain ⟨b, hb, hregs, hmem⟩ : ∃ b, + ExecWithin 8 boot (.init raw) b 11 ∧ + (b.regs src = 6 ∧ b.regs size = 2 ∧ b.regs counts = 10 ∧ + b.regs queries = 14 ∧ b.regs modulus = 4) ∧ + b.mem = (Data.init raw : Data 20).mem := by + refine ⟨?b, ?run, ?args, ?memory⟩ + case run => ram_eval [boot, raw, input, Data.init, Fits] + case args => decide + case memory => rfl + obtain ⟨hs, hn, hc, hq, hp⟩ := hregs + obtain ⟨y, hy, -, hcounts, hpairs, hframe⟩ := exec (w := 8) b residues (by decide) + (by + intro q hq' + rw [hs, hmem] + rw [hn] at hq' + have heq : q = 0 ∨ q = 1 ∨ q = 2 ∨ q = 3 := by omega + rcases heq with rfl | rfl | rfl | rfl <;> decide) + (by rw [hs, hn, hc]) (by rw [hc, hp, hq]) + (by rw [hc, hp]; decide) (by rw [hq, hn, hp]; decide) (by rw [hn]; decide) + have hcregs : y.regs counts = 10 := by + have hpres : program.Preserves counts := by decide + exact (hy.regs_eq hpres).trans hc + obtain ⟨t, ht, he⟩ := hb.seq hy + have hr := he.runs (pc := 0) (p := code.program counts) + (Program.Contains.self _).append_left + have ht' : t ≤ 297 := by simpa only [hn, hp] using ht + have hz := hpairs 0 (by rw [hp]; decide) + have hz0 := hz 0 (by rw [hn]; decide) (by decide) + have hz3 := hz 3 (by rw [hn]; decide) (by decide) + have hcount0 : Nat.count (fun q => residues q = 0) 4 = 2 := by decide + have hcount3 : Nat.count (fun q => residues q = 3) 4 = 0 := by decide + have hrank3 : Nat.count (fun q => residues q = 0) 3 = 1 := by decide + refine ⟨y, t + 1, by omega, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [code, State.init, Nat.zero_add, Nat.add_sub_cancel] using hr + · simpa only [code, State.init, hcregs] using he.executes counts + · simpa [hc, hn, hcount0] using hcounts 0 (by rw [hp]; decide) + · simpa [hc, hn, hcount3] using hcounts 3 (by rw [hp]; decide) + · simpa [hq, hn] using hz0.1 + · simpa [hq, hn] using hz0.2 + · simpa [hq, hn, hrank3] using hz3.1 + · simpa [hq, hn, hrank3] using hz3.2 + · exact (hframe 46 (by rw [hc, hp]; decide) (by rw [hq, hp, hn]; decide)).trans + (congrFun hmem 46) + · exact (hframe 6 (by rw [hc, hp]; decide) (by rw [hq, hp, hn]; decide)).trans + (congrFun hmem 6) + · exact hr.bounded_init (by decide : InputFits 8 raw) + +end ComplexityTests.WordRAMExactTriangleQueries diff --git a/ComplexityTests/WordRAMExactTriangleResidues.lean b/ComplexityTests/WordRAMExactTriangleResidues.lean new file mode 100644 index 0000000000..9fb5b54b58 --- /dev/null +++ b/ComplexityTests/WordRAMExactTriangleResidues.lean @@ -0,0 +1,213 @@ +/- +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.ExactTriangle.Catalogue +import Complexity.Algorithms.WordRAM.ExactTriangle.ResidueClasses + +/-! # Signed initialized residue/class preparation clients -/ + +namespace ComplexityTests.WordRAMExactTriangleResidues + +open Cslib.WordRAM Cslib.Algorithms.WordRAM.ExactTriangle.ResidueClasses + +private def values : List ℤ := [-1, 0, 1, -3, -4, 5, 0, 2, 7, -8, 9, -10] + +private def observe (w n p : ℕ) (xs : List ℤ) : Option (List ℕ × List ℕ × ℕ × ℕ) := + (program.eval w (xs.length + p + 1) (.init (input n p xs))).map fun yn => + ((List.range (3 * (n * n))).map fun q => yn.1.mem (3 + 3 * (n * n) + q), + (List.range p).map fun j => yn.1.mem (3 + 6 * (n * n) + j), + yn.1.mem 3, yn.1.mem (3 + 6 * (n * n) + p + 2 * (n * n) * p)) + +-- Unfolding twelve signed remainder iterations and the composed class loop exceeds the +-- default interpreter-expression recursion depth. +set_option maxRecDepth 4096 in +example : observe 8 2 3 values = + some ([2, 0, 1, 0, 2, 2, 0, 2, 1, 1, 0, 2], [2, 1, 1], 3, 0) := by decide + +example : observe 8 0 3 [] = some ([], [0, 0, 0], 0, 0) := by decide + +example : observe 4 2 3 values = none := by decide + +/-- The complete signed residue/class execution theorem has every premise discharged, and its +memory witnesses are connected to the actual initialized compiled run. -/ +example : ∃ y t, t ≤ 517 ∧ + Runs 8 (program.program counts) (State.init (input 2 3 values)) ⟨program.size, y⟩ (t - 1) ∧ + Executes 8 (program.program counts) (State.init (input 2 3 values)) 27 t ∧ + y.mem 15 = 2 ∧ y.mem 26 = 2 ∧ y.mem 27 = 2 ∧ + y.mem 30 = 0 ∧ y.mem 31 = 1 ∧ y.mem 32 = 1 ∧ y.mem 33 = 1 ∧ + y.mem 3 = 3 ∧ y.mem 54 = 0 ∧ y.Bounded 8 := by + obtain ⟨y, hy, -, hc, -, hr, hcounts, hpairs, hframe⟩ := exec_input (w := 6) 2 3 values + (by decide) (by decide) (by decide) + (by + intro q hq + have hq' : q < 12 := hq + have hfit : ∀ i : Fin 12, Cslib.GenericRAM.Signed.Fits 6 (values.getD i.val 0) := by + decide + exact hfit ⟨q, hq'⟩) + (by unfold addressBound; decide) + have hr0 := hr 0 (by decide) + have hr11 := hr 11 (by decide) + have hc0 := hcounts 0 (by decide) + have hp0 := hpairs 0 (by decide) 1 (by decide) (by decide) + have hp3 := hpairs 0 (by decide) 3 (by decide) (by decide) + have hcount0 : Nat.count (fun q => (values.getD q 0 % (3 : ℤ)).toNat = 0) 4 = 2 := by + decide + have hrank1 : Nat.count (fun q => (values.getD q 0 % (3 : ℤ)).toNat = 0) 1 = 0 := by + decide + have hrank3 : Nat.count (fun q => (values.getD q 0 % (3 : ℤ)).toNat = 0) 3 = 1 := by + decide + obtain ⟨t, ht, he⟩ := hy + have hrun := he.runs (pc := 0) (p := program.program counts) + (Program.Contains.self _).append_left + refine ⟨y, t + 1, by omega, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [Nat.zero_add, State.init, Nat.add_sub_cancel] using hrun + · simpa only [State.init, hc] using he.executes counts + · simpa [values] using hr0 + · simpa [values] using hr11 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, hcount0] using hc0 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, Nat.reduceDiv, hrank1] using hp0.1 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, Nat.reduceMod, hrank1] using hp0.2 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, Nat.reduceDiv, hrank3] using hp3.1 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, Nat.reduceMod, hrank3] using hp3.2 + · exact hframe 3 (by decide) + · exact hframe 54 (by decide) + · exact hrun.bounded_init (by decide : InputFits 8 (input 2 3 values)) + +private def callerInput : List ℕ := [2, 1, 2, 3] ++ values.map Signed.encode + +private def caller : Data 32 := + ((Data.init callerInput).set 7 23).write 0 199 |>.write 60 77 + +-- The same twelve signed iterations exceed the default interpreter-expression recursion depth. +set_option maxRecDepth 4096 in +example : ((programAt 3).eval 8 16 caller).map (fun (y, _) => + ((List.range 12).map fun q => y.mem (17 + q), + (List.range 3).map fun j => y.mem (29 + j), + [y.mem 0, y.mem 1, y.mem 2, y.mem 60])) = + some ([2, 0, 1, 0, 2, 2, 0, 2, 1, 1, 0, 2], [2, 1, 1], [199, 2, 1, 77]) := by decide + +/-- Caller header and signed payload are actual memory, with unrelated prefix words preserved. -/ +example : ∃ y, ExecWithin 8 (programAt 3) caller y 516 ∧ + y.mem 0 = 199 ∧ y.mem 1 = 2 ∧ y.mem 2 = 1 ∧ + y.mem 17 = 2 ∧ y.mem 29 = 2 ∧ y.mem 60 = 77 := by + have hwords : ∀ i : Fin 12, + caller.mem (5 + i.val) = Signed.encode (values.getD i.val 0) := by decide + have hvalues : ∀ i : Fin 12, + Cslib.GenericRAM.Signed.Fits 6 (values.getD i.val 0) := by decide + obtain ⟨y, hy, -, -, -, hr, hc, -, hf⟩ := exec_at (w := 6) 3 2 3 + (fun q => values.getD q 0) caller (by decide) (by decide) + (by intro q hq; exact hwords ⟨q, hq⟩) (by decide) (by decide) + (by intro q hq; exact hvalues ⟨q, hq⟩) (by unfold addressBoundAt; decide) + have hr0 := hr 0 (by decide) + have hc0 := hc 0 (by decide) + have hcount : Nat.count (fun q => (values.getD q 0 % (3 : ℤ)).toNat = 0) 4 = 2 := by + decide + refine ⟨y, hy, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact (hf 0 (Or.inl (by decide))).trans (by decide) + · exact (hf 1 (Or.inl (by decide))).trans (by decide) + · exact (hf 2 (Or.inl (by decide))).trans (by decide) + · simpa [values] using hr0 + · simpa only [Nat.cast_ofNat, Nat.reduceMul, Nat.reduceAdd, hcount] using hc0 + · exact (hf 60 (Or.inr (by decide))).trans (by decide) + +namespace CompleteCatalogue + +open Cslib.Algorithms.WordRAM.ExactTriangle + +private def raw : List ℕ := [1, 1, 2, 3] ++ values.map Signed.encode + +private def tiny : List ℕ := [1, 1, 1, 2] ++ ([-1, 0, 1] : List ℤ).map Signed.encode + +-- The composed controller includes signed residue loops, two square-root loops and nested +-- class/chunk loops in one interpreter expression. +set_option maxRecDepth 4096 in +example : (Catalogue.program.eval 10 4 (.init tiny)).map (fun (y, _) => + ([y.mem 17, y.mem 18, y.mem 19], + [y.regs Catalogue.pieceLength, y.regs Catalogue.pieces, + y.regs ChunkCatalogue.capacity, y.regs ChunkCatalogue.written, + y.regs ChunkCatalogue.heap, y.mem 5, y.mem 8, y.mem 20])) = + some ([1, 15, 1], [1, 1, 1, 1, 20, 3, 1, 0]) := by decide +kernel + +/-- Every supplied value is an actual initialized word, and both parameter computations and +the descriptor count/readback come from the same charged compiled run. -/ +example : ∃ y t, t ≤ 861 ∧ + Runs 10 (Catalogue.program.program ChunkCatalogue.written) (State.init raw) + ⟨Catalogue.program.size, y⟩ (t - 1) ∧ + Executes 10 (Catalogue.program.program ChunkCatalogue.written) (State.init raw) 3 t ∧ + y.regs Catalogue.pieceLength = 1 ∧ y.regs Catalogue.pieces = 2 ∧ + y.mem 56 = 0 ∧ y.mem 57 = 32 ∧ y.mem 58 = 2 ∧ + y.mem 63 = 48 ∧ y.mem 64 = 1 ∧ y.mem 5 = 3 ∧ y.mem 65 = 0 := by + have hwords : ∀ i : Fin 12, + (Data.init raw : Data 32).mem (5 + i.val) = Signed.encode (values.getD i.val 0) := by + decide + have hvalues : ∀ i : Fin 12, + Cslib.GenericRAM.Signed.Fits 8 (values.getD i.val 0) := by decide + obtain ⟨y, hy, -, -, -, -, hd, hcap, hlen, hpieces, hcount, hheap, hr, + hc, hq, hdesc, hf⟩ := Catalogue.exec_at (w := 8) 2 1 1 3 + (fun q => values.getD q 0) (.init raw) + (by decide) (by decide) (by decide) (by decide) + (by intro q hq; exact hwords ⟨q, hq⟩) + (by decide) (by decide) (by decide) (by decide) (by decide) + (by intro q hq; exact hvalues ⟨q, hq⟩) (by unfold Catalogue.budget; decide) + have hQ : Cslib.Algorithms.ExactTriangle.queryCap 2 1 = 4 := by + have hlow : 4 ≤ Nat.sqrt 16 := Nat.le_sqrt.mpr (by decide) + have hhigh : Nat.sqrt 16 < 5 := Nat.sqrt_lt.mpr (by decide) + have heq : Nat.sqrt 16 = 4 := by omega + simpa only [Cslib.Algorithms.ExactTriangle.queryCap, Nat.reducePow, Nat.div_one] using heq + have hB : Cslib.Algorithms.ExactTriangle.pieceSize 1 1 = 1 := by decide + have hH : Cslib.Algorithms.ExactTriangle.numPieces 2 1 1 = 2 := by decide + let C := fun rho => Nat.count (fun q => (values.getD q 0 % (3 : ℤ)).toNat = rho) 4 + have hc0 : C 0 = 2 := by decide + have hc1 : C 1 = 1 := by decide + have hc2 : C 2 = 1 := by decide + change y.regs ChunkCatalogue.written = + ChunkCatalogue.before C (Cslib.Algorithms.ExactTriangle.queryCap 2 1) 3 at hcount + have hbefore0 : ChunkCatalogue.before C 4 0 = 0 := by simp [ChunkCatalogue.before] + have hbefore2 : ChunkCatalogue.before C 4 2 = 2 := by + simp only [ChunkCatalogue.before, Finset.sum_range_succ, Finset.sum_range_zero, + hc0, hc1, Nat.zero_add, Nat.ceilDiv_eq_add_pred_div] + have hbefore3 : ChunkCatalogue.before C 4 3 = 3 := by + simp only [ChunkCatalogue.before, Finset.sum_range_succ, Finset.sum_range_zero, + hc0, hc1, hc2, Nat.zero_add, Nat.ceilDiv_eq_add_pred_div] + have hcTotal : y.regs ChunkCatalogue.written = 3 := by + simpa only [hQ, hbefore3] using hcount + change (∀ rho < 3, ∀ k < C rho ⌈/⌉ Cslib.Algorithms.ExactTriangle.queryCap 2 1, + y.mem (56 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 2 1) rho + k)) = rho ∧ + y.mem (56 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 2 1) rho + k) + 1) = + 32 + 2 * rho * 4 + 2 * (k * Cslib.Algorithms.ExactTriangle.queryCap 2 1) ∧ + y.mem (56 + 3 * (ChunkCatalogue.before C + (Cslib.Algorithms.ExactTriangle.queryCap 2 1) rho + k) + 2) = + min (Cslib.Algorithms.ExactTriangle.queryCap 2 1) + (C rho - k * Cslib.Algorithms.ExactTriangle.queryCap 2 1)) at hdesc + have first := hdesc 0 (by decide) 0 (by rw [hQ, hc0]; decide) + have last := hdesc 2 (by decide) 0 (by rw [hQ, hc2]; decide) + obtain ⟨t, ht, he⟩ := hy + have hRun := he.runs (pc := 0) (p := Catalogue.program.program ChunkCatalogue.written) + (Program.Contains.self _).append_left + have hcost : Catalogue.cost 2 1 3 = 860 := by + norm_num [Catalogue.cost, hQ, Cslib.Algorithms.ExactTriangle.sOf] + refine ⟨y, t + 1, by rw [hcost] at ht; omega, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [State.init, Nat.zero_add, Nat.add_sub_cancel] using hRun + · simpa only [State.init, hcTotal] using he.executes ChunkCatalogue.written + · simpa only [hB] using hlen + · simpa only [hH] using hpieces + · simpa only [hQ, hbefore0, Nat.reduceAdd, Nat.reduceMul] using first.1 + · simpa only [hQ, hbefore0, Nat.reduceAdd, Nat.reduceMul] using first.2.1 + · simpa only [hQ, hbefore0, hc0, Nat.reduceAdd, Nat.reduceMul, + Nat.sub_zero, min_eq_right (by decide : 2 ≤ 4)] + using first.2.2 + · simpa only [hQ, hbefore2, Nat.reduceAdd, Nat.reduceMul] using last.2.1 + · simpa only [hQ, hbefore2, hc2, Nat.reduceAdd, Nat.reduceMul, + Nat.sub_zero, min_eq_right (by decide : 1 ≤ 4)] + using last.2.2 + · exact hf 5 (Or.inl (by decide)) + · exact hf 65 (Or.inr (by simpa only [hcTotal] using (by decide : 65 ≤ 65))) + +end CompleteCatalogue + +end ComplexityTests.WordRAMExactTriangleResidues diff --git a/NOTICE b/NOTICE index dcdc12ee98..fe02c32e8b 100644 --- a/NOTICE +++ b/NOTICE @@ -2,6 +2,7 @@ The 3SUM/Exact Triangle reductions, finite counting/tensor lemmas and native mat Complexity/Algorithms/ExactTriangle/{Defs,Weights,Lopsided,Hashing,Splitting,Witnesses}.lean, Complexity/Algorithms/MatrixMultiplication/Schonhage/, Complexity/Algorithms/ThreeSum/, +Complexity/Algorithms/WordRAM/ExactTriangle/, Complexity/Algorithms/WordRAM/ThreeSum/, Complexity/Foundations/Algebra/{FiniteMagnitude,GeometricTail}.lean, Complexity/Foundations/Combinatorics/FunctionPlaces.lean, @@ -27,3 +28,7 @@ This product is licensed under the Apache License, Version 2.0 (see LICENSE). The prime-window proof also adapts Mathlib/NumberTheory/Bertrand.lean, Copyright (c) 2020 Patrick Stevens; authors Patrick Stevens, Bolton Bailey, under the Apache License, Version 2.0. + +The shared tensor encoder in +Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/ +is also part of the Anthropic adaptation described above. diff --git a/ROADMAP.md b/ROADMAP.md index 5fc6cb5a6f..572bf81a3b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -434,6 +434,9 @@ each statement against the numbered result. Organize modules by subject, not cha theorems; simulating operations by programs over smaller bases; structured programs and time classes over arbitrary value domains with weighted or bit costs; simulations between instances; the typed core proposed in [CSLib #1158](https://github.com/leanprover/cslib/pull/1158). +- **Shared native matrix encoders** ([#1493](https://github.com/cslib-community/complexitylib/issues/1493)): compute all `10^L` leaf values from `7^L` + signed inputs within `240*(L+1)*10^L` standard WordRAM instructions, plus the final halt, + with fixed code/registers and concrete initialized inputs, frames and explicit width bounds. - **Word RAM:** an AC⁰ basis; wider-fragment and Turing-machine simulations; transfer of Anthropic's modular machine and 3SUM/APSP algorithms; direct 3SUM adaptation ([#1394]) has the Convolution-3SUM/Exact Triangle correctness reduction, a charged native single-instance @@ -442,9 +445,9 @@ each statement against the numbered result. Organize modules by subject, not cha interface ([#1438]) adds padding, detection and signed digit reconstruction. Modular counting and the selected-prime false-positive bound ([#1442]), finite residue/chunk/piece splitting and witness recovery ([#1447]) are proved mathematically. The tensor identity, full recursion, - shared encodings and pruned-evaluation core ([#1459]) include exact visited-leaf and passed-set + shared encodings and pruned-evaluation core ([#1459](https://github.com/cslib-community/complexitylib/issues/1459)) include exact visited-leaf and passed-set bounds, with the empty zero-depth root handled explicitly. The finite thin-product component - ([#1463]) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and + ([#1463](https://github.com/cslib-community/complexitylib/issues/1463)) proves valid layouts, arbitrary-size padding/tiling, exact queried signed products and sparse encoding/leaf/passed-set totals. These counts are not native instruction costs. It still needs native tensor/sparse-query programs, native Chan–He preprocessing, the reduction host, the fast Exact Triangle solver and full @@ -477,6 +480,12 @@ each statement against the numbered result. Organize modules by subject, not cha `ShortestPathSpec p bound` predicate in `Dijkstra/HeapStatement.lean` across `LogLinearSpec`, `FibonacciSpec`, and `FibonacciLinearSpec`. +- **Native ExactTriangle preparation** ([#1532](https://github.com/cslib-community/complexitylib/issues/1532)): + initialized execution computes the complete catalogue and raw zero/one instance matrices, + with exact finite readback, logarithmic width, polynomial prefix words and address space, + and a `690n²Dg` charged bound in the source's admissible parameter regime. Prime selection, + solving the prepared instances, product/backend composition and the full #1394 endpoint remain. + ## Coverage and reuse The long-term bar is to support formalizing every paper in the diff --git a/UPSTREAM.md b/UPSTREAM.md index b009e0bce4..465a374f77 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -154,6 +154,22 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he theorems preserve the complete source memory and register bank. This is forward correctness, not reflection of source faults; transfer from Anthropic's modular signed machine and its 3SUM/APSP algorithms remains separate. +* **Native shared tensor encoder.** `Algorithms/WordRAM/MatrixMultiplication/Schonhage/` implements the complete shared left/right + encoding milestone ([#1493](https://github.com/cslib-community/complexitylib/issues/1493)). + Fixed standard-WordRAM code with 32 registers transforms one coordinate at a time between + two disjoint `10^L` buffers. It starts from `State.init` with a depth header and `7^L` + sign-and-magnitude words, computes all public `encodingL`/`encodingR` values, and preserves + the input and every cell outside the two buffers. Mathlib's `finFunctionFinEquiv` supplies + least-significant-first string numbering. All stages, powers, indices, address arithmetic, + signed add/subtract blocks and the final output copy cost at most `240*(L+1)*10^L` + instructions; halt adds one. Signed operands have magnitude bounded from the original + input by `7^L*A < 2^(w-1)`; the explicit natural bound `L+64+64*10^L` must fit target width + `w+2`. The two extra bits and actual arithmetic-block costs come from the existing native + signed kernels, without a copied source machine or a whole-program simulation hypothesis. + The existing array-copy interface is retained, with a narrow generalization for disjoint + blocks in either address order or identical blocks. + This is the encoder component only; band/tile preparation and full native composition remain + in [#1394](https://github.com/cslib-community/complexitylib/issues/1394). * **Direct 3SUM adaptation.** `Algorithms/ThreeSum/Convolution.lean` adapts the explicit Convolution-3SUM to Exact Triangle correctness reduction from Anthropic's [`formal-math@16a5ce41304fcdaea4e0be7c132e1c361dc26da5`](https://github.com/anthropics/formal-math/tree/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp), @@ -211,6 +227,20 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he witness recovery component ([#1447](https://github.com/cslib-community/complexitylib/issues/1447)), using the existing matrices, lopsided graph and `Set.EqOn` answer API. Computable integer parameters retain the source's exact real floor/ceiling specifications through checked bridges. + `Algorithms/WordRAM/ExactTriangle/` implements the complete initialized preparation component + ([#1532](https://github.com/cslib-community/complexitylib/issues/1532)). Fixed sixty-four-register + code of at most 641 flat instructions consumes raw `(D,g,n,p)` headers and signed-encoded + A–B, B–C and A–C weights. It computes the residue classes, query chunks and records and writes + both padded instance matrices. These matrices contain raw unsigned zero/one words; a signed + product consumer must charge their conversion. The supplied positive modulus is not selected + by this program. In the source regime `16 ≤ D ≤ n`, `1 ≤ g` and `p` in the square-root prime + window, actual initialized execution including halt costs at most `690n²Dg`. Every sufficiently + wide physical machine has the same output. Under `D,g,p ≤ n` and input magnitudes bounded by + `(n+2)^κ`, sufficient width is at most `(κ+18)clog₂(n+2)`, and every prefix's words and address + support are bounded by `(n+2)^(κ+19)`. The generic width and signed-fit topic modules are reused + unchanged from #1537 and #1552. These are actual execution and fit proofs, without supplied + layout, execution or solver contracts. Product composition #1581 and the full #1394 endpoint + remain separate. `Algorithms/MatrixMultiplication/Schonhage/` adapts the tensor-recursion core from the same pin's `Sec2/{Lemma6,Recursion,Lemma7_8,Lemma10}.lean`: the ten-term polynomial identity with explicit error, full evaluation, shared encodings, pruned values, visited leaves and passed-set totals.