diff --git a/Complexity.lean b/Complexity.lean index cfa5796f19..667d55ebe8 100644 --- a/Complexity.lean +++ b/Complexity.lean @@ -31,16 +31,21 @@ public import Complexity.Algorithms.Lean.VanEmdeBoas.Insert 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.BandBounds 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 public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Levels public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Magnitude +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Orders public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Parameters public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Pruned +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.PrunedCounts public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Sparse +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.SplitDigits public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling public import Complexity.Algorithms.Reachability.BreadthFirst @@ -111,6 +116,82 @@ 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.BandAll +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCatalogue +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCollect +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCopy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCost +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCount +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandEncode +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandExec +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandFlat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandHeader +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandInput +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandMeaning +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandParameters +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPopulation +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPrepare +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandProgram +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandRead +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandRoot +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandSplit +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandTables +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandThin +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.Pruned.Accumulate +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.AccumulateStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Add +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Bounds +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Child +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Clear +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Codes +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Control +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Emit +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.EmptyChild +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Evaluate +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Finish +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Flat +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Frame +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Initialize +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Input +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Kernel +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Leaf +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.LeafStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Node +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.NodeFrame +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Powers +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Program +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Progress +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressChild +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressEmpty +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressFinish +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressReturn +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Push +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Ready +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Recursive +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Report +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Return +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Root +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Scan +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanInsert +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanSet +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Values +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Weight +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Width +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 +1165,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 @@ -1708,6 +1790,7 @@ public import Complexity.Computability.USTCON.Reader.Encode public import Complexity.Computability.USTCON.Reader.Invariant public import Complexity.Computability.USTCON.Space public import Complexity.Computability.WordRAM.Allocation +public import Complexity.Computability.WordRAM.ArrayAppend public import Complexity.Computability.WordRAM.Basis public import Complexity.Computability.WordRAM.Compile public import Complexity.Computability.WordRAM.Controller @@ -1718,6 +1801,7 @@ public import Complexity.Computability.WordRAM.ExactNotation public import Complexity.Computability.WordRAM.Function public import Complexity.Computability.WordRAM.Generic public import Complexity.Computability.WordRAM.Heap +public import Complexity.Computability.WordRAM.InputArray public import Complexity.Computability.WordRAM.ListArray public import Complexity.Computability.WordRAM.MapOp public import Complexity.Computability.WordRAM.Multiword @@ -1739,6 +1823,7 @@ public import Complexity.Computability.WordRAM.Multiword.Workspace public import Complexity.Computability.WordRAM.Native public import Complexity.Computability.WordRAM.Notation public import Complexity.Computability.WordRAM.PackedCount +public import Complexity.Computability.WordRAM.Power public import Complexity.Computability.WordRAM.RAM public import Complexity.Computability.WordRAM.RAMInput public import Complexity.Computability.WordRAM.Randomized.Defs @@ -2201,6 +2286,7 @@ public import Complexity.Foundations.Data.Finset.Expect public import Complexity.Foundations.Data.Finset.Finpartition public import Complexity.Foundations.Data.Finset.Markov public import Complexity.Foundations.Data.Finset.OrderGlue +public import Complexity.Foundations.Data.Finset.OrderedFilter public import Complexity.Foundations.Data.Finset.Quantile public import Complexity.Foundations.Data.Finset.Quotient public import Complexity.Foundations.Data.Finset.Sampling @@ -2223,6 +2309,7 @@ public import Complexity.Foundations.Data.List.Lex public import Complexity.Foundations.Data.List.Lookup public import Complexity.Foundations.Data.List.OfFn public import Complexity.Foundations.Data.List.Range +public import Complexity.Foundations.Data.List.RowMajor public import Complexity.Foundations.Data.List.SelectDistinct public import Complexity.Foundations.Data.Matrix.Binary public import Complexity.Foundations.Data.Matrix.IntegerBits @@ -2258,6 +2345,7 @@ public import Complexity.Foundations.Data.Nat.Div public import Complexity.Foundations.Data.Nat.Factorial public import Complexity.Foundations.Data.Nat.IntermediateValue public import Complexity.Foundations.Data.Nat.Log +public import Complexity.Foundations.Data.Nat.OfDigits public import Complexity.Foundations.Data.Nat.PolynomialBound.Closure public import Complexity.Foundations.Data.Nat.PolynomialBound.Exponential public import Complexity.Foundations.Data.Nat.PolynomialBound.Log diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/BandBounds.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/BandBounds.lean new file mode 100644 index 0000000000..4fc53bbe64 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/BandBounds.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.Algorithms.MatrixMultiplication.Schonhage.Layout +import Mathlib.Data.Nat.Choose.Bounds +import Complexity.Algorithms.MatrixMultiplication.Schonhage.Tiling + +/-! +# Bounds for guarded band addressing + +## Main results + +* `bandSize_le_seven_pow`: a complete band fits within the tensor input dimension. +* `band_position_bounds`: all padded row/column arithmetic stays below `N+7^L` before + the native guard. Row-major matrix addresses are computed only after that guard. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +private theorem mul_add_lt_mul {a b c d : ℕ} (ha : a < b) (hc : c < d) : + a * d + c < b * d := by + have h := Nat.mul_le_mul_right d (Nat.succ_le_of_lt ha) + rw [Nat.succ_mul] at h + omega + +/-- The entire band dimension is bounded by the flat tensor input dimension. -/ +theorem bandSize_le_seven_pow (L m : ℕ) : bandSize L m ≤ 7 ^ L := by + have hK : K0 L m ≤ 2 ^ L := (Nat.sqrt_le_self _).trans (Nat.choose_le_two_pow _ _) + have hN : N0 L m ≤ 3 ^ L := Nat.pow_le_pow_right (by decide) (Nat.sub_le _ _) + calc bandSize L m ≤ 2 ^ L * 3 ^ L := Nat.mul_le_mul hK hN + _ = 6 ^ L := by rw [← Nat.mul_pow] + _ ≤ 7 ^ L := Nat.pow_le_pow_left (by decide) _ + +/-- Even the last padded band has small block arithmetic. This bound precedes the `r 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/MatrixMultiplication/Schonhage/MaskTable.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/MaskTable.lean new file mode 100644 index 0000000000..b011cf1221 --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/MaskTable.lean @@ -0,0 +1,315 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Layout +public import Mathlib.Data.Fintype.Powerset +public import Mathlib.Algebra.BigOperators.Fin + +/-! +# Deterministic mask tables for matrix bands + +Size-`m` level sets are ordered by their binary masks. The first `K0²` masks are assigned +in row-major order to block pairs. This specifies a deterministic instance of the existing +`Layout`; a native scan can compute the table without receiving indexing data as an input. + +## Main definitions + +* `MaskTable.maskSet`, `MaskTable.maskCode`: explicit binary masks and level sets. +* `MaskTable.population`: the truncated binary population count computed by division loops. +* `MaskTable.validMasks`: the masks with the required cardinality. + +## Main results + +* `MaskTable.card_validMasks`: exactly `L.choose m` valid masks exist. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +open Finset + +/-- The set of levels whose binary digit is one. -/ +def maskSet {L : ℕ} (a : Fin (2 ^ L)) : Finset (Fin L) := + univ.filter fun i => finFunctionFinEquiv.symm a i = (1 : Fin 2) + +/-- The integer mask of a finite level set, with level zero least significant. -/ +def maskCode {L : ℕ} (Q : Finset (Fin L)) : Fin (2 ^ L) := + finFunctionFinEquiv fun i => if i ∈ Q then (1 : Fin 2) else 0 + +/-- Decoding the explicitly numbered mask recovers the original set. -/ +theorem maskSet_maskCode {L : ℕ} (Q : Finset (Fin L)) : maskSet (maskCode Q) = Q := by + ext i + simp [maskSet, maskCode] + +/-- Encoding a decoded bounded mask recovers the original integer. -/ +theorem maskCode_maskSet {L : ℕ} (a : Fin (2 ^ L)) : maskCode (maskSet a) = a := by + apply finFunctionFinEquiv.symm.injective + funext i + simp only [maskCode, Equiv.symm_apply_apply, maskSet, mem_filter, mem_univ, true_and] + have h := (finFunctionFinEquiv.symm a i).isLt + split <;> apply Fin.ext <;> simp_all only [Fin.ext_iff, Fin.val_one, Fin.val_zero] + omega + +/-- Binary-mask numbering is an explicit bijection, not a chosen enumeration. -/ +def maskEquiv (L : ℕ) : Fin (2 ^ L) ≃ Finset (Fin L) where + toFun := maskSet + invFun := maskCode + left_inv := maskCode_maskSet + right_inv := maskSet_maskCode + +/-- Number of one digits among exactly the first `L` binary digits. -/ +def population (L a : ℕ) : ℕ := ∑ i : Fin L, a / 2 ^ i.val % 2 + +/-- A zero-depth scan has population zero. -/ +theorem population_zero (a : ℕ) : population 0 a = 0 := by simp [population] + +/-- One native remainder/division step removes the lowest digit. -/ +theorem population_succ (L a : ℕ) : + population (L + 1) a = a % 2 + population L (a / 2) := by + simp only [population, Fin.sum_univ_succ, Fin.val_zero, pow_zero, Nat.div_one, + Fin.val_succ, pow_succ', ← Nat.div_div_eq_div_mul] + +/-- There are at most `L` one digits, even for an unbounded natural argument. -/ +theorem population_le (L a : ℕ) : population L a ≤ L := by + induction L generalizing a with + | zero => rw [population_zero] + | succ L ih => + rw [population_succ] + have hm := Nat.mod_lt a (by decide : 0 < 2) + have ht := ih (a / 2) + omega + +/-- The loop's digit sum is the cardinality of the represented level set. -/ +theorem population_eq_card {L : ℕ} (a : Fin (2 ^ L)) : + population L a.val = (maskSet a).card := by + rw [maskSet, card_eq_sum_ones, sum_filter] + apply sum_congr rfl + intro i _ + have h : (finFunctionFinEquiv.symm a i).val = a.val / 2 ^ i.val % 2 := rfl + have hb := (finFunctionFinEquiv.symm a i).isLt + split <;> simp_all only [Fin.ext_iff, Fin.val_one] + omega + +/-- All valid masks, in the ordinary linear order on bounded naturals. -/ +def validMasks (L m : ℕ) : Finset (Fin (2 ^ L)) := + univ.filter fun a => population L a.val = m + +/-- Every size-`m` subset occurs once in the bounded mask scan. -/ +theorem card_validMasks (L m : ℕ) : (validMasks L m).card = K L m := by + calc + (validMasks L m).card = ((univ : Finset (Fin L)).powersetCard m).card := by + apply card_bij (fun a _ => maskSet a) + · intro a ha + rw [mem_powersetCard_univ] + rw [← population_eq_card] + exact (mem_filter.mp ha).2 + · intro a _ b _ h + exact (maskEquiv L).injective h + · intro Q hQ + refine ⟨maskCode Q, ?_, maskSet_maskCode Q⟩ + simp only [validMasks, mem_filter, mem_univ, true_and, population_eq_card, + maskSet_maskCode] + exact mem_powersetCard_univ.mp hQ + _ = K L m := by rw [card_powersetCard, card_univ, Fintype.card_fin]; rfl + +/-- Number of valid masks strictly below the scan counter. -/ +def prefixCount (L m t : ℕ) : ℕ := ((range t).filter fun a => population L a = m).card + +/-- The scan begins without any accepted mask. -/ +theorem prefixCount_zero (L m : ℕ) : prefixCount L m 0 = 0 := by simp [prefixCount] + +/-- A scan accepts at most one mask in each iteration. -/ +theorem prefixCount_le (L m t : ℕ) : prefixCount L m t ≤ t := + (card_filter_le _ _).trans (by rw [card_range]) + +/-- The actual scalar counter update for one mask. -/ +theorem prefixCount_succ (L m t : ℕ) : + prefixCount L m (t + 1) = prefixCount L m t + if population L t = m then 1 else 0 := by + by_cases h : population L t = m <;> simp [prefixCount, range_add_one, filter_insert, h] + +/-- Extending a scan cannot decrease the accepted-mask counter. -/ +theorem prefixCount_mono (L m : ℕ) {a b : ℕ} (h : a ≤ b) : + prefixCount L m a ≤ prefixCount L m b := + card_le_card (filter_subset_filter _ (range_mono h)) + +/-- Earlier valid masks have strictly smaller accepted-mask ranks. -/ +theorem prefixCount_lt {L m a b : ℕ} (ha : population L a = m) (hab : a < b) : + prefixCount L m a < prefixCount L m b := by + have h := prefixCount_mono L m (show a + 1 ≤ b by omega) + rw [prefixCount_succ] at h + simp only [ha, ↓reduceIte] at h + omega + +/-- The finite complete scan has exactly the binomial count. -/ +theorem prefixCount_full (L m : ℕ) : prefixCount L m (2 ^ L) = K L m := by + rw [← card_validMasks] + apply card_bij (fun a ha => (⟨a, mem_range.mp (mem_filter.mp ha).1⟩ : Fin (2 ^ L))) + · intro a ha + exact mem_filter.mpr ⟨mem_univ _, (mem_filter.mp ha).2⟩ + · intro a _ b _ h + exact congrArg Fin.val h + · intro a ha + exact ⟨a.val, mem_filter.mpr ⟨mem_range.mpr a.isLt, (mem_filter.mp ha).2⟩, rfl⟩ + +private theorem card_below_orderEmb {α : Type*} [LinearOrder α] (s : Finset α) + {n : ℕ} (h : s.card = n) (i : Fin n) : + (s.filter fun a => a < s.orderEmbOfFin h i).card = i.val := by + rw [← card_range i.val] + symm + apply card_bij (fun j hj => s.orderEmbOfFin h + ⟨j, (mem_range.mp hj).trans i.isLt⟩) + · intro j hj + exact mem_filter.mpr ⟨s.orderEmbOfFin_mem h _, + (s.orderEmbOfFin h).strictMono (mem_range.mp hj)⟩ + · intro j hj k hk hjk + exact congrArg Fin.val ((s.orderEmbOfFin h).injective hjk) + · intro a ha + let j := (s.orderIsoOfFin h).symm ⟨a, (mem_filter.mp ha).1⟩ + have hj : s.orderEmbOfFin h j = a := + congrArg Subtype.val ((s.orderIsoOfFin h).apply_symm_apply ⟨a, (mem_filter.mp ha).1⟩) + have hji : j < i := (s.orderEmbOfFin h).lt_iff_lt.mp (hj ▸ (mem_filter.mp ha).2) + exact ⟨j.val, mem_range.mpr hji, hj⟩ + +/-- The scalar scan count equals the rank among the bounded valid masks. -/ +theorem prefixCount_eq_bounded (L m t : ℕ) (ht : t ≤ 2 ^ L) : + prefixCount L m t = ((validMasks L m).filter fun a => a.val < t).card := by + apply card_bij (fun a ha => + (⟨a, (mem_range.mp (mem_filter.mp ha).1).trans_le ht⟩ : Fin (2 ^ L))) + · intro a ha + exact mem_filter.mpr ⟨mem_filter.mpr ⟨mem_univ _, (mem_filter.mp ha).2⟩, + mem_range.mp (mem_filter.mp ha).1⟩ + · intro a _ b _ h + exact congrArg Fin.val h + · intro a ha + exact ⟨a.val, mem_filter.mpr ⟨mem_range.mpr (mem_filter.mp ha).2, + (mem_filter.mp (mem_filter.mp ha).1).2⟩, rfl⟩ + +/-- Row-major block-pair rank in the full list of size-`m` masks. -/ +def pairRank {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : Fin (K L m) := + Fin.castLE (by simpa only [K0] using Nat.sqrt_le (K L m)) (finProdFinEquiv gh) + +/-- The selected mask at a block pair is the corresponding mask in increasing order. -/ +def selectedMask {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : Fin (2 ^ L) := + (validMasks L m).orderEmbOfFin (card_validMasks L m) (pairRank gh) + +/-- The computed prefix counter at a selected mask is its row-major block-pair rank. -/ +theorem prefixCount_selectedMask {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : + prefixCount L m (selectedMask gh).val = (pairRank gh).val := by + rw [prefixCount_eq_bounded _ _ _ (selectedMask gh).isLt.le] + exact card_below_orderEmb (validMasks L m) (card_validMasks L m) (pairRank gh) + +/-- The dense inverse-table word: zero for an unselected mask, otherwise rank plus one. -/ +def lookup (L m a : ℕ) : ℕ := + if a < 2 ^ L ∧ population L a = m ∧ prefixCount L m a < K0 L m * K0 L m + then prefixCount L m a + 1 else 0 + +/-- Exactly the selected valid masks have nonzero inverse-table words. -/ +theorem lookup_ne_zero_iff (L m a : ℕ) : lookup L m a ≠ 0 ↔ + a < 2 ^ L ∧ population L a = m ∧ prefixCount L m a < K0 L m * K0 L m := by + simp [lookup] + +/-- Every inverse-table word is bounded by its original mask plus one. -/ +theorem lookup_le (L m a : ℕ) : lookup L m a ≤ a + 1 := by + unfold lookup + split + · exact Nat.add_le_add_right (prefixCount_le L m a) 1 + · exact Nat.zero_le _ + +/-- The inverse-table word at every selected block pair has the promised rank. -/ +theorem lookup_selectedMask {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : + lookup L m (selectedMask gh).val = (pairRank gh).val + 1 := by + have hm := (validMasks L m).orderEmbOfFin_mem (card_validMasks L m) (pairRank gh) + have hp := (mem_filter.mp hm).2 + change population L (selectedMask gh).val = m at hp + have hr : (pairRank gh).val < K0 L m * K0 L m := (finProdFinEquiv gh).isLt + simp only [lookup, (selectedMask gh).isLt, hp, prefixCount_selectedMask, + hr, and_self, ↓reduceIte] + +/-- A nonzero inverse-table word belongs to one of the selected block pairs. -/ +theorem exists_selectedMask_of_lookup {L m a : ℕ} (h : lookup L m a ≠ 0) : + ∃ gh : Fin (K0 L m) × Fin (K0 L m), (selectedMask gh).val = a := by + unfold lookup at h + split_ifs at h with hc + · obtain ⟨ha, hp, hr⟩ := hc + let aa : Fin (2 ^ L) := ⟨a, ha⟩ + have hmem : aa ∈ validMasks L m := mem_filter.mpr ⟨mem_univ _, hp⟩ + let j := (validMasks L m).orderIsoOfFin (card_validMasks L m) |>.symm ⟨aa, hmem⟩ + have hj : (validMasks L m).orderEmbOfFin (card_validMasks L m) j = aa := + congrArg Subtype.val ((validMasks L m).orderIsoOfFin (card_validMasks L m) + |>.apply_symm_apply ⟨aa, hmem⟩) + have hjrank : j.val = prefixCount L m a := by + rw [prefixCount_eq_bounded L m a ha.le] + have hbelow := card_below_orderEmb (validMasks L m) (card_validMasks L m) j + rw [hj] at hbelow + exact hbelow.symm + let gh := (@finProdFinEquiv (K0 L m) (K0 L m)).symm ⟨prefixCount L m a, hr⟩ + have hprval : (pairRank gh).val = prefixCount L m a := + congrArg Fin.val ((@finProdFinEquiv (K0 L m) (K0 L m)).apply_symm_apply + ⟨prefixCount L m a, hr⟩) + have hpr : pairRank gh = j := Fin.ext (hprval.trans hjrank.symm) + refine ⟨gh, ?_⟩ + change ((validMasks L m).orderEmbOfFin (card_validMasks L m) (pairRank gh)).val = a + rw [hpr, hj] + · exact (h rfl).elim + +/-- Inverse-table division and remainder recover exactly the two block indices. -/ +theorem lookup_blockPair {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : + (lookup L m (selectedMask gh).val - 1) / K0 L m = gh.1.val ∧ + (lookup L m (selectedMask gh).val - 1) % K0 L m = gh.2.val := by + rw [lookup_selectedMask, Nat.add_sub_cancel] + have hpos : 0 < K0 L m := by have := gh.2.isLt; omega + change (gh.2.val + K0 L m * gh.1.val) / K0 L m = gh.1.val ∧ + (gh.2.val + K0 L m * gh.1.val) % K0 L m = gh.2.val + rw [Nat.add_mul_div_left _ _ hpos, Nat.div_eq_of_lt gh.2.isLt, Nat.zero_add, + Nat.add_mul_mod_self_left, Nat.mod_eq_of_lt gh.2.isLt] + exact ⟨rfl, rfl⟩ + +/-- Both decoded block indices are in range whenever the inverse-table word is nonzero. -/ +theorem lookup_block_lt {L m a : ℕ} (h : lookup L m a ≠ 0) (rightSide : Bool) : + (if rightSide then (lookup L m a - 1) % K0 L m + else (lookup L m a - 1) / K0 L m) < K0 L m := by + obtain ⟨gh, hs⟩ := exists_selectedMask_of_lookup h + have hp := lookup_blockPair gh + rw [hs] at hp + cases rightSide + · simpa only [Bool.false_eq_true, ↓reduceIte, hp.1] using gh.1.isLt + · simpa only [↓reduceIte, hp.2] using gh.2.isLt + +/-- Selected masks always have exactly the required cardinality. -/ +theorem card_selectedMask {L m : ℕ} (gh : Fin (K0 L m) × Fin (K0 L m)) : + (maskSet (selectedMask gh)).card = m := by + have h := (validMasks L m).orderEmbOfFin_mem (card_validMasks L m) (pairRank gh) + exact (population_eq_card _).symm.trans ((mem_filter.mp h).2) + +/-- Distinct block pairs receive distinct level sets. -/ +theorem selectedMask_injective (L m : ℕ) : + Function.Injective (fun gh : Fin (K0 L m) × Fin (K0 L m) => maskSet (selectedMask gh)) := by + intro gh gh' h + have hmask := (maskEquiv L).injective h + have hrank := (validMasks L m).orderEmbOfFin (card_validMasks L m) |>.injective hmask + apply finProdFinEquiv.injective + apply Fin.ext + exact congrArg (fun i : Fin (K L m) => i.val) hrank + +/-- The thin-dimension pair digit uses row-major order, as do the encoder's inner variables. -/ +def innerDigits : Fin 4 ≃ Fin 2 × Fin 2 := (@finProdFinEquiv 2 2).symm + +/-- Explicit base-three outer strings and base-four strings of index pairs, with the first +`K0²` binary masks in increasing order as the shared block table. -/ +def layout {L m : ℕ} (hmL : m ≤ L) : Layout L m where + hmL := hmL + rowIdx := finFunctionFinEquiv.symm + colIdx := finFunctionFinEquiv.symm + innerIdx := finFunctionFinEquiv.symm.trans (Equiv.piCongrRight fun _ => innerDigits) + table gh := maskSet (selectedMask gh) + table_card := card_selectedMask + table_injective := selectedMask_injective L m + +end Cslib.Algorithms.MatrixMultiplication.Schonhage.MaskTable diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/PrunedCounts.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/PrunedCounts.lean new file mode 100644 index 0000000000..2fb1204b9e --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/PrunedCounts.lean @@ -0,0 +1,149 @@ +/- +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.Pruned + +/-! +# Counts of nonempty calls in the finite pruned recursion + +## Main results + +* `Pruned.calls_irrel`: the call trace depends only on the requests, not the encoded values. +* `Pruned.card_calls_le_totalSize_add_one`: requested-set cardinalities pay for every call, + with one extra root for the empty-query boundary. + +These are combinatorial facts about the existing finite trace. They do not assign an instruction +cost to the recursion. Native execution bounds must separately charge their actual instructions. + +## References + +The trace is from [AlmanWilliams2026, Section 2.4.2] and Anthropic's formal-math revision +`16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, [pinned source](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2/Lemma10.lean). +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage + +open Finset + +/-- An empty request set has no child requests. -/ +@[simp] +theorem childSet_empty {n : ℕ} (lam : Term) : childSet (∅ : Finset (OutStr (n + 1))) lam = ∅ := by + ext w + simp [mem_childSet] + +/-- Values of the two encoded arrays do not affect which calls are made. -/ +theorem Pruned.calls_irrel {n : ℕ} (a b a' b' : Leaf n → ℤ) (S : Finset (OutStr n)) : + Pruned.calls n a b S = Pruned.calls n a' b' S := by + induction n with + | zero => rfl + | succ n ih => + rw [Pruned.calls_succ, Pruned.calls_succ] + apply congrArg (Multiset.cons (⟨⟨0, Fin.elim0⟩, ⟨n + 1, S⟩⟩ : PrunedCall)) + apply congrArg (fun f => univ.val.bind f) + funext lam + by_cases h : (childSet S lam).Nonempty + · simp only [Pruned.childCalls, h, ↓reduceIte] + exact congrArg (Multiset.map (PrunedCall.under lam)) (ih _ _ _ _ _) + · simp only [Pruned.childCalls, h, ↓reduceIte] + +/-- One root call and all nonempty child traces, counted with their multiplicities. -/ +theorem Pruned.card_calls_succ {n : ℕ} (a b : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) : + (Pruned.calls (n + 1) a b S).card = 1 + ∑ lam, + if (childSet S lam).Nonempty then + (Pruned.calls n (sliceAt a lam) (sliceAt b lam) (childSet S lam)).card else 0 := by + rw [Pruned.calls_succ, Multiset.card_cons, Multiset.card_bind] + rw [Nat.add_comm] + apply congrArg (Nat.add 1) + apply sum_congr rfl + intro lam _ + simp only [Function.comp_apply, Multiset.card_map, Pruned.childCalls] + split_ifs <;> rfl + +/-- The passed-set total is the root size plus the totals of its nonempty children. -/ +theorem Pruned.totalSize_succ {n : ℕ} (a b : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) : + Pruned.totalSize a b S = S.card + ∑ lam, + if (childSet S lam).Nonempty then + Pruned.totalSize (sliceAt a lam) (sliceAt b lam) (childSet S lam) else 0 := by + unfold Pruned.totalSize + rw [Pruned.calls_succ, Multiset.map_cons, Multiset.sum_cons, Multiset.map_bind, + Multiset.sum_bind] + apply congrArg (Nat.add S.card) + apply sum_congr rfl + intro lam _ + by_cases h : (childSet S lam).Nonempty <;> + simp only [Pruned.childCalls, h, ↓reduceIte, Multiset.map_map, + Multiset.map_zero, Multiset.sum_zero] + all_goals rfl + +/-- A nonempty call carries at least one query, so the passed-set total pays for every call. -/ +theorem Pruned.card_calls_le_totalSize {n : ℕ} (a b : Leaf n → ℤ) + (S : Finset (OutStr n)) (hS : S.Nonempty) : + (Pruned.calls n a b S).card ≤ Pruned.totalSize a b S := by + induction n with + | zero => simpa [Pruned.calls, Pruned.run, Pruned.totalSize] using one_le_card.mpr hS + | succ n ih => + rw [Pruned.card_calls_succ, Pruned.totalSize_succ] + apply Nat.add_le_add (one_le_card.mpr hS) + apply sum_le_sum + intro lam _ + split_ifs with h + · exact ih _ _ _ h + · exact le_rfl + +/-- Empty requests make only the root call, including at zero depth. -/ +theorem Pruned.card_calls_empty {n : ℕ} (a b : Leaf n → ℤ) : + (Pruned.calls n a b ∅).card = 1 := by + cases n with + | zero => rfl + | succ n => simp [Pruned.card_calls_succ] + +/-- Every call is paid for by a requested-set element, except possibly an empty root. -/ +theorem Pruned.card_calls_le_totalSize_add_one {n : ℕ} (a b : Leaf n → ℤ) + (S : Finset (OutStr n)) : + (Pruned.calls n a b S).card ≤ Pruned.totalSize a b S + 1 := by + rcases S.eq_empty_or_nonempty with rfl | hS + · rw [Pruned.card_calls_empty] + omega + · exact (Pruned.card_calls_le_totalSize a b S hS).trans (Nat.le_add_right _ _) + +/-- Encoded values do not affect the total size of the request sets. -/ +theorem Pruned.totalSize_irrel {n : ℕ} (a b a' b' : Leaf n → ℤ) (S : Finset (OutStr n)) : + Pruned.totalSize a b S = Pruned.totalSize a' b' S := by + unfold Pruned.totalSize + rw [Pruned.calls_irrel a b a' b' S] + +/-- Even without pruning, a ten-way depth-`n` tree has fewer than twice `10^n` nodes. +This ordinary count supplies a word-size bound for native timestamp generations. -/ +theorem Pruned.card_calls_add_one_le {n : ℕ} (a b : Leaf n → ℤ) + (S : Finset (OutStr n)) : + (Pruned.calls n a b S).card + 1 ≤ 2 * 10 ^ n := by + induction n with + | zero => rfl + | succ n ih => + let f : Term → ℕ := fun lam => if (childSet S lam).Nonempty then + (Pruned.calls n (sliceAt a lam) (sliceAt b lam) (childSet S lam)).card else 0 + have hpoint (lam : Term) : f lam + 1 ≤ 2 * 10 ^ n := by + dsimp only [f] + split_ifs + · exact ih _ _ _ + · have hp : 0 < (10 : ℕ) ^ n := Nat.pow_pos (by decide) + omega + have hsum := sum_le_sum (s := univ) (fun lam _ => hpoint lam) + have hten : Fintype.card Term = 10 := by decide + simp only [sum_add_distrib, sum_const, card_univ, hten, smul_eq_mul] at hsum + rw [Pruned.card_calls_succ] + change 1 + (∑ lam, f lam) + 1 ≤ _ + rw [pow_succ] + omega + +end Cslib.Algorithms.MatrixMultiplication.Schonhage diff --git a/Complexity/Algorithms/MatrixMultiplication/Schonhage/SplitDigits.lean b/Complexity/Algorithms/MatrixMultiplication/Schonhage/SplitDigits.lean new file mode 100644 index 0000000000..a610801d1a --- /dev/null +++ b/Complexity/Algorithms/MatrixMultiplication/Schonhage/SplitDigits.lean @@ -0,0 +1,103 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable +public import Complexity.Foundations.Data.Nat.OfDigits +public import Complexity.Foundations.Data.Finset.OrderedFilter + +/-! +# Splitting flat tensor digits into band indices + +Outer digits `0,1,2` contribute to a base-three offset. Inner digits `3,4,5,6` +contribute to a base-four offset and set the corresponding binary mask bit. All positions +are least significant first, in the encoder's existing numbering. + +## Main definitions + +* `BandDigits.parts`: the three indices computed by the native digit loop. +* `BandDigits.sevenDigits`: exactly `L` digits, retaining leading zeros. + +## Main results + +* `BandDigits.parts_bounds`: component bounds derived from the original depth. +* `BandDigits.parts_lists`: the compressed digit-list interpretation of all three indices. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.MatrixMultiplication.Schonhage.BandDigits + +/-- Binary level mask and the two compressed offsets. -/ +structure Parts where + /-- Inner-level mask. -/ + mask : ℕ + /-- Base-three outer offset. -/ + outer : ℕ + /-- Base-four inner offset. -/ + inner : ℕ + deriving DecidableEq, Repr + +/-- Remove one base-seven digit, recursively interpreting the remaining higher levels. -/ +def parts : ℕ → ℕ → Parts + | 0, _ => ⟨0, 0, 0⟩ + | L + 1, a => + let tail := parts L (a / 7) + if a % 7 < 3 then ⟨2 * tail.mask, a % 7 + 3 * tail.outer, tail.inner⟩ + else ⟨1 + 2 * tail.mask, tail.outer, (a % 7 - 3) + 4 * tail.inner⟩ + +/-- Component fits depend only on the input depth, even for an unbounded argument. -/ +theorem parts_bounds (L a : ℕ) : + (parts L a).mask < 2 ^ L ∧ (parts L a).outer < 3 ^ L ∧ (parts L a).inner < 4 ^ L := by + induction L generalizing a with + | zero => simp [parts] + | succ L ih => + obtain ⟨hm, ho, hi⟩ := ih (a / 7) + have hd : a % 7 < 7 := Nat.mod_lt _ (by decide) + rw [parts] + split <;> dsimp only + · simp only [pow_succ] + constructor + · omega + constructor <;> omega + · simp only [pow_succ] + constructor + · omega + constructor <;> omega + +/-- A fixed number of digits, so that zero digits at the end are retained. -/ +def sevenDigits : ℕ → ℕ → List ℕ + | 0, _ => [] + | L + 1, a => a % 7 :: sevenDigits L (a / 7) + +/-- These are exactly the digits in Mathlib's finite-function numbering. -/ +theorem sevenDigits_eq_ofFn (L a : ℕ) : + sevenDigits L a = List.ofFn (fun i : Fin L => a / 7 ^ i.val % 7) := by + induction L generalizing a with + | zero => simp [sevenDigits] + | succ L ih => + rw [sevenDigits, ih, List.ofFn_succ] + simp only [Fin.val_zero, pow_zero, Nat.div_one, Fin.val_succ, pow_succ', + ← Nat.div_div_eq_div_mul] + +/-- The native indices are precisely the binary mask and the two compressed digit lists. -/ +theorem parts_lists (L a : ℕ) : + (parts L a).mask = Nat.ofDigits 2 ((sevenDigits L a).map fun d => if d < 3 then 0 else 1) ∧ + (parts L a).outer = Nat.ofDigits 3 ((sevenDigits L a).filter fun d => d < 3) ∧ + (parts L a).inner = Nat.ofDigits 4 + (((sevenDigits L a).filter fun d => ¬d < 3).map fun d => d - 3) := by + induction L generalizing a with + | zero => simp [parts, sevenDigits] + | succ L ih => + obtain ⟨hm, ho, hi⟩ := ih (a / 7) + by_cases hd : a % 7 < 3 + · simp [parts, sevenDigits, hd, Nat.ofDigits, hm, ho, hi] + · have hg : 3 ≤ a % 7 := by omega + simp [parts, sevenDigits, hd, hg, Nat.ofDigits, hm, ho, hi] + +end Cslib.Algorithms.MatrixMultiplication.Schonhage.BandDigits diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandAll.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandAll.lean new file mode 100644 index 0000000000..1cea6cc669 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandAll.lean @@ -0,0 +1,171 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandEncode + +/-! +# Every shared band, computed once + +## Main results + +* `BandPreparation.exec_encodeBands`: the complete native loop prepares, encodes and collects + all bands of one matrix side. Catalogue cells retain the existing flat leaf numbering. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +-- Static preservation checks reduce the finite persistent-register set. +set_option maxRecDepth 4096 + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Catalogue blocks are ordered by band index, without duplicating them per tile. -/ +theorem catalogue_address_lt {L m N β γ p : ℕ} (side : Bool) (hβγ : β < γ) + (hp : p < 10 ^ L) : + catalogueAddress L m N side β p < catalogueAddress L m N side γ 0 := by + have ht : 0 < 10 ^ L := Nat.pow_pos (by decide) + unfold catalogueAddress + nlinarith only [hβγ, hp, ht] + +/-- The full one-side catalogue interval ends at the block immediately after its last band. -/ +theorem catalogue_block_inside {L m N β : ℕ} (side : Bool) (hβ : β < numBands L m N) : + catalogueAddress L m N side 0 0 ≤ catalogueAddress L m N side β 0 ∧ + catalogueAddress L m N side β 0 + 10 ^ L ≤ + catalogueAddress L m N side (numBands L m N) 0 := by + have ht : 0 < 10 ^ L := Nat.pow_pos (by decide) + unfold catalogueAddress + constructor <;> nlinarith only [hβ, ht, Nat.zero_le β] + +/-- All ordinary band counters fit from the original explicit allocation budget. -/ +theorem bands_fits {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) : + Fits w (numBands L m N) := by + apply hfit.of_le + unfold wordBudget + have ht : 0 < 10 ^ L := Nat.pow_pos (by decide) + nlinarith only [ht, Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (numBands L m N)] + +/-- The actual native loop calls the complete encoder once for every shared band, charges +all loop control and counter increments, and preserves any outside caller memory. -/ +theorem exec_encodeBands {w L m N : ℕ} (hw : 0 < w) (side : Bool) (x : Data 96) + (hx : BandParameters L m N x) + (htable : ∀ t < 2 ^ L, x.mem (inverseBase L m N + t) = MaskTable.lookup L m t) + (matrix : ℕ → ℕ → ℤ) + (hmat : ∀ r < N, ∀ k < D m, + x.mem (matrixAddress L m N side r k) = encode (matrix r k)) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ r < N, ∀ k < D m, |matrix r k| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + ∃ y, ExecWithin (w + 2) (encodeBands side) x y + (numBands L m N * (bandTime L + 3) + 3) ∧ + BandParameters L m N y ∧ + (∀ β < numBands L m N, ∀ p < 10 ^ L, + y.mem (catalogueAddress L m N side β p) = + encode (catalogueValue L m N side matrix β p)) ∧ + (∀ b, ¬(1 ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + ¬(catalogueAddress L m N side 0 0 ≤ b ∧ + b < catalogueAddress L m N side (numBands L m N) 0) → + y.mem b = x.mem b) := by + let start := x.set band 0 + have hs : ExecWithin (B := .standard) (w + 2) (ram[ band ← #0 ]) x start 1 := + ExecWithin.const (fits_zero _) + have hp : BandParameters L m N start := parameters_reframe hx (by + intro i hi + have hne : i ≠ band := by + have hp : ∀ i ∈ parameterRegisters, i ≠ band := by decide + exact hp i hi + simp only [start, Data.set_regs, Function.update_of_ne hne]) + let I (y : Data 96) : Prop := BandParameters L m N y ∧ + y.regs band ≤ numBands L m N ∧ + (∀ β < y.regs band, ∀ p < 10 ^ L, + y.mem (catalogueAddress L m N side β p) = + encode (catalogueValue L m N side matrix β p)) ∧ + (∀ b, ¬(1 ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + ¬(catalogueAddress L m N side 0 0 ≤ b ∧ + b < catalogueAddress L m N side (numBands L m N) 0) → + y.mem b = x.mem b) + have hstart : I start := by + refine ⟨hp, ?_, ?_, ?_⟩ + · simp [start] + · simp [start] + · intro b _ _; rfl + have hbody (y : Data 96) (hy : I y) (hc : (Cond.lt band bands).Holds y) : + ∃ z, ExecWithin (w + 2) (bandBody side) y z (bandTime L + 1) ∧ I z ∧ + numBands L m N - z.regs band < numBands L m N - y.regs band := by + let β := y.regs band + have hβ : β < numBands L m N := by simpa only [Cond.holds_lt, hy.1.bands] using hc + have hlow := workspace_le_snapshot L m N + have hcat := catalogue_le_address L m N 0 side + have htable' (t : ℕ) (ht : t < 2 ^ L) : + y.mem (inverseBase L m N + t) = MaskTable.lookup L m t := by + rw [hy.2.2.2 _ (by unfold inverseBase; omega) (by + unfold catalogueBase tableBase at hcat + omega)] + exact htable t ht + have hmat' (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + y.mem (matrixAddress L m N side r k) = encode (matrix r k) := by + have hbound := matrixAddress_lt_inverse (L := L) side hr hk + rw [hy.2.2.2 _ (by unfold matrixAddress; omega) (by + unfold catalogueBase tableBase at hcat + have := Nat.zero_le (2 ^ L) + omega)] + exact hmat r hr k hk + obtain ⟨t, he, htp, htb, hvalues, hframe⟩ := exec_band hw side β y hy.1 hβ rfl + htable' matrix hmat' A hA ha hmag hfit + let z := t.set band (β + 1) + have hz : ExecWithin (B := .standard) (w + 2) (ram[ band ← band + one ]) t z 1 := by + ram_eval [z, htb, htp.fixed.one, (bands_fits hfit).of_le (Nat.succ_le_of_lt hβ), + band, one, Fin.mk.injEq] + have hzb : z.regs band = β + 1 := by simp [z] + refine ⟨z, he.seq hz, ?_, ?_⟩ + · refine ⟨parameters_reframe htp ?_, ?_, ?_, ?_⟩ + · intro i hi + have hp : ∀ i ∈ parameterRegisters, i ≠ band := by decide + simp only [z, Data.set_regs, Function.update_of_ne (hp i hi)] + · rw [hzb]; omega + · intro γ hγ p hp + rw [hzb] at hγ + change t.mem _ = _ + by_cases hγβ : γ = β + · subst γ; exact hvalues p hp + · have hγlt : γ < β := by omega + have hprior := catalogue_address_lt (m := m) (N := N) side hγlt hp + rw [hframe _ ?_ (by omega)] + · exact hy.2.2.1 γ hγlt p hp + · have haddr := catalogue_le_address L m N γ side + unfold catalogueAddress at haddr + have hsep : Encoding.scratchBase L + 10 ^ L ≤ catalogueBase L m N := by + unfold catalogueBase tableBase inverseBase + have := Nat.zero_le (2 ^ L) + omega + unfold catalogueAddress + omega + · intro b hb hc + change t.mem b = x.mem b + have hinside := catalogue_block_inside side hβ + rw [hframe b hb (by omega)] + exact hy.2.2.2 b hb hc + · rw [hzb]; change numBands L m N - (β + 1) < numBands L m N - β; omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => numBands L m N - y.regs band) hbody hstart + have hb : y.regs band = numBands L m N := by + simp only [Cond.holds_lt, hy.1.bands] at hc + have := hy.2.1 + omega + refine ⟨y, ?_, hy.1, ?_, hy.2.2.2⟩ + · apply (hs.seq he).mono + have hz : start.regs band = 0 := by simp [start] + simp only [hz, Nat.sub_zero, Nat.add_assoc, Nat.reduceAdd] + omega + · simpa only [hb] using hy.2.2.1 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCatalogue.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCatalogue.lean new file mode 100644 index 0000000000..163d03c14a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCatalogue.lean @@ -0,0 +1,70 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandFlat + +/-! +# Shared catalogue inputs for an existing mathematical tile + +## Main definitions + +* `BandPreparation.tileInputBases`: the two shared band pointers of a tile in `tiles`. + +## Main results + +* `BandPreparation.tile_input_values`: both pointed arrays contain the corresponding + unchanged public band encodings, in `Encoding.leafIndex` order. +* `BandPreparation.table_word_layout`: the computed forward table encodes the actual + deterministic `Layout` table, rather than caller-supplied indexing data. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- A tile `(rowBand,columnBand)` reads these two shared `10^L` arrays. The left pointer +depends only on its row band, and the right pointer only on its column band. -/ +def tileInputBases (L m N : ℕ) (T : ℕ × ℕ) : ℕ × ℕ := + (catalogueAddress L m N false T.1 0, catalogueAddress L m N true T.2 0) + +/-- The catalogue results returned by `runs` directly supply both inputs of every original +mathematical tile. Repeated row/column bands refer to the same stored arrays. -/ +theorem tile_input_values {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) (y : Data 96) + (hleft : ∀ β < numBands L m N, ∀ τ, + y.mem (catalogueAddress L m N false β (Encoding.leafIndex τ).val) = + encode (encodingL (bandArrayL (MaskTable.layout hm) X β) τ)) + (hright : ∀ β < numBands L m N, ∀ τ, + y.mem (catalogueAddress L m N true β (Encoding.leafIndex τ).val) = + encode (encodingR (bandArrayR (MaskTable.layout hm) Y β) τ)) + (T : ℕ × ℕ) (hT : T ∈ tiles L m N) (τ : Leaf L) : + y.mem ((tileInputBases L m N T).1 + (Encoding.leafIndex τ).val) = + encode (encodingL (bandArrayL (MaskTable.layout hm) X T.1) τ) ∧ + y.mem ((tileInputBases L m N T).2 + (Encoding.leafIndex τ).val) = + encode (encodingR (bandArrayR (MaskTable.layout hm) Y T.2) τ) := by + have ht : T.1 < numBands L m N ∧ T.2 < numBands L m N := by + simpa only [tiles, Finset.mem_product, Finset.mem_range] using hT + simpa only [tileInputBases, catalogueAddress, Nat.add_zero] using + And.intro (hleft T.1 ht.1 τ) (hright T.2 ht.2 τ) + +/-- The retained native forward words encode exactly the computed mathematical layout. -/ +theorem table_word_layout {L m N : ℕ} (hm : m ≤ L) (y : Data 96) + (hforward : ∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tableBase L m N + (MaskTable.pairRank gh).val) = + (MaskTable.selectedMask gh).val) + (gh : Fin (K0 L m) × Fin (K0 L m)) : + y.mem (tableBase L m N + (MaskTable.pairRank gh).val) = + (MaskTable.maskCode ((MaskTable.layout hm).table gh)).val := by + rw [hforward] + exact congrArg Fin.val (MaskTable.maskCode_maskSet (MaskTable.selectedMask gh)).symm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCollect.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCollect.lean new file mode 100644 index 0000000000..ba061da3e0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCollect.lean @@ -0,0 +1,119 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCopy +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandParameters + +/-! +# Charged copies into the shared band catalogue + +## Main results + +* `BandPreparation.catalogue_block_le`: each band has one disjoint catalogue block. +* `BandPreparation.exec_collectBand`: the real native encoder-output copy, including + every address calculation and the unchanged copy kernel's instruction count. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +-- Static register-frame proofs reduce the fixed finite code and register bank. +set_option maxRecDepth 4096 + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Every complete band block lies in the catalogue allocation. -/ +theorem catalogue_block_le {L m N β : ℕ} (side : Bool) (hβ : β < numBands L m N) : + catalogueAddress L m N side β 0 + 10 ^ L ≤ memoryEnd L m N := by + have ht : 0 < 10 ^ L := Nat.pow_pos (by decide) + unfold catalogueAddress memoryEnd + cases side <;> simp only [Bool.false_eq_true, ↓reduceIte, Nat.zero_add, Nat.add_zero] + all_goals nlinarith only [hβ, ht] + +/-- No band output is placed in the protected snapshot, tables or native workspace. -/ +theorem catalogue_le_address (L m N β : ℕ) (side : Bool) : + catalogueBase L m N ≤ catalogueAddress L m N side β 0 := by + unfold catalogueAddress + omega + +/-- Address arithmetic and the full copy are charged. The copy's source and destination are +disjoint by the concrete allocation; arbitrary words outside its destination are retained. -/ +theorem exec_collectBand {w L m N : ℕ} (side : Bool) (β : ℕ) (x : Data 96) + (hx : BandParameters L m N x) (hβ : β < numBands L m N) (hb : x.regs band = β) + (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w (collectBand side) x y (7 * 10 ^ L + 10) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (Encoding.outputBase L) + (catalogueAddress L m N side β 0) (10 ^ L) ∧ + BandParameters L m N y ∧ y.regs band = β := by + let b := (if side then numBands L m N else 0) + β + let dest := catalogueAddress L m N side β 0 + have hdest : Fits w (dest + 10 ^ L) := (memory_fits hfit).of_le + (catalogue_block_le side hβ) + have hd0 : Fits w dest := hdest.of_le (Nat.le_add_right _ _) + have hp : Fits w (b * 10 ^ L) := hd0.of_le (by + dsimp only [dest, catalogueAddress, b] + omega) + have hbfit : Fits w b := hp.of_le (Nat.le_mul_of_pos_right _ + (Nat.pow_pos (by decide))) + have hs : Fits w (7 ^ L + 2) := (encoder_fits hfit).of_le (by + have h7 := Nat.pow_le_pow_left (by decide : 7 ≤ 10) L + unfold Encoding.addressBound + nlinarith only [h7, Nat.zero_le L, Nat.zero_le (10 ^ L)]) + have hsrc : Fits w (Encoding.outputBase L + 10 ^ L) := + ((memory_fits hfit).of_le (snapshot_le_memoryEnd L m N)).of_le (by + have h := workspace_le_snapshot L m N + unfold Encoding.scratchBase at h + have := Nat.zero_le (10 ^ L) + omega) + let start := ((((((x.set copySrc (7 ^ L + 2)).set temporary b).set temporary + (b * 10 ^ L)).set temporary dest).set copyDst dest).set copyLen (10 ^ L)) + dsimp only [dest, catalogueAddress] at hd0 + have hhead : ExecWithin w (collectHead side) x start 6 := by + cases side <;> + simp only [b, Bool.false_eq_true, ↓reduceIte, Nat.zero_add, Nat.add_zero] at hbfit hp hd0 <;> + ram_eval [collectHead, start, dest, b, catalogueAddress, hx.cells, hx.leaves, + hx.catalogue, hx.bands, hx.fixed.two, hb, hs, hd0, hp, hbfit, + copySrc, copyDst, copyLen, temporary, cells, two, leaves, catalogue, bands, + band, Fin.mk.injEq, Nat.zero_add, Nat.add_zero] + obtain ⟨y, he, hmem, _⟩ := exec_copyKernel start (small_fits hfit (by decide)) + (by simpa [start, copySrc, copyLen, temporary, Encoding.outputBase, Nat.add_comm] using hsrc) + (by simpa [start, copyDst, copyLen, temporary] using hdest) + (Or.inl (by + have hlow := workspace_le_snapshot L m N + have hcat : snapshotBase L m N ≤ catalogueBase L m N := by + unfold catalogueBase tableBase inverseBase + have := Nat.zero_le (2 ^ L) + omega + have hsep : Encoding.outputBase L + 10 ^ L ≤ dest := by + have hn := Nat.zero_le (10 ^ L) + unfold Encoding.scratchBase at hlow + exact (by omega : Encoding.outputBase L + 10 ^ L ≤ snapshotBase L m N).trans + (hcat.trans (catalogue_le_address L m N β side)) + simpa [start, copySrc, copyDst, copyLen, temporary, Encoding.outputBase, Nat.add_comm] + using hsep)) + have hwhole : ExecWithin w (collectBand side) x y (7 * 10 ^ L + 10) := by + apply (hhead.seq he).mono + have hn : start.regs copyLen = 10 ^ L := by simp [start] + rw [hn] + omega + refine ⟨y, hwhole, ?_, parameters_reframe hx ?_, ?_⟩ + · simpa [start, copySrc, copyDst, copyLen, temporary, Encoding.outputBase, Nat.add_comm] + using hmem + · intro i hi + apply hwhole.regs_eq + have hp : ∀ side i, i ∈ parameterRegisters → (collectBand side).Preserves i := by + intro side + cases side <;> simp +decide only [] + exact hp side i hi + · exact (hwhole.regs_eq (by + cases side <;> simp +decide only [])).trans hb + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCopy.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCopy.lean new file mode 100644 index 0000000000..151981f38d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCopy.lean @@ -0,0 +1,134 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandHeader + +/-! +# Native copies used by the band-preparation caller + +## Main results + +* `BandPreparation.exec_copyKernel`: reuse the actual array-copy program in its fixed low + register bank, preserving every persistent register and permitting both address orders. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM + +/-- The seven low copy registers are embedded injectively. -/ +theorem copyRegisters_injective : Function.Injective copyRegisters := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 96 => i.val) h) + +/-- The unchanged encoder occupies precisely its original low register bank. -/ +theorem encoderRegisters_injective : Function.Injective encoderRegisters := by + intro i j h + exact Fin.ext (congrArg (fun i : Fin 96 => i.val) h) + +/-- Copying uses only the original seven-register routine and its actual native cost. +The persistent high register bank is untouched; arbitrary outside memory is preserved +by the explicit `copyPrefix` result. -/ +theorem exec_copyKernel {w : ℕ} (x : Data 96) (h1 : Fits w 1) + (hs : Fits w (x.regs copySrc + x.regs copyLen)) + (hd : Fits w (x.regs copyDst + x.regs copyLen)) + (hsep : x.regs copySrc + x.regs copyLen ≤ x.regs copyDst ∨ + x.regs copyDst + x.regs copyLen ≤ x.regs copySrc ∨ x.regs copySrc = x.regs copyDst) : + ∃ y, ExecWithin w (ArrayCopy.program.rename copyRegisters) x y (7 * x.regs copyLen + 4) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (x.regs copySrc) (x.regs copyDst) (x.regs copyLen) ∧ + (∀ i, 7 ≤ i.val → y.regs i = x.regs i) := by + obtain ⟨z, hz, hm, _, _, _⟩ := ArrayCopy.exec_of_separated_or_eq (x.comap copyRegisters) + h1 hs hd hsep + have he := hz.rename copyRegisters_injective + refine ⟨z.extend copyRegisters x, he, hm, ?_⟩ + intro i hi + apply he.regs_eq + have hf : ∀ i : Fin 96, 7 ≤ i.val → (ArrayCopy.program.rename copyRegisters).Preserves i := by + simp only [ArrayCopy.program, ArrayCopy.body, Code.rename, Code.Preserves, + Op.rename, Op.bin, Op.dest, copyRegisters] + decide + exact hf i hi + +private theorem lowSetup_regs (x : Data 96) (s d n : ℕ) (i : Fin 96) (hi : 7 ≤ i.val) : + (((x.set copySrc s).set copyDst d).set copyLen n).regs i = x.regs i := by + have hs : i ≠ copySrc := by intro h; subst i; norm_num [copySrc] at hi + have hd : i ≠ copyDst := by intro h; subst i; norm_num [copyDst] at hi + have hn : i ≠ copyLen := by intro h; subst i; norm_num [copyLen] at hi + simp only [Data.set_regs, Function.update_of_ne hs, Function.update_of_ne hd, + Function.update_of_ne hn] + +/-- Protect the entire original initialized prefix, including header zero, with the actual +native copy. Source words and all memory outside the snapshot block remain untouched. -/ +theorem exec_saveInput {w L m N : ℕ} (x : Data 96) (hx : HeaderPost L m N x) + (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w saveInput x y (7 * inputEnd m N + 7) ∧ + y.mem = ArrayCopy.copyPrefix x.mem 0 (snapshotBase L m N) (inputEnd m N) ∧ + (∀ i, 7 ≤ i.val → y.regs i = x.regs i) := by + let start := ((x.set copySrc 0).set copyDst (snapshotBase L m N)).set copyLen (inputEnd m N) + have hb : Fits w (inputEnd m N) := (memory_fits hfit).of_le (input_le_memoryEnd L m N) + have hi : Fits w (inverseBase L m N) := + (memory_fits hfit).of_le (inverse_le_memoryEnd L m N) + obtain ⟨y, hy, hm, hf⟩ := exec_copyKernel start (small_fits hfit (by decide)) + (by simpa [start, copySrc, copyDst, copyLen] using hb) + (by simpa [start, copySrc, copyDst, copyLen, inverseBase] using hi) + (Or.inl (by simpa [start, copySrc, copyDst, copyLen] using input_le_snapshot L m N)) + have h0 : ExecWithin (B := .standard) w (.op (.const copySrc 0)) + x (x.set copySrc 0) 1 := ExecWithin.const (fits_zero w) + have h1 : ExecWithin (B := .standard) w (.op (.copy copyDst snapshot)) + (x.set copySrc 0) ((x.set copySrc 0).set copyDst (snapshotBase L m N)) 1 := by + ram_eval [hx.snapshot, copySrc, copyDst, snapshot, Fin.mk.injEq] + have h2 : ExecWithin (B := .standard) w (.op (.copy copyLen prefixSize)) + ((x.set copySrc 0).set copyDst (snapshotBase L m N)) start 1 := by + ram_eval [start, hx.prefixSize, copySrc, copyDst, copyLen, prefixSize, Fin.mk.injEq] + refine ⟨y, ?_, ?_, ?_⟩ + · apply (h0.seq (h1.seq (h2.seq hy))).mono + have hn : start.regs copyLen = inputEnd m N := by simp [start] + rw [hn] + omega + · simpa [start, copySrc, copyDst, copyLen] using hm + · intro i hi + exact (hf i hi).trans (lowSetup_regs x _ _ _ i hi) + +/-- Restore the complete original input using the reverse disjoint-copy case. This copies +the actual protected words rather than reconstructing or assuming the input is unchanged. -/ +theorem exec_restoreInput {w L m N : ℕ} (x : Data 96) (hx : HeaderPost L m N x) + (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w restoreInput x y (7 * inputEnd m N + 7) ∧ + y.mem = ArrayCopy.copyPrefix x.mem (snapshotBase L m N) 0 (inputEnd m N) ∧ + (∀ i, 7 ≤ i.val → y.regs i = x.regs i) := by + let start := ((x.set copySrc (snapshotBase L m N)).set copyDst 0).set copyLen (inputEnd m N) + have hb : Fits w (inputEnd m N) := (memory_fits hfit).of_le (input_le_memoryEnd L m N) + have hi : Fits w (inverseBase L m N) := + (memory_fits hfit).of_le (inverse_le_memoryEnd L m N) + obtain ⟨y, hy, hm, hf⟩ := exec_copyKernel start (small_fits hfit (by decide)) + (by simpa [start, copySrc, copyDst, copyLen, inverseBase] using hi) + (by simpa [start, copySrc, copyDst, copyLen] using hb) + (Or.inr (Or.inl (by + simpa [start, copySrc, copyDst, copyLen] using input_le_snapshot L m N))) + have h0 : ExecWithin (B := .standard) w (.op (.copy copySrc snapshot)) + x (x.set copySrc (snapshotBase L m N)) 1 := by ram_eval [hx.snapshot] + have h1 : ExecWithin (B := .standard) w (.op (.const copyDst 0)) + (x.set copySrc (snapshotBase L m N)) + ((x.set copySrc (snapshotBase L m N)).set copyDst 0) 1 := ExecWithin.const (fits_zero w) + have h2 : ExecWithin (B := .standard) w (.op (.copy copyLen prefixSize)) + ((x.set copySrc (snapshotBase L m N)).set copyDst 0) start 1 := by + ram_eval [start, hx.prefixSize, copySrc, copyDst, copyLen, prefixSize, Fin.mk.injEq] + refine ⟨y, ?_, ?_, ?_⟩ + · apply (h0.seq (h1.seq (h2.seq hy))).mono + have hn : start.regs copyLen = inputEnd m N := by simp [start] + rw [hn] + omega + · simpa [start, copySrc, copyDst, copyLen] using hm + · intro i hi + exact (hf i hi).trans (lowSetup_regs x _ _ _ i hi) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCost.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCost.lean new file mode 100644 index 0000000000..f8c74f3fa7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCost.lean @@ -0,0 +1,70 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandProgram +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.BandBounds + +/-! +# Numerical cost bounds for shared band preparation + +## Main definitions + +* `BandPreparation.timeBound`: a uniform budget for all setup, tables, input copying, + band preparation, shared encoder calls and catalogue copies. + +## Main results + +* `BandPreparation.thin_timeBound`: its explicit finite thin-regime bound. This numerical + lemma is separate from the theorem connecting the budget to the actual native execution. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Generic numerical instruction budget; a final halt is charged separately. -/ +def timeBound (L m N : ℕ) : ℕ := + 4096 * (L + 1) * (1 + N * D m + 2 ^ L + 2 * numBands L m N * 10 ^ L) + +/-- At `L=19m`, all original input and table overhead is absorbed by the saving for shared +encodings. The displayed constants and polynomial exponent are fixed and positive. -/ +theorem thin_timeBound (m N : ℕ) (hN : D m ^ 18 ≤ N) : + (timeBound (19 * m) m N : ℝ) ≤ + 40960 * ((19 * m + 1 : ℕ) : ℝ) * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + have hNpos : 0 < N := (Nat.pow_pos (show 0 < D m from Nat.pow_pos (by decide))).trans_le hN + have hs := preparation_shape_le (show m ≤ 19 * m by omega) hNpos + have hsR : ((1 + N * D m + 2 ^ (19 * m) + + 2 * numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) ≤ + 5 * ((numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) := by exact_mod_cast hs + have he := encodings_total_le m N hN + have hshape : ((1 + N * D m + 2 ^ (19 * m) + + 2 * numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) ≤ + 10 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2 := by + push_cast at hsR he ⊢ + nlinarith only [hsR, he] + calc (timeBound (19 * m) m N : ℝ) + = 4096 * ((19 * m + 1 : ℕ) : ℝ) * ((1 + N * D m + 2 ^ (19 * m) + + 2 * numBands (19 * m) m N * 10 ^ (19 * m) : ℕ) : ℝ) := by + simp only [timeBound, Nat.cast_mul, Nat.cast_ofNat] + _ ≤ 4096 * ((19 * m + 1 : ℕ) : ℝ) * + (10 * (D m : ℝ) ^ (-(1 / 18 : ℝ)) * (N : ℝ) ^ 2) := + mul_le_mul_of_nonneg_left hshape (by positivity) + _ = _ := by ring + +/-- Equivalent division form of the thin-regime bound requested by the matrix algorithm. -/ +theorem thin_timeBound_div (m N : ℕ) (hN : D m ^ 18 ≤ N) : + (timeBound (19 * m) m N : ℝ) ≤ + 40960 * ((19 * m + 1 : ℕ) : ℝ) * (N : ℝ) ^ 2 / (D m : ℝ) ^ (1 / 18 : ℝ) := by + have h := thin_timeBound m N hN + rw [Real.rpow_neg (by positivity)] at h + simpa only [div_eq_mul_inv, mul_assoc, mul_left_comm, mul_comm] using h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCount.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCount.lean new file mode 100644 index 0000000000..6fb297b6e9 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandCount.lean @@ -0,0 +1,112 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandPopulation + +/-! +# Native counting of all valid subset masks + +## Main results + +* `BandPreparation.exec_countMasks`: the complete binary-mask scan computes the binomial + coefficient, with its inner population loops and all outer controls charged. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open MaskTable + +/-- Scanning all masks computes `K L m`; neither that count nor a valid table is supplied +as an input. Every mask-counter and accepted-counter fit follows from the scan range. -/ +theorem exec_countMasks {w : ℕ} (L m : ℕ) (x : Data 96) + (hL : x.regs levels = L) (hm : x.regs inner = m) (hM : x.regs masks = 2 ^ L) + (h1 : x.regs one = 1) (h2 : x.regs two = 2) (hfit : Fits w (2 ^ L + L + 4)) : + ∃ y, ExecWithin w countMasks x y ((6 * L + 11) * 2 ^ L + 4) ∧ + y.regs count = K L m ∧ y.regs mask = 2 ^ L ∧ y.mem = x.mem ∧ + (∀ i, i ≠ mask → i ≠ count → i ≠ residue → i ≠ population → i ≠ counter → + i ≠ digit → y.regs i = x.regs i) := by + have h0 : Fits w 0 := hfit.of_le (by omega) + let start := (x.set mask 0).set count 0 + have hs : ExecWithin (B := .standard) w (ram[ mask ← #0; count ← #0 ]) x start 2 := by + ram_eval [start, h0, mask, count, Fin.mk.injEq] + let I (y : Data 96) : Prop := + y.mem = x.mem ∧ y.regs levels = L ∧ y.regs inner = m ∧ y.regs masks = 2 ^ L ∧ + y.regs one = 1 ∧ y.regs two = 2 ∧ y.regs mask ≤ 2 ^ L ∧ + y.regs count = prefixCount L m (y.regs mask) + have hi : I start := by + simp [I, start, hL, hm, hM, h1, h2, levels, inner, masks, one, two, mask, count, + prefixCount_zero] + have hb (y : Data 96) (hy : I y) (hc : (Cond.lt mask masks).Holds y) : + ∃ z, ExecWithin w countBody y z (6 * L + 9) ∧ I z ∧ + 2 ^ L - z.regs mask < 2 ^ L - y.regs mask := by + obtain ⟨hmem, hL, hm, hM, h1, h2, ht, hcount⟩ := hy + have htlt : y.regs mask < 2 ^ L := by simpa only [Cond.holds_lt, hM] using hc + obtain ⟨u, hu, hpop, humem, huf⟩ := exec_population L (y.regs mask) y hL rfl h1 h2 + (hfit.of_le (by omega)) + have hul : u.regs levels = L := + (huf levels (by decide) (by decide) (by decide) (by decide)).trans hL + have hui : u.regs inner = m := + (huf inner (by decide) (by decide) (by decide) (by decide)).trans hm + have huM : u.regs masks = 2 ^ L := + (huf masks (by decide) (by decide) (by decide) (by decide)).trans hM + have hu1 : u.regs one = 1 := + (huf one (by decide) (by decide) (by decide) (by decide)).trans h1 + have hu2 : u.regs two = 2 := + (huf two (by decide) (by decide) (by decide) (by decide)).trans h2 + have hut : u.regs mask = y.regs mask := + huf mask (by decide) (by decide) (by decide) (by decide) + have huc : u.regs count = prefixCount L m (y.regs mask) := + (huf count (by decide) (by decide) (by decide) (by decide)).trans hcount + have hfnext : Fits w (y.regs mask + 1) := hfit.of_le (by omega) + have hfc : Fits w (prefixCount L m (y.regs mask) + 1) := + hfit.of_le (by have := prefixCount_le L m (y.regs mask); omega) + let z := (u.set count (prefixCount L m (y.regs mask + 1))).set mask (y.regs mask + 1) + have hz : ExecWithin w (ram[ + if population = inner then [ count ← count + one ] else [ count ← count ]; + mask ← mask + one ]) u z 4 := by + by_cases h : MaskTable.population L (y.regs mask) = m <;> + ram_eval [z, prefixCount_succ, h, hpop, hui, hu1, hut, huc, hfnext, hfc, + population, inner, count, one, mask, Fin.mk.injEq] + refine ⟨z, hu.seq hz, ?_, ?_⟩ + · simpa +decide [I, z, levels, inner, masks, one, two, mask, count, + Function.update_apply] using + (show u.mem = x.mem ∧ u.regs levels = L ∧ u.regs inner = m ∧ + u.regs masks = 2 ^ L ∧ u.regs one = 1 ∧ u.regs two = 2 ∧ + y.regs mask + 1 ≤ 2 ^ L from + ⟨humem.trans hmem, hul, hui, huM, hu1, hu2, by omega⟩) + · have hzt : z.regs mask = y.regs mask + 1 := by simp [z] + rw [hzt] + omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => 2 ^ L - y.regs mask) hb hi + have ht : y.regs mask = 2 ^ L := by + simp only [Cond.holds_lt, hy.2.2.2.1] at hc + have := hy.2.2.2.2.2.2.1 + omega + have hwhole : ExecWithin w countMasks x y ((6 * L + 11) * 2 ^ L + 4) := by + apply (hs.seq he).mono + have hs0 : start.regs mask = 0 := by simp [start, mask, count] + rw [hs0, Nat.sub_zero] + nlinarith + refine ⟨y, hwhole, ?_, ht, hy.1, ?_⟩ + · have h := hy.2.2.2.2.2.2.2 + simpa only [ht, prefixCount_full] using h + · intro i hm hc hr hp hj hd + apply hwhole.regs_eq + have hf : ∀ i : Fin 96, i ≠ mask → i ≠ count → i ≠ residue → i ≠ population → + i ≠ counter → i ≠ digit → countMasks.Preserves i := by + simp only [countMasks, countBody, populationCode, Code.Preserves, Op.dest, Op.bin] + decide + exact hf i hm hc hr hp hj hd + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandEncode.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandEncode.lean new file mode 100644 index 0000000000..a3831dde56 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandEncode.lean @@ -0,0 +1,137 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandPrepare +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCollect + +/-! +# Actual native encoder calls on the prepared bands + +## Main definitions + +* `BandPreparation.catalogueValue`: flat shared-transform values, using the existing + coordinate transform and the computed, padded band array. + +## Main results + +* `BandPreparation.exec_band`: one complete native preparation, unchanged encoder call and + catalogue copy, with charged instructions and an explicit memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +-- The persistent-register checks reduce the fixed 96-register parameter bank. +set_option maxRecDepth 4096 + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The shared catalogue uses exactly the existing flat coordinate-transform values. +`Encoding.value_final_left/right` and `bandValue_left/right` identify these with the public +`encodingL/R` of the original mathematical `bandArrayL/R`. -/ +def catalogueValue (L m N : ℕ) (side : Bool) (matrix : ℕ → ℕ → ℤ) (β p : ℕ) : ℤ := + CoordinateTransform.value (CoordinateTransform.table (Encoding.coefficients side)) + L 0 (bandValue L m N β side matrix) p 0 + +/-- The guarded band only reads real bounded matrix entries; all other entries are zero. -/ +theorem abs_bandValue_le {L m N β : ℕ} (side : Bool) (matrix : ℕ → ℕ → ℤ) + (A : ℤ) (hA : 0 ≤ A) (hmat : ∀ r < N, ∀ k < D m, |matrix r k| ≤ A) + (a : Fin (7 ^ L)) : |bandValue L m N β side matrix a.val| ≤ A := by + cases side <;> unfold bandValue <;> simp only [Bool.false_eq_true, ↓reduceIte] + all_goals + split_ifs with hq hr + · simpa only [abs_zero] using hA + · exact hmat _ hr _ (selected_parts_bounds a hq).2 + · simpa only [abs_zero] using hA + +/-- A real call of the unchanged encoder in its low 32-register bank. Parameters in the +other registers, arbitrary input headers and arbitrary outside memory are preserved. -/ +theorem exec_encoderKernel {w L m N : ℕ} (hw : 0 < w) (side : Bool) + (arr : ℕ → ℤ) (x : Data 96) (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)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + ∃ y, ExecWithin (w + 2) ((Encoding.program side).rename encoderRegisters) x y + (Encoding.timeBound L) ∧ + (∀ p < 10 ^ L, y.mem (Encoding.outputBase L + p) = encode + (CoordinateTransform.value (CoordinateTransform.table (Encoding.coefficients side)) + L 0 arr p 0)) ∧ + (∀ b, ¬(Encoding.outputBase L ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + y.mem b = x.mem b) ∧ (∀ i, 32 ≤ i.val → y.regs i = x.regs i) := by + obtain ⟨z, hz, hv, hf, _, _⟩ := Encoding.exec hw side arr (x.comap encoderRegisters) + hheader hinput A hA ha hmag (encoder_fits hfit) + refine ⟨z.extend encoderRegisters x, hz.rename encoderRegisters_injective, hv, hf, ?_⟩ + intro i hi + change Function.extend encoderRegisters z.regs x.regs i = x.regs i + apply Function.extend_apply' + rintro ⟨j, hj⟩ + have hv := congrArg (fun k : Fin 96 => k.val) hj + change j.val = i.val at hv + omega + +/-- Native cost of preparing, encoding and collecting one shared band. -/ +def bandTime (L : ℕ) : ℕ := + (13 * L + 39) * 7 ^ L + 5 + Encoding.timeBound L + (7 * 10 ^ L + 10) + +/-- Every band invokes the actual encoder code and copies its complete output. Only the low +workspace and this single shared catalogue block can change. Original matrix/table data and +arbitrary caller query regions outside those blocks remain untouched. -/ +theorem exec_band {w L m N : ℕ} (hw : 0 < w) (side : Bool) (β : ℕ) (x : Data 96) + (hx : BandParameters L m N x) (hβ : β < numBands L m N) (hb : x.regs band = β) + (htable : ∀ t < 2 ^ L, x.mem (inverseBase L m N + t) = MaskTable.lookup L m t) + (matrix : ℕ → ℕ → ℤ) + (hmat : ∀ r < N, ∀ k < D m, + x.mem (matrixAddress L m N side r k) = encode (matrix r k)) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ r < N, ∀ k < D m, |matrix r k| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + ∃ y, ExecWithin (w + 2) + (.seq (prepareBand side) (.seq ((Encoding.program side).rename encoderRegisters) + (collectBand side))) x y (bandTime L) ∧ + BandParameters L m N y ∧ y.regs band = β ∧ + (∀ p < 10 ^ L, y.mem (catalogueAddress L m N side β p) = + encode (catalogueValue L m N side matrix β p)) ∧ + (∀ b, ¬(1 ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + ¬(catalogueAddress L m N side β 0 ≤ b ∧ + b < catalogueAddress L m N side β 0 + 10 ^ L) → y.mem b = x.mem b) := by + obtain ⟨p, hep, hpp, hpb, hheader, hinput, hpf⟩ := + exec_prepareBand side β x hx hβ hb htable matrix hmat hfit + obtain ⟨e, hee, hev, hef, her⟩ := exec_encoderKernel hw side + (bandValue L m N β side matrix) p hheader hinput A hA + (fun i hi => abs_bandValue_le side matrix A hA ha ⟨i, hi⟩) hmag hfit + have hep' : BandParameters L m N e := parameters_reframe hpp (by + intro i hi + apply her + have hp : ∀ i ∈ parameterRegisters, 32 ≤ i.val := by decide + exact hp i hi) + have heb : e.regs band = β := (her band (by decide)).trans hpb + obtain ⟨y, hec, hmem, hyp, hyb⟩ := exec_collectBand side β e hep' hβ heb hfit + refine ⟨y, ?_, hyp, hyb, ?_, ?_⟩ + · simpa only [bandTime, Nat.add_assoc] using hep.seq (hee.seq hec) + · intro p hp + have heq : catalogueAddress L m N side β p = + catalogueAddress L m N side β 0 + p := by simp [catalogueAddress] + rw [heq, hmem, ArrayCopy.copyPrefix_read hp] + exact hev p hp + · intro b hb hc + rw [hmem, ArrayCopy.copyPrefix_outside hc] + calc + e.mem b = p.mem b := hef b (by + unfold Encoding.outputBase + have := Nat.zero_le (7 ^ L) + omega) + _ = x.mem b := hpf b (by + unfold Encoding.scratchBase Encoding.outputBase at hb + have := Nat.zero_le (10 ^ L) + omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandExec.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandExec.lean new file mode 100644 index 0000000000..c299842d75 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandExec.lean @@ -0,0 +1,245 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandAll +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandTables +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCost + +/-! +# Complete native shared-band execution + +## Main results + +* `BandPreparation.exec`: compute both tables and every shared encoding, from real scalar + headers and matrix words, restoring the full original input prefix and every outside cell. + All setup, copies, loops and encoder calls contribute to the instruction bound. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +-- Static frame checks reduce the fixed parameter bank and finite native table code. +set_option maxRecDepth 4096 + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The two tables and the protected input all precede the output catalogue. -/ +theorem snapshot_le_catalogue (L m N : ℕ) : snapshotBase L m N ≤ catalogueBase L m N := by + unfold catalogueBase tableBase inverseBase + have := Nat.zero_le (2 ^ L) + omega + +/-- The one-side catalogue end is within the complete allocation. -/ +theorem catalogue_side_end_le (L m N : ℕ) (side : Bool) : + catalogueAddress L m N side (numBands L m N) 0 ≤ memoryEnd L m N := by + unfold catalogueAddress memoryEnd + cases side <;> simp only [Bool.false_eq_true, ↓reduceIte, Nat.zero_add, Nat.add_zero] + all_goals nlinarith only [Nat.zero_le (numBands L m N * 10 ^ L)] + +private theorem band_frame_protected {L m N : ℕ} {side : Bool} {μ ν : ℕ → ℕ} + (h : ∀ b, ¬(1 ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + ¬(catalogueAddress L m N side 0 0 ≤ b ∧ + b < catalogueAddress L m N side (numBands L m N) 0) → ν b = μ b) + (a : ℕ) (ha : snapshotBase L m N ≤ a ∧ a < catalogueBase L m N) : ν a = μ a := by + apply h + · have := workspace_le_snapshot L m N + omega + · have := catalogue_le_address L m N 0 side + omega + +private theorem band_frame_above {L m N : ℕ} {side : Bool} {μ ν : ℕ → ℕ} + (h : ∀ b, ¬(1 ≤ b ∧ b < Encoding.scratchBase L + 10 ^ L) → + ¬(catalogueAddress L m N side 0 0 ≤ b ∧ + b < catalogueAddress L m N side (numBands L m N) 0) → ν b = μ b) + (a : ℕ) (ha : memoryEnd L m N ≤ a) : ν a = μ a := by + apply h + · have := workspace_le_snapshot L m N + have := (snapshot_le_catalogue L m N).trans (catalogue_le_memoryEnd L m N) + omega + · have := catalogue_side_end_le L m N side + omega + +/-- A coarse uniform bound for one actual shared-band call and its outer loop control. -/ +theorem bandTime_le (L : ℕ) : bandTime L + 3 ≤ 512 * (L + 1) * 10 ^ L := by + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have ht : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + have hs := Nat.mul_le_mul_left (13 * L + 39) h7 + unfold bandTime Encoding.timeBound + nlinarith only [hs, ht, Nat.zero_le L, Nat.zero_le (L * 10 ^ L)] + +/-- The sum of the previously proved native component charges fits the generic bound. -/ +theorem execution_time_le (L m N : ℕ) : + (16 * L + 49) + ((7 * inputEnd m N + 7) + + (((6 * L + 16) * 2 ^ L + 14) + (((6 * L + 17) * 2 ^ L + 4) + + ((numBands L m N * (bandTime L + 3) + 3) + + ((numBands L m N * (bandTime L + 3) + 3) + (7 * inputEnd m N + 7)))))) ≤ + timeBound L m N := by + have hb := Nat.mul_le_mul_left (numBands L m N) (bandTime_le L) + have h2 : 1 ≤ 2 ^ L := Nat.one_le_pow L 2 (by decide) + unfold timeBound inputEnd + nlinarith only [hb, h2, Nat.zero_le L, Nat.zero_le (N * D m), + Nat.zero_le (L * (N * D m)), Nat.zero_le (L * 2 ^ L), + Nat.zero_le (numBands L m N * 10 ^ L), + Nat.zero_le (L * (numBands L m N * 10 ^ L))] + +/-- The full fixed native program. The caller supplies only ordinary `L,m,N` headers and +signed matrix words; both tables and every padded/encoded band are computed. Header zero +and arbitrary outside memory need not be zero or describe a standalone input list. -/ +theorem exec {w L m N : ℕ} (hw : 0 < w) (hm : m ≤ L) (x : Data 96) + (hL : x.mem 1 = L) (hm' : x.mem 2 = m) (hN : x.mem 3 = N) + (matrix : Bool → ℕ → ℕ → ℤ) + (hmat : ∀ side r, r < N → ∀ k, k < D m → + x.mem (matrixOffset m N side r k + 4) = encode (matrix side r k)) + (A : ℤ) (hA : 0 ≤ A) (ha : ∀ side r, r < N → ∀ k, k < D m → |matrix side r k| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + ∃ y, ExecWithin (w + 2) program x y (timeBound L m N) ∧ + BandParameters L m N y ∧ + (∀ side β, β < numBands L m N → ∀ p, p < 10 ^ L → + y.mem (catalogueAddress L m N side β p) = + encode (catalogueValue L m N side (matrix side) β p)) ∧ + (∀ a < 2 ^ L, y.mem (inverseBase L m N + a) = MaskTable.lookup L m a) ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tableBase L m N + (MaskTable.pairRank gh).val) = + (MaskTable.selectedMask gh).val) ∧ + (∀ b, ¬(inputEnd m N ≤ b ∧ b < memoryEnd L m N) → y.mem b = x.mem b) := by + obtain ⟨h, heh, hhp, hhmem⟩ := exec_header hm x hL hm' hN hfit + obtain ⟨s, hes, hsmem, hsr⟩ := exec_saveInput h hhp hfit + have hsp : HeaderPost L m N s := headerPost_reframe hhp (by + intro i hi + have hp : ∀ i ∈ headerRegisters, 7 ≤ i.val := by decide + exact hsr i (hp i hi)) + obtain ⟨c, hec, hcp, hcmem⟩ := exec_countCode s hsp hfit + have htableParams : TableParameters L m (inverseBase L m N) (tableBase L m N) c := + ⟨hcp.fixed.levels, hcp.fixed.inner, hcp.masks, hcp.inverse, hcp.table, hcp.square, + hcp.fixed.zero, hcp.fixed.one, hcp.fixed.two⟩ + obtain ⟨t, het, _, hlookup, hforward, htframe⟩ := exec_tables L m + (inverseBase L m N) (tableBase L m N) c htableParams (Nat.le_refl _) + (tables_fit hfit) + have htp : BandParameters L m N t := parameters_reframe hcp (by + intro i hi + apply het.regs_eq + have hp : ∀ i ∈ parameterRegisters, tablesCode.Preserves i := by + simp +decide only [] + exact hp i hi) + have hinv : inverseBase L m N ≤ catalogueBase L m N := by + unfold catalogueBase tableBase + have := Nat.zero_le (2 ^ L) + omega + have hsnapshot (a : ℕ) (ha : a < inputEnd m N) : + t.mem (snapshotBase L m N + a) = x.mem a := by + rw [htframe _ (by unfold inverseBase; omega) (by unfold tableBase inverseBase; omega), + hcmem, hsmem, ArrayCopy.copyPrefix_read ha, hhmem, Nat.zero_add] + have hmatrix (side : Bool) (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + t.mem (matrixAddress L m N side r k) = encode (matrix side r k) := by + have hb := matrixAddress_lt_inverse (L := L) side hr hk + unfold matrixAddress inverseBase at hb + have ho : matrixOffset m N side r k + 4 < inputEnd m N := by omega + rw [matrixAddress, hsnapshot _ ho] + exact hmat side r hr k hk + obtain ⟨l, hel, hlp, hlvalues, hlframe⟩ := exec_encodeBands hw false t htp + hlookup (matrix false) (hmatrix false) A hA (ha false) hmag hfit + have hlprotected := band_frame_protected hlframe + have hinverse (a : ℕ) (ha : a < 2 ^ L) : + snapshotBase L m N ≤ inverseBase L m N + a ∧ + inverseBase L m N + a < catalogueBase L m N := by + have hs : snapshotBase L m N ≤ inverseBase L m N := Nat.le_add_right _ _ + have ht : inverseBase L m N + a < tableBase L m N := by + change inverseBase L m N + a < inverseBase L m N + 2 ^ L + omega + exact ⟨hs.trans (Nat.le_add_right _ _), ht.trans_le (Nat.le_add_right _ _)⟩ + have hlookup' (a : ℕ) (ha : a < 2 ^ L) : + l.mem (inverseBase L m N + a) = MaskTable.lookup L m a := by + rw [hlprotected _ (hinverse a ha)] + exact hlookup a ha + have hmatrix' (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + l.mem (matrixAddress L m N true r k) = encode (matrix true r k) := by + have hb := matrixAddress_lt_inverse (L := L) true hr hk + rw [hlprotected (matrixAddress L m N true r k) ⟨Nat.le_add_right _ _, hb.trans_le hinv⟩] + exact hmatrix true r hr k hk + obtain ⟨r, her, hrp, hrvalues, hrframe⟩ := exec_encodeBands hw true l hlp + hlookup' (matrix true) hmatrix' A hA (ha true) hmag hfit + have hrprotected := band_frame_protected hrframe + obtain ⟨y, hey, hymem, hyr⟩ := exec_restoreInput r hrp.toHeaderPost hfit + have hyp : BandParameters L m N y := parameters_reframe hrp (by + intro i hi + have hp : ∀ i ∈ parameterRegisters, 7 ≤ i.val := by decide + exact hyr i (hp i hi)) + have hrestore (a : ℕ) (ha : inputEnd m N ≤ a) : y.mem a = r.mem a := by + rw [hymem, ArrayCopy.copyPrefix_outside (by omega)] + have hprotected (a : ℕ) (ha : snapshotBase L m N ≤ a ∧ a < catalogueBase L m N) : + r.mem a = t.mem a := (hrprotected a ha).trans (hlprotected a ha) + have hcatlow : Encoding.scratchBase L + 10 ^ L ≤ catalogueBase L m N := + (workspace_le_snapshot L m N).trans (snapshot_le_catalogue L m N) + have hinputcat : inputEnd m N ≤ catalogueBase L m N := + (input_le_snapshot L m N).trans (snapshot_le_catalogue L m N) + refine ⟨y, ?_, hyp, ?_, ?_, ?_, ?_⟩ + · apply (heh.seq (hes.seq (hec.seq (het.seq (hel.seq (her.seq hey)))))).mono + exact execution_time_le L m N + · intro side β hβ p hp + have hlow : inputEnd m N ≤ catalogueAddress L m N side β p := by + have := catalogue_le_address L m N β side + unfold catalogueAddress at * + omega + rw [hrestore _ hlow] + cases side + · rw [hrframe _ ?_ ?_] + · exact hlvalues β hβ p hp + · unfold catalogueAddress + simp only [Bool.false_eq_true, ↓reduceIte] + omega + · have hlt : catalogueAddress L m N false β p < + catalogueAddress L m N true 0 0 := by + unfold catalogueAddress + simp only [Bool.false_eq_true, ↓reduceIte, Nat.zero_add, Nat.add_zero] + have ht : 0 < 10 ^ L := Nat.pow_pos (by decide) + nlinarith only [hβ, hp, ht] + omega + · exact hrvalues β hβ p hp + · intro a ha + have hb := hinverse a ha + rw [hrestore _ ((input_le_snapshot L m N).trans hb.1), hprotected _ hb] + exact hlookup a ha + · intro gh + have hK : K L m ≤ 2 ^ L := Nat.choose_le_two_pow _ _ + have hi := (MaskTable.pairRank gh).isLt + have hb : snapshotBase L m N ≤ tableBase L m N + (MaskTable.pairRank gh).val ∧ + tableBase L m N + (MaskTable.pairRank gh).val < catalogueBase L m N := by + unfold catalogueBase tableBase inverseBase + omega + rw [hrestore _ ((input_le_snapshot L m N).trans hb.1), hprotected _ hb] + exact hforward gh + · intro b hb + by_cases hbin : b < inputEnd m N + · have hread : y.mem b = r.mem (snapshotBase L m N + b) := by + rw [hymem] + simpa only [Nat.zero_add] using (ArrayCopy.copyPrefix_read + (mem := r.mem) (src := snapshotBase L m N) (dst := 0) hbin) + rw [hread, hprotected _ (by + unfold catalogueBase tableBase inverseBase + have := Nat.zero_le (2 ^ L) + omega)] + exact hsnapshot b hbin + · have hbig : memoryEnd L m N ≤ b := by omega + rw [hrestore b (by omega), band_frame_above hrframe b hbig, + band_frame_above hlframe b hbig, htframe b ?_ ?_, hcmem, hsmem, + ArrayCopy.copyPrefix_outside ?_, hhmem] + · have hend := inverse_le_memoryEnd L m N + change snapshotBase L m N + inputEnd m N ≤ memoryEnd L m N at hend + omega + · have hend := table_le_memoryEnd L m N + change inverseBase L m N + 2 ^ L ≤ memoryEnd L m N at hend + omega + · have hend := catalogue_le_memoryEnd L m N + change tableBase L m N + 2 ^ L ≤ memoryEnd L m N at hend + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandFlat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandFlat.lean new file mode 100644 index 0000000000..1b356a8f0e --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandFlat.lean @@ -0,0 +1,118 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandInput + +/-! +# Flat native preparation of every public band encoding + +## Main results + +* `BandPreparation.runs`: actual flat `State.init` run, all public left/right encodings for + the computed deterministic layout, retained computed tables, input/outside frame and charged halt. +* `BandPreparation.program_length_le`: one fixed finite program, independent of every parameter. + +This is a bounded preparation component under the ordinary native 3SUM objective, not a +whole 3SUM algorithm or a pruned-query evaluation theorem. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The left catalogue retains the original mathematical band and leaf interfaces. -/ +theorem catalogueValue_left {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (matrix : ℕ → ℕ → ℤ) + (hX : ∀ i k, matrix i.val k.val = X i k) (β : ℕ) (τ : Leaf L) : + catalogueValue L m N false matrix β (Encoding.leafIndex τ).val = + encodingL (bandArrayL (MaskTable.layout hm) X β) τ := by + apply Encoding.value_final_left + intro u + simpa only [Equiv.symm_apply_apply] using + (bandValue_left hm X matrix hX β (finFunctionFinEquiv u)) + +/-- The right catalogue uses the same original flat leaf order. -/ +theorem catalogueValue_right {L m N : ℕ} (hm : m ≤ L) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (matrix : ℕ → ℕ → ℤ) + (hY : ∀ k i, matrix i.val k.val = Y k i) (β : ℕ) (τ : Leaf L) : + catalogueValue L m N true matrix β (Encoding.leafIndex τ).val = + encodingR (bandArrayR (MaskTable.layout hm) Y β) τ := by + apply Encoding.value_final_right + intro u + simpa only [Equiv.symm_apply_apply] using + (bandValue_right hm Y matrix (fun i k => hY k i) β (finFunctionFinEquiv u)) + +set_option maxRecDepth 4096 in +-- Static size arithmetic traverses the new finite blocks, reusing both encoder size bounds. +/-- One finite code-size bound for the 96-register program and its final halt. -/ +theorem program_length_le : (program.program catalogue).length ≤ 16384 := by + have hl := Encoding.program_length_le false + have hr := Encoding.program_length_le true + simp only [Code.program, List.length_append, Code.length_assemble, + List.length_cons, List.length_nil] at hl hr ⊢ + simp only [program, header, headerLoads, headerPowers, headerLayout, powerCode, + saveInput, countCode, countMasks, countBody, populationCode, rootCode, rootBody, + bandCount, tablesCode, tableBody, tableWrite, encodeBands, bandBody, prepareBand, + prepareHead, prepareBody, splitCode, splitHead, splitBody, readCell, collectBand, + collectHead, restoreInput, Code.size, Code.size_rename, Power.program, Power.body, + ArrayCopy.program, ArrayCopy.body, Bool.false_eq_true, ↓reduceIte, Nat.reduceAdd] + omega + +set_option maxRecDepth 4096 in +-- This finite structural check covers the same fixed instruction list, not an execution. +/-- The uniform program does not inspect its target word width. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +/-- Every row and column band is encoded exactly once into its shared flat block. The +actual input contains only the three scalar parameters and signed matrices. `w` is the signed +source magnitude width and `w+2` is the target width used by the existing native kernels. +The prefix run exposes the actual final memory; its next instruction is the charged halt. +No unrelated caller memory or header is required to be zero in the general `exec` theorem. -/ +theorem runs {w L m N : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (A : ℤ) (hA : 0 ≤ A) (hX : ∀ i k, |X i k| ≤ A) (hY : ∀ k j, |Y k j| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + InputFits (w + 2) (input L m N X Y) ∧ + ∃ y n, n ≤ timeBound L m N ∧ + Runs (w + 2) (program.program catalogue) (State.init (input L m N X Y)) + ⟨program.size, y⟩ n ∧ + Executes (w + 2) (program.program catalogue) (State.init (input L m N X Y)) + (catalogueBase L m N) (n + 1) ∧ + y.Bounded (w + 2) ∧ + (∀ β < numBands L m N, ∀ τ, + y.mem (catalogueAddress L m N false β (Encoding.leafIndex τ).val) = + encode (encodingL (bandArrayL (MaskTable.layout hm) X β) τ)) ∧ + (∀ β < numBands L m N, ∀ τ, + y.mem (catalogueAddress L m N true β (Encoding.leafIndex τ).val) = + encode (encodingR (bandArrayR (MaskTable.layout hm) Y β) τ)) ∧ + (∀ a < 2 ^ L, y.mem (inverseBase L m N + a) = MaskTable.lookup L m a) ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tableBase L m N + (MaskTable.pairRank gh).val) = + (MaskTable.selectedMask gh).val) ∧ + (∀ b, ¬(inputEnd m N ≤ b ∧ b < memoryEnd L m N) → + y.mem b = (State.init (input L m N X Y) : State 96).data.mem b) := by + obtain ⟨hi, y, he, hb, hp, hv, ht, hf, hframe⟩ := + exec_input hw hm X Y A hA hX hY hmag hfit + obtain ⟨n, hn, he⟩ := he + refine ⟨hi, y, n, hn, ?_, ?_, hb, ?_, ?_, ht, hf, hframe⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := program.program catalogue) (Program.Contains.self _).append_left + · simpa only [hp.catalogue, State.init] using he.executes catalogue + · intro β hβ τ + rw [hv false β hβ _ (Encoding.leafIndex τ).isLt, + catalogueValue_left hm X _ (by intro i k; simp [matrixValues])] + · intro β hβ τ + rw [hv true β hβ _ (Encoding.leafIndex τ).isLt, + catalogueValue_right hm Y _ (by intro k i; simp [matrixValues])] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandHeader.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandHeader.lean new file mode 100644 index 0000000000..abc4fe7e9c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandHeader.lean @@ -0,0 +1,278 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandProgram + +/-! +# Actual header loads, native powers and concrete band addresses + +## Main results + +* `BandPreparation.exec_powerCode`: exact reuse of the five-register native power routine. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Renaming the actual power routine has no instruction overhead and changes only its +accumulator, counter and constant-one register. -/ +theorem exec_powerCode {w : ℕ} (v b n : Fin 96) + (hinj : Function.Injective (powerRegisters v b n)) (B M : ℕ) (hB : 1 ≤ B) + (x : Data 96) (hb : x.regs b = B) (hn : x.regs n = M) + (hfit : Fits w (B ^ M + M + 2)) : + ∃ y, ExecWithin w (powerCode v b n) x y (4 * M + 5) ∧ + y.regs v = B ^ M ∧ y.regs one = 1 ∧ y.mem = x.mem ∧ + (∀ i, i ≠ v → i ≠ counter → i ≠ one → y.regs i = x.regs i) := by + obtain ⟨z, hz, hv, _, ho, hm, _, _⟩ := + Power.exec B M hB (x.comap (powerRegisters v b n)) hb hn hfit + have he := hz.rename hinj + refine ⟨z.extend (powerRegisters v b n) x, he, ?_, ?_, hm, ?_⟩ + · have h := congrArg (fun d : Data 5 => d.regs Power.value) (Data.comap_extend hinj z x) + exact h.trans hv + · have h := congrArg (fun d : Data 5 => d.regs Power.one) (Data.comap_extend hinj z x) + exact h.trans ho + · intro i hv hc ho + apply he.regs_eq + simp [Power.program, Power.body, Code.rename, Code.Preserves, Op.rename, Op.dest, Op.bin, + powerRegisters, Power.value, Power.counter, Power.base, Power.exponent, Power.one, + Ne.symm hv, Ne.symm hc, Ne.symm ho] + +/-- Scalar parameters loaded from the three actual input headers and small constants. -/ +structure BasicParameters (L m N : ℕ) (x : Data 96) : Prop where + /-- Input depth. -/ + levels : x.regs levels = L + /-- Input inner cardinality. -/ + inner : x.regs inner = m + /-- Input matrix dimension. -/ + rows : x.regs rows = N + /-- Constant zero. -/ + zero : x.regs zero = 0 + /-- Constant one. -/ + one : x.regs one = 1 + /-- Constant two. -/ + two : x.regs two = 2 + /-- Constant three. -/ + three : x.regs three = 3 + /-- Constant four. -/ + four : x.regs four = 4 + /-- Constant seven. -/ + seven : x.regs seven = 7 + /-- Constant ten. -/ + ten : x.regs ten = 10 + +private def basicRegisters : Finset (Fin 96) := + {levels, inner, rows, zero, two, three, four, seven, ten} + +private theorem BasicParameters.of_frame {L m N : ℕ} {x y : Data 96} + (hx : BasicParameters L m N x) (hone : y.regs BandPreparation.one = 1) + (h : ∀ i ∈ basicRegisters, y.regs i = x.regs i) : BasicParameters L m N y := + ⟨(h _ (by decide)).trans hx.levels, (h _ (by decide)).trans hx.inner, + (h _ (by decide)).trans hx.rows, (h _ (by decide)).trans hx.zero, hone, + (h _ (by decide)).trans hx.two, (h _ (by decide)).trans hx.three, + (h _ (by decide)).trans hx.four, (h _ (by decide)).trans hx.seven, + (h _ (by decide)).trans hx.ten⟩ + +private theorem BasicParameters.after_power {L m N : ℕ} {v : Fin 96} {x y : Data 96} + (hx : BasicParameters L m N x) (hv : v ∉ basicRegisters) + (hone : y.regs BandPreparation.one = 1) + (hf : ∀ i, i ≠ v → i ≠ counter → i ≠ BandPreparation.one → y.regs i = x.regs i) : + BasicParameters L m N y := hx.of_frame hone (by + intro i hi + have hstatic : ∀ i ∈ basicRegisters, i ≠ counter ∧ i ≠ BandPreparation.one := by decide + exact hf i (fun heq => hv (heq ▸ hi)) (hstatic i hi).1 (hstatic i hi).2) + +/-- The global numerical budget bounds every final header power and its exponent. -/ +theorem power_fits {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) + (b n : ℕ) (hb : b ≤ 10) (hn : n ≤ L) : Fits w (b ^ n + n + 2) := by + have hp : b ^ n ≤ 10 ^ L := + (Nat.pow_le_pow_left hb _).trans (Nat.pow_le_pow_right (by decide) hn) + have hpos : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + apply hfit.of_le + unfold wordBudget + nlinarith only [hp, hn, hpos, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +/-- All header powers are computed by five actual native loops. Their register embeddings +are injective and their complete cost is linear in the original depth. -/ +theorem exec_headerPowers {w L m N : ℕ} (hm : m ≤ L) (x : Data 96) + (hx : BasicParameters L m N x) (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w headerPowers x y (16 * L + 26) ∧ BasicParameters L m N y ∧ + y.regs columns = D m ∧ y.regs outerSize = N0 L m ∧ y.regs masks = 2 ^ L ∧ + y.regs cells = 7 ^ L ∧ y.regs leaves = 10 ^ L ∧ y.mem = x.mem := by + obtain ⟨x1, h1, hD, ho1, hm1, hf1⟩ := exec_powerCode columns four inner (by decide) + 4 m (by decide) x hx.four hx.inner (power_fits hfit 4 m (by decide) hm) + have hp1 : BasicParameters L m N x1 := hx.after_power (by decide) ho1 hf1 + let x2 := x1.set temporary (L - m) + have hsub : ExecWithin w (ram[ temporary ← levels - inner ]) x1 x2 1 := by + have hL := power_fits hfit 1 L (by decide) le_rfl + have hs : Fits w (L - m) := hL.of_le (by simp only [one_pow]; omega) + ram_eval [x2, hp1.levels, hp1.inner, hs, temporary, levels, inner] + have ho2 : x2.regs one = 1 := by simpa [x2, temporary, one] using hp1.one + have hp2 : BasicParameters L m N x2 := hp1.of_frame ho2 (by + intro i hi + apply hsub.regs_eq + have hf : ∀ i ∈ basicRegisters, (ram[ temporary ← levels - inner ]).Preserves i := by + simp only [Code.Preserves, Op.dest_bin] + decide + exact hf i hi) + have htemp : x2.regs temporary = L - m := by simp [x2] + obtain ⟨x3, h3, hN0, ho3, hm3, hf3⟩ := exec_powerCode outerSize three temporary (by decide) + 3 (L - m) (by decide) x2 hp2.three htemp + (power_fits hfit 3 (L - m) (by decide) (Nat.sub_le _ _)) + have hp3 : BasicParameters L m N x3 := hp2.after_power (by decide) ho3 hf3 + obtain ⟨x4, h4, hS2, ho4, hm4, hf4⟩ := exec_powerCode masks two levels (by decide) + 2 L (by decide) x3 hp3.two hp3.levels (power_fits hfit 2 L (by decide) le_rfl) + have hp4 : BasicParameters L m N x4 := hp3.after_power (by decide) ho4 hf4 + obtain ⟨x5, h5, hS7, ho5, hm5, hf5⟩ := exec_powerCode cells seven levels (by decide) + 7 L (by decide) x4 hp4.seven hp4.levels (power_fits hfit 7 L (by decide) le_rfl) + have hp5 : BasicParameters L m N x5 := hp4.after_power (by decide) ho5 hf5 + obtain ⟨y, h6, hS10, ho6, hm6, hf6⟩ := exec_powerCode leaves ten levels (by decide) + 10 L (by decide) x5 hp5.ten hp5.levels (power_fits hfit 10 L (by decide) le_rfl) + refine ⟨y, ?_, hp5.after_power (by decide) ho6 hf6, ?_, ?_, ?_, ?_, hS10, ?_⟩ + · apply (h1.seq (hsub.seq (h3.seq (h4.seq (h5.seq h6))))).mono + omega + · have hD2 : x2.regs columns = 4 ^ m := by simpa [x2, temporary, columns] using hD + exact (hf6 columns (by decide) (by decide) (by decide)).trans + ((hf5 columns (by decide) (by decide) (by decide)).trans + ((hf4 columns (by decide) (by decide) (by decide)).trans + ((hf3 columns (by decide) (by decide) (by decide)).trans hD2))) + · exact (hf6 outerSize (by decide) (by decide) (by decide)).trans + ((hf5 outerSize (by decide) (by decide) (by decide)).trans + ((hf4 outerSize (by decide) (by decide) (by decide)).trans hN0)) + · exact (hf6 masks (by decide) (by decide) (by decide)).trans + ((hf5 masks (by decide) (by decide) (by decide)).trans hS2) + · exact (hf6 cells (by decide) (by decide) (by decide)).trans hS7 + · exact hm6.trans (hm5.trans (hm4.trans (hm3.trans hm1))) + +/-- Concrete computed powers and addresses after native header initialization. -/ +structure HeaderPost (L m N : ℕ) (x : Data 96) : Prop where + /-- Original scalar headers and constants. -/ + fixed : BasicParameters L m N x + /-- Thin dimension. -/ + columns : x.regs columns = D m + /-- Outer block dimension. -/ + outerSize : x.regs outerSize = N0 L m + /-- Complete mask scan range. -/ + masks : x.regs masks = 2 ^ L + /-- Flat input length of each band array. -/ + cells : x.regs cells = 7 ^ L + /-- Flat output length of each band encoding. -/ + leaves : x.regs leaves = 10 ^ L + /-- Original initialized prefix length. -/ + prefixSize : x.regs prefixSize = inputEnd m N + /-- Protected original-input copy address. -/ + snapshot : x.regs snapshot = snapshotBase L m N + /-- Dense inverse-table address. -/ + inverse : x.regs inverse = inverseBase L m N + /-- Selected-mask forward-table address. -/ + table : x.regs table = tableBase L m N + /-- Shared band-encoding output address. -/ + catalogue : x.regs catalogue = catalogueBase L m N + +/-- Header parameters are loaded from real memory, all five powers are computed natively, +and every concrete address calculation is charged. Header zero and all unrelated cells may +contain arbitrary words; initialization reads only addresses one through three. -/ +theorem exec_header {w L m N : ℕ} (hm : m ≤ L) (x : Data 96) + (hL : x.mem 1 = L) (hmemory : x.mem 2 = m) (hN : x.mem 3 = N) + (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w header x y (16 * L + 49) ∧ HeaderPost L m N y ∧ y.mem = x.mem := by + have h256 : 256 ≤ wordBudget L m N := Nat.le_mul_of_pos_right _ (Nat.succ_pos _) + have hsmall (v : ℕ) (hv : v ≤ 256) : Fits w v := hfit.of_le (hv.trans h256) + let x0 := (((((((((((((x.set address 1).set levels L).set address 2).set inner m).set + address 3).set rows N).set zero 0).set one 1).set two 2).set three 3).set four 4).set + seven 7).set ten 10) + have hs : ExecWithin w headerLoads x x0 13 := by + ram_eval [headerLoads, x0, hL, hmemory, hN, hsmall 0 (by decide), hsmall 1 (by decide), + hsmall 2 (by decide), hsmall 3 (by decide), hsmall 4 (by decide), + hsmall 7 (by decide), hsmall 10 (by decide), + address, levels, inner, rows, zero, one, two, three, four, seven, ten, Fin.mk.injEq] + have hp : BasicParameters L m N x0 := by + constructor <;> simp [x0, address, levels, inner, rows, zero, one, two, three, four, seven, ten] + obtain ⟨z, hz, hbasic, hD, hN0, hS2, hS7, hS10, hzmem⟩ := exec_headerPowers hm x0 hp hfit + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have h2 : 2 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have hlocal : Fits w (4 * (N * D m) + 8 * 10 ^ L + 16) := by + apply hfit.of_le + unfold wordBudget + nlinarith only [Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + let B := N * D m * 2 + 4 + let S := 10 ^ L * 2 + 7 ^ L + 2 + B + let I := S + B + let T := I + 2 ^ L + let C := T + 2 ^ L + have hnd : Fits w (N * D m) := hlocal.of_le (by omega) + have hnd2 : Fits w (N * D m * 2) := hlocal.of_le (by omega) + have hB : Fits w B := hlocal.of_le (by dsimp [B]; omega) + have ht2 : Fits w (10 ^ L * 2) := hlocal.of_le (by omega) + have hts : Fits w (10 ^ L * 2 + 7 ^ L) := hlocal.of_le (by omega) + have hts2 : Fits w (10 ^ L * 2 + 7 ^ L + 2) := hlocal.of_le (by omega) + have hS : Fits w S := hlocal.of_le (by dsimp [S, B]; omega) + have hI : Fits w I := hlocal.of_le (by dsimp [I, S, B]; omega) + have hT : Fits w T := hlocal.of_le (by dsimp [T, I, S, B]; omega) + have hC : Fits w C := hlocal.of_le (by dsimp [C, T, I, S, B]; omega) + let y := ((((((((((z.set prefixSize (N * D m)).set prefixSize (N * D m * 2)).set + prefixSize B).set snapshot (10 ^ L * 2)).set snapshot (10 ^ L * 2 + 7 ^ L)).set + snapshot (10 ^ L * 2 + 7 ^ L + 2)).set snapshot S).set inverse I).set table T).set catalogue C) + have hy : ExecWithin w headerLayout z y 10 := by + ram_eval [headerLayout, y, B, S, I, T, C, hbasic.rows, hbasic.two, hbasic.four, + hS2, hS7, hS10, hD, hnd, hnd2, hB, ht2, hts, hts2, hS, hI, hT, hC, + prefixSize, snapshot, inverse, table, catalogue, rows, columns, leaves, cells, + masks, two, four, Fin.mk.injEq, Function.update_idem] + have hfixed : BasicParameters L m N y := hbasic.of_frame (by + simpa [y, prefixSize, snapshot, inverse, table, catalogue, one] using hbasic.one) (by + intro i hi + apply hy.regs_eq + have hf : ∀ i ∈ basicRegisters, headerLayout.Preserves i := by + simp only [headerLayout, Code.Preserves, Op.dest_bin] + decide + exact hf i hi) + refine ⟨y, ?_, ?_, hzmem⟩ + · exact (hs.seq (hz.seq hy)).mono (by omega) + · constructor + · exact hfixed + · simpa [y, prefixSize, snapshot, inverse, table, catalogue, columns] using hD + · simpa [y, prefixSize, snapshot, inverse, table, catalogue, outerSize] using hN0 + · simpa [y, prefixSize, snapshot, inverse, table, catalogue, masks] using hS2 + · simpa [y, prefixSize, snapshot, inverse, table, catalogue, cells] using hS7 + · simpa [y, prefixSize, snapshot, inverse, table, catalogue, leaves] using hS10 + · have hb : y.regs prefixSize = B := by + simp +decide [y, prefixSize, snapshot, inverse, table, catalogue] + rw [hb] + dsimp only [B, inputEnd] + omega + · have hs : y.regs snapshot = S := by + simp +decide [y, prefixSize, snapshot, inverse, table, catalogue] + rw [hs] + dsimp only [S, B, snapshotBase, inputEnd, Encoding.scratchBase, Encoding.outputBase] + omega + · have hi : y.regs inverse = I := by + simp +decide [y, prefixSize, snapshot, inverse, table, catalogue] + rw [hi] + dsimp only [I, S, B, inverseBase, snapshotBase, inputEnd, + Encoding.scratchBase, Encoding.outputBase] + omega + · have ht : y.regs table = T := by + simp +decide [y, prefixSize, snapshot, inverse, table, catalogue] + rw [ht] + dsimp only [T, I, S, B, tableBase, inverseBase, snapshotBase, inputEnd, + Encoding.scratchBase, Encoding.outputBase] + omega + · have hc : y.regs catalogue = C := by simp [y] + rw [hc] + dsimp only [C, T, I, S, B, catalogueBase, tableBase, inverseBase, snapshotBase, + inputEnd, Encoding.scratchBase, Encoding.outputBase] + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandInput.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandInput.lean new file mode 100644 index 0000000000..5a60b0da5f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandInput.lean @@ -0,0 +1,149 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandExec +public import Complexity.Foundations.Data.List.RowMajor +import Mathlib.Data.List.GetD + +/-! +# Actual initialized header and signed-matrix inputs + +## Main definitions + +* `BandPreparation.matrixValues`: zero extension of the two original finite matrices for + stating the already proved native guarded-load specification. + +## Main results + +* `BandPreparation.input_fits`: the actual initialized list fits from original magnitude + and parameter bounds. +* `BandPreparation.exec_input`: complete charged native execution from `State.init`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Original matrix entries, zero extended to natural indices. Right-side `r` denotes a +column of `Y`. This is only the mathematical memory specification; the program loads the +concrete row-major input words, after its actual bounds guard. -/ +def matrixValues {m N : ℕ} (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (side : Bool) (r k : ℕ) : ℤ := + if hr : r < N then if hk : k < D m then + if side then Y ⟨k, hk⟩ ⟨r, hr⟩ else X ⟨r, hr⟩ ⟨k, hk⟩ else 0 else 0 + +/-- The input prefix consists of the native length header, three scalar words and both +original matrices, with no powers, tables or precomputed outputs. -/ +theorem input_length {m N : ℕ} (L : ℕ) (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) : (input L m N X Y).length + 1 = inputEnd m N := by + simp only [input, List.flatMap_ofFn_prod, List.length_append, List.length_ofFn, + List.length_cons, List.length_nil, inputEnd] + ring + +private theorem init_matrix {L m N : ℕ} (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (side : Bool) (r : ℕ) (hr : r < N) + (k : ℕ) (hk : k < D m) : + (State.init (input L m N X Y) : State 96).data.mem (matrixOffset m N side r k + 4) = + encode (matrixValues X Y side r k) := by + let xs := (List.finRange N).flatMap (fun i => List.ofFn fun k => encode (X i k)) + let ys := (List.finRange (D m)).flatMap (fun k => List.ofFn fun j => encode (Y k j)) + have hxs : xs.length = N * D m := by + simp only [xs, List.flatMap_ofFn_prod, List.length_ofFn] + change (Data.init (input L m N X Y) : Data 96).mem _ = _ + rw [show matrixOffset m N side r k + 4 = (matrixOffset m N side r k + 3) + 1 by omega, + Data.init_mem_succ] + change ((L :: m :: N :: (xs ++ ys)).getD (matrixOffset m N side r k + 3) 0) = _ + rw [show matrixOffset m N side r k + 3 = + ((matrixOffset m N side r k + 1) + 1) + 1 by omega] + simp only [List.getD_cons_succ] + cases side + · have hi : r * D m + k < N * D m := by + have h := (finProdFinEquiv (⟨r, hr⟩, ⟨k, hk⟩)).isLt + simpa only [finProdFinEquiv, Equiv.coe_fn_mk, Nat.add_comm, Nat.mul_comm] using h + simp only [matrixOffset, Bool.false_eq_true, ↓reduceIte] + rw [List.getD_append _ _ _ _ (by simpa only [hxs] using hi)] + simpa only [xs, matrixValues, dite_eq_left hr, dite_eq_left hk, + Bool.false_eq_true, ↓reduceIte] using + (List.getD_flatMap_ofFn (fun i k => encode (X i k)) 0 ⟨r, hr⟩ ⟨k, hk⟩) + · simp only [matrixOffset, ↓reduceIte] + rw [List.getD_append_right _ _ _ _ (by rw [hxs]; omega), hxs, + Nat.add_sub_cancel_right] + simpa only [ys, matrixValues, dite_eq_left hr, dite_eq_left hk, ↓reduceIte] using + (List.getD_flatMap_ofFn (fun k j => encode (Y k j)) 0 ⟨k, hk⟩ ⟨r, hr⟩) + +/-- The sign/magnitude input encoding uses at most `w+1` bits; the actual native program +uses `w+2` bits for its signed arithmetic kernels. Every input word is covered. -/ +theorem input_fits {w L m N : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (A : ℤ) (hA : 0 ≤ A) (hX : ∀ i k, |X i k| ≤ A) (hY : ∀ k j, |Y k j| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : InputFits (w + 2) (input L m N X Y) := by + have hscalar : Fits (w + 2) (L + N) := (local_fits hfit).of_le (by + nlinarith only [Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)]) + 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 only [hpow, hA, hmag] + have hvalue (z : ℤ) (hz : |z| ≤ A) : Fits (w + 2) (encode z) := by + have hb := abs_lt.mp (hz.trans_lt hAfit) + exact (encode_fits (show GenericRAM.Signed.Fits w z from ⟨hw, hb.1.le, hb.2⟩)).mono + (by omega) + refine ⟨?_, ?_⟩ + · apply ((memory_fits hfit).of_le (input_le_memoryEnd L m N)).of_le + have := input_length L X Y + omega + · intro v hv + simp only [input, List.mem_append, List.mem_cons, List.not_mem_nil, or_false] at hv + rcases hv with (hv | hv) | hv + · rcases hv with rfl | rfl | rfl <;> exact hscalar.of_le (by omega) + · obtain ⟨i, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨k, rfl⟩ := List.mem_ofFn.mp hv + exact hvalue _ (hX i k) + · obtain ⟨k, _, hv⟩ := List.mem_flatMap.mp hv + obtain ⟨j, rfl⟩ := List.mem_ofFn.mp hv + exact hvalue _ (hY k j) + +/-- Complete actual `State.init` execution, with both encoded catalogues, computed tables, +preserved input and outside frame, bounded final state and the complete charged time bound. -/ +theorem exec_input {w L m N : ℕ} (hw : 0 < w) (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (A : ℤ) (hA : 0 ≤ A) (hX : ∀ i k, |X i k| ≤ A) (hY : ∀ k j, |Y k j| ≤ A) + (hmag : (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget L m N)) : + InputFits (w + 2) (input L m N X Y) ∧ + ∃ y, ExecWithin (w + 2) program (State.init (input L m N X Y)).data y (timeBound L m N) ∧ + y.Bounded (w + 2) ∧ BandParameters L m N y ∧ + (∀ side β, β < numBands L m N → ∀ p, p < 10 ^ L → + y.mem (catalogueAddress L m N side β p) = + encode (catalogueValue L m N side (matrixValues X Y side) β p)) ∧ + (∀ a < 2 ^ L, y.mem (inverseBase L m N + a) = MaskTable.lookup L m a) ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tableBase L m N + (MaskTable.pairRank gh).val) = + (MaskTable.selectedMask gh).val) ∧ + (∀ b, ¬(inputEnd m N ≤ b ∧ b < memoryEnd L m N) → + y.mem b = (State.init (input L m N X Y) : State 96).data.mem b) := by + have hi := input_fits hw hm X Y A hA hX hY hmag hfit + let x : Data 96 := (State.init (input L m N X Y)).data + have hL : x.mem 1 = L := by simp [x, State.init, Data.init, input] + have hm' : x.mem 2 = m := by simp [x, State.init, Data.init, input] + have hN : x.mem 3 = N := by simp [x, State.init, Data.init, input] + have hvalues (side : Bool) (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + |matrixValues X Y side r k| ≤ A := by + cases side <;> simp only [matrixValues, dite_eq_left hr, dite_eq_left hk, + Bool.false_eq_true, ↓reduceIte] + · exact hX _ _ + · exact hY _ _ + obtain ⟨y, he, hp, hv, ht, hf, hframe⟩ := exec hw hm x hL hm' hN + (matrixValues X Y) (init_matrix X Y) A hA hvalues hmag hfit + exact ⟨hi, y, he, he.bounded (Data.Bounded.init hi), hp, hv, ht, hf, hframe⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMeaning.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMeaning.lean new file mode 100644 index 0000000000..67daa9a4e0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMeaning.lean @@ -0,0 +1,351 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandProgram +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.SplitDigits + +/-! +# Meaning of the native band indices + +## Main results + +* `BandPreparation.leftAt_isInner`, `BandPreparation.rightAt_isInner`: the original + encoder numberings classify the same inner positions as the native decoder. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Encoding BandDigits + +/-- Digits three through six are exactly the inner left variables. -/ +theorem leftAt_isInner (d : Fin 7) : (leftAt d).IsInner ↔ ¬d.val < 3 := by decide +revert + +/-- The same four digits classify the inner right variables. -/ +theorem rightAt_isInner (d : Fin 7) : (rightAt d).IsInner ↔ ¬d.val < 3 := by decide +revert + +/-- An outer left digit is its ordinary base-three value. -/ +theorem leftAt_outer (d : Fin 7) (h : d.val < 3) : + (leftAt d).outerIndex.val = d.val := by decide +revert + +/-- An outer right digit is its ordinary base-three value. -/ +theorem rightAt_outer (d : Fin 7) (h : d.val < 3) : + (rightAt d).outerIndex.val = d.val := by decide +revert + +/-- The four inner left digits use the existing row-major pair numbering. -/ +theorem leftAt_inner (d : Fin 7) (h : ¬d.val < 3) : + (finProdFinEquiv (leftAt d).innerIndex).val = d.val - 3 := by decide +revert + +/-- The four inner right digits use the same pair numbering. -/ +theorem rightAt_inner (d : Fin 7) (h : ¬d.val < 3) : + (finProdFinEquiv (rightAt d).innerIndex).val = d.val - 3 := by decide +revert + +private theorem filter_outer {L : ℕ} (u : Fin L → Fin 7) + (Q : Finset (Fin L)) (hQ : ∀ ℓ, ℓ ∈ Q ↔ (u ℓ).val < 3) + (n : ℕ) (hn : Q.card = n) (f : Fin L → ℕ) + (hf : ∀ ℓ ∈ Q, f ℓ = (u ℓ).val) : + (List.ofFn fun ℓ => (u ℓ).val).filter (fun d => d < 3) = + List.ofFn (fun k => f (Q.orderEmbOfFin hn k)) := by + rw [Finset.ofFn_orderEmbOfFin, Finset.sort_eq_filter_finRange, + List.ofFn_eq_map, List.filter_map] + have hp : (fun ℓ : Fin L => decide ((u ℓ).val < 3)) = (fun ℓ => decide (ℓ ∈ Q)) := by + funext ℓ + simp only [hQ] + simp only [Function.comp_def, hp] + apply List.map_congr_left + intro ℓ hℓ + exact (hf ℓ (by simpa only [List.mem_filter, List.mem_finRange, true_and, + decide_eq_true_eq] using hℓ)).symm + +/-- Base-three compression follows the increasing outer positions used by `outerPartL`. -/ +theorem outer_digits_left {L m : ℕ} (u : Fin L → Fin 7) + (h : (innerSetL (fun ℓ => leftAt (u ℓ))).card = m) : + (List.ofFn fun ℓ => (u ℓ).val).filter (fun d => d < 3) = + List.ofFn (fun k => (outerPartL (fun ℓ => leftAt (u ℓ)) h k).val) := by + have hQ : ∀ ℓ, ℓ ∈ (innerSetL (fun ℓ => leftAt (u ℓ)))ᶜ ↔ (u ℓ).val < 3 := by + intro ℓ + simp [innerSetL, leftAt_isInner] + apply filter_outer u (innerSetL (fun ℓ => leftAt (u ℓ)))ᶜ hQ (L - m) + (Finset.card_compl_of_card_eq _ h) (fun ℓ => (leftAt (u ℓ)).outerIndex.val) + intro ℓ hℓ + exact leftAt_outer _ ((hQ ℓ).mp hℓ) + +/-- Base-three compression follows the increasing outer positions used by `outerPartR`. -/ +theorem outer_digits_right {L m : ℕ} (u : Fin L → Fin 7) + (h : (innerSetR (fun ℓ => rightAt (u ℓ))).card = m) : + (List.ofFn fun ℓ => (u ℓ).val).filter (fun d => d < 3) = + List.ofFn (fun k => (outerPartR (fun ℓ => rightAt (u ℓ)) h k).val) := by + have hQ : ∀ ℓ, ℓ ∈ (innerSetR (fun ℓ => rightAt (u ℓ)))ᶜ ↔ (u ℓ).val < 3 := by + intro ℓ + simp [innerSetR, rightAt_isInner] + apply filter_outer u (innerSetR (fun ℓ => rightAt (u ℓ)))ᶜ hQ (L - m) + (Finset.card_compl_of_card_eq _ h) (fun ℓ => (rightAt (u ℓ)).outerIndex.val) + intro ℓ hℓ + exact rightAt_outer _ ((hQ ℓ).mp hℓ) + +private theorem filter_inner {L : ℕ} (u : Fin L → Fin 7) + (Q : Finset (Fin L)) (hQ : ∀ ℓ, ℓ ∈ Q ↔ ¬(u ℓ).val < 3) + (n : ℕ) (hn : Q.card = n) (f : Fin L → ℕ) + (hf : ∀ ℓ ∈ Q, f ℓ = (u ℓ).val - 3) : + (((List.ofFn fun ℓ => (u ℓ).val).filter (fun d => ¬d < 3)).map fun d => d - 3) = + List.ofFn (fun k => f (Q.orderEmbOfFin hn k)) := by + rw [Finset.ofFn_orderEmbOfFin, Finset.sort_eq_filter_finRange, + List.ofFn_eq_map, List.filter_map, List.map_map] + have hp : (fun ℓ : Fin L => decide (¬(u ℓ).val < 3)) = (fun ℓ => decide (ℓ ∈ Q)) := by + funext ℓ + simp only [hQ] + simp only [Function.comp_def, hp] + apply List.map_congr_left + intro ℓ hℓ + exact (hf ℓ (by simpa only [List.mem_filter, List.mem_finRange, true_and, + decide_eq_true_eq] using hℓ)).symm + +/-- Base-four compression follows the increasing inner positions used by `innerPartL`. -/ +theorem inner_digits_left {L m : ℕ} (u : Fin L → Fin 7) + (h : (innerSetL (fun ℓ => leftAt (u ℓ))).card = m) : + (((List.ofFn fun ℓ => (u ℓ).val).filter (fun d => ¬d < 3)).map fun d => d - 3) = + List.ofFn (fun k => (finProdFinEquiv (innerPartL (fun ℓ => leftAt (u ℓ)) h k)).val) := by + have hQ : ∀ ℓ, ℓ ∈ innerSetL (fun ℓ => leftAt (u ℓ)) ↔ ¬(u ℓ).val < 3 := by + intro ℓ + simp [innerSetL, leftAt_isInner] + apply filter_inner u (innerSetL (fun ℓ => leftAt (u ℓ))) hQ m h + (fun ℓ => (finProdFinEquiv (leftAt (u ℓ)).innerIndex).val) + intro ℓ hℓ + exact leftAt_inner _ ((hQ ℓ).mp hℓ) + +/-- The right inner index uses the identical four pair digits. -/ +theorem inner_digits_right {L m : ℕ} (u : Fin L → Fin 7) + (h : (innerSetR (fun ℓ => rightAt (u ℓ))).card = m) : + (((List.ofFn fun ℓ => (u ℓ).val).filter (fun d => ¬d < 3)).map fun d => d - 3) = + List.ofFn (fun k => (finProdFinEquiv (innerPartR (fun ℓ => rightAt (u ℓ)) h k)).val) := by + have hQ : ∀ ℓ, ℓ ∈ innerSetR (fun ℓ => rightAt (u ℓ)) ↔ ¬(u ℓ).val < 3 := by + intro ℓ + simp [innerSetR, rightAt_isInner] + apply filter_inner u (innerSetR (fun ℓ => rightAt (u ℓ))) hQ m h + (fun ℓ => (finProdFinEquiv (rightAt (u ℓ)).innerIndex).val) + intro ℓ hℓ + exact rightAt_inner _ ((hQ ℓ).mp hℓ) + +/-- Native mask compression agrees with the exact inner level set of the original array. -/ +theorem parts_mask_left {L : ℕ} (a : Fin (7 ^ L)) : + (parts L a.val).mask = + (MaskTable.maskCode (innerSetL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ)))).val := by + rw [(parts_lists L a.val).1, sevenDigits_eq_ofFn] + change _ = (finFunctionFinEquiv (fun ℓ => + if ℓ ∈ innerSetL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ)) + then (1 : Fin 2) else 0)).val + rw [← Nat.ofDigits_finFunctionFinEquiv, List.map_ofFn] + congr 1 + apply congrArg List.ofFn + funext ℓ + change (if (finFunctionFinEquiv.symm a ℓ).val < 3 then 0 else 1) = _ + by_cases h : (finFunctionFinEquiv.symm a ℓ).val < 3 <;> + simp only [innerSetL, Finset.mem_filter, Finset.mem_univ, true_and, + leftAt_isInner, h, not_true_eq_false, not_false_eq_true, ↓reduceIte, + Fin.val_zero, Fin.val_one] + +/-- The right mask is classified identically, without changing the encoder numbering. -/ +theorem parts_mask_right {L : ℕ} (a : Fin (7 ^ L)) : + (parts L a.val).mask = + (MaskTable.maskCode (innerSetR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ)))).val := by + rw [(parts_lists L a.val).1, sevenDigits_eq_ofFn] + change _ = (finFunctionFinEquiv (fun ℓ => + if ℓ ∈ innerSetR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ)) + then (1 : Fin 2) else 0)).val + rw [← Nat.ofDigits_finFunctionFinEquiv, List.map_ofFn] + congr 1 + apply congrArg List.ofFn + funext ℓ + change (if (finFunctionFinEquiv.symm a ℓ).val < 3 then 0 else 1) = _ + by_cases h : (finFunctionFinEquiv.symm a ℓ).val < 3 <;> + simp only [innerSetR, Finset.mem_filter, Finset.mem_univ, true_and, + rightAt_isInner, h, not_true_eq_false, not_false_eq_true, ↓reduceIte, + Fin.val_zero, Fin.val_one] + +/-- Native left outer offset, with the exact public mathematical string interface. -/ +theorem parts_outer_left {L m : ℕ} (a : Fin (7 ^ L)) + (h : (innerSetL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ))).card = m) : + (parts L a.val).outer = + (finFunctionFinEquiv + (outerPartL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ)) h)).val := by + rw [(parts_lists L a.val).2.1, sevenDigits_eq_ofFn] + change Nat.ofDigits 3 ((List.ofFn fun ℓ => (finFunctionFinEquiv.symm a ℓ).val).filter + (fun d => d < 3)) = _ + rw [outer_digits_left _ h, Nat.ofDigits_finFunctionFinEquiv] + +/-- Native right outer offset in the same least-significant-first convention. -/ +theorem parts_outer_right {L m : ℕ} (a : Fin (7 ^ L)) + (h : (innerSetR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ))).card = m) : + (parts L a.val).outer = + (finFunctionFinEquiv + (outerPartR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ)) h)).val := by + rw [(parts_lists L a.val).2.1, sevenDigits_eq_ofFn] + change Nat.ofDigits 3 ((List.ofFn fun ℓ => (finFunctionFinEquiv.symm a ℓ).val).filter + (fun d => d < 3)) = _ + rw [outer_digits_right _ h, Nat.ofDigits_finFunctionFinEquiv] + +/-- Native left thin-dimension offset agrees with the row-major pair digits of `Layout`. -/ +theorem parts_inner_left {L m : ℕ} (a : Fin (7 ^ L)) + (h : (innerSetL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ))).card = m) : + (parts L a.val).inner = (finFunctionFinEquiv fun k => + finProdFinEquiv (innerPartL (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ)) h k)).val := by + rw [(parts_lists L a.val).2.2, sevenDigits_eq_ofFn] + change Nat.ofDigits 4 (((List.ofFn fun ℓ => (finFunctionFinEquiv.symm a ℓ).val).filter + (fun d => ¬d < 3)).map fun d => d - 3) = _ + rw [inner_digits_left _ h, Nat.ofDigits_finFunctionFinEquiv] + +/-- Native right thin-dimension offset uses the same `Layout` equivalence. -/ +theorem parts_inner_right {L m : ℕ} (a : Fin (7 ^ L)) + (h : (innerSetR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ))).card = m) : + (parts L a.val).inner = (finFunctionFinEquiv fun k => + finProdFinEquiv (innerPartR (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ)) h k)).val := by + rw [(parts_lists L a.val).2.2, sevenDigits_eq_ofFn] + change Nat.ofDigits 4 (((List.ofFn fun ℓ => (finFunctionFinEquiv.symm a ℓ).val).filter + (fun d => ¬d < 3)).map fun d => d - 3) = _ + rw [inner_digits_right _ h, Nat.ofDigits_finFunctionFinEquiv] + +/-- Numeric specification of one guarded band-array read. `matrix r k` denotes an original +matrix cell (or the transposed indexing of the right matrix); the native implementation +computes its actual row-major address and loads it. -/ +def bandValue (L m N β : ℕ) (rightSide : Bool) (matrix : ℕ → ℕ → ℤ) (a : ℕ) : ℤ := + let p := parts L a + let q := MaskTable.lookup L m p.mask + if q = 0 then 0 else + let b := if rightSide then (q - 1) % K0 L m else (q - 1) / K0 L m + let r := (β * K0 L m + b) * N0 L m + p.outer + if r < N then matrix r p.inner else 0 + +/-- The numeric left-band specification is the unchanged mathematical `bandArrayL` for +the computed deterministic layout, including all unselected strings and padding. -/ +theorem bandValue_left {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (matrix : ℕ → ℕ → ℤ) + (hX : ∀ i k, matrix i.val k.val = X i k) (β : ℕ) (a : Fin (7 ^ L)) : + bandValue L m N β false matrix a.val = + bandArrayL (MaskTable.layout hm) X β (fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ)) := by + let u : LeftStr L := fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ) + let lay := MaskTable.layout hm + have hmask : (parts L a.val).mask = (MaskTable.maskCode (innerSetL u)).val := + parts_mask_left a + by_cases hz : MaskTable.lookup L m (parts L a.val).mask = 0 + · have hmissing : ∀ gh, lay.table gh ≠ innerSetL u := by + intro gh heq + have hc := congrArg (fun Q => (MaskTable.maskCode Q).val) heq + change (MaskTable.maskCode (MaskTable.maskSet (MaskTable.selectedMask gh))).val = _ at hc + rw [MaskTable.maskCode_maskSet] at hc + have ha : (MaskTable.selectedMask gh).val = (parts L a.val).mask := hc.trans hmask.symm + have hne : MaskTable.lookup L m (MaskTable.selectedMask gh).val ≠ 0 := by + rw [MaskTable.lookup_selectedMask] + omega + exact hne (ha ▸ hz) + have hf := bandFamilyL_eq_zero lay X β hmissing + simp only [bandValue, ↓reduceIte, hz] + change 0 = (arrayL m (bandFamilyL lay X β)) u + unfold arrayL + split <;> simp only [hf, Matrix.zero_apply] + · obtain ⟨gh, hs⟩ := MaskTable.exists_selectedMask_of_lookup hz + have hcode : MaskTable.selectedMask gh = MaskTable.maskCode (innerSetL u) := + Fin.ext (hs.trans hmask) + have hset : lay.table gh = innerSetL u := by + change MaskTable.maskSet (MaskTable.selectedMask gh) = _ + rw [hcode, MaskTable.maskSet_maskCode] + have hcard : (innerSetL u).card = m := hset ▸ lay.table_card gh + have hf : bandFamilyL lay X β (innerSetL u) = rowBlock lay X (β * K0 L m + gh.1) := by + rw [← hset] + exact bandFamilyL_table lay X β gh.1 gh.2 + have ho : (parts L a.val).outer = (lay.rowIdx.symm (outerPartL u hcard)).val := + parts_outer_left a hcard + have hi : (parts L a.val).inner = (lay.innerIdx.symm (innerPartL u hcard)).val := by + change (parts L a.val).inner = + (finFunctionFinEquiv fun k => finProdFinEquiv (innerPartL u hcard k)).val + exact parts_inner_left a hcard + have hg : (MaskTable.lookup L m (parts L a.val).mask - 1) / K0 L m = gh.1.val := by + rw [← hs] + exact (MaskTable.lookup_blockPair gh).1 + change bandValue L m N β false matrix a.val = (arrayL m (bandFamilyL lay X β)) u + rw [arrayL, dite_eq_left hcard, hf] + simp only [bandValue, hz, ↓reduceIte, Bool.false_eq_true, hg, ho, hi, + rowBlock, padRows] + split_ifs with hr + · exact hX ⟨_, hr⟩ _ + · rfl + +/-- The numeric right-band specification is the unchanged mathematical `bandArrayR`. +Its natural row parameter refers to a column of the original right matrix. -/ +theorem bandValue_right {L m N : ℕ} (hm : m ≤ L) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) (matrix : ℕ → ℕ → ℤ) + (hY : ∀ i k, matrix i.val k.val = Y k i) (β : ℕ) (a : Fin (7 ^ L)) : + bandValue L m N β true matrix a.val = + bandArrayR (MaskTable.layout hm) Y β (fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ)) := by + let u : RightStr L := fun ℓ => rightAt (finFunctionFinEquiv.symm a ℓ) + let lay := MaskTable.layout hm + have hmask : (parts L a.val).mask = (MaskTable.maskCode (innerSetR u)).val := + parts_mask_right a + by_cases hz : MaskTable.lookup L m (parts L a.val).mask = 0 + · have hmissing : ∀ gh, lay.table gh ≠ innerSetR u := by + intro gh heq + have hc := congrArg (fun Q => (MaskTable.maskCode Q).val) heq + change (MaskTable.maskCode (MaskTable.maskSet (MaskTable.selectedMask gh))).val = _ at hc + rw [MaskTable.maskCode_maskSet] at hc + have ha : (MaskTable.selectedMask gh).val = (parts L a.val).mask := hc.trans hmask.symm + have hne : MaskTable.lookup L m (MaskTable.selectedMask gh).val ≠ 0 := by + rw [MaskTable.lookup_selectedMask] + omega + exact hne (ha ▸ hz) + have hf := bandFamilyR_eq_zero lay Y β hmissing + simp only [bandValue, ↓reduceIte, hz] + change 0 = (arrayR m (bandFamilyR lay Y β)) u + unfold arrayR + split <;> simp only [hf, Matrix.zero_apply] + · obtain ⟨gh, hs⟩ := MaskTable.exists_selectedMask_of_lookup hz + have hcode : MaskTable.selectedMask gh = MaskTable.maskCode (innerSetR u) := + Fin.ext (hs.trans hmask) + have hset : lay.table gh = innerSetR u := by + change MaskTable.maskSet (MaskTable.selectedMask gh) = _ + rw [hcode, MaskTable.maskSet_maskCode] + have hcard : (innerSetR u).card = m := hset ▸ lay.table_card gh + have hf : bandFamilyR lay Y β (innerSetR u) = colBlock lay Y (β * K0 L m + gh.2) := by + rw [← hset] + exact bandFamilyR_table lay Y β gh.1 gh.2 + have ho : (parts L a.val).outer = (lay.colIdx.symm (outerPartR u hcard)).val := + parts_outer_right a hcard + have hi : (parts L a.val).inner = (lay.innerIdx.symm (innerPartR u hcard)).val := by + change (parts L a.val).inner = + (finFunctionFinEquiv fun k => finProdFinEquiv (innerPartR u hcard k)).val + exact parts_inner_right a hcard + have hg : (MaskTable.lookup L m (parts L a.val).mask - 1) % K0 L m = gh.2.val := by + rw [← hs] + exact (MaskTable.lookup_blockPair gh).2 + change bandValue L m N β true matrix a.val = (arrayR m (bandFamilyR lay Y β)) u + rw [arrayR, dite_eq_left hcard, hf] + simp only [bandValue, hz, ↓reduceIte, hg, ho, hi, colBlock, padCols] + split_ifs with hr + · exact hY ⟨_, hr⟩ _ + · rfl + +/-- A selected native mask ensures that its compressed offsets fit the actual matrix +dimensions, rather than merely the coarser depth-based bounds. -/ +theorem selected_parts_bounds {L m : ℕ} (a : Fin (7 ^ L)) + (hz : MaskTable.lookup L m (parts L a.val).mask ≠ 0) : + (parts L a.val).outer < N0 L m ∧ (parts L a.val).inner < D m := by + obtain ⟨gh, hs⟩ := MaskTable.exists_selectedMask_of_lookup hz + let u : LeftStr L := fun ℓ => leftAt (finFunctionFinEquiv.symm a ℓ) + have hc : MaskTable.selectedMask gh = MaskTable.maskCode (innerSetL u) := + Fin.ext (hs.trans (parts_mask_left a)) + have hset : MaskTable.maskSet (MaskTable.selectedMask gh) = innerSetL u := by + rw [hc, MaskTable.maskSet_maskCode] + have hcard : (innerSetL u).card = m := hset ▸ MaskTable.card_selectedMask gh + constructor + · rw [parts_outer_left a hcard] + exact (finFunctionFinEquiv (outerPartL u hcard)).isLt + · rw [parts_inner_left a hcard] + exact (finFunctionFinEquiv (fun k => finProdFinEquiv (innerPartL u hcard k))).isLt + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMemory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMemory.lean new file mode 100644 index 0000000000..44f7f18870 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandMemory.lean @@ -0,0 +1,124 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandProgram + +/-! +# Concrete disjoint memory regions and derived native fits + +## Main results + +* `BandPreparation.workspace_le_snapshot`: protected data are above the complete encoder. +* `BandPreparation.memory_fits`: the actual allocation end fits the explicit input budget. +* `BandPreparation.tables_fit`, `BandPreparation.split_fits`, `BandPreparation.encoder_fits`: + component inequalities derived from the same budget, without an intermediate-state oracle. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The native input prefix is disjoint from its protected copy. -/ +theorem input_le_snapshot (L m N : ℕ) : inputEnd m N ≤ snapshotBase L m N := by + unfold snapshotBase + exact (Nat.le_add_right (inputEnd m N) (Encoding.scratchBase L)).trans + (Nat.le_add_right _ (10 ^ L)) + +/-- The encoder's complete low workspace is below the protected original input. -/ +theorem workspace_le_snapshot (L m N : ℕ) : + Encoding.scratchBase L + 10 ^ L ≤ snapshotBase L m N := by + unfold snapshotBase + omega + +/-- The protected original-input copy ends exactly at the inverse table. -/ +theorem snapshot_end (L m N : ℕ) : + snapshotBase L m N + inputEnd m N = inverseBase L m N := rfl + +/-- The inverse table ends exactly at the selected-mask table. -/ +theorem inverse_end (L m N : ℕ) : inverseBase L m N + 2 ^ L = tableBase L m N := rfl + +/-- The selected-mask table ends exactly at the shared output catalogue. -/ +theorem table_end (L m N : ℕ) : tableBase L m N + 2 ^ L = catalogueBase L m N := rfl + +/-- The catalogue base is below the complete allocation end, including an empty catalogue. -/ +theorem catalogue_le_memoryEnd (L m N : ℕ) : catalogueBase L m N ≤ memoryEnd L m N := + Nat.le_add_right _ _ + +/-- Each table block precedes the complete allocation end. -/ +theorem table_le_memoryEnd (L m N : ℕ) : tableBase L m N ≤ memoryEnd L m N := + (Nat.le_add_right _ _ : tableBase L m N ≤ tableBase L m N + 2 ^ L).trans + (catalogue_le_memoryEnd L m N) + +/-- The protected prefix ends below every table/output allocation. -/ +theorem inverse_le_memoryEnd (L m N : ℕ) : inverseBase L m N ≤ memoryEnd L m N := + (Nat.le_add_right _ _ : inverseBase L m N ≤ inverseBase L m N + 2 ^ L).trans + (table_le_memoryEnd L m N) + +/-- The snapshot base is within the complete allocation. -/ +theorem snapshot_le_memoryEnd (L m N : ℕ) : snapshotBase L m N ≤ memoryEnd L m N := + (Nat.le_add_right _ _ : snapshotBase L m N ≤ snapshotBase L m N + inputEnd m N).trans + (inverse_le_memoryEnd L m N) + +/-- Every original input address also fits the complete allocation. -/ +theorem input_le_memoryEnd (L m N : ℕ) : inputEnd m N ≤ memoryEnd L m N := + (input_le_snapshot L m N).trans (snapshot_le_memoryEnd L m N) + +/-- The constant margin in the original budget covers all fixed program constants. -/ +theorem small_fits {w L m N v : ℕ} (hfit : Fits w (wordBudget L m N)) (hv : v ≤ 256) : + Fits w v := + hfit.of_le (hv.trans (Nat.le_mul_of_pos_right _ (Nat.succ_pos _))) + +/-- The entire real allocation, including both catalogue halves, fits the original numerical +word-budget inequality. The sizes of unreachable padded row-major products are irrelevant. -/ +theorem memory_fits {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) : + Fits w (memoryEnd L m N) := by + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + apply hfit.of_le + unfold memoryEnd catalogueBase tableBase inverseBase snapshotBase inputEnd + Encoding.scratchBase Encoding.outputBase wordBudget + nlinarith only [h7, Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +/-- Both table-store addresses and all population/count arithmetic fit before table execution. -/ +theorem tables_fit {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) : + Fits w (inverseBase L m N + tableBase L m N + 2 ^ L + L + 4) := by + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + apply hfit.of_le + unfold tableBase inverseBase snapshotBase inputEnd Encoding.scratchBase Encoding.outputBase + wordBudget + nlinarith only [h7, Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +/-- Every original band index admits the already proved native digit-loop fits. -/ +theorem split_fits {w L m N a : ℕ} (hfit : Fits w (wordBudget L m N)) (ha : a < 7 ^ L) : + Fits w (a + L + 16 * 10 ^ L + 16) := by + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + apply hfit.of_le + unfold wordBudget + nlinarith only [ha, h7, Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +/-- The unchanged native encoder's arithmetic/address premise follows from this client's +explicit original-parameter bound. Its signed magnitude premise remains separate. -/ +theorem encoder_fits {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) : + Fits w (Encoding.addressBound L) := by + apply hfit.of_le + unfold Encoding.addressBound wordBudget + have hpos : 1 ≤ 10 ^ L := Nat.one_le_pow L 10 (by decide) + nlinarith only [hpos, Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandParameters.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandParameters.lean new file mode 100644 index 0000000000..519cb3b84a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandParameters.lean @@ -0,0 +1,152 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandHeader +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCount +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandRoot +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.BandBounds + +/-! +# Complete computed scalar parameters for native bands + +## Main results + +* `BandPreparation.exec_countCode`: compute binomial count, block count and band count + from actual scalar parameters, retaining the previously computed header layout. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Registers containing the persistent header and concrete layout. -/ +def headerRegisters : Finset (Fin 96) := + {levels, inner, rows, columns, outerSize, masks, cells, leaves, prefixSize, + snapshot, inverse, table, catalogue, zero, one, two, three, four, seven, ten} + +/-- The concrete layout survives any native execution that preserves its scalar registers. -/ +theorem headerPost_reframe {L m N : ℕ} {x y : Data 96} (hx : HeaderPost L m N x) + (h : ∀ i ∈ headerRegisters, y.regs i = x.regs i) : HeaderPost L m N y := by + refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact ⟨(h _ (by decide)).trans hx.fixed.levels, (h _ (by decide)).trans hx.fixed.inner, + (h _ (by decide)).trans hx.fixed.rows, (h _ (by decide)).trans hx.fixed.zero, + (h _ (by decide)).trans hx.fixed.one, (h _ (by decide)).trans hx.fixed.two, + (h _ (by decide)).trans hx.fixed.three, (h _ (by decide)).trans hx.fixed.four, + (h _ (by decide)).trans hx.fixed.seven, (h _ (by decide)).trans hx.fixed.ten⟩ + all_goals first + | exact (h _ (by decide)).trans hx.columns + | exact (h _ (by decide)).trans hx.outerSize + | exact (h _ (by decide)).trans hx.masks + | exact (h _ (by decide)).trans hx.cells + | exact (h _ (by decide)).trans hx.leaves + | exact (h _ (by decide)).trans hx.prefixSize + | exact (h _ (by decide)).trans hx.snapshot + | exact (h _ (by decide)).trans hx.inverse + | exact (h _ (by decide)).trans hx.table + | exact (h _ (by decide)).trans hx.catalogue + +/-- Persistent concrete layout and the additional scalar results actually computed by +the mask-count/root/band-count code. No fit or execution bound is bundled here. -/ +structure BandParameters (L m N : ℕ) (x : Data 96) : Prop extends HeaderPost L m N x where + /-- Computed floor square root of the binomial count. -/ + root : x.regs root = K0 L m + /-- Number of selected subset masks. -/ + square : x.regs square = K0 L m * K0 L m + /-- Computed number of shared bands per side. -/ + bands : x.regs bands = numBands L m N + +/-- All scalar parameters retained throughout preparation and encoder calls. -/ +def parameterRegisters : Finset (Fin 96) := headerRegisters ∪ {root, square, bands} + +/-- Preserve the computed parameters by preserving their actual registers. -/ +theorem parameters_reframe {L m N : ℕ} {x y : Data 96} (hx : BandParameters L m N x) + (h : ∀ i ∈ parameterRegisters, y.regs i = x.regs i) : BandParameters L m N y := by + refine ⟨headerPost_reframe hx.toHeaderPost (fun i hi => h i (Finset.mem_union_left _ hi)), + (h _ (by decide)).trans hx.root, (h _ (by decide)).trans hx.square, + (h _ (by decide)).trans hx.bands⟩ + +/-- A convenient local numerical bound, derived from the original global word budget. -/ +theorem local_fits {w L m N : ℕ} (hfit : Fits w (wordBudget L m N)) : + Fits w (16 * (L + N + N * D m + 2 ^ L + 10 ^ L + 1)) := by + apply hfit.of_le + unfold wordBudget + nlinarith only [Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L), + Nat.zero_le (2 * numBands L m N * 10 ^ L)] + +/-- Compute the root and band counts with the actual native loops. -/ +theorem exec_countCode {w L m N : ℕ} (x : Data 96) + (hx : HeaderPost L m N x) (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w countCode x y ((6 * L + 16) * 2 ^ L + 14) ∧ + BandParameters L m N y ∧ y.mem = x.mem := by + have hl := local_fits hfit + have hK : K L m ≤ 2 ^ L := Nat.choose_le_two_pow _ _ + obtain ⟨c, hc, hcount, _, hcmem, _⟩ := exec_countMasks L m x hx.fixed.levels hx.fixed.inner + hx.masks hx.fixed.one hx.fixed.two (hl.of_le (by + nlinarith only [Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)])) + have hcp : HeaderPost L m N c := headerPost_reframe hx (by + intro i hi + apply hc.regs_eq + have hp : ∀ i ∈ headerRegisters, countMasks.Preserves i := by + simp only [countMasks, countBody, populationCode, Code.Preserves, Op.dest, Op.bin] + decide + exact hp i hi) + obtain ⟨r, hr, hroot, hsquare, hrmem, _⟩ := exec_root (K L m) c hcount hcp.fixed.one + (hl.of_le (by + nlinarith only [hK, Nat.zero_le L, Nat.zero_le N, + Nat.zero_le (N * D m), Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)])) + have hrp : HeaderPost L m N r := headerPost_reframe hcp (by + intro i hi + apply hr.regs_eq + have hp : ∀ i ∈ headerRegisters, rootCode.Preserves i := by + simp only [rootCode, rootBody, Code.Preserves, Op.dest, Op.bin] + decide + exact hp i hi) + have hB : bandSize L m ≤ 7 ^ L := bandSize_le_seven_pow L m + have h7 : 7 ^ L ≤ 10 ^ L := Nat.pow_le_pow_left (by decide) _ + have hbfit : Fits w (bandSize L m) := hl.of_le (by + nlinarith only [hB, h7, Nat.zero_le L, Nat.zero_le N, + Nat.zero_le (N * D m), Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)]) + have hnfit : Fits w (N + bandSize L m) := hl.of_le (by + nlinarith only [hB, h7, Nat.zero_le L, Nat.zero_le N, + Nat.zero_le (N * D m), Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)]) + have hnsubfit : Fits w (N + bandSize L m - 1) := + hnfit.of_le (Nat.sub_le _ _) + have hdivfit : Fits w ((N + bandSize L m - 1) / bandSize L m) := + hnsubfit.of_le (Nat.div_le_self _ _) + let y := (((r.set temporary (bandSize L m)).set bands (N + bandSize L m)).set + bands (N + bandSize L m - 1)).set bands (numBands L m N) + have hroot0 : r.regs root = K0 L m := by simpa only [K0] using hroot + simp only [bandSize] at hbfit hnfit hnsubfit hdivfit + have hy : ExecWithin w bandCount r y 4 := by + ram_eval [bandCount, y, hroot0, hrp.outerSize, hrp.fixed.rows, hrp.fixed.one, + bandSize, numBands, hbfit, hnfit, hnsubfit, hdivfit, + temporary, root, outerSize, bands, rows, one, Fin.mk.injEq] + have he : ExecWithin w countCode x y ((6 * L + 16) * 2 ^ L + 14) := by + apply (hc.seq (hr.seq hy)).mono + have hsqrt : Nat.sqrt (K L m) ≤ 2 ^ L := (Nat.sqrt_le_self _).trans hK + nlinarith + have hyp : HeaderPost L m N y := headerPost_reframe hx (by + intro i hi + apply he.regs_eq + have hp : ∀ i ∈ headerRegisters, countCode.Preserves i := by + simp only [countCode, countMasks, countBody, populationCode, rootCode, rootBody, + bandCount, Code.Preserves, Op.dest, Op.bin] + decide + exact hp i hi) + refine ⟨y, he, ⟨hyp, ?_, ?_, ?_⟩, hrmem.trans hcmem⟩ + · simpa [y, temporary, bands, root] using hroot0 + · simpa [y, temporary, bands, square, K0] using hsquare + · simp [y] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPopulation.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPopulation.lean new file mode 100644 index 0000000000..7e6315663c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPopulation.lean @@ -0,0 +1,114 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandProgram +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +/-! +# Charged native binary-mask population scans + +## Main results + +* `BandPreparation.exec_population`: the actual remainder/division loop computes the + population in `6L+5` instructions, preserves memory and changes only its four local registers. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Cslib.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +/-- Popcount is a native loop, with a fit derived for each arithmetic operation from a +bound on its original mask and depth. No memory cell is touched. -/ +theorem exec_population {w : ℕ} (L a : ℕ) (x : Data 96) + (hl : x.regs levels = L) (ha : x.regs mask = a) + (ho : x.regs one = 1) (ht : x.regs two = 2) (hfit : Fits w (a + L + 2)) : + ∃ y, ExecWithin w populationCode x y (6 * L + 5) ∧ + y.regs population = MaskTable.population L a ∧ y.mem = x.mem ∧ + (∀ i, i ≠ residue → i ≠ population → i ≠ counter → i ≠ digit → + y.regs i = x.regs i) := by + have hzero : Fits w 0 := hfit.of_le (by omega) + let start := ((x.set residue a).set population 0).set counter 0 + let I (y : Data 96) : Prop := + y.mem = x.mem ∧ y.regs levels = L ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs counter ≤ L ∧ y.regs residue ≤ a ∧ + y.regs population + MaskTable.population (L - y.regs counter) (y.regs residue) = + MaskTable.population L a + have hi : I start := by + simp [I, start, hl, ho, ht, counter, residue, population, levels, one, two] + have hb (y : Data 96) (hy : I y) (hc : (Cond.lt counter levels).Holds y) : + ∃ z, ExecWithin w (ram[ + digit ← residue % two; population ← population + digit; + residue ← residue / two; counter ← counter + one ]) y z 4 ∧ + I z ∧ L - z.regs counter < L - y.regs counter := by + obtain ⟨hm, hL, h1, h2, hj, hr, hp⟩ := hy + have hjL : y.regs counter < L := by simpa only [Cond.holds_lt, hL] using hc + have hrec : MaskTable.population (L - y.regs counter) (y.regs residue) = + y.regs residue % 2 + + MaskTable.population (L - (y.regs counter + 1)) (y.regs residue / 2) := by + rw [show L - y.regs counter = (L - (y.regs counter + 1)) + 1 by omega, + population_succ] + have hpop := population_le L a + have hfpop : Fits w (y.regs population + y.regs residue % 2) := + hfit.of_le (by omega) + have hfmod : Fits w (y.regs residue % 2) := hfit.of_le (by omega) + have hfdiv : Fits w (y.regs residue / 2) := + hfit.of_le ((Nat.div_le_self _ _).trans (by omega)) + have hfnext : Fits w (y.regs counter + 1) := hfit.of_le (by omega) + let z := (((y.set digit (y.regs residue % 2)).set population + (y.regs population + y.regs residue % 2)).set residue (y.regs residue / 2)).set + counter (y.regs counter + 1) + have he : ExecWithin w (ram[ + digit ← residue % two; population ← population + digit; + residue ← residue / two; counter ← counter + one ]) y z 4 := by + ram_eval [z, hfpop, hfmod, hfdiv, hfnext, h1, h2, + digit, residue, population, counter, one, two, Fin.mk.injEq] + refine ⟨z, he, ?_, ?_⟩ + · have hsum : (y.regs population + y.regs residue % 2) + + MaskTable.population (L - (y.regs counter + 1)) (y.regs residue / 2) = + MaskTable.population L a := by omega + simpa +decide [I, z, levels, one, two, residue, population, counter, digit, + Function.update_apply] using + (show y.mem = x.mem ∧ y.regs levels = L ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ + y.regs counter + 1 ≤ L ∧ y.regs residue / 2 ≤ a ∧ + (y.regs population + y.regs residue % 2) + + MaskTable.population (L - (y.regs counter + 1)) (y.regs residue / 2) = + MaskTable.population L a from + ⟨hm, hL, h1, h2, by omega, (Nat.div_le_self _ _).trans hr, hsum⟩) + · simp only [z, Data.set_regs, Function.update_self] + omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => L - y.regs counter) hb hi + have hj : y.regs counter = L := by + simp only [Cond.holds_lt, hy.2.1] at hc + have := hy.2.2.2.2.1 + omega + have hwhole : ExecWithin w populationCode x y (6 * L + 5) := by + have hs : ExecWithin (B := .standard) w (.op (.copy residue mask)) + x (x.set residue a) 1 := by + simpa only [ha] using (ExecWithin.copy (B := .standard) (w := w) + (x := x) (d := residue) (s := mask)) + have hwhole := hs.seq ((ExecWithin.const (d := population) hzero).seq + ((ExecWithin.const (d := counter) hzero).seq he)) + apply hwhole.mono + simp only [start, Data.set_regs, Function.update_self, Nat.sub_zero] + omega + refine ⟨y, hwhole, ?_, hy.1, ?_⟩ + · have h := hy.2.2.2.2.2.2 + simpa only [hj, Nat.sub_self, population_zero, Nat.add_zero] using h + · intro i hr hp hc hd + apply hwhole.regs_eq + have hframe : ∀ i : Fin 96, i ≠ residue → i ≠ population → i ≠ counter → i ≠ digit → + populationCode.Preserves i := by decide + exact hframe i hr hp hc hd + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPrepare.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPrepare.lean new file mode 100644 index 0000000000..1364489d73 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandPrepare.lean @@ -0,0 +1,182 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandRead + +/-! +# Complete native padded-band preparation + +## Main results + +* `BandPreparation.exec_prepareBand`: initialize and fill every band-array cell by actual + native digit decoding, inverse-table lookup and guarded signed matrix loads. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +-- The static frame checks reduce finite programs over 96 registers and their parameter sets. +set_option maxRecDepth 4096 + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Every prepared-band cell is below the protected original input. -/ +theorem band_input_le_snapshot (L m N : ℕ) : 2 + 7 ^ L ≤ snapshotBase L m N := by + have h := workspace_le_snapshot L m N + unfold Encoding.scratchBase Encoding.outputBase at h + have := Nat.zero_le (10 ^ L) + omega + +private theorem split_preserves_parameters : + ∀ i ∈ parameterRegisters, splitCode.Preserves i := by + simp only [parameterRegisters, headerRegisters, splitCode, splitHead, splitBody, + Code.Preserves, Op.dest, Op.bin] + decide + +private theorem prepare_preserves_parameters (side : Bool) : + ∀ i ∈ parameterRegisters, (prepareBody side).Preserves i := by + cases side <;> simp +decide only [] + +/-- The entire input band is materialized with charged initialization, digit/index work, +padding and loop control. No cell outside the depth header and the band input is changed. -/ +theorem exec_prepareBand {w L m N : ℕ} (side : Bool) (β : ℕ) (x : Data 96) + (hx : BandParameters L m N x) (hβ : β < numBands L m N) (hb : x.regs band = β) + (htable : ∀ t < 2 ^ L, x.mem (inverseBase L m N + t) = MaskTable.lookup L m t) + (matrix : ℕ → ℕ → ℤ) + (hmat : ∀ r < N, ∀ k < D m, + x.mem (matrixAddress L m N side r k) = encode (matrix r k)) + (hfit : Fits w (wordBudget L m N)) : + ∃ y, ExecWithin w (prepareBand side) x y ((13 * L + 39) * 7 ^ L + 5) ∧ + BandParameters L m N y ∧ y.regs band = β ∧ y.mem 1 = L ∧ + (∀ a < 7 ^ L, y.mem (2 + a) = encode (bandValue L m N β side matrix a)) ∧ + (∀ b, ¬(1 ≤ b ∧ b < 2 + 7 ^ L) → y.mem b = x.mem b) := by + have hL : Fits w L := (local_fits hfit).of_le (by + nlinarith only [Nat.zero_le L, Nat.zero_le N, Nat.zero_le (N * D m), + Nat.zero_le (2 ^ L), Nat.zero_le (10 ^ L)]) + let start := ((x.set address 1).write 1 L).set index 0 + have hs : ExecWithin w prepareHead x start 3 := by + ram_eval [prepareHead, start, hx.fixed.levels, hL, + small_fits hfit (by decide : 1 ≤ 256), address, index, levels, Fin.mk.injEq] + have hsp : BandParameters L m N start := parameters_reframe hx (by + intro i hi + apply hs.regs_eq + have hp : ∀ i ∈ parameterRegisters, prepareHead.Preserves i := by + simp only [parameterRegisters, headerRegisters, prepareHead, Code.Preserves, Op.dest] + decide + exact hp i hi) + let I (y : Data 96) : Prop := BandParameters L m N y ∧ y.regs band = β ∧ + y.regs index ≤ 7 ^ L ∧ y.mem 1 = L ∧ + (∀ a < y.regs index, y.mem (2 + a) = encode (bandValue L m N β side matrix a)) ∧ + (∀ b, ¬(1 ≤ b ∧ b < 2 + 7 ^ L) → y.mem b = x.mem b) + have hstart : I start := by + refine ⟨hsp, ?_, ?_, ?_, ?_, ?_⟩ + · simpa [start, band, address, index] using hb + · simp [start] + · simp [start] + · simp [start] + · intro b hbb + have hne : b ≠ 1 := by + have := Nat.one_le_pow L 7 (by decide) + omega + simp [start, hne] + have hbody (y : Data 96) (hy : I y) (hc : (Cond.lt index cells).Holds y) : + ∃ z, ExecWithin w (prepareBody side) y z (13 * L + 37) ∧ I z ∧ + 7 ^ L - z.regs index < 7 ^ L - y.regs index := by + let i := y.regs index + have hi : i < 7 ^ L := by simpa only [Cond.holds_lt, hy.1.cells] using hc + have hsplit : SplitParameters L y := ⟨hy.1.fixed.levels, hy.1.fixed.one, + hy.1.fixed.two, hy.1.fixed.three, hy.1.fixed.four, hy.1.fixed.seven⟩ + obtain ⟨s, hes, _, hmask, houter, hinner, hsmem⟩ := + exec_split L i y hsplit rfl (split_fits hfit hi) + have hsp : BandParameters L m N s := parameters_reframe hy.1 + (fun j hj => hes.regs_eq (split_preserves_parameters j hj)) + have hsband : s.regs band = β := (hes.regs_eq (by + simp only [splitCode, splitHead, splitBody, Code.Preserves, Op.dest, Op.bin] + decide)).trans hy.2.1 + have hsindex : s.regs index = i := hes.regs_eq (by + simp only [splitCode, splitHead, splitBody, Code.Preserves, Op.dest, Op.bin] + decide) + have htable' (t : ℕ) (ht : t < 2 ^ L) : + s.mem (inverseBase L m N + t) = MaskTable.lookup L m t := by + rw [hsmem, hy.2.2.2.2.2 _ (by + have hlow := band_input_le_snapshot L m N + unfold inverseBase + omega)] + exact htable t ht + have hmat' (r : ℕ) (hr : r < N) (k : ℕ) (hk : k < D m) : + s.mem (matrixAddress L m N side r k) = encode (matrix r k) := by + rw [hsmem, hy.2.2.2.2.2 _ (by + have hlow := band_input_le_snapshot L m N + unfold matrixAddress + omega)] + exact hmat r hr k hk + obtain ⟨t, het, hv, htmem⟩ := exec_readCell side ⟨i, hi⟩ β s hsp hβ hsband + hmask houter hinner htable' matrix hmat' hfit + have htindex : t.regs index = i := (het.regs_eq (by + cases side <;> simp only [readCell, Code.Preserves, Op.dest, Op.bin] <;> decide)).trans + hsindex + have htone : t.regs one = 1 := (het.regs_eq (by + cases side <;> simp only [readCell, Code.Preserves, Op.dest, Op.bin] <;> decide)).trans + hsp.fixed.one + have httwo : t.regs two = 2 := (het.regs_eq (by + cases side <;> simp only [readCell, Code.Preserves, Op.dest, Op.bin] <;> decide)).trans + hsp.fixed.two + have hadd : Fits w (i + 2) := + ((memory_fits hfit).of_le (snapshot_le_memoryEnd L m N)).of_le (by + have hlow := band_input_le_snapshot L m N + omega) + have hinc : Fits w (i + 1) := hadd.of_le (by omega) + let z := ((t.set address (i + 2)).write (i + 2) + (encode (bandValue L m N β side matrix i))).set index (i + 1) + have hez : ExecWithin w (ram[ + address ← index + two; *address ← value; index ← index + one ]) t z 3 := by + ram_eval [z, htindex, htone, httwo, hv, hadd, hinc, + address, index, two, one, value, Fin.mk.injEq] + have he : ExecWithin w (prepareBody side) y z (13 * L + 37) := by + simpa only [prepareBody, Nat.add_assoc] using hes.seq (het.seq hez) + have hzindex : z.regs index = i + 1 := by simp [z] + have hzmem : z.mem = Function.update y.mem (i + 2) + (encode (bandValue L m N β side matrix i)) := by + simp only [z, Data.set_mem, Data.write_mem, htmem, hsmem] + refine ⟨z, he, ?_, ?_⟩ + · refine ⟨parameters_reframe hy.1 (fun j hj => + he.regs_eq (prepare_preserves_parameters side j hj)), ?_, ?_, ?_, ?_, ?_⟩ + · exact (he.regs_eq (by + cases side <;> + simp +decide only [])).trans hy.2.1 + · rw [hzindex]; omega + · rw [hzmem, Function.update_of_ne (by omega : 1 ≠ i + 2)] + exact hy.2.2.2.1 + · intro a ha + rw [hzindex] at ha + rw [hzmem] + by_cases hai : a = i + · subst a; rw [Nat.add_comm 2 i, Function.update_self] + · rw [Function.update_of_ne (by omega : 2 + a ≠ i + 2)] + exact hy.2.2.2.2.1 a (by omega) + · intro b hbb + rw [hzmem, Function.update_of_ne (by omega : b ≠ i + 2)] + exact hy.2.2.2.2.2 b hbb + · rw [hzindex]; change 7 ^ L - (i + 1) < 7 ^ L - i; omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => 7 ^ L - y.regs index) hbody hstart + have hindex : y.regs index = 7 ^ L := by + simp only [Cond.holds_lt, hy.1.cells] at hc + have := hy.2.2.1 + omega + refine ⟨y, ?_, hy.1, hy.2.1, hy.2.2.2.1, ?_, hy.2.2.2.2.2⟩ + · apply (hs.seq he).mono + have hzero : start.regs index = 0 := by simp [start] + rw [hzero, Nat.sub_zero] + nlinarith only [Nat.zero_le (7 ^ L)] + · simpa only [hindex] using hy.2.2.2.2.1 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandProgram.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandProgram.lean new file mode 100644 index 0000000000..127663f3c0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandProgram.lean @@ -0,0 +1,322 @@ +/- +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.Flat +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.ThinProduct +public import Complexity.Computability.WordRAM.Power + +/-! +# Native preparation and encoding of shared matrix bands + +The input is `L :: m :: N :: X ++ Y`, with both matrices in row-major order and their +signed entries encoded by `Signed.encode`. The native length header occupies address zero. +The program snapshots the complete initialized input before using the encoder's low workspace, +computes its subset tables, prepares and encodes each band once, and restores the input. + +The dense inverse table uses zero for an unselected mask and rank plus one for a selected mask. +The forward table lists the first `K0²` masks of cardinality `m` in increasing numeric order. +The shared output catalogue consists of all left bands followed by all right bands, with +`10^L` consecutive cells per band. A tile refers to its two band entries in this catalogue. + +`LeftVar.equiv` and `RightVar.equiv` identify the mathematical alphabets with +`Fin 3 ⊕ (Fin 2 × Fin 2)`, and `Term.equiv` identifies terms with `Option (Fin 3 × Fin 3)`. +The existing native `Encoding.leftDigits`, `Encoding.rightDigits` and `Encoding.termDigits` +also fix the executable flat `Fin 7`/`Fin 10` order: the nine product terms are row-major, +with digit `3*i+j`, and `P0` has digit 9. This preparation retains that numbering unchanged. + +## Main definitions + +* `BandPreparation.program`: fixed native code, including the actual shared encoders. +* `BandPreparation.input`: flat header and matrix input for `State.init`. +* `BandPreparation.catalogueAddress`: the location of a band's encoding leaf. + +## References + +Selective adaptation of the band preparation in [AlmanWilliams2026, Section 2.4.1] and +Anthropic's formal-math revision `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +[band-array preparation](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/BandArray.lean) +and [all-band encoding](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Encode/AllBands.lean). +The mask scans, dense inverse table, native control and memory layout here are new; +the source's Light machine and execution semantics are not used. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Original initialized input prefix, including the native length header and three parameters. -/ +def inputEnd (m N : ℕ) : ℕ := 4 + 2 * (N * D m) + +/-- The encoder's low workspace and the original input are both below the snapshot. -/ +def snapshotBase (L m N : ℕ) : ℕ := inputEnd m N + Encoding.scratchBase L + 10 ^ L + +/-- Dense mask-to-rank inverse table. -/ +def inverseBase (L m N : ℕ) : ℕ := snapshotBase L m N + inputEnd m N + +/-- Forward selected-mask table; `2^L` cells are reserved. -/ +def tableBase (L m N : ℕ) : ℕ := inverseBase L m N + 2 ^ L + +/-- Shared encoding catalogue, after the snapshot and both tables. -/ +def catalogueBase (L m N : ℕ) : ℕ := tableBase L m N + 2 ^ L + +/-- Every left band, then every right band, occupies one block of `10^L` words. -/ +def catalogueAddress (L m N : ℕ) (rightSide : Bool) (β leaf : ℕ) : ℕ := + catalogueBase L m N + ((if rightSide then numBands L m N else 0) + β) * 10 ^ L + leaf + +/-- One past all allocated memory. -/ +def memoryEnd (L m N : ℕ) : ℕ := catalogueBase L m N + 2 * numBands L m N * 10 ^ L + +/-- Explicit input-parameter bound used to derive every unsigned arithmetic/address fit. +The signed magnitude condition is separate. This is an ordinary numerical inequality, +without quantification over executions or intermediate states. -/ +def wordBudget (L m N : ℕ) : ℕ := + 256 * (L + N + N * D m + 2 ^ L + 10 ^ L + 2 * numBands L m N * 10 ^ L + 1) + +/-- Row-major signed matrix input; no table, power or encoded band is supplied by the caller. -/ +def input (L m N : ℕ) (X : Matrix (Fin N) (Fin (D m)) ℤ) + (Y : Matrix (Fin (D m)) (Fin N) ℤ) : List ℕ := + [L, m, N] ++ + (List.finRange N).flatMap (fun i => List.ofFn fun k => Signed.encode (X i k)) ++ + (List.finRange (D m)).flatMap (fun k => List.ofFn fun j => Signed.encode (Y k j)) + +/-- Low registers are reserved for the existing encoder and copy routines. Persistent +parameters and preparation counters occupy registers 32 through 84 of a fixed bank of 96. -/ +abbrev levels : Fin 96 := 32 +@[inherit_doc levels] abbrev copySrc : Fin 96 := 0 +@[inherit_doc levels] abbrev copyDst : Fin 96 := 1 +@[inherit_doc levels] abbrev copyLen : Fin 96 := 2 +@[inherit_doc levels] abbrev inner : Fin 96 := 33 +@[inherit_doc levels] abbrev rows : Fin 96 := 34 +@[inherit_doc levels] abbrev columns : Fin 96 := 35 +@[inherit_doc levels] abbrev outerSize : Fin 96 := 36 +@[inherit_doc levels] abbrev masks : Fin 96 := 37 +@[inherit_doc levels] abbrev cells : Fin 96 := 38 +@[inherit_doc levels] abbrev leaves : Fin 96 := 39 +@[inherit_doc levels] abbrev prefixSize : Fin 96 := 40 +@[inherit_doc levels] abbrev snapshot : Fin 96 := 41 +@[inherit_doc levels] abbrev inverse : Fin 96 := 42 +@[inherit_doc levels] abbrev table : Fin 96 := 43 +@[inherit_doc levels] abbrev catalogue : Fin 96 := 44 +@[inherit_doc levels] abbrev bands : Fin 96 := 45 +@[inherit_doc levels] abbrev count : Fin 96 := 46 +@[inherit_doc levels] abbrev root : Fin 96 := 47 +@[inherit_doc levels] abbrev square : Fin 96 := 48 +@[inherit_doc levels] abbrev temporary : Fin 96 := 49 +@[inherit_doc levels] abbrev one : Fin 96 := 50 +@[inherit_doc levels] abbrev two : Fin 96 := 51 +@[inherit_doc levels] abbrev three : Fin 96 := 52 +@[inherit_doc levels] abbrev four : Fin 96 := 53 +@[inherit_doc levels] abbrev seven : Fin 96 := 54 +@[inherit_doc levels] abbrev ten : Fin 96 := 55 +@[inherit_doc levels] abbrev counter : Fin 96 := 56 +@[inherit_doc levels] abbrev mask : Fin 96 := 57 +@[inherit_doc levels] abbrev population : Fin 96 := 58 +@[inherit_doc levels] abbrev residue : Fin 96 := 59 +@[inherit_doc levels] abbrev digit : Fin 96 := 60 +@[inherit_doc levels] abbrev limit : Fin 96 := 61 +@[inherit_doc levels] abbrev rank : Fin 96 := 62 +@[inherit_doc levels] abbrev address : Fin 96 := 63 +@[inherit_doc levels] abbrev band : Fin 96 := 64 +@[inherit_doc levels] abbrev index : Fin 96 := 66 +@[inherit_doc levels] abbrev maskAcc : Fin 96 := 67 +@[inherit_doc levels] abbrev outerAcc : Fin 96 := 68 +@[inherit_doc levels] abbrev innerAcc : Fin 96 := 69 +@[inherit_doc levels] abbrev maskPower : Fin 96 := 70 +@[inherit_doc levels] abbrev outerPower : Fin 96 := 71 +@[inherit_doc levels] abbrev innerPower : Fin 96 := 72 +@[inherit_doc levels] abbrev level : Fin 96 := 73 +@[inherit_doc levels] abbrev block : Fin 96 := 75 +@[inherit_doc levels] abbrev row : Fin 96 := 77 +@[inherit_doc levels] abbrev source : Fin 96 := 79 +@[inherit_doc levels] abbrev value : Fin 96 := 80 +@[inherit_doc levels] abbrev zero : Fin 96 := 84 + +/-- Register embedding for the unchanged shared encoders. -/ +def encoderRegisters (i : Fin 32) : Fin 96 := ⟨i.val, by omega⟩ + +/-- Register embedding for the unchanged seven-register copy program. -/ +def copyRegisters (i : Fin 7) : Fin 96 := ⟨i.val, by omega⟩ + +/-- Embed the five-register native power routine in the caller's chosen scalar registers. -/ +def powerRegisters (v b n : Fin 96) : Fin 5 → Fin 96 := ![v, counter, b, n, one] + +/-- The actual native power routine, with register renaming only. -/ +def powerCode (v b n : Fin 96) : Code .standard 96 := + Power.program.rename (powerRegisters v b n) + +/-- Popcount is computed by an explicit fixed-depth loop of divisions and remainders. -/ +def populationCode : Code .standard 96 := ram[ + residue ← mask; population ← #0; counter ← #0; + while counter < levels do [ + digit ← residue % two; population ← population + digit; + residue ← residue / two; counter ← counter + one ] ] + +/-- Load the three actual header parameters and the small constants. -/ +def headerLoads : Code .standard 96 := ram[ + address ← #1; levels ← *address; address ← #2; inner ← *address; + address ← #3; rows ← *address; + zero ← #0; one ← #1; two ← #2; three ← #3; four ← #4; seven ← #7; ten ← #10 ] + +/-- Compute all five powers with actual renamed native routines. -/ +def headerPowers : Code .standard 96 := + .seq (powerCode columns four inner) (.seq (ram[ temporary ← levels - inner ]) + (.seq (powerCode outerSize three temporary) (.seq (powerCode masks two levels) + (.seq (powerCode cells seven levels) (powerCode leaves ten levels))))) + +/-- Compute concrete snapshot, table and catalogue addresses. -/ +def headerLayout : Code .standard 96 := ram[ + prefixSize ← rows * columns; prefixSize ← prefixSize * two; + prefixSize ← prefixSize + four; + snapshot ← leaves * two; snapshot ← snapshot + cells; snapshot ← snapshot + two; + snapshot ← snapshot + prefixSize; + inverse ← snapshot + prefixSize; table ← inverse + masks; catalogue ← table + masks ] + +/-- Compute the three header parameters, powers and addresses with charged native code. -/ +def header : Code .standard 96 := .seq headerLoads (.seq headerPowers headerLayout) + +/-- Save every original input word, including the actual length header. -/ +def saveInput : Code .standard 96 := ram[ + copySrc ← #0; copyDst ← snapshot; copyLen ← prefixSize; + ⟪ArrayCopy.program.rename copyRegisters⟫ ] + +/-- Visit one mask and update the number of accepted masks. -/ +def countBody : Code .standard 96 := + .seq populationCode (ram[ + if population = inner then [ count ← count + one ] else [ count ← count ]; + mask ← mask + one ]) + +/-- Count every size-`m` mask. -/ +def countMasks : Code .standard 96 := + .seq (ram[ mask ← #0; count ← #0 ]) (.loop (.lt mask masks) countBody) + +/-- One explicitly squared trial in the integer square-root search. -/ +def rootBody : Code .standard 96 := ram[ + root ← root + one; temporary ← root + one; square ← temporary * temporary ] + +/-- Compute the integer square root by squared comparisons. No square-root instruction +is added to the native basis. -/ +def rootCode : Code .standard 96 := + .seq (ram[ + root ← #0; square ← #1; limit ← count + one + ]) (.seq (.loop (.lt square limit) rootBody) (ram[ square ← root * root ])) + +/-- Compute the ceiling quotient giving the number of bands. -/ +def bandCount : Code .standard 96 := ram[ + temporary ← root * outerSize; + bands ← rows + temporary; bands ← bands - one; bands ← bands / temporary ] + +/-- Complete scalar band parameters from native mask counting and root computation. -/ +def countCode : Code .standard 96 := .seq countMasks (.seq rootCode bandCount) + +/-- Store the two table entries belonging to the current mask and advance both counters. -/ +def tableWrite : Code .standard 96 := ram[ + address ← inverse + mask; *address ← zero; + if population = inner then [ + if rank < square then [ + temporary ← rank + one; *address ← temporary; + address ← table + rank; *address ← mask ] else [ rank ← rank ]; + rank ← rank + one ] else [ rank ← rank ]; + mask ← mask + one ] + +/-- One complete table iteration, including the actual population scan. -/ +def tableBody : Code .standard 96 := .seq populationCode tableWrite + +/-- Materialize both tables, explicitly initializing every inverse-table cell. -/ +def tablesCode : Code .standard 96 := + .seq (ram[ mask ← #0; rank ← #0 ]) (.loop (.lt mask masks) tableBody) + +/-- Initialize the three digit accumulators and their positional weights. -/ +def splitHead : Code .standard 96 := ram[ + residue ← index; maskAcc ← #0; outerAcc ← #0; innerAcc ← #0; + maskPower ← #1; outerPower ← #1; innerPower ← #1; level ← #0 ] + +/-- One base-seven digit, with only ordinary native arithmetic and control. -/ +def splitBody : Code .standard 96 := ram[ + digit ← residue % seven; residue ← residue / seven; + if digit < three then [ + temporary ← digit * outerPower; outerAcc ← outerAcc + temporary; + outerPower ← outerPower * three ] else [ + digit ← digit - three; temporary ← digit * innerPower; + innerAcc ← innerAcc + temporary; innerPower ← innerPower * four; + maskAcc ← maskAcc + maskPower ]; + maskPower ← maskPower * two; level ← level + one ] + +/-- Decode a base-seven string into its inner mask and compressed base-three/base-four +offsets, in increasing level order. Every division, remainder, product and increment is native. -/ +def splitCode : Code .standard 96 := .seq splitHead (.loop (.lt level levels) splitBody) + +/-- Read a real matrix cell only after checking the padded row/column against `N`. +In particular, the potentially oversized row-major product is inside the guard. -/ +def readCell (rightSide : Bool) : Code .standard 96 := + .seq (ram[ + address ← inverse + maskAcc; rank ← *address; value ← #0 ]) + (.branch (.lt zero rank) + (.seq (ram[ rank ← rank - one ]) + (.seq (if rightSide then ram[ block ← rank % root ] else ram[ block ← rank / root ]) + (ram[ + temporary ← band * root; block ← temporary + block; + row ← block * outerSize; row ← row + outerAcc; + if row < rows then [ + ⟪if rightSide then ram[ + address ← innerAcc * rows; address ← address + row; + temporary ← rows * columns; address ← address + temporary ] + else ram[ address ← row * columns; address ← address + innerAcc ]⟫; + address ← address + four; address ← snapshot + address; value ← *address + ] else [ value ← value ] ]))) + (ram[ value ← value ])) + +/-- Actual header and counter initialization for each band. -/ +def prepareHead : Code .standard 96 := ram[ + address ← #1; *address ← levels; index ← #0 ] + +/-- Decode, guard, load and store one band-array entry. -/ +def prepareBody (rightSide : Bool) : Code .standard 96 := ram[ + ⟪splitCode⟫; ⟪readCell rightSide⟫; + address ← index + two; *address ← value; index ← index + one ] + +/-- Prepare every entry of one low-workspace band array, including all zero padding. -/ +def prepareBand (rightSide : Bool) : Code .standard 96 := + .seq prepareHead (.loop (.lt index cells) (prepareBody rightSide)) + +/-- Set up the actual encoder-output copy, including its catalogue address arithmetic. -/ +def collectHead (rightSide : Bool) : Code .standard 96 := ram[ + copySrc ← cells + two; + ⟪if rightSide then ram[ temporary ← bands + band ] else ram[ temporary ← band ]⟫; + temporary ← temporary * leaves; temporary ← catalogue + temporary; + copyDst ← temporary; copyLen ← leaves ] + +/-- Copy the actual encoder output to this band's shared catalogue entry. -/ +def collectBand (rightSide : Bool) : Code .standard 96 := + .seq (collectHead rightSide) (ArrayCopy.program.rename copyRegisters) + +/-- Prepare, encode and collect one shared band, then advance its counter. -/ +def bandBody (rightSide : Bool) : Code .standard 96 := + .seq (.seq (prepareBand rightSide) + (.seq ((Encoding.program rightSide).rename encoderRegisters) (collectBand rightSide))) + (ram[ band ← band + one ]) + +/-- Each row/column band calls the actual unchanged finite native encoder exactly once. -/ +def encodeBands (rightSide : Bool) : Code .standard 96 := + .seq (ram[ band ← #0 ]) (.loop (.lt band bands) (bandBody rightSide)) + +/-- Restore the complete original initialized input after all low-workspace calls. -/ +def restoreInput : Code .standard 96 := ram[ + copySrc ← snapshot; copyDst ← #0; copyLen ← prefixSize; + ⟪ArrayCopy.program.rename copyRegisters⟫ ] + +/-- One fixed finite native program for the entire band-preparation component. Halt returns +`catalogue`, the base address of the shared encodings; it is appended by `Code.program`. -/ +def program : Code .standard 96 := + .seq header (.seq saveInput (.seq countCode (.seq tablesCode + (.seq (encodeBands false) (.seq (encodeBands true) restoreInput))))) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRead.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRead.lean new file mode 100644 index 0000000000..cc88b44cc8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRead.lean @@ -0,0 +1,196 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandParameters +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandSplit + +/-! +# Guarded native matrix reads for band arrays + +## Main definitions + +* `BandPreparation.matrixAddress`: real row-major matrix addresses in the protected input. + +## Main results + +* `BandPreparation.exec_readCell`: the actual guarded read returns exactly one encoded + band value; padded rows are rejected before row-major multiplication. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open BandDigits + +/-- Offset in the concatenated original matrix inputs. The right matrix has `D` rows and +`N` columns, while its band specification labels a column by `r`. -/ +def matrixOffset (m N : ℕ) (rightSide : Bool) (r k : ℕ) : ℕ := + if rightSide then k * N + r + N * D m else r * D m + k + +/-- Address of an original signed input word in its protected snapshot. -/ +def matrixAddress (L m N : ℕ) (rightSide : Bool) (r k : ℕ) : ℕ := + snapshotBase L m N + (matrixOffset m N rightSide r k + 4) + +private theorem matrix_indices_lt {m N r k : ℕ} (hr : r < N) (hk : k < D m) : + r * D m + k < N * D m ∧ k * N + r < N * D m := by + have hleft := Nat.mul_le_mul_right (D m) (Nat.succ_le_of_lt hr) + have hright := Nat.mul_le_mul_right N (Nat.succ_le_of_lt hk) + rw [Nat.succ_mul] at hleft hright + rw [Nat.mul_comm (D m) N] at hright + constructor <;> omega + +/-- A real matrix cell lies strictly inside its protected snapshot. This uses the +real row/column guard, before constructing its flat address. -/ +theorem matrixAddress_lt_inverse {L m N r k : ℕ} (side : Bool) + (hr : r < N) (hk : k < D m) : + matrixAddress L m N side r k < inverseBase L m N := by + obtain ⟨hl, hr'⟩ := matrix_indices_lt hr hk + unfold matrixAddress matrixOffset inverseBase inputEnd + cases side <;> simp only [Bool.false_eq_true, ↓reduceIte] <;> omega + +/-- The complete guarded load uses at most twenty-four actual target instructions. +All intermediate products are bounded using the original parameters and the real `r + simp only [Bool.false_eq_true, ↓reduceIte] at hbfit hblockfit hrowfit hrfit hr <;> + simp only [Bool.false_eq_true, ↓reduceIte] at hleftMul hleftAdd hrightAdd + <;> simp only [Bool.false_eq_true, ↓reduceIte] at hrightOff hleftFour hrightFour + <;> simp only [Bool.false_eq_true, ↓reduceIte] at hleftAddress hrightAddress hcell <;> + ram_eval [readCell, y, v, u, b, r, hx.inverse, hx.snapshot, hx.root, hx.outerSize, + hx.columns, hx.fixed.zero, hx.fixed.one, hx.fixed.four, hx.fixed.rows, + hm, ho, hi, hb, hlookup, haddr, hqpos, hr, + small_fits hfit (by decide : 0 ≤ 256), hqfit, hbfit, hβfit, hblockfit, + hrowfit, hrfit, hnd, hleftMul, hleftAdd, hrightMul, hrightAdd, + hrightOff, hleftFour, hrightFour, hleftAddress, hrightAddress, + hcell, matrixAddress, matrixOffset, + inverse, snapshot, root, outerSize, columns, zero, one, four, rows, + maskAcc, outerAcc, innerAcc, band, address, rank, value, block, temporary, + row, Fin.mk.injEq] + · change y.regs value = encode (if q = 0 then 0 else + if r < N then matrix r p.inner else 0) + rw [ite_eq_right hq, ite_eq_left hr] + cases side <;> simp [y] + · cases side <;> rfl + · let y := v.set value 0 + dsimp only [b, r] at hbfit hblockfit hrowfit hrfit hr + refine ⟨y, ?_, ?_, rfl⟩ + · cases side <;> + simp only [Bool.false_eq_true, ↓reduceIte] at hbfit hblockfit hrowfit hrfit hr <;> + ram_eval [readCell, y, v, u, b, r, hx.inverse, hx.root, hx.outerSize, + hx.fixed.zero, hx.fixed.one, hx.fixed.rows, hm, ho, hi, hb, + hlookup, haddr, hqpos, hr, small_fits hfit (by decide : 0 ≤ 256), + hqfit, hbfit, hβfit, hblockfit, hrowfit, hrfit, + inverse, root, outerSize, zero, one, rows, maskAcc, outerAcc, band, + address, rank, value, block, temporary, row, Fin.mk.injEq] + · change y.regs value = encode (if q = 0 then 0 else + if r < N then matrix r p.inner else 0) + rw [ite_eq_right hq, ite_eq_right hr] + simp only [y, Data.set_regs, Function.update_self] + rfl + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRoot.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRoot.lean new file mode 100644 index 0000000000..48ca528194 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandRoot.lean @@ -0,0 +1,101 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandProgram + +/-! +# Native integer square root for band parameters + +## Main results + +* `BandPreparation.exec_root`: successive squared comparisons compute the ordinary natural + square root within `5 * sqrt k + 6` instructions, with every trial square proved to fit. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM + +/-- The root loop uses only increment, multiplication and unsigned comparison. The bound on +trial squares is derived from the original count, including the first failing trial. -/ +theorem exec_root {w : ℕ} (k : ℕ) (x : Data 96) + (hk : x.regs count = k) (ho : x.regs one = 1) (hfit : Fits w (4 * k + 4)) : + ∃ y, ExecWithin w rootCode x y (5 * Nat.sqrt k + 6) ∧ + y.regs root = Nat.sqrt k ∧ y.regs square = Nat.sqrt k * Nat.sqrt k ∧ + y.mem = x.mem ∧ + (∀ i, i ≠ root → i ≠ square → i ≠ temporary → i ≠ limit → y.regs i = x.regs i) := by + have h0 : Fits w 0 := hfit.of_le (by omega) + have h1 : Fits w 1 := hfit.of_le (by omega) + have hnext : Fits w (k + 1) := hfit.of_le (by omega) + let start := ((x.set root 0).set square 1).set limit (k + 1) + have hs : ExecWithin w (ram[ root ← #0; square ← #1; limit ← count + one ]) + x start 3 := by + ram_eval [start, hk, ho, h0, h1, hnext, root, square, limit, count, one, Fin.mk.injEq] + let I (y : Data 96) : Prop := + y.mem = x.mem ∧ y.regs one = 1 ∧ y.regs limit = k + 1 ∧ + y.regs root ≤ Nat.sqrt k ∧ + y.regs square = (y.regs root + 1) * (y.regs root + 1) + have hi : I start := by simp [I, start, ho, root, square, limit, one] + have hb (y : Data 96) (hy : I y) (hc : (Cond.lt square limit).Holds y) : + ∃ z, ExecWithin w rootBody y z 3 ∧ I z ∧ + Nat.sqrt k - z.regs root < Nat.sqrt k - y.regs root := by + obtain ⟨hm, h1, hlimit, hr, hsq⟩ := hy + have htrial : (y.regs root + 1) * (y.regs root + 1) ≤ k := by + simpa only [Cond.holds_lt, hlimit, hsq, Nat.lt_add_one_iff] using hc + have hrnext : y.regs root + 1 ≤ Nat.sqrt k := Nat.le_sqrt.mpr htrial + have hlarge : (y.regs root + 2) * (y.regs root + 2) ≤ 4 * k + 4 := by + nlinarith only [htrial] + have hrfit : Fits w (y.regs root + 1) := + hfit.of_le (by have := Nat.sqrt_le_self k; omega) + have htfit : Fits w (y.regs root + 2) := + hfit.of_le (by have := Nat.sqrt_le_self k; omega) + have hsfit : Fits w ((y.regs root + 2) * (y.regs root + 2)) := hfit.of_le hlarge + let z := ((y.set root (y.regs root + 1)).set temporary (y.regs root + 2)).set + square ((y.regs root + 2) * (y.regs root + 2)) + have he : ExecWithin w rootBody y z 3 := by + ram_eval [rootBody, z, h1, hrfit, htfit, hsfit, + root, temporary, square, one, Fin.mk.injEq, Nat.add_assoc] + refine ⟨z, he, ?_, ?_⟩ + · simpa +decide [I, z, root, temporary, square, one, limit, Function.update_apply, + Nat.add_assoc] using + (show y.mem = x.mem ∧ y.regs one = 1 ∧ y.regs limit = k + 1 ∧ + y.regs root + 1 ≤ Nat.sqrt k ∧ + (y.regs root + 2) * (y.regs root + 2) = + (y.regs root + 1 + 1) * (y.regs root + 1 + 1) from + ⟨hm, h1, hlimit, hrnext, by ring⟩) + · have hz : z.regs root = y.regs root + 1 := by simp [z, root, square, temporary] + rw [hz] + omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => Nat.sqrt k - y.regs root) hb hi + have hr : y.regs root = Nat.sqrt k := by + have hstop : k < (y.regs root + 1) * (y.regs root + 1) := by + simp only [Cond.holds_lt, hy.2.2.1, hy.2.2.2.2] at hc + omega + exact Nat.eq_sqrt.mpr ⟨(Nat.le_sqrt.mp hy.2.2.2.1), hstop⟩ + let z := y.set square (Nat.sqrt k * Nat.sqrt k) + have ht : ExecWithin w (ram[ square ← root * root ]) y z 1 := by + ram_eval [z, hr, hfit.of_le (Nat.sqrt_le k |>.trans (by omega)), square, root] + have hwhole : ExecWithin w rootCode x z (5 * Nat.sqrt k + 6) := by + apply (hs.seq (he.seq ht)).mono + have hsroot : start.regs root = 0 := by simp [start, root, square, limit] + rw [hsroot, Nat.sub_zero] + omega + refine ⟨z, hwhole, ?_, ?_, hy.1, ?_⟩ + · simpa [z, square, root] using hr + · simp [z] + · intro i hr hs ht hl + apply hwhole.regs_eq + have hf : ∀ i : Fin 96, i ≠ root → i ≠ square → i ≠ temporary → i ≠ limit → + rootCode.Preserves i := by decide + exact hf i hr hs ht hl + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandSplit.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandSplit.lean new file mode 100644 index 0000000000..30463b6899 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandSplit.lean @@ -0,0 +1,247 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandMeaning + +/-! +# Charged native splitting of tensor-string digits + +## Main results + +* `BandPreparation.exec_split`: the actual base-seven digit loop computes the binary mask + and both compressed offsets with every native arithmetic operation charged. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open BandDigits + +/-- Fixed scalar inputs to a digit loop, without execution or fit assumptions. -/ +structure SplitParameters (L : ℕ) (x : Data 96) : Prop where + /-- Original depth. -/ + levels : x.regs levels = L + /-- Constant one. -/ + one : x.regs one = 1 + /-- Constant two. -/ + two : x.regs two = 2 + /-- Constant three. -/ + three : x.regs three = 3 + /-- Constant four. -/ + four : x.regs four = 4 + /-- Constant seven. -/ + seven : x.regs seven = 7 + +private def splitParameterRegisters : Finset (Fin 96) := {levels, one, two, three, four, seven} + +private theorem SplitParameters.of_eq {L : ℕ} {x y : Data 96} (hx : SplitParameters L x) + (h : ∀ i ∈ splitParameterRegisters, y.regs i = x.regs i) : SplitParameters L y := + ⟨(h _ (by decide)).trans hx.levels, (h _ (by decide)).trans hx.one, + (h _ (by decide)).trans hx.two, (h _ (by decide)).trans hx.three, + (h _ (by decide)).trans hx.four, (h _ (by decide)).trans hx.seven⟩ + +/-- Remaining-digit equations and elementary counter bounds during the native scan. -/ +structure SplitProgress (L a : ℕ) (x : Data 96) : Prop where + /-- Parameters are retained. -/ + fixed : SplitParameters L x + /-- Number of consumed levels. -/ + level_le : x.regs level ≤ L + /-- Division only decreases the residual index. -/ + residue_le : x.regs residue ≤ a + /-- Binary place weight. -/ + mask_power : x.regs maskPower = 2 ^ x.regs level + /-- At most one outer place is consumed per level. -/ + outer_power : x.regs outerPower ≤ 3 ^ x.regs level + /-- At most one inner place is consumed per level. -/ + inner_power : x.regs innerPower ≤ 4 ^ x.regs level + /-- Partial binary value is below its next place. -/ + mask_acc : x.regs maskAcc < x.regs maskPower + /-- Partial base-three value is below its next place. -/ + outer_acc : x.regs outerAcc < x.regs outerPower + /-- Partial base-four value is below its next place. -/ + inner_acc : x.regs innerAcc < x.regs innerPower + /-- The binary accumulator and remaining digits give the complete mask. -/ + mask_value : x.regs maskAcc + x.regs maskPower * + (parts (L - x.regs level) (x.regs residue)).mask = (parts L a).mask + /-- The outer accumulator and remaining digits give the complete outer offset. -/ + outer_value : x.regs outerAcc + x.regs outerPower * + (parts (L - x.regs level) (x.regs residue)).outer = (parts L a).outer + /-- The inner accumulator and remaining digits give the complete inner offset. -/ + inner_value : x.regs innerAcc + x.regs innerPower * + (parts (L - x.regs level) (x.regs residue)).inner = (parts L a).inner + +/-- One native digit iteration, using only bounds derived from the original index/depth. -/ +theorem exec_splitBody {w : ℕ} (L a : ℕ) (x : Data 96) (hx : SplitProgress L a x) + (hc : (Cond.lt level levels).Holds x) (hfit : Fits w (a + L + 16 * 10 ^ L + 16)) : + ∃ y, ExecWithin w splitBody x y 11 ∧ SplitProgress L a y ∧ y.mem = x.mem ∧ + L - y.regs level < L - x.regs level := by + have hj : x.regs level < L := by simpa only [Cond.holds_lt, hx.fixed.levels] using hc + have hd : x.regs residue % 7 < 7 := Nat.mod_lt _ (by decide) + have hpow (b : ℕ) (hb : b ≤ 10) : b ^ x.regs level ≤ 10 ^ L := + (Nat.pow_le_pow_left hb _).trans (Nat.pow_le_pow_right (by decide) hx.level_le) + have hmp : x.regs maskPower ≤ 10 ^ L := by rw [hx.mask_power]; exact hpow 2 (by decide) + have hop : x.regs outerPower ≤ 10 ^ L := hx.outer_power.trans (hpow 3 (by decide)) + have hip : x.regs innerPower ≤ 10 ^ L := hx.inner_power.trans (hpow 4 (by decide)) + have hdFit : Fits w (x.regs residue % 7) := hfit.of_le (by omega) + have hrFit : Fits w (x.regs residue / 7) := + hfit.of_le ((Nat.div_le_self _ _).trans (by have := hx.residue_le; omega)) + have hmFit : Fits w (x.regs maskPower * 2) := hfit.of_le (by omega) + have hjFit : Fits w (x.regs level + 1) := hfit.of_le (by omega) + have hrest : L - x.regs level = L - (x.regs level + 1) + 1 := by omega + by_cases houter : x.regs residue % 7 < 3 + · have hmul : Fits w (x.regs residue % 7 * x.regs outerPower) := + hfit.of_le (by nlinarith) + have hadd : Fits w (x.regs outerAcc + x.regs residue % 7 * x.regs outerPower) := + hfit.of_le (by have := hx.outer_acc; nlinarith) + have hpower : Fits w (x.regs outerPower * 3) := hfit.of_le (by omega) + let y := (((((((x.set digit (x.regs residue % 7)).set residue (x.regs residue / 7)).set + temporary (x.regs residue % 7 * x.regs outerPower)).set outerAcc + (x.regs outerAcc + x.regs residue % 7 * x.regs outerPower)).set outerPower + (x.regs outerPower * 3)).set maskPower (x.regs maskPower * 2)).set + level (x.regs level + 1)) + have he : ExecWithin w splitBody x y 11 := by + ram_eval [splitBody, y, hx.fixed.one, hx.fixed.two, hx.fixed.three, hx.fixed.seven, + hdFit, hrFit, hmFit, hjFit, hmul, hadd, hpower, houter, + digit, residue, temporary, outerAcc, outerPower, maskPower, level, one, two, + three, seven, Fin.mk.injEq] + have hfixed : SplitParameters L y := hx.fixed.of_eq (by + intro i hi + apply he.regs_eq + have hf : ∀ i ∈ splitParameterRegisters, splitBody.Preserves i := by + simp only [splitBody, Code.Preserves, Op.dest, Op.bin] + decide + exact hf i hi) + have hmval := hx.mask_value + have hoval := hx.outer_value + have hival := hx.inner_value + rw [hrest, parts, ite_eq_left houter] at hmval hoval hival + refine ⟨y, he, ?_, rfl, ?_⟩ + · constructor + · exact hfixed + all_goals simp +decide only [y, Data.set_regs, Function.update_apply, ↓reduceIte] + · omega + · exact (Nat.div_le_self _ _).trans hx.residue_le + · simpa only [pow_succ] using congrArg (fun n => n * 2) hx.mask_power + · rw [pow_succ] + exact Nat.mul_le_mul_right 3 hx.outer_power + · exact hx.inner_power.trans (Nat.pow_le_pow_right (by decide) (by omega)) + · have := hx.mask_acc; omega + · have := hx.outer_acc; nlinarith only [this, houter] + · exact hx.inner_acc + · nlinarith only [hmval] + · nlinarith only [hoval] + · exact hival + · have hyl : y.regs level = x.regs level + 1 := by simp [y] + rw [hyl] + omega + · have hsub : Fits w (x.regs residue % 7 - 3) := hfit.of_le (by omega) + have hsuble : x.regs residue % 7 - 3 < 4 := by omega + have hmul : Fits w ((x.regs residue % 7 - 3) * x.regs innerPower) := + hfit.of_le (by nlinarith only [hip, hsuble]) + have hadd : Fits w (x.regs innerAcc + (x.regs residue % 7 - 3) * x.regs innerPower) := + hfit.of_le (by nlinarith only [hx.inner_acc, hip, hsuble]) + have hpower : Fits w (x.regs innerPower * 4) := hfit.of_le (by omega) + have hmask : Fits w (x.regs maskAcc + x.regs maskPower) := + hfit.of_le (by have := hx.mask_acc; omega) + let y := (((((((((x.set digit (x.regs residue % 7)).set residue (x.regs residue / 7)).set + digit (x.regs residue % 7 - 3)).set temporary + ((x.regs residue % 7 - 3) * x.regs innerPower)).set innerAcc + (x.regs innerAcc + (x.regs residue % 7 - 3) * x.regs innerPower)).set innerPower + (x.regs innerPower * 4)).set maskAcc (x.regs maskAcc + x.regs maskPower)).set + maskPower (x.regs maskPower * 2)).set level (x.regs level + 1)) + have he : ExecWithin w splitBody x y 11 := by + ram_eval [splitBody, y, hx.fixed.one, hx.fixed.two, hx.fixed.three, hx.fixed.four, + hx.fixed.seven, hdFit, hrFit, hmFit, hjFit, hsub, hmul, hadd, hpower, hmask, houter, + digit, residue, temporary, innerAcc, innerPower, maskAcc, maskPower, level, + one, two, three, four, seven, Fin.mk.injEq] + have hfixed : SplitParameters L y := hx.fixed.of_eq (by + intro i hi + apply he.regs_eq + have hf : ∀ i ∈ splitParameterRegisters, splitBody.Preserves i := by + simp only [splitBody, Code.Preserves, Op.dest, Op.bin] + decide + exact hf i hi) + have hmval := hx.mask_value + have hoval := hx.outer_value + have hival := hx.inner_value + rw [hrest, parts, ite_eq_right houter] at hmval hoval hival + refine ⟨y, he, ?_, rfl, ?_⟩ + · constructor + · exact hfixed + all_goals simp +decide only [y, Data.set_regs, Function.update_apply, ↓reduceIte] + · omega + · exact (Nat.div_le_self _ _).trans hx.residue_le + · simpa only [pow_succ] using congrArg (fun n => n * 2) hx.mask_power + · exact hx.outer_power.trans (Nat.pow_le_pow_right (by decide) (by omega)) + · rw [pow_succ] + exact Nat.mul_le_mul_right 4 hx.inner_power + · have := hx.mask_acc; omega + · exact hx.outer_acc + · nlinarith only [hx.inner_acc, hsuble] + · nlinarith only [hmval] + · exact hoval + · nlinarith only [hival] + · have hyl : y.regs level = x.regs level + 1 := by simp [y] + rw [hyl] + omega + +/-- All digits are split by the fixed native loop. Scratch need not start at zero: every +accumulator and place weight is explicitly initialized and its initialization is charged. -/ +theorem exec_split {w : ℕ} (L a : ℕ) (x : Data 96) (hx : SplitParameters L x) + (ha : x.regs index = a) (hfit : Fits w (a + L + 16 * 10 ^ L + 16)) : + ∃ y, ExecWithin w splitCode x y (13 * L + 10) ∧ SplitParameters L y ∧ + y.regs maskAcc = (parts L a).mask ∧ y.regs outerAcc = (parts L a).outer ∧ + y.regs innerAcc = (parts L a).inner ∧ y.mem = x.mem := by + have h0 : Fits w 0 := hfit.of_le (Nat.zero_le _) + have h1 : Fits w 1 := hfit.of_le (by omega) + let start := ((((((((x.set residue a).set maskAcc 0).set outerAcc 0).set innerAcc 0).set + maskPower 1).set outerPower 1).set innerPower 1).set level 0) + have hs : ExecWithin w splitHead x start 8 := by + ram_eval [splitHead, start, ha, h0, h1, residue, index, maskAcc, outerAcc, innerAcc, + maskPower, outerPower, innerPower, level, Fin.mk.injEq] + have hp : SplitParameters L start := hx.of_eq (by + intro i hi + apply hs.regs_eq + have hf : ∀ i ∈ splitParameterRegisters, splitHead.Preserves i := by + simp only [splitHead, Code.Preserves, Op.dest] + decide + exact hf i hi) + have hi : SplitProgress L a start := by + constructor + · exact hp + all_goals simp [start, residue, maskAcc, outerAcc, innerAcc, + maskPower, outerPower, innerPower, level] + let I (y : Data 96) : Prop := SplitProgress L a y ∧ y.mem = x.mem + have hb (y : Data 96) (hy : I y) (hc : (Cond.lt level levels).Holds y) : + ∃ z, ExecWithin w splitBody y z 11 ∧ I z ∧ L - z.regs level < L - y.regs level := by + obtain ⟨z, he, hz, hm, ht⟩ := exec_splitBody L a y hy.1 hc hfit + exact ⟨z, he, ⟨hz, hm.trans hy.2⟩, ht⟩ + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => L - y.regs level) hb ⟨hi, rfl⟩ + have hlevel : y.regs level = L := by + simp only [Cond.holds_lt, hy.1.fixed.levels] at hc + have := hy.1.level_le + omega + have hwhole : ExecWithin w splitCode x y (13 * L + 10) := by + apply (hs.seq he).mono + have hs0 : start.regs level = 0 := by simp [start] + rw [hs0, Nat.sub_zero] + omega + refine ⟨y, hwhole, hy.1.fixed, ?_, ?_, ?_, hy.2⟩ + · simpa only [hlevel, Nat.sub_self, parts, Nat.mul_zero, Nat.add_zero] + using hy.1.mask_value + · simpa only [hlevel, Nat.sub_self, parts, Nat.mul_zero, Nat.add_zero] + using hy.1.outer_value + · simpa only [hlevel, Nat.sub_self, parts, Nat.mul_zero, Nat.add_zero] + using hy.1.inner_value + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandTables.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandTables.lean new file mode 100644 index 0000000000..41bf96539a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandTables.lean @@ -0,0 +1,280 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandPopulation + +/-! +# Charged materialization of the subset tables + +## Main definitions + +* `BandPreparation.tableStep`: the exact memory writes made by one table iteration. + +## Main results + +* `BandPreparation.exec_tableWrite`: actual inverse/forward table stores and counter updates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open MaskTable + +/-- The inverse word is initialized even for an unselected mask. Selected masks also write +one forward-table cell. This is only a mathematical description of the two native stores. -/ +def tableStep (L m inv tab t : ℕ) (μ : ℕ → ℕ) : ℕ → ℕ := + let μ' := Function.update μ (inv + t) (lookup L m t) + if lookup L m t = 0 then μ' else Function.update μ' (tab + prefixCount L m t) t + +/-- The part of both tables computed after visiting `t` masks. The outside frame permits +arbitrary caller memory, including future query inputs and scratch regions. -/ +structure TablesPrefix (L m inv tab t : ℕ) (initial current : ℕ → ℕ) : Prop where + /-- Every visited inverse word has been initialized. -/ + inverse : ∀ a < t, current (inv + a) = lookup L m a + /-- Every selected visited mask has been placed at its prefix rank. -/ + forward : ∀ a < t, lookup L m a ≠ 0 → current (tab + prefixCount L m a) = a + /-- Only the two explicitly reserved table blocks may change. -/ + frame : ∀ a, ¬(inv ≤ a ∧ a < inv + 2 ^ L) → ¬(tab ≤ a ∧ a < tab + 2 ^ L) → + current a = initial a + +/-- Empty scan prefix, with no initialized-memory assumption. -/ +theorem TablesPrefix.zero (L m inv tab : ℕ) (μ : ℕ → ℕ) : + TablesPrefix L m inv tab 0 μ μ := + ⟨fun _ h => (Nat.not_lt_zero _ h).elim, + fun _ h => (Nat.not_lt_zero _ h).elim, fun _ _ _ => rfl⟩ + +/-- The actual two stores extend the initialized table prefix by one mask. -/ +theorem TablesPrefix.step {L m inv tab t : ℕ} {μ μ' : ℕ → ℕ} + (h : TablesPrefix L m inv tab t μ μ') (ht : t < 2 ^ L) + (hsep : inv + 2 ^ L ≤ tab) : + TablesPrefix L m inv tab (t + 1) μ (tableStep L m inv tab t μ') := by + have hr : prefixCount L m t < 2 ^ L := (prefixCount_le L m t).trans_lt ht + constructor + · intro a ha + by_cases hat : a = t + · subst a + unfold tableStep + split + · exact Function.update_self _ _ _ + · rw [Function.update_of_ne (by omega), Function.update_self] + · have haold : a < t := by omega + unfold tableStep + split + · rw [Function.update_of_ne (by omega)] + exact h.inverse a haold + · rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact h.inverse a haold + · intro a ha hselected + by_cases hat : a = t + · subst a + simp only [tableStep, hselected, ↓reduceIte, Function.update_self] + · have haold : a < t := by omega + have hpop := (lookup_ne_zero_iff L m a).mp hselected |>.2.1 + have hless := prefixCount_lt hpop haold + unfold tableStep + split + · rw [Function.update_of_ne (by omega)] + exact h.forward a haold hselected + · rw [Function.update_of_ne (by omega), Function.update_of_ne (by omega)] + exact h.forward a haold hselected + · intro a hi hf + have hia : a ≠ inv + t := by omega + have hta : a ≠ tab + prefixCount L m t := by omega + unfold tableStep + split + · rw [Function.update_of_ne hia] + exact h.frame a hi hf + · rw [Function.update_of_ne hta, Function.update_of_ne hia] + exact h.frame a hi hf + +/-- Execute the stores after the population loop. The code neither searches a supplied +table nor receives a table-validity certificate. -/ +theorem exec_tableWrite {w : ℕ} (L m inv tab t : ℕ) (x : Data 96) + (ht : t < 2 ^ L) (hinv : x.regs inverse = inv) (htab : x.regs table = tab) + (hmask : x.regs mask = t) (hrank : x.regs rank = prefixCount L m t) + (hpop : x.regs population = MaskTable.population L t) (hm : x.regs inner = m) + (hsq : x.regs square = K0 L m * K0 L m) + (h0 : x.regs zero = 0) (h1 : x.regs one = 1) + (hfit : Fits w (inv + tab + 2 ^ L + 4)) : + ∃ y, ExecWithin w tableWrite x y 10 ∧ y.mem = tableStep L m inv tab t x.mem ∧ + y.regs mask = t + 1 ∧ y.regs rank = prefixCount L m (t + 1) ∧ + (∀ i, i ≠ address → i ≠ temporary → i ≠ mask → i ≠ rank → y.regs i = x.regs i) := by + have hbound := prefixCount_le L m t + have ha : Fits w (inv + t) := hfit.of_le (by omega) + have htaddr : Fits w (tab + prefixCount L m t) := hfit.of_le (by omega) + have hr : Fits w (prefixCount L m t + 1) := hfit.of_le (by omega) + have ht1 : Fits w (t + 1) := hfit.of_le (by omega) + by_cases hp : MaskTable.population L t = m + · by_cases hselected : prefixCount L m t < K0 L m * K0 L m + · let y := (((((((x.set address (inv + t)).write (inv + t) 0).set temporary + (prefixCount L m t + 1)).write (inv + t) (prefixCount L m t + 1)).set + address (tab + prefixCount L m t)).write (tab + prefixCount L m t) t).set + rank (prefixCount L m t + 1)).set mask (t + 1) + refine ⟨y, ?_, ?_, ?_, ?_, ?_⟩ + · ram_eval [tableWrite, y, hinv, htab, hmask, hrank, hpop, hm, hsq, h0, h1, + ha, htaddr, hr, ht1, hp, hselected, + inverse, table, mask, rank, population, inner, square, zero, one, address, + temporary, Fin.mk.injEq] + · simp [y, tableStep, lookup, ht, hp, hselected, Data.write, Data.set, + Function.update_idem] + · simp [y, Data.set, address, temporary, mask, rank] + · simp [y, Data.set, address, temporary, mask, rank, prefixCount_succ, hp] + · intro i ha ht hm hr + simp only [y, Data.set_regs, Data.write_regs, + Function.update_of_ne ha, Function.update_of_ne ht, + Function.update_of_ne hm, Function.update_of_ne hr] + · let y := ((((x.set address (inv + t)).write (inv + t) 0).set rank + (x.regs rank)).set rank (prefixCount L m t + 1)).set mask (t + 1) + refine ⟨y, ?_, ?_, ?_, ?_, ?_⟩ + · ram_eval [tableWrite, y, hinv, htab, hmask, hrank, hpop, hm, hsq, h0, h1, + ha, ht1, hr, hp, hselected, inverse, table, mask, rank, population, inner, + square, zero, one, address, temporary, Fin.mk.injEq] + · simp [y, tableStep, lookup, ht, hp, hselected, Data.write, Data.set] + · simp [y, Data.set, address, mask, rank] + · simp [y, Data.set, address, mask, rank, prefixCount_succ, hp] + · intro i ha _ hm hr + simp only [y, Data.set_regs, Data.write_regs, Function.update_of_ne ha, + Function.update_of_ne hm, Function.update_of_ne hr] + · let y := (((x.set address (inv + t)).write (inv + t) 0).set rank + (x.regs rank)).set mask (t + 1) + refine ⟨y, ?_, ?_, ?_, ?_, ?_⟩ + · ram_eval [tableWrite, y, hinv, htab, hmask, hrank, hpop, hm, hsq, h0, h1, + ha, ht1, hp, inverse, mask, rank, population, inner, zero, one, + address, Fin.mk.injEq] + · simp [y, tableStep, lookup, ht, hp, Data.write, Data.set] + · simp [y, Data.set, address, mask, rank] + · simp [y, Data.set, address, mask, rank, prefixCount_succ, hp, hrank] + · intro i ha _ hm hr + simp only [y, Data.set_regs, Data.write_regs, Function.update_of_ne ha, + Function.update_of_ne hm, Function.update_of_ne hr] + +/-- Persistent scalar parameters read by the table loops. These are register contents only; +there is no execution, fit or cost assumption in this postcondition. -/ +structure TableParameters (L m inv tab : ℕ) (x : Data 96) : Prop where + /-- Original depth. -/ + levels : x.regs levels = L + /-- Original inner cardinality. -/ + inner : x.regs inner = m + /-- Computed complete binary-mask range. -/ + masks : x.regs masks = 2 ^ L + /-- Inverse-table base. -/ + inverse : x.regs inverse = inv + /-- Forward-table base. -/ + table : x.regs table = tab + /-- Computed number of selected masks. -/ + square : x.regs square = K0 L m * K0 L m + /-- Constant zero. -/ + zero : x.regs zero = 0 + /-- Constant one. -/ + one : x.regs one = 1 + /-- Constant two. -/ + two : x.regs two = 2 + +private def tableParameterRegisters : Finset (Fin 96) := + {levels, inner, masks, inverse, table, square, zero, one, two} + +private theorem TableParameters.of_eq {L m inv tab : ℕ} {x y : Data 96} + (hx : TableParameters L m inv tab x) + (h : ∀ i ∈ tableParameterRegisters, y.regs i = x.regs i) : + TableParameters L m inv tab y := by + exact ⟨(h _ (by decide)).trans hx.levels, (h _ (by decide)).trans hx.inner, + (h _ (by decide)).trans hx.masks, (h _ (by decide)).trans hx.inverse, + (h _ (by decide)).trans hx.table, (h _ (by decide)).trans hx.square, + (h _ (by decide)).trans hx.zero, (h _ (by decide)).trans hx.one, + (h _ (by decide)).trans hx.two⟩ + +/-- The complete native scan computes both tables, with all inner population scans and +outer controls charged. Outside memory may contain arbitrary caller data. -/ +theorem exec_tables {w : ℕ} (L m inv tab : ℕ) (x : Data 96) + (hx : TableParameters L m inv tab x) (hsep : inv + 2 ^ L ≤ tab) + (hfit : Fits w (inv + tab + 2 ^ L + L + 4)) : + ∃ y, ExecWithin w tablesCode x y ((6 * L + 17) * 2 ^ L + 4) ∧ + TableParameters L m inv tab y ∧ + (∀ a < 2 ^ L, y.mem (inv + a) = lookup L m a) ∧ + (∀ gh : Fin (K0 L m) × Fin (K0 L m), + y.mem (tab + (pairRank gh).val) = (selectedMask gh).val) ∧ + (∀ a, ¬(inv ≤ a ∧ a < inv + 2 ^ L) → ¬(tab ≤ a ∧ a < tab + 2 ^ L) → + y.mem a = x.mem a) := by + have h0 : Fits w 0 := hfit.of_le (Nat.zero_le _) + let start := (x.set mask 0).set rank 0 + have hs : ExecWithin (B := .standard) w (ram[ mask ← #0; rank ← #0 ]) x start 2 := by + ram_eval [start, h0, mask, rank, Fin.mk.injEq] + have hstart : TableParameters L m inv tab start := hx.of_eq (by + intro i hi + have hf : ∀ i ∈ tableParameterRegisters, i ≠ mask ∧ i ≠ rank := by decide + simp only [start, Data.set_regs, Function.update_of_ne (hf i hi).1, + Function.update_of_ne (hf i hi).2]) + let I (y : Data 96) : Prop := + TableParameters L m inv tab y ∧ y.regs mask ≤ 2 ^ L ∧ + y.regs rank = prefixCount L m (y.regs mask) ∧ + TablesPrefix L m inv tab (y.regs mask) x.mem y.mem + have hi : I start := by + refine ⟨hstart, ?_, ?_, ?_⟩ + · simp [start, mask, rank] + · simp [start, mask, rank, prefixCount_zero] + · simpa [start, mask, rank] using TablesPrefix.zero L m inv tab x.mem + have hb (y : Data 96) (hy : I y) (hc : (Cond.lt mask masks).Holds y) : + ∃ z, ExecWithin w tableBody y z (6 * L + 15) ∧ I z ∧ + 2 ^ L - z.regs mask < 2 ^ L - y.regs mask := by + obtain ⟨hp, ht, hrank, hmem⟩ := hy + have htlt : y.regs mask < 2 ^ L := by simpa only [Cond.holds_lt, hp.masks] using hc + obtain ⟨u, hu, hpop, humem, huf⟩ := exec_population L (y.regs mask) y hp.levels rfl + hp.one hp.two (hfit.of_le (by omega)) + have hup : TableParameters L m inv tab u := hp.of_eq (by + intro i hi + have hf : ∀ i ∈ tableParameterRegisters, + i ≠ residue ∧ i ≠ population ∧ i ≠ counter ∧ i ≠ digit := by decide + exact huf i (hf i hi).1 (hf i hi).2.1 (hf i hi).2.2.1 (hf i hi).2.2.2) + have hut : u.regs mask = y.regs mask := + huf mask (by decide) (by decide) (by decide) (by decide) + have hur : u.regs rank = prefixCount L m (y.regs mask) := + (huf rank (by decide) (by decide) (by decide) (by decide)).trans hrank + obtain ⟨z, hz, hzmem, hzt, hzr, _⟩ := exec_tableWrite L m inv tab (y.regs mask) u + htlt hup.inverse hup.table hut hur hpop hup.inner hup.square hup.zero hup.one + (hfit.of_le (by omega)) + have he : ExecWithin w tableBody y z (6 * L + 15) := hu.seq hz + have hzp : TableParameters L m inv tab z := hp.of_eq (by + intro i hi + apply he.regs_eq + have hf : ∀ i ∈ tableParameterRegisters, tableBody.Preserves i := by + simp only [tableBody, tableWrite, populationCode, Code.Preserves, Op.dest, Op.bin] + decide + exact hf i hi) + refine ⟨z, he, ⟨hzp, ?_, ?_, ?_⟩, ?_⟩ + · rw [hzt] + omega + · simpa only [hzt] using hzr + · rw [hzt, hzmem, humem] + exact hmem.step htlt hsep + · rw [hzt] + omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => 2 ^ L - y.regs mask) hb hi + have ht : y.regs mask = 2 ^ L := by + simp only [Cond.holds_lt, hy.1.masks] at hc + have := hy.2.1 + omega + have hwhole : ExecWithin w tablesCode x y ((6 * L + 17) * 2 ^ L + 4) := by + apply (hs.seq he).mono + have hs0 : start.regs mask = 0 := by simp [start, mask, rank] + rw [hs0, Nat.sub_zero] + nlinarith + refine ⟨y, hwhole, hy.1, ?_, ?_, hy.2.2.2.frame⟩ + · intro a ha + exact hy.2.2.2.inverse a (ht ▸ ha) + · intro gh + have hlookup : lookup L m (selectedMask gh).val ≠ 0 := by rw [lookup_selectedMask]; omega + have hb : (selectedMask gh).val < y.regs mask := by rw [ht]; exact (selectedMask gh).isLt + have h := hy.2.2.2.forward (selectedMask gh).val hb hlookup + simpa only [prefixCount_selectedMask] using h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandThin.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandThin.lean new file mode 100644 index 0000000000..eb7c622005 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/BandThin.lean @@ -0,0 +1,76 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.BandFlat + +/-! +# Thin-regime native instruction bound + +## Main results + +* `BandPreparation.runs_thin`: actual initialized native execution, including the halt, + within `81920*(L+1)*N²/(4^m)^(1/18)` at `L=19m`, `N≥(4^m)^18`. +* `BandPreparation.width_exists`: original magnitude and parameter inequalities are jointly + satisfiable for every finite input bound, including positive thin parameters. + +The result concerns shared-band preparation and encoding only. Pruned queries, products and +the whole ordinary 3SUM endpoint remain separate obligations. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- All charged preparation and encoder instructions, including the final halt, satisfy +the finite thin-regime saving. Both the coefficient and polynomial exponent are fixed. -/ +theorem runs_thin {w m N : ℕ} (hw : 0 < w) (hN : D m ^ 18 ≤ N) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) + (A : ℤ) (hA : 0 ≤ A) (hX : ∀ i k, |X i k| ≤ A) (hY : ∀ k j, |Y k j| ≤ A) + (hmag : (7 ^ (19 * m) : ℕ) * A < (2 : ℤ) ^ (w - 1)) + (hfit : Fits (w + 2) (wordBudget (19 * m) m N)) : + ∃ n, Executes (w + 2) (program.program catalogue) + (State.init (input (19 * m) m N X Y)) (catalogueBase (19 * m) m N) n ∧ + (n : ℝ) ≤ 81920 * ((19 * m + 1 : ℕ) : ℝ) * (N : ℝ) ^ 2 / + (D m : ℝ) ^ (1 / 18 : ℝ) := by + obtain ⟨_, _, n, hn, _, he, _⟩ := runs hw (show m ≤ 19 * m by omega) + X Y A hA hX hY hmag hfit + refine ⟨n + 1, he, ?_⟩ + have hpos : 1 ≤ timeBound (19 * m) m N := by + apply Nat.succ_le_of_lt + unfold timeBound + positivity + have hc : n + 1 ≤ 2 * timeBound (19 * m) m N := by omega + have hcR : ((n + 1 : ℕ) : ℝ) ≤ 2 * (timeBound (19 * m) m N : ℝ) := by + exact_mod_cast hc + have ht := thin_timeBound_div m N hN + calc + ((n + 1 : ℕ) : ℝ) ≤ 2 * (timeBound (19 * m) m N : ℝ) := hcR + _ ≤ 2 * (40960 * ((19 * m + 1 : ℕ) : ℝ) * (N : ℝ) ^ 2 / + (D m : ℝ) ^ (1 / 18 : ℝ)) := mul_le_mul_of_nonneg_left ht (by positivity) + _ = _ := by ring + +/-- A concrete, deliberately generous width witnesses simultaneous signed-magnitude and +unsigned-address fits. This proves nonvacuity without enumerating a thin matrix. -/ +theorem width_exists (L m N : ℕ) (A : ℤ) (hA : 0 ≤ A) : + ∃ w, 0 < w ∧ (7 ^ L : ℕ) * A < (2 : ℤ) ^ (w - 1) ∧ + Fits (w + 2) (wordBudget L m N) := by + let q := wordBudget L m N + 7 ^ L * A.natAbs + 3 + refine ⟨q + 1, by omega, ?_, ?_⟩ + · have hnat : 7 ^ L * A.natAbs < 2 ^ q := + (Nat.lt_two_pow_self (n := 7 ^ L * A.natAbs)).trans_le + (Nat.pow_le_pow_right (by decide) (by dsimp [q]; omega)) + have hz : ((7 ^ L * A.natAbs : ℕ) : ℤ) < (2 : ℤ) ^ q := by exact_mod_cast hnat + simpa only [Nat.add_sub_cancel, Nat.cast_mul, Int.natCast_natAbs, + abs_of_nonneg hA] using hz + · exact (fits_self (wordBudget L m N)).mono (by dsimp [q]; omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandPreparation 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/Pruned/Accumulate.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Accumulate.lean new file mode 100644 index 0000000000..59707b3119 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Accumulate.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.MatrixMultiplication.Schonhage.Pruned.AccumulateStep +public import Complexity.Computability.WordRAM.Heap + +/-! +# Complete native accumulation over a distinct parent list + +## Main results + +* `PrunedQuery.exec_accumulateLoop`: the native loop adds a returned child exactly once at + each contributing listed parent query, within `31 N + 3` instructions. + +The loop invariant uses Mathlib's existing `Finset.piecewise` for the updated memory cells. +The source list and child values are preserved by the concrete block separations. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed Finset + +private theorem accumulateLoop_preserves (i : Fin registers) : + ¬AccumulateStepLocal i → accumulateLoop.Preserves i := by decide +kernel +revert + +/-- This is the real indexed signed-add loop. The list's distinctness is its ordinary memory +property, established by the preceding native timestamp scan, not a query-decision oracle. -/ +theorem exec_accumulateLoop {w N capacity : ℕ} (x : Data registers) (input : ℕ → ℕ) + (V B : ℕ → ℤ) (hN : x.regs count = N) + (hinput : ∀ i < N, x.mem (x.regs listBase + i) = input i) + (hcode : ∀ i < N, input i < capacity) + (hinj : Set.InjOn input {i | i < N}) + (hparent : ∀ i < N, x.mem (x.regs valueBase + input i) = encode (V (input i))) + (hchild : ∀ i < N, selected true (x.regs term) (input i) → + x.mem (x.regs childValues + input i / 10) = encode (B (input i / 10))) + (hV : ∀ i < N, GenericRAM.Signed.Fits w (V (input i))) + (hB : ∀ i < N, GenericRAM.Signed.Fits w (B (input i / 10))) + (hsepSource : x.regs listBase + N ≤ x.regs valueBase ∨ + x.regs valueBase + capacity ≤ x.regs listBase) + (hsepChild : x.regs valueBase + capacity ≤ x.regs childValues ∨ + x.regs childValues + capacity ≤ x.regs valueBase) + (h1 : x.regs one = 1) (h9 : x.regs nine = 9) (h10 : x.regs ten = 10) + (hw : Fits (w + 2) (x.regs listBase + N + x.regs valueBase + capacity + + x.regs childValues + capacity + N + 16)) : + ∃ y, ExecWithin (w + 2) accumulateLoop x y (31 * N + 3) ∧ y.regs index = N ∧ + y.mem = (((range N).filter fun i => selected true (x.regs term) (input i)).image + fun i => x.regs valueBase + input i).piecewise + (fun a => encode (V (a - x.regs valueBase) + B ((a - x.regs valueBase) / 10))) x.mem ∧ + Modifies (Block.mk (x.regs valueBase) capacity).footprint x.mem y.mem ∧ + (∀ i < N, y.mem (x.regs listBase + i) = input i) ∧ + (∀ i, ¬AccumulateStepLocal i → y.regs i = x.regs i) := by + let written (k : ℕ) := (((range k).filter fun i => + selected true (x.regs term) (input i)).image fun i => x.regs valueBase + input i) + let newValue (a : ℕ) := + encode (V (a - x.regs valueBase) + B ((a - x.regs valueBase) / 10)) + let x0 := x.set index 0 + let I (y : Data registers) : Prop := y.regs index ≤ N ∧ + y.mem = (written (y.regs index)).piecewise newValue x.mem ∧ + (∀ i, ¬AccumulateStepLocal i → y.regs i = x.regs i) + have hinit : I x0 := by + refine ⟨by simp [x0], ?_, ?_⟩ + · funext a + simp [x0, written, Finset.piecewise] + · intro i hi + have hne : i ≠ index := by intro h; subst h; exact hi (by decide) + simp [x0, hne] + have houtside (y : Data registers) (hy : I y) (a : ℕ) + (ha : ¬(Block.mk (x.regs valueBase) capacity).Owns a) : y.mem a = x.mem a := by + have hnot : a ∉ written (y.regs index) := by + rintro h + obtain ⟨i, hi, rfl⟩ := mem_image.mp h + have hik := mem_range.mp (mem_filter.mp hi).1 + have hq := hcode i (lt_of_lt_of_le hik hy.1) + apply ha + change x.regs valueBase ≤ x.regs valueBase + input i ∧ + x.regs valueBase + input i < x.regs valueBase + capacity + omega + rw [hy.2.1] + simp only [Finset.piecewise, hnot, ↓reduceIte] + have hread (y : Data registers) (hy : I y) (i : ℕ) (hi : i < N) : + y.mem (x.regs listBase + i) = input i := by + rw [houtside y hy] + · exact hinput i hi + · change ¬(x.regs valueBase ≤ x.regs listBase + i ∧ + x.regs listBase + i < x.regs valueBase + capacity) + rcases hsepSource with h | h <;> omega + have hstep (y : Data registers) (hy : I y) (hc : (Cond.lt index count).Holds y) : + ∃ z, ExecWithin (w + 2) accumulateStep y z 29 ∧ I z ∧ + N - z.regs index < N - y.regs index := by + obtain ⟨hle, hmem, hf⟩ := hy + have hcount := (hf count (by decide)).trans hN + have hb := hf listBase (by decide) + have hv := hf valueBase (by decide) + have hcptr := hf childValues (by decide) + have ht := hf term (by decide) + have hk : y.regs index < N := by simpa only [Cond.holds_lt, hcount] using hc + have hq := hcode (y.regs index) hk + have hquot : input (y.regs index) / 10 < capacity := + lt_of_le_of_lt (Nat.div_le_self _ _) hq + have hnot : x.regs valueBase + input (y.regs index) ∉ written (y.regs index) := by + rintro h + obtain ⟨j, hj, he⟩ := mem_image.mp h + have hjk := mem_range.mp (mem_filter.mp hj).1 + have hji := hinj (lt_of_lt_of_le hjk hle) hk (Nat.add_left_cancel he) + omega + have hleft : y.mem (y.regs valueBase + input (y.regs index)) = + encode (V (input (y.regs index))) := by + rw [hv, hmem] + simp only [Finset.piecewise, hnot, ↓reduceIte] + exact hparent _ hk + have hright (hs : selected true (y.regs term) (input (y.regs index))) : + y.mem (y.regs childValues + input (y.regs index) / 10) = + encode (B (input (y.regs index) / 10)) := by + rw [hcptr, houtside y ⟨hle, hmem, hf⟩] + · exact hchild _ hk (by simpa only [ht] using hs) + · change ¬(x.regs valueBase ≤ x.regs childValues + input (y.regs index) / 10 ∧ + x.regs childValues + input (y.regs index) / 10 < x.regs valueBase + capacity) + rcases hsepChild with h | h <;> omega + obtain ⟨z, hz, hi, hm, hregs⟩ := exec_accumulateStep (hV _ hk) (hB _ hk) y + (input (y.regs index)) (by rw [hb]; exact hread y ⟨hle, hmem, hf⟩ _ hk) + ((hf one (by decide)).trans h1) ((hf nine (by decide)).trans h9) + ((hf ten (by decide)).trans h10) (fun _ => hleft) hright + (hw.of_le (by omega)) (hw.of_le (by rw [hb]; omega)) + (hw.of_le (by rw [hv]; omega)) (hw.of_le (by rw [hcptr]; omega)) (hw.of_le (by omega)) + refine ⟨z, hz, ⟨by omega, ?_, fun i hi => (hregs i hi).trans (hf i hi)⟩, by omega⟩ + have hnext : written (y.regs index + 1) = + if selected true (x.regs term) (input (y.regs index)) then + insert (x.regs valueBase + input (y.regs index)) (written (y.regs index)) + else written (y.regs index) := by + by_cases hs : selected true (x.regs term) (input (y.regs index)) <;> + simp [written, range_add_one, filter_insert, hs] + rw [hm, ht, hv, hmem, hi, hnext] + funext a + by_cases hs : selected true (x.regs term) (input (y.regs index)) + · by_cases he : a = x.regs valueBase + input (y.regs index) + · subst a + simp [hs, Finset.piecewise, newValue] + · simp [hs, Finset.piecewise, he, newValue] + · simp [hs, Finset.piecewise, newValue] + obtain ⟨y, hy, hIy, hstop⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs index) + hstep hinit + have hcount := (hIy.2.2 count (by decide)).trans hN + have hindex : y.regs index = N := by + simp only [Cond.holds_lt, hcount, not_lt] at hstop + omega + have hsetup : ExecWithin (w + 2) (ram[ index ← #0 ] : Code .standard registers) x x0 1 := by + ram_eval [x0] + have hloop : ExecWithin (w + 2) (Code.loop (.lt index count) accumulateStep) x0 y + (31 * N + 2) := by + simpa only [x0, Data.set_regs, Function.update_self, Nat.sub_zero, Nat.mul_comm] using hy + have he : ExecWithin (w + 2) accumulateLoop x y (31 * N + 3) := + (hsetup.seq hloop).mono (by omega) + refine ⟨y, he, hindex, ?_, ?_, hread y hIy, + fun i hi => he.regs_eq (accumulateLoop_preserves i hi)⟩ + · change y.mem = (written N).piecewise newValue x.mem + rw [← hindex] + exact hIy.2.1 + · intro a ha + exact houtside y hIy a (by simpa only [Block.mem_footprint] using ha) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/AccumulateStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/AccumulateStep.lean new file mode 100644 index 0000000000..0ac4a1719a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/AccumulateStep.lean @@ -0,0 +1,135 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Add +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanStep + +/-! +# Actual native selection and accumulation of one query + +## Main results + +* `PrunedQuery.exec_accumulateDispatch`: the real digit comparisons select a child's addition. +* `PrunedQuery.exec_accumulateStep`: include the source indexing, load, digit and counter work. + +The child-value memory premise is needed only for selected queries. Unselected child-directory +cells may contain arbitrary prior values and are never loaded by the program. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Registers changed by selected signed accumulation or its unselected no-op. -/ +def AccumulateDispatchLocal (i : Fin registers) : Prop := AddValueLocal i ∨ i = temporary + +instance (i : Fin registers) : Decidable (AccumulateDispatchLocal i) := + inferInstanceAs (Decidable (AddValueLocal i ∨ i = temporary)) + +private theorem accumulateDispatch_preserves (i : Fin registers) : + ¬AccumulateDispatchLocal i → accumulateDispatch.Preserves i := by decide +kernel +revert + +/-- Exactly the contributing parent queries load and add a child value. Selection is proved +from the actual comparisons with digit nine and the current term register. -/ +theorem exec_accumulateDispatch {w : ℕ} {a b : ℤ} + (ha : GenericRAM.Signed.Fits w a) (hb : GenericRAM.Signed.Fits w b) + (x : Data registers) (q : ℕ) (hq : x.regs query = q) (hd : x.regs digit = q % 10) + (h9 : x.regs nine = 9) (h10 : x.regs ten = 10) + (hleft : selected true (x.regs term) q → x.mem (x.regs valueBase + q) = encode a) + (hright : selected true (x.regs term) q → + x.mem (x.regs childValues + q / 10) = encode b) + (hp : Fits (w + 2) (x.regs valueBase + q)) + (hc : Fits (w + 2) (x.regs childValues + q / 10)) : + ∃ y, ExecWithin (w + 2) accumulateDispatch x y 25 ∧ + y.mem = (if selected true (x.regs term) q then + Function.update x.mem (x.regs valueBase + q) (encode (a + b)) else x.mem) ∧ + (∀ i, ¬AccumulateDispatchLocal i → y.regs i = x.regs i) := by + by_cases hsel : selected true (x.regs term) q + · obtain ⟨y, hy, hmem, _⟩ := exec_addValue ha hb x q hq h10 (hleft hsel) (hright hsel) hp hc + have hchoice : q % 10 = 9 ∨ q % 10 = x.regs term := hsel + have he : ExecWithin (w + 2) accumulateDispatch x y 25 := by + by_cases hfirst : q % 10 = 9 + · exact (ExecWithin.branch_pos (by + simpa only [Cond.holds_eq, hd, h9] using hfirst) hy).mono (by decide) + · have hsecond := hchoice.resolve_left hfirst + exact (ExecWithin.branch_neg (by + simpa only [Cond.holds_eq, hd, h9] using hfirst) + (ExecWithin.branch_pos (by simpa only [Cond.holds_eq, hd] using hsecond) hy)).mono + (by decide) + exact ⟨y, he, by simpa only [hsel, ↓reduceIte] using hmem, + fun i hi => he.regs_eq (accumulateDispatch_preserves i hi)⟩ + · have hfirst : q % 10 ≠ 9 := fun h => hsel (Or.inl h) + have hsecond : q % 10 ≠ x.regs term := fun h => hsel (Or.inr h) + let y := x.set temporary (x.regs temporary) + have he : ExecWithin (w + 2) accumulateDispatch x y 5 := + ExecWithin.branch_neg (by simpa only [Cond.holds_eq, hd, h9] using hfirst) + (ExecWithin.branch_neg (by simpa only [Cond.holds_eq, hd] using hsecond) ExecWithin.copy) + exact ⟨y, he.mono (by decide), by simp [hsel, y], + fun i hi => he.regs_eq (accumulateDispatch_preserves i hi)⟩ + +/-- Registers changed by loading, dispatching and advancing one parent query. -/ +def AccumulateStepLocal (i : Fin registers) : Prop := + AccumulateDispatchLocal i ∨ i = query ∨ i = digit ∨ i = index + +instance (i : Fin registers) : Decidable (AccumulateStepLocal i) := + inferInstanceAs (Decidable + (AccumulateDispatchLocal i ∨ i = query ∨ i = digit ∨ i = index)) + +private theorem accumulateStep_preserves (i : Fin registers) : + ¬AccumulateStepLocal i → accumulateStep.Preserves i := by decide +kernel +revert + +/-- One complete native parent-query iteration, including every index/digit/control operation. +The conditional memory hypotheses reflect only which child words are actually requested. -/ +theorem exec_accumulateStep {w : ℕ} {a b : ℤ} + (ha : GenericRAM.Signed.Fits w a) (hb : GenericRAM.Signed.Fits w b) + (x : Data registers) (q : ℕ) + (hq : x.mem (x.regs listBase + x.regs index) = q) + (h1 : x.regs one = 1) (h9 : x.regs nine = 9) (h10 : x.regs ten = 10) + (hleft : selected true (x.regs term) q → x.mem (x.regs valueBase + q) = encode a) + (hright : selected true (x.regs term) q → + x.mem (x.regs childValues + q / 10) = encode b) + (h10fit : Fits (w + 2) 10) + (hr : Fits (w + 2) (x.regs listBase + x.regs index)) + (hp : Fits (w + 2) (x.regs valueBase + q)) + (hc : Fits (w + 2) (x.regs childValues + q / 10)) + (hi : Fits (w + 2) (x.regs index + 1)) : + ∃ y, ExecWithin (w + 2) accumulateStep x y 29 ∧ y.regs index = x.regs index + 1 ∧ + y.mem = (if selected true (x.regs term) q then + Function.update x.mem (x.regs valueBase + q) (encode (a + b)) else x.mem) ∧ + (∀ i, ¬AccumulateStepLocal i → y.regs i = x.regs i) := by + let z := ((x.set address (x.regs listBase + x.regs index)).set query q).set digit (q % 10) + have hmod : Fits (w + 2) (q % 10) := + h10fit.of_le (Nat.le_of_lt (Nat.mod_lt _ (by decide))) + have hload : ExecWithin (w + 2) scanLoad x z 3 := by + ram_eval [scanLoad, z, hq, h10, hr, hmod, address, listBase, index, query, digit, ten, + Fin.mk.injEq] + have hzreg (i : Fin registers) (ha : i ≠ address) (hq' : i ≠ query) (hd : i ≠ digit) : + z.regs i = x.regs i := by simp [z, ha, hq', hd] + obtain ⟨u, hu, hm, hf⟩ := exec_accumulateDispatch ha hb z q + (by simp [z, query, digit]) (by simp [z]) + ((hzreg nine (by decide) (by decide) (by decide)).trans h9) + ((hzreg ten (by decide) (by decide) (by decide)).trans h10) + (by simpa +decide [z, term, address, query, digit, valueBase] using hleft) + (by simpa +decide [z, term, address, query, digit, childValues] using hright) + (by simpa +decide [z, address, query, digit, valueBase] using hp) + (by simpa +decide [z, address, query, digit, childValues] using hc) + have hui : u.regs index = x.regs index := + (hf index (by decide)).trans (hzreg index (by decide) (by decide) (by decide)) + have huo : u.regs one = 1 := + (hf one (by decide)).trans ((hzreg one (by decide) (by decide) (by decide)).trans h1) + let y := u.set index (x.regs index + 1) + have hinc : ExecWithin (w + 2) (ram[ index ← index + one ]) u y 1 := by + ram_eval [y, hui, huo, hi] + have he : ExecWithin (w + 2) accumulateStep x y 29 := hload.seq (hu.seq hinc) + refine ⟨y, he, by simp [y], ?_, fun i hi => he.regs_eq (accumulateStep_preserves i hi)⟩ + simpa +decide [y, z, term, address, query, digit, valueBase] using hm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Add.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Add.lean new file mode 100644 index 0000000000..6f2a5333df --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Add.lean @@ -0,0 +1,77 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Charged consumption of a returned child value + +## Main results + +* `PrunedQuery.exec_addValue`: compute the child suffix address, load both signed words, call + the existing signed-add kernel, and store the sum at the parent query address. + +The instruction bound includes all indexed accesses and the native arithmetic kernel. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The signed workspace and two address-calculation registers used by one accumulation. -/ +def AddValueLocal (i : Fin registers) : Prop := i.val < 9 ∨ i = address ∨ i = suffix + +instance (i : Fin registers) : Decidable (AddValueLocal i) := + inferInstanceAs (Decidable (i.val < 9 ∨ i = address ∨ i = suffix)) + +private theorem addValue_preserves (i : Fin registers) : + ¬AddValueLocal i → addValue.Preserves i := by decide +kernel +revert + +/-- All twenty-two instructions are native standard-basis operations. Both operands must fit +the source signed width; their sum is computed exactly by the existing two-extra-bit kernel. -/ +theorem exec_addValue {w : ℕ} {a b : ℤ} (ha : GenericRAM.Signed.Fits w a) + (hb : GenericRAM.Signed.Fits w b) (x : Data registers) (q : ℕ) + (hq : x.regs query = q) (h10 : x.regs ten = 10) + (hleft : x.mem (x.regs valueBase + q) = encode a) + (hright : x.mem (x.regs childValues + q / 10) = encode b) + (hp : Fits (w + 2) (x.regs valueBase + q)) + (hc : Fits (w + 2) (x.regs childValues + q / 10)) : + ∃ y, ExecWithin (w + 2) addValue x y 22 ∧ + y.mem = Function.update x.mem (x.regs valueBase + q) (encode (a + b)) ∧ + (∀ i, ¬AddValueLocal i → y.regs i = x.regs i) := by + let z := (((((x.set suffix (q / 10)).set address (x.regs valueBase + q)).set + left (encode a)).set address (x.regs childValues + q / 10)).set right (encode b)) + have hquot : Fits (w + 2) (q / 10) := hc.of_le (by omega) + have hload : ExecWithin (w + 2) addValueLoad x z 5 := by + ram_eval [addValueLoad, z, hq, h10, hleft, hright, hp, hc, hquot, suffix, address, + valueBase, query, ten, childValues, left, right, Fin.mk.injEq] + obtain ⟨u, hu, hsum, hframe⟩ := Signed.exec_add (r := 87) ha hb z + (by simp +decide [z, left, right, address]) (by simp [z]) + have hreg (i : Fin registers) (hi : 9 ≤ i.val) (hs : i ≠ suffix) (hd : i ≠ address) : + u.regs i = x.regs i := by + rw [hframe.2 i (Or.inr hi)] + have hl : i ≠ left := by intro he; subst he; exact Nat.not_le_of_lt (by decide) hi + have hr : i ≠ right := by intro he; subst he; exact Nat.not_le_of_lt (by decide) hi + simp only [z, Data.set_regs, Function.update_of_ne hr, Function.update_of_ne hd, + Function.update_of_ne hl, Function.update_of_ne hs] + have huv := hreg valueBase (by decide) (by decide) (by decide) + have huq := (hreg query (by decide) (by decide) (by decide)).trans hq + have hsum' : u.regs result = encode (a + b) := hsum + let y := (u.set address (x.regs valueBase + q)).write + (x.regs valueBase + q) (encode (a + b)) + have hstore : ExecWithin (w + 2) addValueStore u y 2 := by + ram_eval [addValueStore, y, huv, huq, hsum', hp, result, address, Fin.mk.injEq] + have he : ExecWithin (w + 2) addValue x y 22 := hload.seq (hu.seq hstore) + refine ⟨y, he, ?_, fun i hi => he.regs_eq (addValue_preserves i hi)⟩ + simpa only [y, Data.write_mem, Data.set_mem, z] using + congrArg (fun mem => Function.update mem (x.regs valueBase + q) (encode (a + b))) hframe.1 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Bounds.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Bounds.lean new file mode 100644 index 0000000000..3248633e0a --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Bounds.lean @@ -0,0 +1,156 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Values +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Weight +public import Complexity.Computability.GenericRAM.Signed.Fits +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Input-derived bounds for native pruned evaluation + +## Main definitions + +* `PrunedQuery.nodeBudget`: a scalar address/counter budget, including remaining epochs. +* `PrunedQuery.magnitudeBound`: covers both operands and every partial sum. + +## Main results + +* `PrunedQuery.nodeBudget_input_le`: the concrete input-derived bound covers the root budget. +* `PrunedQuery.fits_partialValue`: the original magnitude bound derives partial accumulator fits. + +The only fit assumptions are numerical inequalities on original parameters. No state-by-state +fit assertion or execution/time certificate is supplied. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- A node budget for ordinary pointers, powers, counters and all remaining fresh epochs. -/ +def nodeBudget (L f a b E n : ℕ) : ℕ := + 8 * f + 64 * (L + 1) * 10 ^ L + a + b + 2 * 10 ^ L + E + epochAllowance n + 128 + +/-- Root deduplication and reporting additionally use the original query/output pointers and +the full supplied query count, which may exceed the distinct-set capacity. -/ +def evaluationBudget (L f a b q o Q E : ℕ) : ℕ := + nodeBudget L f a b (E + 1) L + 4 * (q + o + Q + 1) + +/-- The callable kernel also computes its own powers and workspace pointers. Its numeric +admission includes those setup bounds and permits arbitrary separated input/output addresses. -/ +def kernelBudget (L f a b q o Q E : ℕ) : ℕ := + addressBound L Q + evaluationBudget L f a b q o Q E + +/-- The ordinary initialized layout covers evaluation, including repeated supplied queries. -/ +theorem evaluationBudget_input_le (L Q : ℕ) : + evaluationBudget L (scratchBase L Q) 3 (3 + 10 ^ L) (3 + 2 * 10 ^ L) + (outputBase L Q) Q 0 ≤ addressBound L Q := by + have he := epochAllowance_le L + have hp : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + unfold evaluationBudget nodeBudget scratchBase outputBase addressBound + nlinarith only [he, hp] + +/-- The larger concrete standalone bound covers initialization and every root parameter. -/ +theorem nodeBudget_input_le (L Q : ℕ) : + nodeBudget L (scratchBase L Q) 3 (3 + 10 ^ L) 1 L ≤ addressBound L Q := by + have he := epochAllowance_le L + have hp : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + unfold nodeBudget scratchBase outputBase addressBound + nlinarith only [he, hp] + +/-- Epoch allocation to a child is paid for by the remaining parent allowance. -/ +theorem nodeBudget_child_le {L f a b E E' n : ℕ} + (hE : E' + epochAllowance n ≤ E + epochAllowance (n + 1)) : + nodeBudget L f a b E' n ≤ nodeBudget L f a b E (n + 1) := by + unfold nodeBudget + omega + +/-- A single original-parameter envelope covers the two inputs and all tensor partial sums. -/ +def magnitudeBound (L A B : ℕ) : ℕ := A + B + 10 ^ L * (A * B) + +/-- The positive-endpoint inequality makes the nonnegative envelope a signed fitting value. -/ +theorem magnitudeBound_fits {w L A B : ℕ} (hw : 0 < w) + (hfit : magnitudeBound L A B < 2 ^ (w - 1)) : + GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ) := by + refine ⟨hw, ?_, ?_⟩ + · have hp : (0 : ℤ) ≤ 2 ^ (w - 1) := by positivity + have hm : (0 : ℤ) ≤ (magnitudeBound L A B : ℕ) := by positivity + omega + · exact_mod_cast hfit + +/-- The original bound on one input entry derives its signed fit through the common envelope. -/ +theorem fits_left {w L A B : ℕ} {z : ℤ} + (hfit : GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ)) (hz : |z| ≤ (A : ℤ)) : + GenericRAM.Signed.Fits w z := by + apply hfit.of_natAbs_le + have hz' : z.natAbs ≤ A := by + rw [← Int.natCast_natAbs] at hz + exact_mod_cast hz + exact hz'.trans (by unfold magnitudeBound; omega) + +/-- The right operand uses the same envelope, including the zero-input boundary. -/ +theorem fits_right {w L A B : ℕ} {z : ℤ} + (hfit : GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ)) (hz : |z| ≤ (B : ℤ)) : + GenericRAM.Signed.Fits w z := by + apply hfit.of_natAbs_le + have hz' : z.natAbs ≤ B := by + rw [← Int.natCast_natAbs] at hz + exact_mod_cast hz + exact hz'.trans (by unfold magnitudeBound; omega) + +/-- The leaf product fits the same envelope, including when either input bound is zero. -/ +theorem fits_product {w L A B : ℕ} {a b : ℤ} + (hfit : GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ)) + (ha : |a| ≤ (A : ℤ)) (hb : |b| ≤ (B : ℤ)) : GenericRAM.Signed.Fits w (a * b) := by + apply hfit.of_natAbs_le + have ha' : a.natAbs ≤ A := by + rw [← Int.natCast_natAbs] at ha + exact_mod_cast ha + have hb' : b.natAbs ≤ B := by + rw [← Int.natCast_natAbs] at hb + exact_mod_cast hb + rw [Int.natAbs_mul] + have hp : A * B ≤ 10 ^ L * (A * B) := + Nat.le_mul_of_pos_left _ (by positivity : 0 < (10 : ℕ) ^ L) + exact (Nat.mul_le_mul ha' hb').trans (hp.trans (by unfold magnitudeBound; omega)) + +/-- A partial sum at any remaining depth fits by the original global magnitude inequality. -/ +theorem fits_partialValue {w L n A B : ℕ} (hn : n + 1 ≤ L) + (hfit : GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ)) + (a b : Leaf (n + 1) → ℤ) (ha : ∀ v, |a v| ≤ (A : ℤ)) + (hb : ∀ v, |b v| ≤ (B : ℤ)) (S : Finset (OutStr (n + 1))) + (q : OutStr (n + 1)) (k : ℕ) : GenericRAM.Signed.Fits w (partialValue a b S q k) := by + apply hfit.of_natAbs_le + have h := abs_partialValue_le a b A B ha hb S q k + have hnat : (partialValue a b S q k).natAbs ≤ 10 ^ (n + 1) * (A * B) := by + rw [← Int.natCast_natAbs] at h + exact_mod_cast h + have hp := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hn + have hm := Nat.mul_le_mul_right (A * B) hp + exact hnat.trans (hm.trans (by unfold magnitudeBound; omega)) + +/-- Every returned child result fits, including an unselected or skipped zero value. -/ +theorem fits_childResult {w L n A B : ℕ} (hn : n ≤ L) + (hfit : GenericRAM.Signed.Fits w (magnitudeBound L A B : ℤ)) + (a b : Leaf (n + 1) → ℤ) (ha : ∀ v, |a v| ≤ (A : ℤ)) + (hb : ∀ v, |b v| ≤ (B : ℤ)) (S : Finset (OutStr (n + 1))) + (lam : Term) (q : OutStr n) : GenericRAM.Signed.Fits w (Pruned.childResult a b S lam q) := by + apply hfit.of_natAbs_le + have h := abs_childResult_le a b A B ha hb S lam q + have hnat : (Pruned.childResult a b S lam q).natAbs ≤ 10 ^ n * (A * B) := by + rw [← Int.natCast_natAbs] at h + exact_mod_cast h + have hp := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hn + have hm := Nat.mul_le_mul_right (A * B) hp + exact hnat.trans (hm.trans (by unfold magnitudeBound; omega)) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Child.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Child.lean new file mode 100644 index 0000000000..3777af9de8 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Child.lean @@ -0,0 +1,186 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Node +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Scan +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Push + +/-! +# Computed nonempty-child transitions of the native controller + +## Main results + +* `PrunedQuery.exec_node_child`: read the actual frame and requests, construct the child by + native digit tests/division/deduplication, and push its computed frame. + +The nonemptiness premise selects a case of the proof. The machine itself tests the count +that it has just computed; no query-selection or layout certificate is input to its code. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Only the next query row, timestamps and the next four-word frame may change during a +nonempty-child transition. In particular, parent queries, accumulators and frame survive. -/ +def childWrites (L f d : ℕ) : Finset ℕ := + (Block.mk (f + 4 * (L + 1) + (d + 1) * 10 ^ L) (10 ^ L)).footprint ∪ + (Block.mk (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (10 ^ L)).footprint ∪ + (Block.mk (f + 4 * (d + 1)) 4).footprint + +/-- This is the actual full controller transition, not a callee execution assumption. All +three controller comparisons, the complete native scan, four frame stores and index work +fit in `20 N + 42` instructions. The produced directory describes the same final memory. -/ +theorem exec_node_child {w L f a0 b0 d N p k : ℕ} (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hd : x.regs level = d) (hdL : d < L) + (hm : x.regs mode = 1) + (hN : x.mem (f + 4 * d) = N) (hcap : N ≤ 10 ^ L) + (hpref : x.mem (f + 4 * d + 1) = p) (hp : p < 10 ^ d) + (hstride : x.mem (f + 4 * d + 2) = 10 ^ d) + (hterm : x.mem (f + 4 * d + 3) = k) (hk : k < 10) + (input : ℕ → ℕ) + (hinput : ∀ i < N, x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i) + (hcodes : ∀ i < N, input i < 10 ^ L) + (hmarks : ∀ q < 10 ^ L, + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ x.regs epoch) + (hne : (scanSet true k input N).Nonempty) + (hw : Fits w (8 * f + 64 * (L + 1) * 10 ^ L + x.regs epoch + 128)) : + ∃ y, ExecWithin w step x y (20 * N + 42) ∧ KernelRegs L f a0 b0 y ∧ + y.regs level = d + 1 ∧ y.regs mode = 0 ∧ y.regs active = x.regs active ∧ + y.regs epoch = x.regs epoch + 1 ∧ + ScanMemory y.mem (f + 4 * (L + 1) + (d + 1) * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (x.regs epoch + 1) (10 ^ L) + (scanSet true k input N) ∧ + y.mem (f + 4 * (d + 1)) = (scanSet true k input N).card ∧ + y.mem (f + 4 * (d + 1) + 1) = p + k * 10 ^ d ∧ + y.mem (f + 4 * (d + 1) + 2) = 10 ^ (d + 1) ∧ + y.mem (f + 4 * (d + 1) + 3) = 0 ∧ + Modifies (childWrites L f d) x.mem y.mem := by + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + have hmul : (d + 1) * 10 ^ L ≤ (L + 1) * 10 ^ L := + Nat.mul_le_mul_right _ (by omega) + have hnext : (10 : ℕ) ^ d * 10 ≤ 10 ^ L := by + simpa only [← pow_succ] using + Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) (by omega : d + 1 ≤ L) + have hnewp : p + k * 10 ^ d < 10 ^ L := by + nlinarith only [hp, hk, hnext] + obtain ⟨u, hu, humem, _, huc, hup, hus, hut, hul, _, hun, huch, _, hureg⟩ := + exec_loadFrame x hregs.one_eq hregs.frameWidth_eq (hw.of_le (by + rw [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, hd] + nlinarith only [hpow, hL, hmul])) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have hul' : u.regs listBase = f + 4 * (L + 1) + d * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using hul + have huch' : u.regs childList = f + 4 * (L + 1) + (d + 1) * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using huch + have hus' : u.regs stamps = f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L := + (fixed stamps (by decide)).trans hregs.stamps_eq + have huc' : u.regs count = N := by simpa only [hregs.frames_eq, hd, hN] using huc + have hup' : u.regs leafPrefix = p := by + simpa only [hregs.frames_eq, hd, hpref] using hup + have hstride' : u.regs stride = 10 ^ d := by + simpa only [hregs.frames_eq, hd, hstride] using hus + have hut' : u.regs term = k := by simpa only [hregs.frames_eq, hd, hterm] using hut + have hun' : u.regs nextLevel = d + 1 := by simpa only [hd] using hun + have hue : u.regs epoch = x.regs epoch := fixed epoch (by decide) + obtain ⟨v, hv, _, hve, hvc, htable, _, hscanFrame, hvreg⟩ := + exec_scan (N := N) (capacity := 10 ^ L) true u input huc' + (by simpa only [hul', humem] using hinput) + (fun i hi => (Nat.div_le_self _ _).trans_lt (hcodes i hi)) + (Or.inl (by rw [huch', hus']; nlinarith only [hmul, hpow])) + (Or.inl (by rw [hul', huch']; nlinarith only [hcap])) + (Or.inl (by rw [hul', hus']; nlinarith only [hcap, hmul, hpow])) + (by simpa only [hus', humem, hue] using hmarks) + ((fixed one (by decide)).trans hregs.one_eq) + ((fixed ten (by decide)).trans hregs.ten_eq) + ((fixed nine (by decide)).trans hregs.nine_eq) + (hw.of_le (by + rw [hul', huch', hus', hue] + nlinarith only [hcap, hmul, hpow, hL])) + have scanFixed (i : Fin registers) (hi : ¬ScanLocal i) : v.regs i = u.regs i := hvreg i hi + have hvf : v.regs frames = f := (scanFixed frames (by decide)).trans + ((fixed frames (by decide)).trans hregs.frames_eq) + have hvn : v.regs nextLevel = d + 1 := (scanFixed nextLevel (by decide)).trans hun' + have hvp : v.regs leafPrefix = p := (scanFixed leafPrefix (by decide)).trans hup' + have hvst : v.regs stride = 10 ^ d := (scanFixed stride (by decide)).trans hstride' + have hvt : v.regs term = k := (scanFixed term (by decide)).trans hut' + have hv0 := (scanFixed nil (by decide)).trans ((fixed nil (by decide)).trans hregs.nil_eq) + have hv1 := (scanFixed one (by decide)).trans ((fixed one (by decide)).trans hregs.one_eq) + have hv4 := (scanFixed frameWidth (by decide)).trans + ((fixed frameWidth (by decide)).trans hregs.frameWidth_eq) + have hv10 := (scanFixed ten (by decide)).trans ((fixed ten (by decide)).trans hregs.ten_eq) + have hvc' : v.regs childCount = (scanSet true k input N).card := by + simpa only [hut'] using hvc + obtain ⟨y, hy, hyl, hym, _, _, _, hmem, hpushFrame, _⟩ := exec_push v hv0 hv1 hv4 hv10 + (hw.of_le (by + rw [hvf, hvn, hvp, hvst, hvt] + nlinarith only [hcap, hpow, hL, hmul, hnext, hnewp])) + have hnonempty : (Cond.lt nil childCount).Holds v := by + simpa only [Cond.holds_lt, hv0, hvc'] using Finset.card_pos.mpr hne + have hcall : ExecWithin w dispatch u y (20 * N + 22) := by + have hpushBranch : ExecWithin w + (ram[ if nil < childCount then [ ⟪push⟫ ] else [ ⟪advance⟫ ] ]) v y 16 := + hy.branch_pos hnonempty + exact (hv.seq hpushBranch).branch_pos + (by simpa only [Cond.holds_lt, hut', (fixed ten (by decide)).trans hregs.ten_eq] using hk) + have hum : u.regs mode = 1 := (fixed mode (by decide)).trans hm + have hu0 := (fixed nil (by decide)).trans hregs.nil_eq + have hu1 := (fixed one (by decide)).trans hregs.one_eq + have hbranch : ExecWithin w + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y (20 * N + 25) := + (hcall.branch_pos (by simp only [Cond.holds_eq, hum, hu1])).branch_neg + (by simp only [Cond.holds_eq, hum, hu0]; decide) + have he : ExecWithin w step x y (20 * N + 42) := (hu.seq hbranch).mono (by omega) + have hfbase : v.regs frames + v.regs nextLevel * 4 = f + 4 * (d + 1) := by + rw [hvf, hvn] + omega + have hchildFrame : Modifies (Block.mk (f + 4 * (d + 1)) 4).footprint v.mem y.mem := by + simpa only [hfbase] using hpushFrame + have hlistAway (q : ℕ) : ¬(Block.mk (f + 4 * (d + 1)) 4).Owns + (f + 4 * (L + 1) + (d + 1) * 10 ^ L + q) := by + change ¬(_ ≤ _ ∧ _ < f + 4 * (d + 1) + 4) + omega + have hstampAway (q : ℕ) : ¬(Block.mk (f + 4 * (d + 1)) 4).Owns + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) := by + change ¬(_ ≤ _ ∧ _ < f + 4 * (d + 1) + 4) + omega + have htable' : ScanMemory v.mem (f + 4 * (L + 1) + (d + 1) * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (x.regs epoch + 1) (10 ^ L) + (scanSet true k input N) := by + simpa only [huch', hus', hue, hut'] using htable + refine ⟨y, he, hregs.of_step he, hyl.trans hvn, hym, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact (hy.regs_eq (by decide : push.Preserves active)).trans + ((scanFixed active (by decide)).trans (fixed active (by decide))) + · exact (hy.regs_eq (by decide : push.Preserves epoch)).trans (by + simpa only [hue] using hve) + · exact htable'.congr + (fun i _ => hchildFrame _ (by simpa only [Block.mem_footprint] using hlistAway i)) + (fun q _ => hchildFrame _ (by simpa only [Block.mem_footprint] using hstampAway q)) + · rw [hmem, hfbase, hvc'] + simp + · rw [hmem, hfbase, hvp, hvt, hvst] + simp + · rw [hmem, hfbase, hvst] + simp [pow_succ] + · rw [hmem, hfbase] + simp + · intro c hc + have hn := Finset.notMem_union.mp hc + have hs := Finset.notMem_union.mp hn.1 + rw [hchildFrame c hn.2, hscanFrame] + · exact congrFun humem c + · simpa only [Block.mem_footprint, huch'] using hs.1 + · simpa only [Block.mem_footprint, hus'] using hs.2 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Clear.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Clear.lean new file mode 100644 index 0000000000..2ac85e9647 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Clear.lean @@ -0,0 +1,208 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Memory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Control + +/-! +# Sparse initialization of requested accumulators + +## Main definitions + +* `PrunedQuery.zeroQueryPrefix`: the memory after zeroing a listed prefix of query addresses. + +## Main results + +* `PrunedQuery.exec_clearRequested`: actual indexed clearing within `7 N + 3` instructions. + +Repeated requests are permitted. Unlisted cells are preserved, and no dense value directory +is initialized by this routine. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Finset + +/-- Clear precisely the addresses named in the processed prefix of an explicit query list. -/ +def zeroQueryPrefix (mem : ℕ → ℕ) (base : ℕ) (input : ℕ → ℕ) (k : ℕ) : ℕ → ℕ := + ((range k).image (fun i => base + input i)).piecewise (fun _ => 0) mem + +/-- Clearing no requests leaves memory unchanged. -/ +@[simp] +theorem zeroQueryPrefix_zero (mem : ℕ → ℕ) (base : ℕ) (input : ℕ → ℕ) : + zeroQueryPrefix mem base input 0 = mem := by + funext a + simp only [zeroQueryPrefix, Finset.range_zero, Finset.image_empty, Finset.piecewise, + Finset.notMem_empty, ↓reduceIte] + +/-- One additional listed request is exactly one memory store, including repeated requests. -/ +theorem zeroQueryPrefix_succ (mem : ℕ → ℕ) (base : ℕ) (input : ℕ → ℕ) (k : ℕ) : + zeroQueryPrefix mem base input (k + 1) = + Function.update (zeroQueryPrefix mem base input k) (base + input k) 0 := by + unfold zeroQueryPrefix + rw [Finset.range_add_one, Finset.image_insert, Finset.piecewise_insert] + +/-- Every cell outside the concrete value directory is preserved by a bounded prefix. -/ +theorem zeroQueryPrefix_outside {mem : ℕ → ℕ} {base capacity k : ℕ} {input : ℕ → ℕ} + (hcode : ∀ i < k, input i < capacity) {a : ℕ} + (ha : ¬(Block.mk base capacity).Owns a) : zeroQueryPrefix mem base input k a = mem a := by + have hnot : a ∉ (range k).image (fun i => base + input i) := by + rintro h + obtain ⟨i, hi, rfl⟩ := mem_image.mp h + apply ha + change base ≤ base + input i ∧ base + input i < base + capacity + have := hcode i (mem_range.mp hi) + omega + simp only [zeroQueryPrefix, piecewise, hnot, ↓reduceIte] + +/-- Every requested value in the processed prefix is actually zero in the resulting memory. -/ +theorem zeroQueryPrefix_at {mem : ℕ → ℕ} {base k i : ℕ} {input : ℕ → ℕ} (hi : i < k) : + zeroQueryPrefix mem base input k (base + input i) = 0 := by + have hmem : base + input i ∈ (range k).image (fun j => base + input j) := + mem_image.mpr ⟨i, mem_range.mpr hi, rfl⟩ + simp only [zeroQueryPrefix, piecewise, hmem, ↓reduceIte] + +/-- Registers modified by sparse clearing. -/ +def ClearLocal (i : Fin registers) : Prop := i = address ∨ i = query ∨ i = index + +instance (i : Fin registers) : Decidable (ClearLocal i) := + inferInstanceAs (Decidable (i = address ∨ i = query ∨ i = index)) + +private theorem clearStep_preserves (i : Fin registers) : + ¬ClearLocal i → clearValueStep.Preserves i := by decide +kernel +revert + +private theorem clear_preserves (i : Fin registers) : + ¬ClearLocal i → clearRequested.Preserves i := by decide +kernel +revert + +/-- Each requested word costs an indexed source load, indexed zero store and increment. -/ +theorem exec_clearValueStep {w : ℕ} (x : Data registers) (q : ℕ) + (hq : x.mem (x.regs listBase + x.regs index) = q) + (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (hr : Fits w (x.regs listBase + x.regs index)) + (hv : Fits w (x.regs valueBase + q)) (hi : Fits w (x.regs index + 1)) : + ∃ y, ExecWithin w clearValueStep x y 5 ∧ y.regs index = x.regs index + 1 ∧ + y.mem = Function.update x.mem (x.regs valueBase + q) 0 ∧ + (∀ i, ¬ClearLocal i → y.regs i = x.regs i) := by + let y := (((((x.set address (x.regs listBase + x.regs index)).set query q).set + address (x.regs valueBase + q)).write (x.regs valueBase + q) 0).set + index (x.regs index + 1)) + have he : ExecWithin w clearValueStep x y 5 := by + ram_eval [clearValueStep, y, hq, h0, h1, hr, hv, hi, address, query, index, listBase, + valueBase, nil, one, Fin.mk.injEq] + exact ⟨y, he, by simp [y], rfl, fun i hi => he.regs_eq (clearStep_preserves i hi)⟩ + +/-- The fixed native clearing loop visits only explicit requests. Width follows from concrete +source/destination endpoints and lengths, and source preservation follows from their separation. -/ +theorem exec_clearRequested {w N capacity : ℕ} (x : Data registers) (input : ℕ → ℕ) + (hN : x.regs count = N) + (hinput : ∀ i < N, x.mem (x.regs listBase + i) = input i) + (hcode : ∀ i < N, input i < capacity) + (hsep : x.regs listBase + N ≤ x.regs valueBase ∨ + x.regs valueBase + capacity ≤ x.regs listBase) + (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (hw : Fits w (x.regs listBase + N + x.regs valueBase + capacity + N + 2)) : + ∃ y, ExecWithin w clearRequested x y (7 * N + 3) ∧ y.regs index = N ∧ + y.mem = zeroQueryPrefix x.mem (x.regs valueBase) input N ∧ + (∀ i < N, y.mem (x.regs listBase + i) = input i) ∧ + (∀ i, ¬ClearLocal i → y.regs i = x.regs i) := by + let x0 := x.set index 0 + let I (y : Data registers) : Prop := y.regs index ≤ N ∧ + y.mem = zeroQueryPrefix x.mem (x.regs valueBase) input (y.regs index) ∧ + (∀ i, ¬ClearLocal i → y.regs i = x.regs i) + have hinit : I x0 := by + refine ⟨by simp [x0], by simp [x0], ?_⟩ + intro i hi + have hne : i ≠ index := by intro h; subst h; exact hi (Or.inr (Or.inr rfl)) + simp [x0, hne] + have hread (y : Data registers) (hy : I y) (i : ℕ) (hi : i < N) : + y.mem (x.regs listBase + i) = input i := by + rw [hy.2.1, zeroQueryPrefix_outside (fun j hj => hcode j (by omega))] + · exact hinput i hi + · change ¬(x.regs valueBase ≤ x.regs listBase + i ∧ + x.regs listBase + i < x.regs valueBase + capacity) + rcases hsep with h | h <;> omega + have hstep (y : Data registers) (hy : I y) (hc : (Cond.lt index count).Holds y) : + ∃ z, ExecWithin w clearValueStep y z 5 ∧ I z ∧ + N - z.regs index < N - y.regs index := by + obtain ⟨hle, hm, hf⟩ := hy + have hcount := (hf count (by decide)).trans hN + have hb := hf listBase (by decide) + have hv := hf valueBase (by decide) + have hnil := (hf nil (by decide)).trans h0 + have hone := (hf one (by decide)).trans h1 + have hk : y.regs index < N := by simpa only [Cond.holds_lt, hcount] using hc + have hq := hcode (y.regs index) hk + obtain ⟨z, hz, hi, hmem, hregs⟩ := exec_clearValueStep y (input (y.regs index)) + (by rw [hb]; exact hread y ⟨hle, hm, hf⟩ _ hk) hnil hone + (hw.of_le (by rw [hb]; omega)) (hw.of_le (by rw [hv]; omega)) (hw.of_le (by omega)) + refine ⟨z, hz, ⟨by omega, ?_, fun i hi => (hregs i hi).trans (hf i hi)⟩, by omega⟩ + rw [hmem, hm, hv, hi, zeroQueryPrefix_succ] + obtain ⟨y, hy, hIy, hstop⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs index) + hstep hinit + have hcount := (hIy.2.2 count (by decide)).trans hN + have hindex : y.regs index = N := by + simp only [Cond.holds_lt, hcount, not_lt] at hstop + omega + have hsetup : ExecWithin w (ram[ index ← #0 ] : Code .standard registers) x x0 1 := by + ram_eval [x0] + have hloop : ExecWithin w (Code.loop (.lt index count) clearValueStep) x0 y (7 * N + 2) := by + simpa only [x0, Data.set_regs, Function.update_self, Nat.sub_zero, Nat.mul_comm] using hy + have he : ExecWithin w clearRequested x y (7 * N + 3) := + (hsetup.seq hloop).mono (by omega) + refine ⟨y, he, hindex, ?_, hread y hIy, fun i hi => he.regs_eq (clear_preserves i hi)⟩ + simpa only [hindex] using hIy.2.1 + +/-- Registers changed by a complete internal-node initialization. -/ +def ClearValuesLocal (i : Fin registers) : Prop := ClearLocal i ∨ AdvanceLocal i + +instance (i : Fin registers) : Decidable (ClearValuesLocal i) := + inferInstanceAs (Decidable (ClearLocal i ∨ AdvanceLocal i)) + +private theorem clearValues_preserves (i : Fin registers) : + ¬ClearValuesLocal i → clearValues.Preserves i := by decide +kernel +revert + +/-- Sparse clearing and actual stored-frame initialization together cost `7 N + 8` instructions. +The source list is preserved; the only additional memory write is its frame's fourth word. -/ +theorem exec_clearValues {w N capacity : ℕ} (x : Data registers) (input : ℕ → ℕ) + (hN : x.regs count = N) + (hinput : ∀ i < N, x.mem (x.regs listBase + i) = input i) + (hcode : ∀ i < N, input i < capacity) + (hsep : x.regs listBase + N ≤ x.regs valueBase ∨ + x.regs valueBase + capacity ≤ x.regs listBase) + (hframeSep : x.regs frame + 4 ≤ x.regs listBase ∨ + x.regs listBase + N ≤ x.regs frame) + (h0 : x.regs nil = 0) (h1 : x.regs one = 1) (h4 : x.regs frameWidth = 4) + (hw : Fits w (x.regs listBase + N + x.regs valueBase + capacity + N + 2)) + (hf : Fits w (x.regs frame + 4)) : + ∃ y, ExecWithin w clearValues x y (7 * N + 8) ∧ y.regs index = N ∧ + y.regs term = 0 ∧ y.regs mode = 1 ∧ + y.mem = Function.update (zeroQueryPrefix x.mem (x.regs valueBase) input N) + (x.regs frame + 3) 0 ∧ + (∀ i < N, y.mem (x.regs listBase + i) = input i) ∧ + (∀ i, ¬ClearValuesLocal i → y.regs i = x.regs i) := by + obtain ⟨z, hz, hindex, hmem, hread, hregs⟩ := + exec_clearRequested x input hN hinput hcode hsep h0 h1 hw + have hframe := hregs frame (by decide) + obtain ⟨y, hy, hterm, hmode, hmem', hregs'⟩ := exec_clearValuesFinish z + ((hregs one (by decide)).trans h1) ((hregs frameWidth (by decide)).trans h4) + (by simpa only [hframe] using hf) + have he : ExecWithin w clearValues x y (7 * N + 8) := (hz.seq hy).mono (by omega) + have hm : y.mem = Function.update z.mem (x.regs frame + 3) 0 := by + simpa only [hframe] using hmem' + refine ⟨y, he, (hregs' index (by decide)).trans hindex, hterm, hmode, + by rw [hm, hmem], ?_, fun i hi => he.regs_eq (clearValues_preserves i hi)⟩ + intro i hi + have hne : x.regs listBase + i ≠ x.regs frame + 3 := by + rcases hframeSep with h | h <;> omega + rw [hm, Function.update_of_ne hne] + exact hread i hi + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Codes.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Codes.lean new file mode 100644 index 0000000000..0e9ed5be19 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Codes.lean @@ -0,0 +1,199 @@ +/- +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.Pruned +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Meaning + +/-! +# Numerical query codes for native pruned evaluation + +The output variables use the existing term numbering through their private terms: the nine +outer outputs have digits zero through eight and the inner output has digit nine. The first +coordinate is the least significant digit, exactly as for the encoded leaf arrays. + +## Main definitions + +* `PrunedQuery.queryIndex`: the flat code of an output string. +* `PrunedQuery.childCodes`: the child request set, computed by digit tests and division. + +## Main results + +* `PrunedQuery.queryIndex_privateLeaf`: agreement with the existing leaf-array indexing. +* `PrunedQuery.queryIndex_cons`: the numerical first-digit decomposition. + +## References + +The child-set definition is adapted from [AlmanWilliams2026, Section 2.4.2] and Anthropic's +formal source at revision `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +[pinned source](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Sec2/Lemma10.lean). These are finite coding identities, not native run claims. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Output digits follow their private terms in the existing encoder's numbering. -/ +def outputDigits : Fin 10 ≃ OutVar where + toFun t := match Encoding.termAt t with + | .P i j => .z i j + | .P0 => .z0 + invFun z := Encoding.termDigits.symm z.privateTerm + left_inv := by decide + right_inv := by decide + +/-- The least-significant-first flat code of an output string. -/ +def queryIndex {L : ℕ} (w : OutStr L) : Fin (10 ^ L) := + Encoding.leafIndex (privateLeaf w) + +/-- Decode a supplied finite query code with the existing executable digit enumeration. -/ +def queryAt {L : ℕ} (q : Fin (10 ^ L)) : OutStr L := + fun i => outputDigits ((finFunctionFinEquiv.symm q) i) + +/-- Encoding a decoded query returns the actual supplied code. -/ +@[simp] +theorem queryIndex_queryAt {L : ℕ} (q : Fin (10 ^ L)) : queryIndex (queryAt q) = q := by + change (finFunctionFinEquiv fun i => + outputDigits.symm (outputDigits (finFunctionFinEquiv.symm q i))) = q + simp only [Equiv.symm_apply_apply, Equiv.apply_symm_apply] + +/-- Decoding a query's code returns every output coordinate unchanged. -/ +@[simp] +theorem queryAt_queryIndex {L : ℕ} (w : OutStr L) : queryAt (queryIndex w) = w := by + funext i + change outputDigits ((finFunctionFinEquiv.symm + (finFunctionFinEquiv fun j => outputDigits.symm (w j))) i) = w i + simp only [Equiv.symm_apply_apply, Equiv.apply_symm_apply] + +/-- The requested output set is computed from the ordinary initialized list of query codes. +Repeated list entries are allowed and will be reported in their original order. -/ +def requested {L : ℕ} (qs : List (Fin (10 ^ L))) : Finset (OutStr L) := + qs.toFinset.image queryAt + +/-- The request set's numerical image is precisely the set of supplied numerical codes. -/ +theorem image_queryIndex_requested {L : ℕ} (qs : List (Fin (10 ^ L))) : + (requested qs).image (fun w => (queryIndex w).val) = qs.toFinset.image Fin.val := by + simp only [requested, Finset.image_image, Function.comp_def, queryIndex_queryAt] + +/-- Every supplied list entry is one of the requested mathematical outputs. -/ +theorem queryAt_mem_requested {L : ℕ} {qs : List (Fin (10 ^ L))} {q : Fin (10 ^ L)} + (hq : q ∈ qs) : queryAt q ∈ requested qs := + Finset.mem_image.mpr ⟨q, List.mem_toFinset.mpr hq, rfl⟩ + +/-- A query's code is the leaf-array address of its private leaf. -/ +theorem queryIndex_privateLeaf {L : ℕ} (w : OutStr L) : + queryIndex w = Encoding.leafIndex (privateLeaf w) := rfl + +/-- Every flat code denotes exactly one output string. -/ +theorem queryIndex_bijective (L : ℕ) : Function.Bijective (queryIndex (L := L)) := + finFunctionFinEquiv.bijective.comp + (Equiv.piCongrRight fun _ : Fin L => outputDigits.symm).bijective + +/-- Replacing output strings by their numerical codes preserves a request-set cardinality. -/ +theorem card_image_queryIndex {L : ℕ} (S : Finset (OutStr L)) : + (S.image (fun w => (queryIndex w).val)).card = S.card := + Finset.card_image_of_injective S (fun _ _ he => + (queryIndex_bijective L).injective (Fin.ext he)) + +/-- The first query digit is its output-variable digit; the suffix is shifted by one place. -/ +theorem queryIndex_cons {L : ℕ} (z : OutVar) (w : OutStr L) : + (queryIndex (Fin.cons z w) : ℕ) = (outputDigits.symm z).val + 10 * (queryIndex w).val := by + simp only [queryIndex, Encoding.leafIndex, privateLeaf, 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 Finset.sum_congr rfl + intro i _ + ring + +/-- Taking the first numerical digit recovers the first output variable. -/ +theorem queryIndex_mod {L : ℕ} (w : OutStr (L + 1)) : + (queryIndex w).val % 10 = (outputDigits.symm (w 0)).val := by + have he := queryIndex_cons (w 0) (Fin.tail w) + rw [Fin.cons_self_tail] at he + rw [he] + have h := (outputDigits.symm (w 0)).isLt + omega + +/-- Integer division discards the first output coordinate. -/ +theorem queryIndex_div {L : ℕ} (w : OutStr (L + 1)) : + (queryIndex w).val / 10 = (queryIndex (Fin.tail w)).val := by + have he := queryIndex_cons (w 0) (Fin.tail w) + rw [Fin.cons_self_tail] at he + rw [he] + have h := (outputDigits.symm (w 0)).isLt + omega + +/-- A term contributes to the inner digit or to its own outer digit. -/ +theorem contributes_iff_digit (t : Fin 10) (z : OutVar) : + (Encoding.termDigits t).Contributes z ↔ + (outputDigits.symm z).val = 9 ∨ (outputDigits.symm z).val = t.val := by + decide +revert + +/-- Numerical child requests are selected by the actual digit tests, then shortened by division. +The finite-set image removes repeated suffixes. Native code will implement that deduplication. +-/ +def childCodes (S : Finset ℕ) (term : ℕ) : Finset ℕ := + (S.filter fun q => q % 10 = 9 ∨ q % 10 = term).image fun q => q / 10 + +/-- Child suffixes stay within the smaller numerical query space. -/ +theorem childCodes_bounded {n : ℕ} {S : Finset ℕ} (term : ℕ) + (hS : ∀ q ∈ S, q < 10 ^ (n + 1)) : + ∀ q ∈ childCodes S term, q < 10 ^ n := by + intro q hq + obtain ⟨p, hp, rfl⟩ := Finset.mem_image.mp hq + have h := hS p (Finset.mem_filter.mp hp).1 + apply (Nat.div_lt_iff_lt_mul (by decide : 0 < 10)).mpr + simpa only [pow_succ] using h + +/-- The numerical child requests agree exactly with the existing finite pruning definition. -/ +theorem mem_childCodes {L : ℕ} (S : Finset (OutStr (L + 1))) (t : Fin 10) (w : OutStr L) : + (queryIndex w).val ∈ childCodes (S.image fun v => (queryIndex v).val) t.val ↔ + w ∈ childSet S (Encoding.termDigits t) := by + constructor + · intro h + obtain ⟨q, hq, htail⟩ := Finset.mem_image.mp h + obtain ⟨hparent, hmatch⟩ := Finset.mem_filter.mp hq + obtain ⟨v, hv, rfl⟩ := Finset.mem_image.mp hparent + rw [queryIndex_div] at htail + have htw : Fin.tail v = w := (queryIndex_bijective L).injective (Fin.ext htail) + have hc : (Encoding.termDigits t).Contributes (v 0) := by + rw [contributes_iff_digit] + simpa only [queryIndex_mod] using hmatch + rw [mem_childSet] + exact ⟨v, hv, hc, htw⟩ + · intro h + obtain ⟨v, hv, hc, htail⟩ := mem_childSet.mp h + apply Finset.mem_image.mpr + refine ⟨(queryIndex v).val, Finset.mem_filter.mpr ⟨?_, ?_⟩, ?_⟩ + · exact Finset.mem_image.mpr ⟨v, hv, rfl⟩ + · rw [queryIndex_mod] + exact (contributes_iff_digit t (v 0)).mp hc + · rw [queryIndex_div, htail] + +/-- The whole native child-set specification is the numerical image of the existing child set. -/ +theorem childCodes_eq_image {L : ℕ} (S : Finset (OutStr (L + 1))) (t : Fin 10) : + childCodes (S.image fun w => (queryIndex w).val) t.val = + (childSet S (Encoding.termDigits t)).image (fun w => (queryIndex w).val) := by + ext q + constructor + · intro hq + have hbound := childCodes_bounded t.val (S := S.image fun w => (queryIndex w).val) + (fun p hp => by + obtain ⟨v, _, rfl⟩ := Finset.mem_image.mp hp + exact (queryIndex v).isLt) q hq + obtain ⟨w, hw⟩ := (queryIndex_bijective L).surjective ⟨q, hbound⟩ + have he : (queryIndex w).val = q := congrArg Fin.val hw + exact Finset.mem_image.mpr ⟨w, (mem_childCodes S t w).mp (he.symm ▸ hq), he⟩ + · intro hq + obtain ⟨w, hw, rfl⟩ := Finset.mem_image.mp hq + exact (mem_childCodes S t w).mpr hw + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Control.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Control.lean new file mode 100644 index 0000000000..0570474406 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Control.lean @@ -0,0 +1,129 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Charged native return and term-advance transitions + +## Main results + +* `PrunedQuery.exec_pop`: return to the parent's accumulation phase or terminate the root. +* `PrunedQuery.exec_advance`: store the incremented term counter in the actual parent frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers changed by a return transition. -/ +def PopLocal (i : Fin registers) : Prop := i = active ∨ i = level ∨ i = mode + +instance (i : Fin registers) : Decidable (PopLocal i) := + inferInstanceAs (Decidable (i = active ∨ i = level ∨ i = mode)) + +private theorem pop_preserves (i : Fin registers) : ¬PopLocal i → pop.Preserves i := by + decide +kernel +revert + +/-- The return decision is the program's real comparison with zero. Both branches preserve +memory and all registers outside the three controller fields. -/ +theorem exec_pop {w : ℕ} (x : Data registers) (h0 : x.regs nil = 0) + (h1 : x.regs one = 1) (hw : Fits w (x.regs level + 2)) : + ∃ y, ExecWithin w pop x y 4 ∧ y.mem = x.mem ∧ + y.regs level = (if x.regs level = 0 then 0 else x.regs level - 1) ∧ + y.regs mode = (if x.regs level = 0 then x.regs mode else 2) ∧ + y.regs active = (if x.regs level = 0 then 0 else x.regs active) ∧ + (∀ i, ¬PopLocal i → y.regs i = x.regs i) := by + have h2 : Fits w 2 := hw.of_le (by omega) + have hd : Fits w (x.regs level - 1) := hw.of_le (by omega) + by_cases hroot : x.regs level = 0 + · let y := x.set active 0 + have he : ExecWithin w pop x y 2 := by + ram_eval [pop, y, hroot, h0] + refine ⟨y, he.mono (by decide), rfl, ?_, ?_, ?_, + fun i hi => he.regs_eq (pop_preserves i hi)⟩ + all_goals simp +decide [y, hroot, active, level, mode] + · let y := (x.set level (x.regs level - 1)).set mode 2 + have he : ExecWithin w pop x y 4 := by + ram_eval [pop, y, hroot, h0, h1, h2, hd, level, mode, one, Fin.mk.injEq] + refine ⟨y, he, rfl, ?_, ?_, ?_, fun i hi => he.regs_eq (pop_preserves i hi)⟩ + all_goals simp +decide [y, hroot, active, level, mode] + +/-- Registers changed by advancing and storing a term counter. -/ +def AdvanceLocal (i : Fin registers) : Prop := i = term ∨ i = address ∨ i = mode + +instance (i : Fin registers) : Decidable (AdvanceLocal i) := + inferInstanceAs (Decidable (i = term ∨ i = address ∨ i = mode)) + +private theorem advance_preserves (i : Fin registers) : + ¬AdvanceLocal i → advance.Preserves i := by decide +kernel +revert + +/-- Store a term already in its register and switch to dispatch, charging all four operations. -/ +theorem exec_storeTerm {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) (hf : Fits w (x.regs frame + 4)) : + ∃ y, ExecWithin w storeTerm x y 4 ∧ y.regs mode = 1 ∧ + y.mem = Function.update x.mem (x.regs frame + 3) (x.regs term) ∧ + (∀ i, i ≠ address → i ≠ mode → y.regs i = x.regs i) := by + have hmode : Fits w 1 := hf.of_le (by omega) + have hadd : Fits w (x.regs frame + 3) := hf.of_le (by omega) + let y := ((((x.set address (x.regs frame + 4)).set address (x.regs frame + 3)).write + (x.regs frame + 3) (x.regs term)).set mode 1) + have hsub : x.regs frame + 4 - 1 = x.regs frame + 3 := by omega + have he : ExecWithin w storeTerm x y 4 := by + ram_eval [storeTerm, y, h1, h4, hf, hmode, hadd, hsub, address, mode, one, + frame, frameWidth, term, Fin.mk.injEq] + refine ⟨y, he, by simp [y], rfl, ?_⟩ + intro i ha hm + exact he.regs_eq (by decide +kernel +revert) + +/-- The term increment, frame-word address computation, store and phase assignment take five +instructions. Only the fourth frame word changes in memory. -/ +theorem exec_advance {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) (ht : Fits w (x.regs term + 1)) + (hf : Fits w (x.regs frame + 4)) : + ∃ y, ExecWithin w advance x y 5 ∧ y.regs term = x.regs term + 1 ∧ + y.regs mode = 1 ∧ + y.mem = Function.update x.mem (x.regs frame + 3) (x.regs term + 1) ∧ + (∀ i, ¬AdvanceLocal i → y.regs i = x.regs i) := by + let z := x.set term (x.regs term + 1) + have hpre : ExecWithin w (ram[ term ← term + one ]) x z 1 := by + ram_eval [z, h1, ht] + obtain ⟨y, hy, hm, hmem, hreg⟩ := exec_storeTerm z + (by simpa +decide [z, one, term] using h1) + (by simpa +decide [z, frameWidth, term] using h4) + (by simpa +decide [z, frame, term] using hf) + have he : ExecWithin w advance x y 5 := hpre.seq hy + refine ⟨y, he, ?_, hm, ?_, fun i hi => he.regs_eq (advance_preserves i hi)⟩ + · simpa [z] using hreg term (by decide) (by decide) + · simpa +decide [z, frame, term] using hmem + +private theorem clearFinish_preserves (i : Fin registers) : + ¬AdvanceLocal i → clearValuesFinish.Preserves i := by decide +kernel +revert + +/-- Initialization uses the same actual frame store as advancement, with a zero term counter. -/ +theorem exec_clearValuesFinish {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) (hf : Fits w (x.regs frame + 4)) : + ∃ y, ExecWithin w clearValuesFinish x y 5 ∧ y.regs term = 0 ∧ y.regs mode = 1 ∧ + y.mem = Function.update x.mem (x.regs frame + 3) 0 ∧ + (∀ i, ¬AdvanceLocal i → y.regs i = x.regs i) := by + let z := x.set term 0 + have hpre : ExecWithin w (ram[ term ← #0 ] : Code .standard registers) x z 1 := by + ram_eval [z] + obtain ⟨y, hy, hm, hmem, hreg⟩ := exec_storeTerm z + (by simpa +decide [z, one, term] using h1) + (by simpa +decide [z, frameWidth, term] using h4) + (by simpa +decide [z, frame, term] using hf) + have he : ExecWithin w clearValuesFinish x y 5 := hpre.seq hy + refine ⟨y, he, ?_, hm, ?_, fun i hi => he.regs_eq (clearFinish_preserves i hi)⟩ + · simpa [z] using hreg term (by decide) (by decide) + · simpa +decide [z, frame, term] using hmem + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Emit.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Emit.lean new file mode 100644 index 0000000000..75dcb9f0e0 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Emit.lean @@ -0,0 +1,81 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Charged timestamp insertion for native child queries + +## Main results + +* `PrunedQuery.exec_emit`: one native membership test and optional unique suffix insertion, + within nine instructions, with the complete memory update and register frame. + +This kernel does not assume that a child request set was computed elsewhere. The surrounding +native scan supplies each tested suffix and uses the returned timestamp/list updates. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Exactly the registers a timestamp insertion can modify. -/ +def EmitLocal (i : Fin registers) : Prop := + i = address ∨ i = marker ∨ i = temporary ∨ i = childCount + +instance (i : Fin registers) : Decidable (EmitLocal i) := + inferInstanceAs (Decidable (i = address ∨ i = marker ∨ i = temporary ∨ i = childCount)) + +private theorem emit_preserves (i : Fin registers) : ¬EmitLocal i → emit.Preserves i := by + decide +kernel +revert + +/-- Timestamp insertion performs its real load, comparison, optional stores and increment. +The result states the exact memory update even before a caller supplies array separation. -/ +theorem exec_emit {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (hs : Fits w (x.regs stamps + x.regs suffix)) + (hl : Fits w (x.regs childList + x.regs childCount)) + (hn : Fits w (x.regs childCount + 1)) : + ∃ y, ExecWithin w emit x y 9 ∧ + y.regs childCount = + (if x.mem (x.regs stamps + x.regs suffix) = x.regs epoch then + x.regs childCount else x.regs childCount + 1) ∧ + y.mem = + (if x.mem (x.regs stamps + x.regs suffix) = x.regs epoch then x.mem else + Function.update + (Function.update x.mem (x.regs childList + x.regs childCount) (x.regs suffix)) + (x.regs stamps + x.regs suffix) (x.regs epoch)) ∧ + (∀ i, ¬EmitLocal i → y.regs i = x.regs i) := by + let s := x.regs stamps + x.regs suffix + let z := (x.set address s).set marker (x.mem s) + by_cases h : x.mem s = x.regs epoch + · let y := z.set temporary (z.regs temporary) + have he : ExecWithin w emit x y 4 := by + ram_eval [emit, z, y, s, h, hs, address, stamps, suffix, marker, epoch, temporary, + childCount, childList, one, Fin.mk.injEq] + refine ⟨y, he.mono (by decide), ?_, ?_, fun i hi => he.regs_eq (emit_preserves i hi)⟩ + · simp only [s] at h + simp [y, z, h, childCount, address, marker, temporary] + · simp only [s] at h + simp [y, z, h] + · let y := + (((((z.set address (x.regs childList + x.regs childCount)).write + (x.regs childList + x.regs childCount) (x.regs suffix)).set address s).write + s (x.regs epoch)).set childCount (x.regs childCount + 1)) + have he : ExecWithin w emit x y 9 := by + ram_eval [emit, z, y, s, h, hs, hl, hn, h1, address, stamps, suffix, marker, epoch, + temporary, childCount, childList, one, Fin.mk.injEq] + refine ⟨y, he, ?_, ?_, fun i hi => he.regs_eq (emit_preserves i hi)⟩ + · simp only [s] at h + simp [y, h] + · simp only [s] at h + simp [y, z, h, s, Data.write_mem] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/EmptyChild.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/EmptyChild.lean new file mode 100644 index 0000000000..0401bf21eb --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/EmptyChild.lean @@ -0,0 +1,165 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Child + +/-! +# Actual empty-child transitions + +## Main results + +* `PrunedQuery.exec_node_emptyChild`: compute an empty child by real native scanning, + advance the parent term, and preserve its request list and partial values. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- An empty-child transition may change the temporary child list, shared stamps and the +parent frame. Its first three frame words are also explicitly preserved below. -/ +def emptyChildWrites (L f d : ℕ) : Finset ℕ := + (Block.mk (f + 4 * (L + 1) + (d + 1) * 10 ^ L) (10 ^ L)).footprint ∪ + (Block.mk (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (10 ^ L)).footprint ∪ + (Block.mk (f + 4 * d) 4).footprint + +/-- The program itself observes its computed zero child count and skips the recursive push. +Every scan, comparison, increment and stored counter is charged. -/ +theorem exec_node_emptyChild {w L f a0 b0 d N k : ℕ} (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hd : x.regs level = d) (hdL : d < L) + (hm : x.regs mode = 1) + (hN : x.mem (f + 4 * d) = N) (hcap : N ≤ 10 ^ L) + (hterm : x.mem (f + 4 * d + 3) = k) (hk : k < 10) + (input : ℕ → ℕ) + (hinput : ∀ i < N, x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i) + (hcodes : ∀ i < N, input i < 10 ^ L) + (hmarks : ∀ q < 10 ^ L, + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ x.regs epoch) + (hempty : scanSet true k input N = ∅) + (hw : Fits w (8 * f + 64 * (L + 1) * 10 ^ L + x.regs epoch + 128)) : + ∃ y, ExecWithin w step x y (20 * N + 33) ∧ KernelRegs L f a0 b0 y ∧ + y.regs level = d ∧ y.regs mode = 1 ∧ y.regs active = x.regs active ∧ + y.regs epoch = x.regs epoch + 1 ∧ + ScanMemory y.mem (f + 4 * (L + 1) + (d + 1) * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (x.regs epoch + 1) (10 ^ L) ∅ ∧ + (∀ j < 3, y.mem (f + 4 * d + j) = x.mem (f + 4 * d + j)) ∧ + y.mem (f + 4 * d + 3) = k + 1 ∧ + Modifies (emptyChildWrites L f d) x.mem y.mem := by + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + have hmul : (d + 1) * 10 ^ L ≤ (L + 1) * 10 ^ L := + Nat.mul_le_mul_right _ (by omega) + obtain ⟨u, hu, humem, huf, huc, _, _, hut, hul, _, hun, huch, _, hureg⟩ := + exec_loadFrame x hregs.one_eq hregs.frameWidth_eq (hw.of_le (by + rw [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, hd] + nlinarith only [hpow, hL, hmul])) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have hul' : u.regs listBase = f + 4 * (L + 1) + d * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using hul + have huch' : u.regs childList = f + 4 * (L + 1) + (d + 1) * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using huch + have hus' : u.regs stamps = f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L := + (fixed stamps (by decide)).trans hregs.stamps_eq + have huc' : u.regs count = N := by simpa only [hregs.frames_eq, hd, hN] using huc + have hut' : u.regs term = k := by simpa only [hregs.frames_eq, hd, hterm] using hut + have hue : u.regs epoch = x.regs epoch := fixed epoch (by decide) + obtain ⟨v, hv, _, hve, hvc, htable, _, hscanFrame, hvreg⟩ := + exec_scan (N := N) (capacity := 10 ^ L) true u input huc' + (by simpa only [hul', humem] using hinput) + (fun i hi => (Nat.div_le_self _ _).trans_lt (hcodes i hi)) + (Or.inl (by rw [huch', hus']; nlinarith only [hmul, hpow])) + (Or.inl (by rw [hul', huch']; nlinarith only [hcap])) + (Or.inl (by rw [hul', hus']; nlinarith only [hcap, hmul, hpow])) + (by simpa only [hus', humem, hue] using hmarks) + ((fixed one (by decide)).trans hregs.one_eq) + ((fixed ten (by decide)).trans hregs.ten_eq) + ((fixed nine (by decide)).trans hregs.nine_eq) + (hw.of_le (by + rw [hul', huch', hus', hue] + nlinarith only [hcap, hmul, hpow, hL])) + have scanFixed (i : Fin registers) (hi : ¬ScanLocal i) : v.regs i = u.regs i := hvreg i hi + have huf' : u.regs frame = f + 4 * d := by + simpa only [hregs.frames_eq, hd] using huf + have hvf : v.regs frame = f + 4 * d := (scanFixed frame (by decide)).trans huf' + have hvt : v.regs term = k := (scanFixed term (by decide)).trans hut' + have hv0 := (scanFixed nil (by decide)).trans ((fixed nil (by decide)).trans hregs.nil_eq) + have hv1 := (scanFixed one (by decide)).trans ((fixed one (by decide)).trans hregs.one_eq) + have hv4 := (scanFixed frameWidth (by decide)).trans + ((fixed frameWidth (by decide)).trans hregs.frameWidth_eq) + have hvc' : v.regs childCount = 0 := by + simpa only [hut', hempty, Finset.card_empty] using hvc + obtain ⟨y, hy, _, hym, hmem, _⟩ := exec_advance v hv1 hv4 + (hw.of_le (by rw [hvt]; omega)) (hw.of_le (by rw [hvf]; nlinarith only [hL, hdL])) + have hcall : ExecWithin w dispatch u y (20 * N + 13) := by + have hskip : ExecWithin w + (ram[ if nil < childCount then [ ⟪push⟫ ] else [ ⟪advance⟫ ] ]) v y 7 := + hy.branch_neg (by simp only [Cond.holds_lt, hv0, hvc', lt_self_iff_false, + not_false_eq_true]) + exact (hv.seq hskip).branch_pos + (by simpa only [Cond.holds_lt, hut', (fixed ten (by decide)).trans hregs.ten_eq] using hk) + have hum : u.regs mode = 1 := (fixed mode (by decide)).trans hm + have hu0 := (fixed nil (by decide)).trans hregs.nil_eq + have hu1 := (fixed one (by decide)).trans hregs.one_eq + have hbranch : ExecWithin w + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y (20 * N + 16) := + (hcall.branch_pos (by simp only [Cond.holds_eq, hum, hu1])).branch_neg + (by simp only [Cond.holds_eq, hum, hu0]; decide) + have he : ExecWithin w step x y (20 * N + 33) := (hu.seq hbranch).mono (by omega) + have hmem' : y.mem = Function.update v.mem (f + 4 * d + 3) (k + 1) := by + simpa only [hvf, hvt] using hmem + have hframe : Modifies (Block.mk (f + 4 * d) 4).footprint v.mem y.mem := by + rw [hmem'] + exact Modifies.write_of_owns (by constructor <;> omega) _ + have hlistAway (q : ℕ) : ¬(Block.mk (f + 4 * d) 4).Owns + (f + 4 * (L + 1) + (d + 1) * 10 ^ L + q) := by + change ¬(_ ≤ _ ∧ _ < f + 4 * d + 4) + omega + have hstampAway (q : ℕ) : ¬(Block.mk (f + 4 * d) 4).Owns + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) := by + change ¬(_ ≤ _ ∧ _ < f + 4 * d + 4) + omega + have htable' : ScanMemory v.mem (f + 4 * (L + 1) + (d + 1) * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (x.regs epoch + 1) (10 ^ L) ∅ := by + simpa only [huch', hus', hue, hut', hempty] using htable + refine ⟨y, he, hregs.of_step he, ?_, hym, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact (hy.regs_eq (by decide : advance.Preserves level)).trans + ((scanFixed level (by decide)).trans ((fixed level (by decide)).trans hd)) + · exact (hy.regs_eq (by decide : advance.Preserves active)).trans + ((scanFixed active (by decide)).trans (fixed active (by decide))) + · exact (hy.regs_eq (by decide : advance.Preserves epoch)).trans (by + simpa only [hue] using hve) + · exact htable'.congr + (fun i _ => hframe _ (by simpa only [Block.mem_footprint] using hlistAway i)) + (fun q _ => hframe _ (by simpa only [Block.mem_footprint] using hstampAway q)) + · intro j hj + rw [hmem', Function.update_of_ne (by omega : f + 4 * d + j ≠ f + 4 * d + 3), + hscanFrame] + · exact congrFun humem _ + · rw [huch'] + change ¬(f + 4 * (L + 1) + (d + 1) * 10 ^ L ≤ f + 4 * d + j ∧ + f + 4 * d + j < f + 4 * (L + 1) + (d + 1) * 10 ^ L + 10 ^ L) + omega + · rw [hus'] + change ¬(f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L ≤ f + 4 * d + j ∧ + f + 4 * d + j < f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + 10 ^ L) + omega + · simp only [hmem', Function.update_self] + · intro c hc + have hn := Finset.notMem_union.mp hc + have hs := Finset.notMem_union.mp hn.1 + rw [hframe c hn.2, hscanFrame] + · exact congrFun humem c + · simpa only [Block.mem_footprint, huch'] using hs.1 + · simpa only [Block.mem_footprint, hus'] using hs.2 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Evaluate.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Evaluate.lean new file mode 100644 index 0000000000..e3020ce65d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Evaluate.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.MatrixMultiplication.Schonhage.Pruned.Recursive +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Root +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Report + +/-! +# Complete native root evaluation and ordered reporting + +## Main results + +* `PrunedQuery.exec_evaluate`: actual root deduplication, all-depth native traversal and ordered + reporting, allowing duplicate input queries and arbitrary original memory outside the workspace. + +The query function specifies the words of the supplied input list. The machine computes its own +distinct set, every recursive child list and every reported index. All scratch addresses, signed +fits, fresh generations and instruction charges follow from the ordinary numerical premises. +This module is an internal implementation API used to prove the initialized and pointer-based +client interfaces in `Pruned.Flat`, `Pruned.Width` and `Pruned.Kernel`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Evaluate the actual supplied queries after workspace pointers have been computed. The result +uses the existing finite request-set semantics; no initialized directory or traversal certificate +is supplied. Only the original source cells and an upper bound on old timestamps are required. -/ +theorem exec_evaluate {w L f a0 b0 q0 o0 Q E A B : ℕ} + (a b : Leaf L → ℤ) (qs : Fin Q → OutStr L) (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hQ : x.regs queryCount = Q) + (hq0 : x.regs queries = q0) (ho0 : x.regs output = o0) (hE : x.regs epoch = E) + (hinput : ∀ i, x.mem (q0 + i.val) = (queryIndex (qs i)).val) + (hleft : ∀ v, x.mem (a0 + (Encoding.leafIndex v).val) = encode (a v)) + (hright : ∀ v, x.mem (b0 + (Encoding.leafIndex v).val) = encode (b v)) + (hbeforeA : a0 + 10 ^ L ≤ f) (hbeforeB : b0 + 10 ^ L ≤ f) + (hqueryOut : q0 + Q ≤ o0) (houtFrame : o0 + Q ≤ f) + (hmarks : ∀ q < 10 ^ L, + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ E) + (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hw : 0 < w) (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (evaluationBudget L f a0 b0 q0 o0 Q E)) : + ∃ y, ExecWithin (w + 2) (ram[ ⟪rootList⟫; ⟪controller⟫; ⟪report⟫ ]) x y + (29 * Q + traceWeight (Finset.univ.image qs) + 24) ∧ + (∀ i, y.mem (o0 + i.val) = encode + (Pruned.result L a b (Finset.univ.image qs) (qs i))) ∧ + (∀ c, ¬(o0 ≤ c ∧ c < o0 + Q) → + ¬(f ≤ c ∧ c < f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + 10 ^ L) → + y.mem c = x.mem c) ∧ y.regs output = o0 ∧ + E + 1 ≤ y.regs epoch ∧ y.regs epoch ≤ E + 1 + epochAllowance L ∧ + (∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ y.regs epoch) := by + let S : Finset (OutStr L) := Finset.univ.image qs + let codes := fun i => if hi : i < Q then (queryIndex (qs ⟨i, hi⟩)).val else 0 + have himage : (Finset.range Q).image codes = S.image (fun v => (queryIndex v).val) := by + ext c + simp only [Finset.mem_image, Finset.mem_range, S, Finset.mem_univ, true_and] + constructor + · rintro ⟨i, hi, he⟩ + exact ⟨qs ⟨i, hi⟩, ⟨⟨i, hi⟩, rfl⟩, by simpa only [codes, dite_eq_left hi] using he⟩ + · rintro ⟨v, ⟨i, rfl⟩, rfl⟩ + exact ⟨i.val, i.isLt, by simp only [codes, dite_eq_left i.isLt]⟩ + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + have hcodes (i : ℕ) (hi : i < Q) : codes i < 10 ^ L := by + simpa only [codes, dite_eq_left hi] using (queryIndex (qs ⟨i, hi⟩)).isLt + obtain ⟨u, hroot, hul, hum, hua, hue, hur, huc, hup, hus, hut, _, hrootFrame, _⟩ := + exec_rootList (N := Q) (capacity := 10 ^ L) x codes hQ + (fun i hi => by simpa only [hq0, codes, dite_eq_left hi] using hinput ⟨i, hi⟩) hcodes + (by rw [hregs.lists_eq, hregs.stamps_eq]; nlinarith only [hpow]) + (by rw [hq0, hregs.lists_eq]; omega) + (by rw [hregs.frames_eq, hregs.lists_eq]; omega) + (by rw [hq0, hregs.frames_eq]; omega) + (by simpa only [hregs.stamps_eq, hE] using hmarks) + hregs.nil_eq hregs.one_eq hregs.ten_eq hregs.nine_eq + (hfit.of_le (by + rw [hq0, hregs.lists_eq, hregs.stamps_eq, hE, hregs.frames_eq] + unfold evaluationBudget nodeBudget + nlinarith only [hpow, hL])) + have huregs : KernelRegs L f a0 b0 u := by + constructor + · exact (hroot.regs_eq (by decide : rootList.Preserves inputDepth)).trans hregs.depth_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves span)).trans hregs.span_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves arrayA)).trans hregs.left_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves arrayB)).trans hregs.right_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves frames)).trans hregs.frames_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves lists)).trans hregs.lists_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves values)).trans hregs.values_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves stamps)).trans hregs.stamps_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves nil)).trans hregs.nil_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves one)).trans hregs.one_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves ten)).trans hregs.ten_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves nine)).trans hregs.nine_eq + · exact (hroot.regs_eq (by decide : rootList.Preserves frameWidth)).trans hregs.frameWidth_eq + have hu : NodeReady L f a0 b0 L 0 0 (E + 1) S u := by + refine ⟨huregs, by omega, by simp, hul, hum, hua, ?_, ?_, ?_⟩ + · simpa only [hE] using hue + · intro j + change u.mem (f + 4 * 0 + j.val) = ![S.card, 0, 10 ^ 0, 0] j + fin_cases j + · change u.mem (f) = S.card + simpa only [hregs.frames_eq, himage, card_image_queryIndex] using huc + · change u.mem (f + 1) = 0 + simpa only [hregs.frames_eq] using hup + · change u.mem (f + 2) = 1 + simpa only [hregs.frames_eq] using hus + · change u.mem (f + 3) = 0 + simpa only [hregs.frames_eq] using hut + · simpa only [hregs.lists_eq, hregs.stamps_eq, hE, himage, Nat.zero_mul, Nat.add_zero] + using hur + have hrootBefore (c : ℕ) (hc : c < f) : u.mem c = x.mem c := by + apply hrootFrame c + all_goals simp only [hregs.lists_eq, hregs.stamps_eq, hregs.frames_eq, Block.Owns] + all_goals omega + have hsourceA (v : Leaf L) : u.mem (a0 + 0 + 10 ^ 0 * (Encoding.leafIndex v).val) = + encode (a v) := by + simp only [Nat.add_zero, pow_zero, Nat.one_mul] + rw [hrootBefore _ (by have := (Encoding.leafIndex v).isLt; omega)] + exact hleft v + have hsourceB (v : Leaf L) : u.mem (b0 + 0 + 10 ^ 0 * (Encoding.leafIndex v).val) = + encode (b v) := by + simp only [Nat.add_zero, pow_zero, Nat.one_mul] + rw [hrootBefore _ (by have := (Encoding.leafIndex v).isLt; omega)] + exact hright v + obtain ⟨v, hv, hcontroller⟩ := exec_ready L S a b u hu hsourceA hsourceB hbeforeA hbeforeB + ha hb hw hmag (hfit.of_le (by unfold evaluationBudget; omega)) + have hexit : ExecWithin (w + 2) controller v v 2 := ExecWithin.loop_exit + (by simp only [Cond.holds_lt, hv.regs.nil_eq, hv.active_eq]; decide) + have hloop := hcontroller v 2 hexit + have hqv : v.regs queries = q0 := + (hloop.regs_eq (by decide : controller.Preserves queries)).trans + ((hroot.regs_eq (by decide : rootList.Preserves queries)).trans hq0) + have hov : v.regs output = o0 := + (hloop.regs_eq (by decide : controller.Preserves output)).trans + ((hroot.regs_eq (by decide : rootList.Preserves output)).trans ho0) + have hQv : v.regs queryCount = Q := + (hloop.regs_eq (by decide : controller.Preserves queryCount)).trans + ((hroot.regs_eq (by decide : rootList.Preserves queryCount)).trans hQ) + have hquery (i : ℕ) (hi : i < Q) : v.mem (v.regs queries + i) = codes i := by + rw [hqv, hv.frame _ (notMem_nodeWrites_before (Nat.zero_le _) (by omega)), + hrootBefore _ (by omega)] + simpa only [codes, dite_eq_left hi] using hinput ⟨i, hi⟩ + obtain ⟨y, hreport, _, hvalues, hreportFrame, _⟩ := exec_report (N := Q) + (capacity := 10 ^ L) v codes hQv hquery hcodes (Or.inl (by rw [hqv, hov]; omega)) + (Or.inr (by rw [hov, hv.regs.values_eq]; omega)) hv.regs.one_eq + (hfit.of_le (by + rw [hqv, hov, hv.regs.values_eq] + unfold evaluationBudget nodeBudget + nlinarith only [hpow, hL])) + have he : ExecWithin (w + 2) (ram[ ⟪rootList⟫; ⟪controller⟫; ⟪report⟫ ]) x y + (29 * Q + traceWeight S + 24) := (hroot.seq (hloop.seq hreport)).mono (by omega) + refine ⟨y, he, ?_, ?_, (hreport.regs_eq (by decide : report.Preserves output)).trans hov, + ?_, ?_, ?_⟩ + · intro i + have hr := hvalues i.val i.isLt + rw [hov, hv.regs.values_eq] at hr + have hqi : codes i.val = (queryIndex (qs i)).val := by + simp only [codes, dite_eq_left i.isLt] + rw [hqi] at hr + have hval := hv.values (qs i) (Finset.mem_image.mpr ⟨i, Finset.mem_univ _, rfl⟩) + simp only [Nat.zero_mul, Nat.add_zero] at hval + exact hr.trans hval + · intro c hout hscratch + have htwo : 2 * (L + 1) * 10 ^ L = (L + 1) * 10 ^ L + (L + 1) * 10 ^ L := by ring + rw [htwo] at hscratch + have hcap : 10 ^ L ≤ (L + 1) * 10 ^ L := Nat.le_mul_of_pos_left _ (by omega) + rw [hreportFrame c (by simpa only [hov, Block.mem_footprint, Block.Owns] using hout)] + have hnot : c ∉ nodeWrites L f 0 := by + rw [mem_nodeWrites_iff (Nat.zero_le _)] + simp only [Nat.zero_mul, Nat.add_zero, htwo] + omega + rw [hv.frame c hnot] + apply hrootFrame c + all_goals simp only [hregs.lists_eq, hregs.stamps_eq, hregs.frames_eq, Block.Owns, htwo] + all_goals omega + · rw [hreport.regs_eq (by decide : report.Preserves epoch)] + exact hv.epoch_le + · rw [hreport.regs_eq (by decide : report.Preserves epoch)] + exact hv.epoch_upper + · intro q hq + rw [hreportFrame _ (by + rw [Block.mem_footprint] + simp only [hov, Block.Owns] + omega), hreport.regs_eq (by decide : report.Preserves epoch)] + exact hv.timestamps q hq + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Finish.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Finish.lean new file mode 100644 index 0000000000..37e6446002 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Finish.lean @@ -0,0 +1,69 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Node + +/-! +# Actual return after all ten child terms + +## Main results + +* `PrunedQuery.exec_node_finish`: read the completed frame and return through the real + controller comparisons, preserving all memory. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- The stored term counter is read and compared with ten. The controller then either +terminates the root or selects the parent's accumulation phase, within 26 instructions. -/ +theorem exec_node_finish {w L f a0 b0 d : ℕ} (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hd : x.regs level = d) (hdL : d ≤ L) + (hm : x.regs mode = 1) (hterm : x.mem (f + 4 * d + 3) = 10) + (hw : Fits w (8 * f + 64 * (L + 1) * 10 ^ L + 128)) : + ∃ y, ExecWithin w step x y 26 ∧ KernelRegs L f a0 b0 y ∧ y.mem = x.mem ∧ + y.regs level = (if d = 0 then 0 else d - 1) ∧ + y.regs mode = (if d = 0 then 1 else 2) ∧ + y.regs active = (if d = 0 then 0 else x.regs active) ∧ + y.regs epoch = x.regs epoch := by + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + have hmul : (d + 1) * 10 ^ L ≤ (L + 1) * 10 ^ L := + Nat.mul_le_mul_right _ (by omega) + obtain ⟨u, hu, humem, _, _, _, _, hut, _, _, _, _, _, hureg⟩ := + exec_loadFrame x hregs.one_eq hregs.frameWidth_eq (hw.of_le (by + rw [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, hd] + nlinarith only [hpow, hL, hmul])) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have hut' : u.regs term = 10 := by simpa only [hregs.frames_eq, hd, hterm] using hut + have hu0 := (fixed nil (by decide)).trans hregs.nil_eq + have hu1 := (fixed one (by decide)).trans hregs.one_eq + have hu10 := (fixed ten (by decide)).trans hregs.ten_eq + have hum : u.regs mode = 1 := (fixed mode (by decide)).trans hm + have hud : u.regs level = d := (fixed level (by decide)).trans hd + obtain ⟨y, hy, hymem, hyl, hym, hya, _⟩ := exec_pop u hu0 hu1 + (hw.of_le (by rw [hud]; nlinarith only [hL, hdL])) + have hcall : ExecWithin w dispatch u y 6 := hy.branch_neg + (by simp only [Cond.holds_lt, hut', hu10, lt_self_iff_false, not_false_eq_true]) + have hbranch : ExecWithin w + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y 9 := + (hcall.branch_pos (by simp only [Cond.holds_eq, hum, hu1])).branch_neg + (by simp only [Cond.holds_eq, hum, hu0]; decide) + have he : ExecWithin w step x y 26 := hu.seq hbranch + exact ⟨y, he, hregs.of_step he, hymem.trans humem, + by simpa only [hud] using hyl, by simpa only [hud, hum] using hym, + by simpa only [hud, fixed active (by decide)] using hya, + (hy.regs_eq (by decide : pop.Preserves epoch)).trans (fixed epoch (by decide))⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Flat.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Flat.lean new file mode 100644 index 0000000000..03410e5ef5 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Flat.lean @@ -0,0 +1,99 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Input + +/-! +# Flat initialized executions of native pruned queries + +## Main results + +* `PrunedQuery.runs`: prefix `Runs` and charged halting `Executes` from actual `State.init`, + connected to the same final memory, existing finite pruned values and outside frame. +* `PrunedQuery.bounded_reachable`: every reachable prefix has target-sized data words. + +The single fixed program returns the unsigned pointer to its encoded answer list. Queries may +repeat and their order is preserved. Its 96 registers and code-size bound are independent of +depth, query count, input values and physical word width. +These initialized-run theorems, `runs_wide` in `Pruned.Width` and `exec_kernel` in +`Pruned.Kernel` are the client interfaces. Stage `exec_*` lemmas in their dependencies are +internal implementation APIs exported for proof composition. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Actual initialized flat executions. The prefix witness holding all answers is precisely +the state at the final halt, and the halt is charged as one further target instruction. -/ +theorem runs {w L A B : ℕ} (hw : 0 < w) (a b : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L qs.length)) : + InputFits (w + 2) (input L a b qs) ∧ + ∃ y n, n ≤ timeBound qs ∧ + Runs (w + 2) (program.program output) (State.init (input L a b qs)) + ⟨program.size, y⟩ n ∧ + Executes (w + 2) (program.program output) (State.init (input L a b qs)) + (outputBase L qs.length) (n + 1) ∧ y.Bounded (w + 2) ∧ + (∀ i : Fin qs.length, y.mem (outputBase L qs.length + i.val) = encode + (Pruned.result L (fun v => a (Encoding.leafIndex v)) + (fun v => b (Encoding.leafIndex v)) (requested qs) (queryAt (qs.get i)))) ∧ + (∀ c, ¬(outputBase L qs.length ≤ c ∧ c < memoryEnd L qs.length) → + y.mem c = (State.init (input L a b qs) : State registers).data.mem c) := by + obtain ⟨hi, y, ⟨n, hn, he⟩, hb, hv, hf, ho⟩ := exec_input hw a b qs ha hb hmag haddr + refine ⟨hi, y, n, hn, ?_, ?_, hb, hv, hf⟩ + · simpa only [Nat.zero_add, State.init] using he.runs (pc := 0) + (p := program.program output) (Program.Contains.self _).append_left + · simpa only [ho, State.init] using he.executes output + +/-- Ordinary native safety applies to every initialized reachable prefix, not merely the +returned witness. The admission hypotheses are the same numerical original-input bounds. -/ +theorem bounded_reachable {w L A B n : ℕ} (hw : 0 < w) (a b : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L qs.length)) {y : State registers} + (hy : Runs (w + 2) (program.program output) (State.init (input L a b qs)) y n) : + y.data.Bounded (w + 2) := + hy.bounded_init (input_fits hw a b qs ha hb hmag haddr) + +/-- An encoder producer's existing leaf-value contract gives the original bilinear product at +every supplied query. This is the same actual initialized program and charged run as `runs`; +the mathematical specialization adds no machine operation or assumed producer execution. -/ +theorem runs_bilinear {w L A B : ℕ} (hw : 0 < w) (left right : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (a : LeftStr L → ℤ) (b : RightStr L → ℤ) + (hleft : ∀ v, left (Encoding.leafIndex v) = encodingL a v) + (hright : ∀ v, right (Encoding.leafIndex v) = encodingR b v) + (ha : ∀ v, |left v| ≤ (A : ℤ)) (hb : ∀ v, |right v| ≤ (B : ℤ)) + (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L qs.length)) : + InputFits (w + 2) (input L left right qs) ∧ + ∃ y n, n ≤ timeBound qs ∧ + Runs (w + 2) (program.program output) (State.init (input L left right qs)) + ⟨program.size, y⟩ n ∧ + Executes (w + 2) (program.program output) (State.init (input L left right qs)) + (outputBase L qs.length) (n + 1) ∧ y.Bounded (w + 2) ∧ + (∀ i : Fin qs.length, y.mem (outputBase L qs.length + i.val) = + encode (bilinearProduct a b (queryAt (qs.get i)))) ∧ + (∀ c, ¬(outputBase L qs.length ≤ c ∧ c < memoryEnd L qs.length) → + y.mem c = (State.init (input L left right qs) : State registers).data.mem c) := by + obtain ⟨hi, y, n, hn, hr, he, hy, hv, hf⟩ := runs hw left right qs ha hb hmag haddr + refine ⟨hi, y, n, hn, hr, he, hy, ?_, hf⟩ + intro i + have hvalue := hv i + rw [show (fun v => left (Encoding.leafIndex v)) = encodingL a from funext hleft, + show (fun v => right (Encoding.leafIndex v)) = encodingR b from funext hright, + Pruned.result_eq_piecewise_bilinearProduct, + Finset.piecewise_eq_of_mem _ _ _ (queryAt_mem_requested (List.get_mem qs i))] at hvalue + exact hvalue + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Frame.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Frame.lean new file mode 100644 index 0000000000..816abb5b14 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Frame.lean @@ -0,0 +1,139 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Native stack layout and frame loading + +## Main results + +* `PrunedQuery.exec_locate`: compute the actual stack/list/value/timestamp region addresses. +* `PrunedQuery.exec_loadFrame`: load all four stored frame words and compute row addresses. + +Every index multiplication, pointer addition and load is an ordinary charged instruction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Exactly the registers used to locate the concrete workspace regions. -/ +def LocateLocal (i : Fin registers) : Prop := + i = frames ∨ i = temporary ∨ i = temporary₂ ∨ i = lists ∨ i = values ∨ i = stamps + +instance (i : Fin registers) : Decidable (LocateLocal i) := + inferInstanceAs (Decidable + (i = frames ∨ i = temporary ∨ i = temporary₂ ∨ i = lists ∨ i = values ∨ i = stamps)) + +private theorem locate_preserves (i : Fin registers) : + ¬LocateLocal i → locate.Preserves i := by decide +kernel +revert + +/-- The seven actual instructions compute the workspace partition from depth and array size. -/ +theorem exec_locate {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) + (hw : Fits w (x.regs scratch + 4 * (x.regs inputDepth + 1) + + 2 * (x.regs inputDepth + 1) * x.regs span + x.regs inputDepth + 8)) : + ∃ y, ExecWithin w locate x y 7 ∧ y.mem = x.mem ∧ + y.regs frames = x.regs scratch ∧ + y.regs lists = x.regs scratch + 4 * (x.regs inputDepth + 1) ∧ + y.regs values = x.regs scratch + 4 * (x.regs inputDepth + 1) + + (x.regs inputDepth + 1) * x.regs span ∧ + y.regs stamps = x.regs scratch + 4 * (x.regs inputDepth + 1) + + 2 * (x.regs inputDepth + 1) * x.regs span ∧ + (∀ i, ¬LocateLocal i → y.regs i = x.regs i) := by + let d := x.regs inputDepth + 1 + let b := x.regs scratch + 4 * d + let v := b + d * x.regs span + let s := v + d * x.regs span + let y := (((((((x.set frames (x.regs scratch)).set temporary d).set temporary₂ (4 * d)).set + lists b).set temporary₂ (d * x.regs span)).set values v).set stamps s) + have hd : Fits w d := hw.of_le (by dsimp [d]; omega) + have harea : Fits w (4 * d) := hw.of_le (by dsimp [d]; omega) + have hb : Fits w b := hw.of_le (by dsimp [b, d]; omega) + have hds : Fits w (d * x.regs span) := hw.of_le (by dsimp [d]; nlinarith) + have hv : Fits w v := hw.of_le (by dsimp [v, b, d]; nlinarith) + have hs : Fits w s := hw.of_le (by dsimp [s, v, b, d]; nlinarith) + have he : ExecWithin w locate x y 7 := by + ram_eval [locate, y, d, b, v, s, h1, h4, hd, harea, hb, hds, hv, hs, + frames, temporary, temporary₂, lists, values, stamps, scratch, inputDepth, frameWidth, + span, one, Fin.mk.injEq, Nat.mul_comm] + refine ⟨y, he, rfl, ?_, ?_, ?_, ?_, fun i hi => he.regs_eq (locate_preserves i hi)⟩ + all_goals simp +decide [y, b, d, v, s, frames, temporary, temporary₂, lists, values, + stamps, two_mul, add_mul, Nat.add_assoc] + +/-- Registers written while loading one stack frame and computing the two depth rows. -/ +def LoadFrameLocal (i : Fin registers) : Prop := + i = frame ∨ i = address ∨ i = count ∨ i = leafPrefix ∨ i = stride ∨ i = term ∨ + i = temporary ∨ i = listBase ∨ i = valueBase ∨ i = nextLevel ∨ i = childList ∨ + i = childValues + +instance (i : Fin registers) : Decidable (LoadFrameLocal i) := + inferInstanceAs (Decidable + (i = frame ∨ i = address ∨ i = count ∨ i = leafPrefix ∨ i = stride ∨ i = term ∨ + i = temporary ∨ i = listBase ∨ i = valueBase ∨ i = nextLevel ∨ i = childList ∨ + i = childValues)) + +private theorem loadFrame_preserves (i : Fin registers) : + ¬LoadFrameLocal i → loadFrame.Preserves i := by decide +kernel +revert + +/-- Seventeen native instructions load the four words at the computed frame address and +locate the current and child rows. The memory words are read, not supplied as frame arguments. -/ +theorem exec_loadFrame {w : ℕ} (x : Data registers) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) + (hw : Fits w (x.regs frames + 4 * (x.regs level + 1) + x.regs lists + + x.regs values + (x.regs level + 1) * x.regs span + x.regs level + 8)) : + ∃ y, ExecWithin w loadFrame x y 17 ∧ y.mem = x.mem ∧ + y.regs frame = x.regs frames + 4 * x.regs level ∧ + y.regs count = x.mem (x.regs frames + 4 * x.regs level) ∧ + y.regs leafPrefix = x.mem (x.regs frames + 4 * x.regs level + 1) ∧ + y.regs stride = x.mem (x.regs frames + 4 * x.regs level + 2) ∧ + y.regs term = x.mem (x.regs frames + 4 * x.regs level + 3) ∧ + y.regs listBase = x.regs lists + x.regs level * x.regs span ∧ + y.regs valueBase = x.regs values + x.regs level * x.regs span ∧ + y.regs nextLevel = x.regs level + 1 ∧ + y.regs childList = x.regs lists + (x.regs level + 1) * x.regs span ∧ + y.regs childValues = x.regs values + (x.regs level + 1) * x.regs span ∧ + (∀ i, ¬LoadFrameLocal i → y.regs i = x.regs i) := by + let f := x.regs frames + x.regs level * 4 + let j := x.regs level + 1 + let y := (((((((((((((((((x.set frame (x.regs level * 4)).set frame f).set address f).set + count (x.mem f)).set address (f + 1)).set leafPrefix (x.mem (f + 1))).set + address (f + 2)).set stride (x.mem (f + 2))).set address (f + 3)).set + term (x.mem (f + 3))).set temporary (x.regs level * x.regs span)).set + listBase (x.regs lists + x.regs level * x.regs span)).set + valueBase (x.regs values + x.regs level * x.regs span)).set nextLevel j).set + temporary (j * x.regs span)).set childList (x.regs lists + j * x.regs span)).set + childValues (x.regs values + j * x.regs span)) + have hlev : Fits w (x.regs level * 4) := hw.of_le (by omega) + have hf (k : ℕ) (hk : k ≤ 3) : Fits w (f + k) := hw.of_le (by dsimp [f]; omega) + have hf0 : Fits w (x.regs frames + x.regs level * 4) := by + simpa only [f, Nat.add_zero] using hf 0 (by decide) + have hj : Fits w (x.regs level + 1) := hw.of_le (by omega) + have hprod : Fits w (x.regs level * x.regs span) := hw.of_le (by nlinarith) + have hnextprod : Fits w ((x.regs level + 1) * x.regs span) := hw.of_le (by nlinarith) + have hl : Fits w (x.regs lists + x.regs level * x.regs span) := hw.of_le (by nlinarith) + have hv : Fits w (x.regs values + x.regs level * x.regs span) := hw.of_le (by nlinarith) + have hcl : Fits w (x.regs lists + (x.regs level + 1) * x.regs span) := hw.of_le (by nlinarith) + have hcv : Fits w (x.regs values + (x.regs level + 1) * x.regs span) := hw.of_le (by nlinarith) + have hexact : Exec w loadFrame x y 17 := by + ram_eval [loadFrame, y, f, j, h1, h4, hlev, hf0, hf 1 (by decide), + hf 2 (by decide), hf 3 (by decide), hj, hprod, hnextprod, hl, hv, hcl, hcv, + frame, address, count, leafPrefix, stride, term, temporary, listBase, valueBase, + nextLevel, childList, childValues, level, span, frames, lists, values, frameWidth, one, + Fin.mk.injEq, Nat.add_zero] + have he : ExecWithin w loadFrame x y 17 := ⟨17, le_rfl, hexact⟩ + refine ⟨y, he, rfl, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, + fun i hi => he.regs_eq (loadFrame_preserves i hi)⟩ + all_goals simp +decide [y, f, j, frame, address, count, leafPrefix, stride, term, temporary, + listBase, valueBase, nextLevel, childList, childValues, Nat.mul_comm] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Initialize.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Initialize.lean new file mode 100644 index 0000000000..1f07cc5ddd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Initialize.lean @@ -0,0 +1,115 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Powers +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Ready + +/-! +# Native headers, powers and evaluator pointers + +## Main results + +* `PrunedQuery.exec_initialize`: load both actual headers, compute the encoding-array length, + derive every evaluator pointer, and establish the ordinary register calling convention. + +The prefix uses `4L+27` instructions, including its power loop. Its continuation is the existing +structured sequence rule, so composing it with evaluation gives the original fixed program. +This continuation lemma is an internal implementation API for the complete initialized-run +theorems in `Pruned.Flat` and `Pruned.Width`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Initialization derives all pointers from the two ordinary scalar headers. Its output +assertion contains no execution or fit fields; the charged native composition is proved here. -/ +theorem exec_initialize {w L Q : ℕ} (x : Data registers) (hL : x.mem 1 = L) + (hQ : x.mem 2 = Q) (hfit : Fits w (addressBound L Q)) : + ∃ y, y.mem = x.mem ∧ KernelRegs L (scratchBase L Q) 3 (3 + 10 ^ L) y ∧ + y.regs queryCount = Q ∧ y.regs queries = 3 + 2 * 10 ^ L ∧ + y.regs output = outputBase L Q ∧ y.regs epoch = 0 ∧ + ∀ (tail : Code .standard registers) z T, ExecWithin w tail y z T → + ExecWithin w (ram[ ⟪header⟫; ⟪powers⟫; ⟪parameters⟫; ⟪locate⟫; ⟪tail⟫ ]) + x z (4 * L + 27 + T) := by + have hp : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hscalar : L + Q + 10 ^ L + 16 ≤ addressBound L Q := by + unfold addressBound + nlinarith only [hp] + have h1 : Fits w 1 := hfit.of_le (by omega) + have h2 : Fits w 2 := hfit.of_le (by omega) + have h3 : Fits w 3 := hfit.of_le (by omega) + let h := (((x.set address 1).set inputDepth L).set address 2).set queryCount Q + have hheader : ExecWithin w header x h 4 := by + ram_eval [header, h, hL, hQ, h1, h2, address, inputDepth, queryCount, Fin.mk.injEq] + have hdepth : h.regs inputDepth = L := by simp +decide [h, address, inputDepth, queryCount] + obtain ⟨p, hpower, hpmem, hpL, hspan, _, hnil, hone, _, hten, hnine, hfour, _⟩ := + exec_powers h hdepth hfit + have hpQ : p.regs queryCount = Q := + (hpower.regs_eq (by decide : powers.Preserves queryCount)).trans (by simp [h]) + let u := ((((((p.set arrayA 3).set arrayB (3 + 10 ^ L)).set queries (3 + 10 ^ L + 10 ^ L)).set + output (3 + 10 ^ L + 10 ^ L + Q)).set scratch (3 + 10 ^ L + 10 ^ L + Q + Q)).set epoch 0) + have hB : Fits w (3 + 10 ^ L) := hfit.of_le (by omega) + have hqueries : Fits w (3 + 10 ^ L + 10 ^ L) := + hfit.of_le (by unfold addressBound; nlinarith only [hp]) + have hout : Fits w (3 + 10 ^ L + 10 ^ L + Q) := + hfit.of_le (by unfold addressBound; nlinarith only [hp]) + have hs : Fits w (3 + 10 ^ L + 10 ^ L + Q + Q) := + hfit.of_le (by unfold addressBound; nlinarith only [hp]) + have hparameters : ExecWithin w parameters p u 6 := by + ram_eval [parameters, u, hspan, hpQ, h3, hB, hqueries, hout, hs, arrayA, arrayB, queries, + output, scratch, epoch, span, queryCount, Fin.mk.injEq] + have unchanged (i : Fin registers) (hi : parameters.Preserves i) : u.regs i = p.regs i := + hparameters.regs_eq hi + have huL : u.regs inputDepth = L := (unchanged _ (by decide)).trans hpL + have huspan : u.regs span = 10 ^ L := (unchanged _ (by decide)).trans hspan + have hu1 : u.regs one = 1 := (unchanged _ (by decide)).trans hone + have hu4 : u.regs frameWidth = 4 := (unchanged _ (by decide)).trans hfour + have hus : u.regs scratch = scratchBase L Q := by + simp +decide [u, scratch, epoch, scratchBase, outputBase, two_mul, Nat.add_assoc] + obtain ⟨y, hlocate, hymem, hyf, hyl, hyv, hys, _⟩ := exec_locate u hu1 hu4 + (hfit.of_le (by + rw [hus, huL, huspan] + unfold scratchBase outputBase addressBound + have hm : L ≤ L * 10 ^ L := Nat.le_mul_of_pos_right _ (by positivity) + nlinarith only [hp, hm])) + have hyconstant (i : Fin registers) (hi : locate.Preserves i) + (hi' : parameters.Preserves i) : y.regs i = p.regs i := + (hlocate.regs_eq hi).trans (unchanged i hi') + have hregs : KernelRegs L (scratchBase L Q) 3 (3 + 10 ^ L) y := by + constructor + · exact (hyconstant _ (by decide) (by decide)).trans hpL + · exact (hyconstant _ (by decide) (by decide)).trans hspan + · exact (hlocate.regs_eq (by decide : locate.Preserves arrayA)).trans (by + simp +decide [u, arrayA, arrayB, queries, output, scratch, epoch]) + · exact (hlocate.regs_eq (by decide : locate.Preserves arrayB)).trans (by + simp +decide [u, arrayA, arrayB, queries, output, scratch, epoch]) + · exact hyf.trans hus + · simpa only [hus, huL] using hyl + · simpa only [hus, huL, huspan] using hyv + · simpa only [hus, huL, huspan] using hys + · exact (hyconstant _ (by decide) (by decide)).trans hnil + · exact (hyconstant _ (by decide) (by decide)).trans hone + · exact (hyconstant _ (by decide) (by decide)).trans hten + · exact (hyconstant _ (by decide) (by decide)).trans hnine + · exact (hyconstant _ (by decide) (by decide)).trans hfour + refine ⟨y, ?_, hregs, ?_, ?_, ?_, ?_, ?_⟩ + · exact hymem.trans (show u.mem = p.mem from rfl) |>.trans hpmem + · exact (hyconstant _ (by decide) (by decide)).trans hpQ + · exact (hlocate.regs_eq (by decide : locate.Preserves queries)).trans (by + simp +decide [u, queries, output, scratch, epoch, two_mul, Nat.add_assoc]) + · exact (hlocate.regs_eq (by decide : locate.Preserves output)).trans (by + simp +decide [u, output, scratch, epoch, outputBase, two_mul, Nat.add_assoc]) + · exact (hlocate.regs_eq (by decide : locate.Preserves epoch)).trans (by simp [u]) + · intro tail z T htail + exact (hheader.seq (hpower.seq (hparameters.seq (hlocate.seq htail)))).mono (by omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Input.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Input.lean new file mode 100644 index 0000000000..10e06c67b6 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Input.lean @@ -0,0 +1,168 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Initialize +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Evaluate +public import Complexity.Computability.WordRAM.InputArray +import Mathlib.Data.List.GetD + +/-! +# Actual initialized-input execution of native pruned queries + +## Main definitions + +* `PrunedQuery.timeBound`: the full charged prefix, including setup and duplicate reporting. + +## Main results + +* `PrunedQuery.exec_input`: the fixed program starts at `State.init`, computes the existing + finite pruned values, preserves the input and outside memory, and leaves bounded native words. + +The input contains ordinary depth/count headers, two canonical signed arrays and query codes. +All child lists and directories are computed. No layout, execution or timing certificate is an +input word or an admission hypothesis. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Full prefix charge: actual header/power/pointer work, root deduplication, every recursive +controller instruction, loop exit and reporting in the original supplied order. -/ +def timeBound {L : ℕ} (qs : List (Fin (10 ^ L))) : ℕ := + 4 * L + 29 * qs.length + traceWeight (requested qs) + 51 + +/-- Pruning gives a bound in terms of the actual contributing leaf set, retaining the setup +and duplicate-query costs. This includes one additional final halt instruction. -/ +theorem timeBound_add_one_le {L : ℕ} (qs : List (Fin (10 ^ L))) : + timeBound qs + 1 ≤ + 8192 * (L + qs.length + 1 + (L + 1) * (leaves (requested qs)).card) := by + have ht := traceWeight_le (requested qs) + unfold timeBound + nlinarith only [ht] + +/-- Both scalar headers, both signed arrays, all query codes and the machine's unsigned length +header fit the same admitted target width. -/ +theorem input_fits {w L A B : ℕ} (hw : 0 < w) (a b : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L qs.length)) : InputFits (w + 2) (input L a b qs) := by + have henv := magnitudeBound_fits hw hmag + have hp : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hsmall : L + qs.length + 10 ^ L + 16 ≤ addressBound L qs.length := by + unfold addressBound + nlinarith only [hp] + refine ⟨?_, ?_⟩ + · rw [length_input] + exact haddr.of_le (by unfold addressBound; nlinarith only [hp]) + · intro v hv + simp only [input, List.mem_append, List.mem_cons, List.not_mem_nil, or_false] at hv + rcases hv with (((rfl | rfl) | hv) | hv) | hv + · exact haddr.of_le (by omega) + · exact haddr.of_le (by omega) + · obtain ⟨i, rfl⟩ := List.mem_ofFn.mp hv + exact (encode_fits (fits_left henv (ha i))).mono (by omega) + · obtain ⟨i, rfl⟩ := List.mem_ofFn.mp hv + exact (encode_fits (fits_right henv (hb i))).mono (by omega) + · obtain ⟨q, _, rfl⟩ := List.mem_map.mp hv + exact haddr.of_le (by have := q.isLt; omega) + +/-- Execution of the complete native program on actual initialized memory. The final memory +witness is shared by execution, mathematical outputs, frame preservation and boundedness. -/ +theorem exec_input {w L A B : ℕ} (hw : 0 < w) (a b : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (haddr : Fits (w + 2) (addressBound L qs.length)) : + InputFits (w + 2) (input L a b qs) ∧ + ∃ y, ExecWithin (w + 2) program (State.init (input L a b qs)).data y (timeBound qs) ∧ + y.Bounded (w + 2) ∧ + (∀ i : Fin qs.length, y.mem (outputBase L qs.length + i.val) = encode + (Pruned.result L (fun v => a (Encoding.leafIndex v)) + (fun v => b (Encoding.leafIndex v)) (requested qs) (queryAt (qs.get i)))) ∧ + (∀ c, ¬(outputBase L qs.length ≤ c ∧ c < memoryEnd L qs.length) → + y.mem c = (State.init (input L a b qs) : State registers).data.mem c) ∧ + y.regs output = outputBase L qs.length := by + have hi := input_fits hw a b qs ha hb hmag haddr + let x : Data registers := (State.init (input L a b qs)).data + have hheaderL : x.mem 1 = L := by simp [x, State.init, Data.init, input] + have hheaderQ : x.mem 2 = qs.length := by simp [x, State.init, Data.init, input] + have hthree (n : ℕ) : 1 + (1 + (1 + n)) = 3 + n := by omega + have hleft (i : Fin (10 ^ L)) : x.mem (3 + i.val) = encode (a i) := by + have h := Data.init_realizes_append (r := registers) [L, qs.length] + (List.ofFn (fun j => encode (a j))) (List.ofFn (fun j => encode (b j)) ++ qs.map Fin.val) + have hr := h ⟨i.val, by simp⟩ + simpa [x, State.init, input, Block.read, List.append_assoc] using hr + have hright (i : Fin (10 ^ L)) : x.mem (3 + 10 ^ L + i.val) = encode (b i) := by + have h := Data.init_realizes_append (r := registers) + ([L, qs.length] ++ List.ofFn (fun j => encode (a j))) + (List.ofFn (fun j => encode (b j))) (qs.map Fin.val) + have hr := h ⟨i.val, by simp⟩ + simpa [x, State.init, input, Block.read, List.append_assoc, Nat.add_comm, + Nat.add_left_comm, Nat.add_assoc, hthree] using hr + have hqueries (i : Fin qs.length) : + x.mem (3 + 2 * 10 ^ L + i.val) = (qs.get i).val := by + have h := Data.init_realizes_append (r := registers) + ([L, qs.length] ++ List.ofFn (fun j => encode (a j)) ++ + List.ofFn (fun j => encode (b j))) (qs.map Fin.val) [] + have hr := h ⟨i.val, by simp⟩ + simpa [x, State.init, input, Block.read, List.append_assoc, two_mul, Nat.add_comm, + Nat.add_left_comm, Nat.add_assoc, hthree] using hr + have hzero (c : ℕ) (hc : (input L a b qs).length + 1 ≤ c) : x.mem c = 0 := by + have hc0 : c ≠ 0 := by omega + change (if c = 0 then (input L a b qs).length else (input L a b qs).getD (c - 1) 0) = 0 + rw [ite_eq_right hc0, List.getD_eq_default _ _ (by omega)] + obtain ⟨u, humem, hregs, hQ, hqueriesReg, hout, hE, hinitialize⟩ := + exec_initialize x hheaderL hheaderQ haddr + let queryList := fun i : Fin qs.length => queryAt (qs.get i) + have hset : Finset.univ.image queryList = requested qs := by + ext q + simp only [Finset.mem_image, Finset.mem_univ, true_and, requested, List.mem_toFinset] + constructor + · rintro ⟨i, rfl⟩ + exact ⟨qs.get i, List.get_mem _ i, rfl⟩ + · rintro ⟨c, hc, rfl⟩ + obtain ⟨i, rfl⟩ := List.mem_iff_get.mp hc + exact ⟨i, rfl⟩ + obtain ⟨y, hevaluate, hvalues, hframe, hreturn, _, _, _⟩ := exec_evaluate + (Q := qs.length) (q0 := 3 + 2 * 10 ^ L) (o0 := outputBase L qs.length) + (f := scratchBase L qs.length) + (a := fun v => a (Encoding.leafIndex v)) (b := fun v => b (Encoding.leafIndex v)) + queryList u hregs hQ hqueriesReg hout hE + (by intro i; rw [humem, queryIndex_queryAt]; exact hqueries i) + (by intro v; rw [humem]; exact hleft _) + (by intro v; rw [humem]; exact hright _) + (by unfold scratchBase outputBase; omega) (by unfold scratchBase outputBase; omega) + (by unfold outputBase; omega) (by unfold scratchBase; omega) + (by + intro q _ + rw [humem, hzero _ (by + rw [length_input] + unfold scratchBase outputBase + omega)]) + (fun v => ha _) (fun v => hb _) hw hmag + (haddr.of_le (evaluationBudget_input_le L qs.length)) + have he : ExecWithin (w + 2) program x y (timeBound qs) := by + have h := hinitialize _ y _ hevaluate + simp only [hset] at h + exact h.mono (by unfold timeBound; omega) + refine ⟨hi, y, he, he.bounded (Data.Bounded.init hi), ?_, ?_, hreturn⟩ + · intro i + simpa only [hset, queryList] using hvalues i + · intro c hc + rw [hframe c (by + unfold memoryEnd stampsBase valuesBase listsBase scratchBase at hc + omega) (by + simp only [memoryEnd, stampsBase, valuesBase, listsBase, scratchBase, + two_mul, Nat.add_mul, Nat.add_assoc] at hc ⊢ + omega), humem] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Kernel.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Kernel.lean new file mode 100644 index 0000000000..72d1067f13 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Kernel.lean @@ -0,0 +1,104 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Evaluate +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Powers + +/-! +# Callable native pruned-query kernel + +## Main results + +* `PrunedQuery.exec_kernel`: compute powers/layout, deduplicate supplied queries, execute the + complete pruned traversal and report answers using ordinary caller pointers and a fresh epoch. + +Input arrays contain canonical `Signed.encode` words. Query codes, pointers, depth and count are +raw unsigned words. The caller supplies only the source arrays/list and old timestamp bound; +all frames, child lists and decisions are computed by the fixed code. The returned directory +bound permits reuse across tiles without a free dense-directory initialization per call. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- The actual pointer-argument kernel, with computed powers and workspace partition, preserves +all memory outside its output and explicit scratch intervals. No initialized frame/list or child +execution premise is used. The original input magnitude envelope derives every signed fit. -/ +theorem exec_kernel {w L f a0 b0 q0 o0 Q E A B : ℕ} + (a b : Leaf L → ℤ) (qs : Fin Q → OutStr L) (x : Data registers) + (hL : x.regs inputDepth = L) (hA : x.regs arrayA = a0) (hB : x.regs arrayB = b0) + (hQ : x.regs queryCount = Q) (hq0 : x.regs queries = q0) + (ho0 : x.regs output = o0) (hf : x.regs scratch = f) (hE : x.regs epoch = E) + (hinput : ∀ i, x.mem (q0 + i.val) = (queryIndex (qs i)).val) + (hleft : ∀ v, x.mem (a0 + (Encoding.leafIndex v).val) = encode (a v)) + (hright : ∀ v, x.mem (b0 + (Encoding.leafIndex v).val) = encode (b v)) + (hbeforeA : a0 + 10 ^ L ≤ f) (hbeforeB : b0 + 10 ^ L ≤ f) + (hqueryOut : q0 + Q ≤ o0) (houtFrame : o0 + Q ≤ f) + (hmarks : ∀ q < 10 ^ L, + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ E) + (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hw : 0 < w) (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (kernelBudget L f a0 b0 q0 o0 Q E)) : + ∃ y, ExecWithin (w + 2) kernel x y + (4 * L + 29 * Q + traceWeight (Finset.univ.image qs) + 41) ∧ + (∀ i, y.mem (o0 + i.val) = encode + (Pruned.result L a b (Finset.univ.image qs) (qs i))) ∧ + (∀ c, ¬(o0 ≤ c ∧ c < o0 + Q) → + ¬(f ≤ c ∧ c < f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + 10 ^ L) → + y.mem c = x.mem c) ∧ y.regs output = o0 ∧ + E + 1 ≤ y.regs epoch ∧ y.regs epoch ≤ E + 1 + epochAllowance L ∧ + (∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ y.regs epoch) := by + obtain ⟨p, hp, hpmem, hpL, hspan, _, hnil, hone, _, hten, hnine, hfour, _⟩ := + exec_powers (Q := Q) x hL (hfit.of_le (by unfold kernelBudget; omega)) + have hpFrame : p.regs scratch = f := + (hp.regs_eq (by decide : powers.Preserves scratch)).trans hf + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hLcap : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + obtain ⟨u, hu, humem, huf, hul, huv, hus, _⟩ := exec_locate p hone hfour + (hfit.of_le (by + rw [hpFrame, hpL, hspan] + unfold kernelBudget evaluationBudget nodeBudget + nlinarith only [hpow, hLcap])) + have fixed (i : Fin registers) (hi : locate.Preserves i) (hi' : powers.Preserves i) : + u.regs i = x.regs i := (hu.regs_eq hi).trans (hp.regs_eq hi') + have hregs : KernelRegs L f a0 b0 u := by + constructor + · exact (hu.regs_eq (by decide : locate.Preserves inputDepth)).trans hpL + · exact (hu.regs_eq (by decide : locate.Preserves span)).trans hspan + · exact (fixed _ (by decide) (by decide)).trans hA + · exact (fixed _ (by decide) (by decide)).trans hB + · exact huf.trans hpFrame + · simpa only [hpFrame, hpL] using hul + · simpa only [hpFrame, hpL, hspan] using huv + · simpa only [hpFrame, hpL, hspan] using hus + · exact (hu.regs_eq (by decide : locate.Preserves nil)).trans hnil + · exact (hu.regs_eq (by decide : locate.Preserves one)).trans hone + · exact (hu.regs_eq (by decide : locate.Preserves ten)).trans hten + · exact (hu.regs_eq (by decide : locate.Preserves nine)).trans hnine + · exact (hu.regs_eq (by decide : locate.Preserves frameWidth)).trans hfour + have hmem : u.mem = x.mem := humem.trans hpmem + obtain ⟨y, hy, hvalues, hframe, hout, hel, heu, htable⟩ := exec_evaluate a b qs u hregs + ((fixed _ (by decide) (by decide)).trans hQ) + ((fixed _ (by decide) (by decide)).trans hq0) + ((fixed _ (by decide) (by decide)).trans ho0) + ((fixed _ (by decide) (by decide)).trans hE) + (by simpa only [hmem] using hinput) (by simpa only [hmem] using hleft) + (by simpa only [hmem] using hright) hbeforeA hbeforeB hqueryOut houtFrame + (by simpa only [hmem] using hmarks) ha hb hw hmag + (hfit.of_le (by unfold kernelBudget; omega)) + refine ⟨y, ?_, hvalues, ?_, hout, hel, heu, htable⟩ + · exact (hp.seq (hu.seq hy)).mono (by omega) + · simpa only [hmem] using hframe + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Leaf.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Leaf.lean new file mode 100644 index 0000000000..e21c6c9e61 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Leaf.lean @@ -0,0 +1,132 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Control + +/-! +# Actual native leaf multiplication + +## Main results + +* `PrunedQuery.exec_leafProduct`: load the two encoded leaf words and store their exact signed + product, within nineteen native instructions. +* `PrunedQuery.exec_leaf`: take the actual empty/nonempty branch and return to the parent. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The existing signed workspace and the indexed-load address register. -/ +def LeafLocal (i : Fin registers) : Prop := i.val < 9 ∨ i = address + +instance (i : Fin registers) : Decidable (LeafLocal i) := + inferInstanceAs (Decidable (i.val < 9 ∨ i = address)) + +private theorem leafProduct_preserves (i : Fin registers) : + ¬LeafLocal i → leafProduct.Preserves i := by decide +kernel +revert + +/-- Both loads, the existing fourteen-instruction signed multiplication kernel and the store +are charged. The ordinary mathematical product must fit the source signed width. -/ +theorem exec_leafProduct {w : ℕ} {a b : ℤ} (ha : GenericRAM.Signed.Fits w a) + (hb : GenericRAM.Signed.Fits w b) (hab : GenericRAM.Signed.Fits w (a * b)) + (x : Data registers) + (hleft : x.mem (x.regs arrayA + x.regs leafPrefix) = encode a) + (hright : x.mem (x.regs arrayB + x.regs leafPrefix) = encode b) + (hp : Fits (w + 2) (x.regs arrayA + x.regs leafPrefix)) + (hq : Fits (w + 2) (x.regs arrayB + x.regs leafPrefix)) : + ∃ y, ExecWithin (w + 2) leafProduct x y 19 ∧ + y.mem = Function.update x.mem (x.regs valueBase) (encode (a * b)) ∧ + (∀ i, ¬LeafLocal i → y.regs i = x.regs i) := by + let z := ((((x.set address (x.regs arrayA + x.regs leafPrefix)).set left (encode a)).set + address (x.regs arrayB + x.regs leafPrefix)).set right (encode b)) + have hload : ExecWithin (w + 2) leafLoad x z 4 := by + ram_eval [leafLoad, z, hleft, hright, hp, hq, address, arrayA, arrayB, leafPrefix, + left, right, Fin.mk.injEq] + obtain ⟨u, hu, hproduct, hframe⟩ := Signed.exec_multiply (r := 87) ha hb hab z + (by simp +decide [z, left, right, address]) (by simp [z]) + have hv : u.regs valueBase = x.regs valueBase := by + rw [hframe.2 valueBase (Or.inr (by decide))] + simp +decide [z, valueBase, address, left, right] + have hproduct' : u.regs result = encode (a * b) := hproduct + let y := u.write (x.regs valueBase) (encode (a * b)) + have hstore : ExecWithin (w + 2) + (ram[ *valueBase ← result ] : Code .standard registers) u y 1 := by + ram_eval [y, hv, hproduct'] + have he : ExecWithin (w + 2) leafProduct x y 19 := hload.seq (hu.seq hstore) + refine ⟨y, he, ?_, fun i hi => he.regs_eq (leafProduct_preserves i hi)⟩ + simpa only [y, Data.write_mem, z, Data.set_mem] using + congrArg (fun mem => Function.update mem (x.regs valueBase) (encode (a * b))) hframe.1 + +/-- Registers used by the full leaf phase, including its actual parent-return decision. -/ +def LeafReturnLocal (i : Fin registers) : Prop := + LeafLocal i ∨ PopLocal i ∨ i = temporary + +instance (i : Fin registers) : Decidable (LeafReturnLocal i) := + inferInstanceAs (Decidable (LeafLocal i ∨ PopLocal i ∨ i = temporary)) + +private theorem leaf_preserves (i : Fin registers) : + ¬LeafReturnLocal i → leaf.Preserves i := by decide +kernel +revert + +/-- The empty leaf performs no operand loads or multiplication. A requested leaf computes +its signed product. Both execute the real return branch; root termination and parent +accumulation are decided by the stored depth register, not by an external call rule. -/ +theorem exec_leaf {w : ℕ} {a b : ℤ} (ha : GenericRAM.Signed.Fits w a) + (hb : GenericRAM.Signed.Fits w b) (hab : GenericRAM.Signed.Fits w (a * b)) + (x : Data registers) (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (hleft : x.mem (x.regs arrayA + x.regs leafPrefix) = encode a) + (hright : x.mem (x.regs arrayB + x.regs leafPrefix) = encode b) + (hp : Fits (w + 2) (x.regs arrayA + x.regs leafPrefix)) + (hq : Fits (w + 2) (x.regs arrayB + x.regs leafPrefix)) + (hd : Fits (w + 2) (x.regs level + 2)) : + ∃ y, ExecWithin (w + 2) leaf x y 24 ∧ + y.mem = (if x.regs count = 0 then x.mem else + Function.update x.mem (x.regs valueBase) (encode (a * b))) ∧ + y.regs level = (if x.regs level = 0 then 0 else x.regs level - 1) ∧ + y.regs mode = (if x.regs level = 0 then x.regs mode else 2) ∧ + y.regs active = (if x.regs level = 0 then 0 else x.regs active) ∧ + (∀ i, ¬LeafReturnLocal i → y.regs i = x.regs i) := by + have hpre : ∃ v, ExecWithin (w + 2) + (ram[ if nil < count then [ ⟪leafProduct⟫ ] else [ temporary ← temporary ] ]) + x v 20 ∧ + v.mem = (if x.regs count = 0 then x.mem else + Function.update x.mem (x.regs valueBase) (encode (a * b))) ∧ + (∀ i, ¬LeafLocal i → v.regs i = x.regs i) := by + by_cases hc : x.regs count = 0 + · have hcopy : ExecWithin (w + 2) + (ram[ temporary ← temporary ] : Code .standard registers) x x 1 := by + simpa only [Data.set, Function.update_eq_self] using + (Exec.copy (w := w + 2) (B := .standard) (x := x) + (d := temporary) (s := temporary)).within + have he : ExecWithin (w + 2) + (ram[ if nil < count then [ ⟪leafProduct⟫ ] else [ temporary ← temporary ] ]) + x x 3 := by + exact hcopy.branch_neg (by simp only [Cond.holds_lt, h0, hc, lt_self_iff_false, + not_false_eq_true]) + exact ⟨x, he.mono (by decide), by simp only [hc, ↓reduceIte], fun _ _ => rfl⟩ + · obtain ⟨v, hv, hmem, hreg⟩ := exec_leafProduct ha hb hab x hleft hright hp hq + have hcond : (Cond.lt nil count).Holds x := by + simpa only [Cond.holds_lt, h0] using Nat.pos_of_ne_zero hc + exact ⟨v, hv.branch_pos hcond, by simpa only [hc, ↓reduceIte] using hmem, hreg⟩ + obtain ⟨v, hv, hmem, hreg⟩ := hpre + have hv0 := (hreg nil (by decide)).trans h0 + have hv1 := (hreg one (by decide)).trans h1 + have hvd := hreg level (by decide) + have hvm := hreg mode (by decide) + have hva := hreg active (by decide) + obtain ⟨y, hy, hymem, hyl, hym, hya, _⟩ := exec_pop v hv0 hv1 + (by simpa only [hvd] using hd) + have he : ExecWithin (w + 2) leaf x y 24 := hv.seq hy + exact ⟨y, he, hymem.trans hmem, by simpa only [hvd] using hyl, + by simpa only [hvd, hvm] using hym, by simpa only [hvd, hva] using hya, + fun i hi => he.regs_eq (leaf_preserves i hi)⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/LeafStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/LeafStep.lean new file mode 100644 index 0000000000..014e4c5e71 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/LeafStep.lean @@ -0,0 +1,121 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Frame +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Leaf + +/-! +# The terminal case of the actual pruned-query controller + +## Main results + +* `PrunedQuery.exec_leafStep`: load the stored frame, select the leaf phase and return. +* `PrunedQuery.exec_controller_leaf`: prepend this real iteration to the controller's + remaining execution, with its charged loop test. + +These use the existing native `ExecWithin` semantics throughout. The continuation is a +conclusion of the execution proof, rather than a hypothesis supplying a solver or its cost. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Registers written by frame loading and a terminal controller phase. -/ +def LeafStepLocal (i : Fin registers) : Prop := LoadFrameLocal i ∨ LeafReturnLocal i + +instance (i : Fin registers) : Decidable (LeafStepLocal i) := + inferInstanceAs (Decidable (LoadFrameLocal i ∨ LeafReturnLocal i)) + +/-- The actual controller reads its frame and takes both real entry/leaf comparisons. +The only possible store is the requested product at this depth's value-row base. -/ +theorem exec_leafStep {w p : ℕ} {a b : ℤ} (ha : GenericRAM.Signed.Fits w a) + (hb : GenericRAM.Signed.Fits w b) (hab : GenericRAM.Signed.Fits w (a * b)) + (x : Data registers) (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) (hm : x.regs mode = 0) + (hd : x.regs level = x.regs inputDepth) + (hpref : x.mem (x.regs frames + 4 * x.regs level + 1) = p) + (hleft : x.mem (x.regs arrayA + p) = encode a) + (hright : x.mem (x.regs arrayB + p) = encode b) + (hw : Fits (w + 2) (x.regs frames + 4 * (x.regs level + 1) + x.regs lists + + x.regs values + (x.regs level + 1) * x.regs span + x.regs level + + x.regs arrayA + p + x.regs arrayB + p + 12)) : + ∃ y, ExecWithin (w + 2) step x y 43 ∧ + y.mem = (if x.mem (x.regs frames + 4 * x.regs level) = 0 then x.mem else + Function.update x.mem (x.regs values + x.regs level * x.regs span) + (encode (a * b))) ∧ + y.regs level = (if x.regs level = 0 then 0 else x.regs level - 1) ∧ + y.regs mode = (if x.regs level = 0 then 0 else 2) ∧ + y.regs active = (if x.regs level = 0 then 0 else x.regs active) ∧ + (∀ i, ¬LeafStepLocal i → y.regs i = x.regs i) := by + obtain ⟨u, hu, humem, _, huc, hup, _, _, _, huv, _, _, _, hureg⟩ := + exec_loadFrame x h1 h4 (hw.of_le (by omega)) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have hu0 := (fixed nil (by decide)).trans h0 + have hu1 := (fixed one (by decide)).trans h1 + have hud := fixed level (by decide) + have hum := (fixed mode (by decide)).trans hm + have hua := fixed active (by decide) + have huA := fixed arrayA (by decide) + have huB := fixed arrayB (by decide) + have hup' : u.regs leafPrefix = p := hup.trans hpref + obtain ⟨y, hy, hymem, hyl, hym, hya, hyreg⟩ := exec_leaf ha hb hab u hu0 hu1 + (by simpa only [humem, huA, hup'] using hleft) + (by simpa only [humem, huB, hup'] using hright) + (hw.of_le (by rw [huA, hup']; omega)) + (hw.of_le (by rw [huB, hup']; omega)) + (hw.of_le (by rw [hud]; omega)) + have hentry : ExecWithin (w + 2) enter u y 25 := + hy.branch_pos (by simpa only [Cond.holds_eq, hud, fixed inputDepth (by decide)] using hd) + have hbranch : ExecWithin (w + 2) + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y 26 := + hentry.branch_pos (by simp only [Cond.holds_eq, hum, hu0]) + refine ⟨y, hu.seq hbranch, ?_, ?_, ?_, ?_, ?_⟩ + · simpa only [huc, huv, humem] using hymem + · simpa only [hud] using hyl + · simpa only [hud, hum] using hym + · simpa only [hud, hua] using hya + · intro i hi + exact (hyreg i (fun h => hi (Or.inr h))).trans + (hureg i (fun h => hi (Or.inl h))) + +/-- A terminal node is one actual iteration of the existing native loop. Its 43 body +instructions plus the two loop instructions are charged before any continuation. -/ +theorem exec_controller_leaf {w p : ℕ} {a b : ℤ} (ha : GenericRAM.Signed.Fits w a) + (hb : GenericRAM.Signed.Fits w b) (hab : GenericRAM.Signed.Fits w (a * b)) + (x : Data registers) (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (h4 : x.regs frameWidth = 4) (hm : x.regs mode = 0) + (hd : x.regs level = x.regs inputDepth) (hactive : x.regs active = 1) + (hpref : x.mem (x.regs frames + 4 * x.regs level + 1) = p) + (hleft : x.mem (x.regs arrayA + p) = encode a) + (hright : x.mem (x.regs arrayB + p) = encode b) + (hw : Fits (w + 2) (x.regs frames + 4 * (x.regs level + 1) + x.regs lists + + x.regs values + (x.regs level + 1) * x.regs span + x.regs level + + x.regs arrayA + p + x.regs arrayB + p + 12)) : + ∃ y, y.mem = (if x.mem (x.regs frames + 4 * x.regs level) = 0 then x.mem else + Function.update x.mem (x.regs values + x.regs level * x.regs span) + (encode (a * b))) ∧ + y.regs level = (if x.regs level = 0 then 0 else x.regs level - 1) ∧ + y.regs mode = (if x.regs level = 0 then 0 else 2) ∧ + y.regs active = (if x.regs level = 0 then 0 else 1) ∧ + (∀ i, ¬LeafStepLocal i → y.regs i = x.regs i) ∧ + ∀ z U, ExecWithin (w + 2) controller y z U → + ExecWithin (w + 2) controller x z (45 + U) := by + obtain ⟨y, hy, hmem, hyl, hym, hya, hreg⟩ := + exec_leafStep ha hb hab x h0 h1 h4 hm hd hpref hleft hright hw + refine ⟨y, hmem, hyl, hym, by simpa only [hactive] using hya, hreg, ?_⟩ + intro z U hz + have he := ExecWithin.loop_iter (by simp only [Cond.holds_lt, h0, hactive]; decide) hy hz + exact he.mono (by omega) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Memory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Memory.lean new file mode 100644 index 0000000000..2bf6c5df70 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Memory.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Program + +/-! +# Concrete input and scratch layout for native pruned queries + +## Main definitions + +* `PrunedQuery.input`: two ordinary headers, two encoded leaf arrays and an explicit query list. +* `PrunedQuery.outputBase`, `PrunedQuery.scratchBase`, `PrunedQuery.memoryEnd`: the output and + the complete explicit-stack workspace. + +The standard `State.init` length cell precedes this list. Query codes and the two source arrays +are read-only. The timestamp region is initially zero because it lies beyond the supplied input. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- Flat initialized input: depth, query count, both signed arrays, then explicit query codes. -/ +def input (L : ℕ) (a b : Fin (10 ^ L) → ℤ) (qs : List (Fin (10 ^ L))) : List ℕ := + [L, qs.length] ++ List.ofFn (fun i => encode (a i)) ++ + List.ofFn (fun i => encode (b i)) ++ qs.map Fin.val + +/-- Start of the returned values, in the original query-list order. -/ +def outputBase (L Q : ℕ) : ℕ := 3 + 2 * 10 ^ L + Q + +/-- First stack-frame cell, immediately after the returned values. -/ +def scratchBase (L Q : ℕ) : ℕ := outputBase L Q + Q + +/-- Query lists occupy one `10^L` block per possible stack depth. -/ +def listsBase (L Q : ℕ) : ℕ := scratchBase L Q + 4 * (L + 1) + +/-- Values occupy a separate `10^L` block per depth. -/ +def valuesBase (L Q : ℕ) : ℕ := listsBase L Q + (L + 1) * 10 ^ L + +/-- One shared timestamp directory suffices for all child-set constructions. -/ +def stampsBase (L Q : ℕ) : ℕ := valuesBase L Q + (L + 1) * 10 ^ L + +/-- One-past-end address of the complete output and scratch footprint. -/ +def memoryEnd (L Q : ℕ) : ℕ := stampsBase L Q + 10 ^ L + +/-- A scalar budget for the concrete addresses, counters, powers and timestamp generations. -/ +def addressBound (L Q : ℕ) : ℕ := 256 * (L + 1) * 10 ^ L + 32 * Q + 512 + +/-- The initialized payload includes no precomputed child requests or recursion certificate. -/ +theorem length_input (L : ℕ) (a b : Fin (10 ^ L) → ℤ) (qs : List (Fin (10 ^ L))) : + (input L a b qs).length = 2 + 2 * 10 ^ L + qs.length := by + simp only [input, List.length_append, List.length_cons, List.length_nil, List.length_ofFn, + List.length_map] + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Node.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Node.lean new file mode 100644 index 0000000000..35ef19f19f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Node.lean @@ -0,0 +1,285 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.LeafStep +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Clear +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Weight +public import Complexity.Computability.WordRAM.Heap + +/-! +# Memory and register invariants for actual controller nodes + +## Main definitions + +* `PrunedQuery.KernelRegs`: the unchanged native parameters and computed workspace pointers. +* `PrunedQuery.nodeWrites`: the descendant rows that one node may change. + +## Main results + +* `PrunedQuery.exec_node_zero`: the terminal node case, with the existing mathematical result, + concrete frame and timestamp preservation, and charged native continuation. + +The invariants describe ordinary register and memory values. They contain no execution, +solver, time-bound or all-state-fit field. Execution is proved separately in native semantics. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Fixed caller parameters and the explicit workspace partition computed by `locate`. +This is a register-value assertion, with no assumed execution or resource guarantee. -/ +structure KernelRegs (L f a b : ℕ) (x : Data registers) : Prop where + /-- Original recursion depth. -/ + depth_eq : x.regs inputDepth = L + /-- Actual source-array and row capacity. -/ + span_eq : x.regs span = 10 ^ L + /-- Left input-array pointer. -/ + left_eq : x.regs arrayA = a + /-- Right input-array pointer. -/ + right_eq : x.regs arrayB = b + /-- First stored frame. -/ + frames_eq : x.regs frames = f + /-- First query row, after the four-word frames. -/ + lists_eq : x.regs lists = f + 4 * (L + 1) + /-- First value row, after all query rows. -/ + values_eq : x.regs values = f + 4 * (L + 1) + (L + 1) * 10 ^ L + /-- Shared timestamp directory, after all value rows. -/ + stamps_eq : x.regs stamps = f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + /-- Constant zero. -/ + nil_eq : x.regs nil = 0 + /-- Constant one. -/ + one_eq : x.regs one = 1 + /-- Constant ten. -/ + ten_eq : x.regs ten = 10 + /-- Constant inner-output digit. -/ + nine_eq : x.regs nine = 9 + /-- Four ordinary words per frame. -/ + frameWidth_eq : x.regs frameWidth = 4 + +/-- The exact permitted workspace rows for a node at depth `d`. Its own query row is read-only; +descendant query rows, current/descendant value rows, frame suffix and timestamps may change. -/ +def nodeWrites (L f d : ℕ) : Finset ℕ := + (Block.mk (f + 4 * d) (4 * (L + 1 - d))).footprint ∪ + (Block.mk (f + 4 * (L + 1) + (d + 1) * 10 ^ L) ((L - d) * 10 ^ L)).footprint ∪ + (Block.mk (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) + ((L + 1 - d) * 10 ^ L)).footprint ∪ + (Block.mk (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (10 ^ L)).footprint + +private theorem KernelRegs.of_leafStep {L f a b : ℕ} {x y : Data registers} + (h : KernelRegs L f a b x) + (hy : ∀ i, ¬LeafStepLocal i → y.regs i = x.regs i) : KernelRegs L f a b y := by + constructor + · exact (hy inputDepth (by decide)).trans h.depth_eq + · exact (hy span (by decide)).trans h.span_eq + · exact (hy arrayA (by decide)).trans h.left_eq + · exact (hy arrayB (by decide)).trans h.right_eq + · exact (hy frames (by decide)).trans h.frames_eq + · exact (hy lists (by decide)).trans h.lists_eq + · exact (hy values (by decide)).trans h.values_eq + · exact (hy stamps (by decide)).trans h.stamps_eq + · exact (hy nil (by decide)).trans h.nil_eq + · exact (hy one (by decide)).trans h.one_eq + · exact (hy ten (by decide)).trans h.ten_eq + · exact (hy nine (by decide)).trans h.nine_eq + · exact (hy frameWidth (by decide)).trans h.frameWidth_eq + +/-- Every branch of the actual controller preserves the fixed caller parameters. -/ +theorem KernelRegs.of_step {w T L f a b : ℕ} {x y : Data registers} + (h : KernelRegs L f a b x) (he : ExecWithin w step x y T) : KernelRegs L f a b y := by + constructor + · exact (he.regs_eq (by decide : step.Preserves inputDepth)).trans h.depth_eq + · exact (he.regs_eq (by decide : step.Preserves span)).trans h.span_eq + · exact (he.regs_eq (by decide : step.Preserves arrayA)).trans h.left_eq + · exact (he.regs_eq (by decide : step.Preserves arrayB)).trans h.right_eq + · exact (he.regs_eq (by decide : step.Preserves frames)).trans h.frames_eq + · exact (he.regs_eq (by decide : step.Preserves lists)).trans h.lists_eq + · exact (he.regs_eq (by decide : step.Preserves values)).trans h.values_eq + · exact (he.regs_eq (by decide : step.Preserves stamps)).trans h.stamps_eq + · exact (he.regs_eq (by decide : step.Preserves nil)).trans h.nil_eq + · exact (he.regs_eq (by decide : step.Preserves one)).trans h.one_eq + · exact (he.regs_eq (by decide : step.Preserves ten)).trans h.ten_eq + · exact (he.regs_eq (by decide : step.Preserves nine)).trans h.nine_eq + · exact (he.regs_eq (by decide : step.Preserves frameWidth)).trans h.frameWidth_eq + +/-- The terminal induction case is a proved actual controller iteration. The requested values +are the existing `Pruned.result`; an empty set leaves the value row untouched. The memory +frame and timestamp bound hold at the same final state as the charged continuation. -/ +theorem exec_node_zero {w L f a0 b0 p E : ℕ} {a b : ℤ} + (ha : GenericRAM.Signed.Fits w a) (hb : GenericRAM.Signed.Fits w b) + (hab : GenericRAM.Signed.Fits w (a * b)) + (S : Finset (OutStr 0)) (x : Data registers) (hregs : KernelRegs L f a0 b0 x) + (hd : x.regs level = L) (hm : x.regs mode = 0) (hactive : x.regs active = 1) + (hcount : x.mem (f + 4 * L) = S.card) (hpref : x.mem (f + 4 * L + 1) = p) + (hleft : x.mem (a0 + p) = encode a) (hright : x.mem (b0 + p) = encode b) + (hE : x.regs epoch = E) + (hmarks : ∀ q < 10 ^ L, + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ E) + (hw : Fits (w + 2) (f + 4 * (L + 1) + (f + 4 * (L + 1)) + + (f + 4 * (L + 1) + (L + 1) * 10 ^ L) + (L + 1) * 10 ^ L + L + + a0 + p + b0 + p + 12)) : + ∃ y, KernelRegs L f a0 b0 y ∧ + y.regs level = (if L = 0 then 0 else L - 1) ∧ + y.regs mode = (if L = 0 then 0 else 2) ∧ + y.regs active = (if L = 0 then 0 else 1) ∧ y.regs epoch = E ∧ + (∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ E) ∧ + (∀ q ∈ S, y.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + L * 10 ^ L + + (queryIndex q).val) = encode (Pruned.result 0 (fun _ => a) (fun _ => b) S q)) ∧ + Modifies (nodeWrites L f L) x.mem y.mem ∧ + (∀ i, ¬LeafStepLocal i → y.regs i = x.regs i) ∧ + ∀ z U, ExecWithin (w + 2) controller y z U → + ExecWithin (w + 2) controller x z (traceWeight S + U) := by + obtain ⟨y, hmem, hyl, hym, hya, hreg, hrun⟩ := exec_controller_leaf ha hb hab x + hregs.nil_eq hregs.one_eq hregs.frameWidth_eq hm (hd.trans hregs.depth_eq.symm) + hactive + (by simpa only [hregs.frames_eq, hd] using hpref) + (by simpa only [hregs.left_eq] using hleft) + (by simpa only [hregs.right_eq] using hright) + (by simpa only [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, + hregs.left_eq, hregs.right_eq, hd] using hw) + have hm' : y.mem = if S.card = 0 then x.mem else + Function.update x.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + L * 10 ^ L) + (encode (a * b)) := by + simpa only [hregs.frames_eq, hd, hcount, hregs.values_eq, hregs.span_eq] using hmem + have hpow : 0 < (10 : ℕ) ^ L := by positivity + have haway (q : ℕ) : f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q ≠ + f + 4 * (L + 1) + (L + 1) * 10 ^ L + L * 10 ^ L := by nlinarith + refine ⟨y, hregs.of_leafStep hreg, ?_, ?_, ?_, ?_, ?_, ?_, ?_, hreg, ?_⟩ + · simpa only [hd] using hyl + · simpa only [hd] using hym + · simpa only [hd] using hya + · exact (hreg epoch (by decide)).trans hE + · intro q hq + rw [hm'] + split_ifs + · exact hmarks q hq + · rw [Function.update_of_ne (haway q)] + exact hmarks q hq + · intro q hq + have hc : S.card ≠ 0 := Finset.card_ne_zero.mpr ⟨q, hq⟩ + have hq0 : (queryIndex q).val = 0 := by have := (queryIndex q).isLt; omega + simp only [hm', hc, ↓reduceIte, hq0, Nat.add_zero, Function.update_self, + Pruned.result, Pruned.run, Finset.piecewise, hq] + · intro c hc + rw [hm'] + split_ifs + · rfl + · apply Function.update_of_ne + intro he + apply hc + apply Finset.mem_union_left + apply Finset.mem_union_right + rw [Block.mem_footprint] + change _ ≤ c ∧ c < _ + subst c + simp only [Nat.add_sub_cancel_left] + constructor <;> omega + · intro z U hz + exact (hrun z U hz).mono (by rw [traceWeight_zero]; omega) + +/-- Enter an internal node by clearing only its listed accumulators and storing the actual +zero term counter. This is a complete controller step, including frame loads and comparisons. +Its displayed address budget contains only ordinary workspace parameters and list length. -/ +theorem exec_node_enter {w L f a0 b0 d N : ℕ} (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hd : x.regs level = d) (hdL : d < L) + (hm : x.regs mode = 0) (hN : x.mem (f + 4 * d) = N) (hcap : N ≤ 10 ^ L) + (input : ℕ → ℕ) + (hinput : ∀ i < N, x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i) + (hcodes : ∀ i < N, input i < 10 ^ L) + (hw : Fits w (8 * f + 64 * (L + 1) * 10 ^ L + 128)) : + ∃ y, ExecWithin w step x y (7 * N + 28) ∧ KernelRegs L f a0 b0 y ∧ + y.regs level = d ∧ y.regs mode = 1 ∧ y.regs epoch = x.regs epoch ∧ + y.regs active = x.regs active ∧ + y.mem = Function.update + (zeroQueryPrefix x.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) + input N) (f + 4 * d + 3) 0 ∧ + (∀ i < N, y.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i) ∧ + Modifies (nodeWrites L f d) x.mem y.mem := by + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hmul : (d + 1) * 10 ^ L ≤ (L + 1) * 10 ^ L := + Nat.mul_le_mul_right _ (by omega) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + obtain ⟨u, hu, humem, huf, huc, _, _, _, hul, huv, _, _, _, hureg⟩ := + exec_loadFrame x hregs.one_eq hregs.frameWidth_eq (hw.of_le (by + rw [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, hd] + nlinarith only [hpow, hmul, hL, hdL, hcap])) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have huf' : u.regs frame = f + 4 * d := by simpa only [hregs.frames_eq, hd] using huf + have hul' : u.regs listBase = f + 4 * (L + 1) + d * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using hul + have huv' : u.regs valueBase = f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L := by + simpa only [hregs.values_eq, hregs.span_eq, hd] using huv + have hc : u.regs count = N := by simpa only [hregs.frames_eq, hd, hN] using huc + have hu0 := (fixed nil (by decide)).trans hregs.nil_eq + have hu1 := (fixed one (by decide)).trans hregs.one_eq + have hu4 := (fixed frameWidth (by decide)).trans hregs.frameWidth_eq + obtain ⟨y, hy, _, _, hym, hymem, hyread, _⟩ := exec_clearValues u input hc + (by simpa only [hul', humem] using hinput) hcodes + (Or.inl (by rw [hul', huv']; nlinarith only [hpow, hmul, hL, hdL, hcap])) + (Or.inl (by rw [huf', hul']; omega)) hu0 hu1 hu4 + (hw.of_le (by rw [hul', huv']; nlinarith only [hpow, hmul, hL, hdL, hcap])) + (hw.of_le (by rw [huf']; nlinarith only [hpow, hmul, hL, hdL, hcap])) + have hnotLeaf : ¬(Cond.eq level inputDepth).Holds u := by + simp only [Cond.holds_eq, fixed level (by decide), fixed inputDepth (by decide), + hd, hregs.depth_eq] + omega + have hentry : ExecWithin w enter u y (7 * N + 10) := hy.branch_neg hnotLeaf + have hbranch : ExecWithin w + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y (7 * N + 11) := + hentry.branch_pos (by simp only [Cond.holds_eq, fixed mode (by decide), + fixed nil (by decide), hm, hregs.nil_eq]) + have he : ExecWithin w step x y (7 * N + 28) := (hu.seq hbranch).mono (by omega) + have hmem : y.mem = Function.update + (zeroQueryPrefix x.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) + input N) (f + 4 * d + 3) 0 := by + simpa only [humem, huv', huf'] using hymem + refine ⟨y, he, hregs.of_step he, ?_, hym, ?_, ?_, hmem, ?_, ?_⟩ + · exact ((hy.regs_eq (by decide : clearValues.Preserves level)).trans + (fixed level (by decide))).trans hd + · exact (hy.regs_eq (by decide : clearValues.Preserves epoch)).trans + (fixed epoch (by decide)) + · exact (hy.regs_eq (by decide : clearValues.Preserves active)).trans + (fixed active (by decide)) + · simpa only [hul'] using hyread + · intro c hc' + have hf : ¬(Block.mk (f + 4 * d) (4 * (L + 1 - d))).Owns c := by + intro h + exact hc' (Finset.mem_union_left _ (Finset.mem_union_left _ + (Finset.mem_union_left _ (Block.mem_footprint.mpr h)))) + have hv : ¬(Block.mk + (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) (10 ^ L)).Owns c := by + intro h + apply hc' + apply Finset.mem_union_left + apply Finset.mem_union_right + rw [Block.mem_footprint] + change f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L ≤ c ∧ + c < f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + + (L + 1 - d) * 10 ^ L + change f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L ≤ c ∧ + c < f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + 10 ^ L at h + have hrem : 1 ≤ L + 1 - d := by omega + have hm' := Nat.mul_le_mul_right (10 ^ L) hrem + exact ⟨h.1, by omega⟩ + rw [hmem, Function.update_of_ne (by + intro heq + apply hf + change f + 4 * d ≤ c ∧ c < f + 4 * d + 4 * (L + 1 - d) + have hrem : 1 ≤ L + 1 - d := by omega + omega)] + exact zeroQueryPrefix_outside hcodes hv + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/NodeFrame.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/NodeFrame.lean new file mode 100644 index 0000000000..7237d2337d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/NodeFrame.lean @@ -0,0 +1,132 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.EmptyChild + +/-! +# Frames for the recursive controller proof + +## Main results + +* `PrunedQuery.nodeWrites_child_subset`: a recursive child's writes stay in its parent's frame. +* `PrunedQuery.notMem_nodeWrites_list`: a node's request list remains read-only. +* `PrunedQuery.notMem_nodeWrites_ancestor_values`: a child preserves its parent's accumulators. + +The footprints are the concrete intervals used by the native program. These facts impose no +execution or fit hypotheses; they provide the spatial part of the node induction. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Normalize every row interval to its fixed workspace endpoint. -/ +theorem mem_nodeWrites_iff {L f d c : ℕ} (hd : d ≤ L) : c ∈ nodeWrites L f d ↔ + (((f + 4 * d ≤ c ∧ c < f + 4 * (L + 1)) ∨ + (f + 4 * (L + 1) + (d + 1) * 10 ^ L ≤ c ∧ + c < f + 4 * (L + 1) + (L + 1) * 10 ^ L)) ∨ + (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L ≤ c ∧ + c < f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L)) ∨ + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L ≤ c ∧ + c < f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + 10 ^ L) := by + have hf : f + 4 * d + 4 * (L + 1 - d) = f + 4 * (L + 1) := by omega + have hl : f + 4 * (L + 1) + (d + 1) * 10 ^ L + (L - d) * 10 ^ L = + f + 4 * (L + 1) + (L + 1) * 10 ^ L := by + calc + _ = f + 4 * (L + 1) + (d + 1 + (L - d)) * 10 ^ L := by ring + _ = _ := by rw [show d + 1 + (L - d) = L + 1 by omega] + have hv : f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + + (L + 1 - d) * 10 ^ L = f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L := by + calc + _ = f + 4 * (L + 1) + ((L + 1) + (d + (L + 1 - d))) * 10 ^ L := by ring + _ = _ := by rw [show d + (L + 1 - d) = L + 1 by omega]; ring + simp only [nodeWrites, Finset.mem_union, Block.mem_footprint, Block.Owns, hf, hl, hv] + +/-- The recursive child's full writable frame is contained in the parent's frame. -/ +theorem nodeWrites_child_subset {L f d : ℕ} (hd : d < L) : + nodeWrites L f (d + 1) ⊆ nodeWrites L f d := by + intro c hc + rw [mem_nodeWrites_iff (by omega : d + 1 ≤ L)] at hc + rw [mem_nodeWrites_iff hd.le] + have hv := Nat.mul_le_mul_right (10 ^ L) (by omega : d ≤ d + 1) + have hl := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 1 ≤ d + 1 + 1) + omega + +/-- The scan-and-push transition changes only cells in the current node's frame. -/ +theorem childWrites_subset_nodeWrites {L f d : ℕ} (hd : d < L) : + childWrites L f d ⊆ nodeWrites L f d := by + intro c hc + simp only [childWrites, Finset.mem_union, Block.mem_footprint, Block.Owns] at hc + rw [mem_nodeWrites_iff hd.le] + have hprod := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 2 ≤ L + 1) + have hstep : (d + 1) * 10 ^ L + 10 ^ L = (d + 2) * 10 ^ L := by ring + rcases hc with (hc | hc) | hc + · left; left; right + exact ⟨hc.1, by omega⟩ + · right + exact hc + · left; left; left + omega + +/-- The scan-and-skip transition also stays inside the current node's frame. -/ +theorem emptyChildWrites_subset_nodeWrites {L f d : ℕ} (hd : d < L) : + emptyChildWrites L f d ⊆ nodeWrites L f d := by + intro c hc + simp only [emptyChildWrites, Finset.mem_union, Block.mem_footprint, Block.Owns] at hc + rw [mem_nodeWrites_iff hd.le] + have hprod := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 2 ≤ L + 1) + have hstep : (d + 1) * 10 ^ L + 10 ^ L = (d + 2) * 10 ^ L := by ring + rcases hc with (hc | hc) | hc + · left; left; right + exact ⟨hc.1, by omega⟩ + · right + exact hc + · left; left; left + omega + +/-- No node changes the original input or any other cell below the workspace. -/ +theorem notMem_nodeWrites_before {L f d c : ℕ} (hd : d ≤ L) (hc : c < f) : + c ∉ nodeWrites L f d := by + rw [mem_nodeWrites_iff hd] + omega + +/-- A node preserves every occupied or unoccupied cell of its own request row. -/ +theorem notMem_nodeWrites_list {L f d q : ℕ} (hd : d ≤ L) (hq : q < 10 ^ L) : + f + 4 * (L + 1) + d * 10 ^ L + q ∉ nodeWrites L f d := by + rw [mem_nodeWrites_iff hd] + have hprod := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 1 ≤ L + 1) + have hnext : d * 10 ^ L + q < (d + 1) * 10 ^ L := by nlinarith only [hq] + have hvals : d * 10 ^ L + q < (L + 1) * 10 ^ L := hnext.trans_le hprod + have hsplit : (L + 1) * 10 ^ L ≤ 2 * (L + 1) * 10 ^ L := by + rw [Nat.mul_assoc] + omega + omega + +/-- Returning from a child preserves all four words of every ancestor frame. -/ +theorem notMem_nodeWrites_ancestor_frame {L f d e j : ℕ} (hd : d ≤ L) (he : e < d) + (hj : j < 4) : f + 4 * e + j ∉ nodeWrites L f d := by + rw [mem_nodeWrites_iff hd] + omega + +/-- Returning from a child preserves every value-directory cell in an ancestor's row. -/ +theorem notMem_nodeWrites_ancestor_values {L f d e q : ℕ} (hd : d ≤ L) (he : e < d) + (hq : q < 10 ^ L) : + f + 4 * (L + 1) + (L + 1) * 10 ^ L + e * 10 ^ L + q ∉ nodeWrites L f d := by + rw [mem_nodeWrites_iff hd] + have hprod := Nat.mul_le_mul_right (10 ^ L) (by omega : e + 1 ≤ d) + have hprodL := Nat.mul_le_mul_right (10 ^ L) (by omega : d ≤ L + 1) + have hnext : e * 10 ^ L + q < (e + 1) * 10 ^ L := by nlinarith only [hq] + have hlt : e * 10 ^ L + q < d * 10 ^ L := hnext.trans_le hprod + have hts : (L + 1) * 10 ^ L + e * 10 ^ L + q < 2 * (L + 1) * 10 ^ L := by + nlinarith only [hlt, hprodL] + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Powers.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Powers.lean new file mode 100644 index 0000000000..93023bad47 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Powers.lean @@ -0,0 +1,117 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory + +/-! +# Charged initialization and encoding-array size + +## Main results + +* `PrunedQuery.exec_powers`: compute `10^L` using the actual native multiplication loop, + preserving all caller parameters and memory. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- The scalar registers initialized or advanced by the power loop. -/ +def PowersLocal (i : Fin registers) : Prop := + i = nil ∨ i = one ∨ i = two ∨ i = ten ∨ i = nine ∨ i = frameWidth ∨ i = span ∨ i = index + +instance (i : Fin registers) : Decidable (PowersLocal i) := + inferInstanceAs (Decidable + (i = nil ∨ i = one ∨ i = two ∨ i = ten ∨ i = nine ∨ i = frameWidth ∨ i = span ∨ i = index)) + +private theorem powers_preserves (i : Fin registers) : ¬PowersLocal i → powers.Preserves i := by + decide +kernel +revert + +/-- The power loop charges its eight initializations, both body instructions, loop tests and +jumps. The only width premise is a bound on ordinary input-derived natural numbers. -/ +theorem exec_powers {w L Q : ℕ} (x : Data registers) (hL : x.regs inputDepth = L) + (hbound : Fits w (addressBound L Q)) : + ∃ y, ExecWithin w powers x y (4 * L + 10) ∧ y.mem = x.mem ∧ + y.regs inputDepth = L ∧ y.regs span = 10 ^ L ∧ y.regs index = L ∧ + y.regs nil = 0 ∧ y.regs one = 1 ∧ y.regs two = 2 ∧ y.regs ten = 10 ∧ + y.regs nine = 9 ∧ y.regs frameWidth = 4 ∧ + (∀ i, ¬PowersLocal i → y.regs i = x.regs i) := by + have hspan : 1 ≤ (10 : ℕ) ^ L := by + have hp : 0 < (10 : ℕ) ^ L := by positivity + omega + have hscalar : L + 128 + 128 * 10 ^ L ≤ addressBound L Q := by + have hm := Nat.mul_le_mul_left L hspan + unfold addressBound + nlinarith + have hbase := hbound.of_le hscalar + have h0 : Fits w 0 := fits_zero w + have h1 : Fits w 1 := hbase.of_le (by omega) + have h2 : Fits w 2 := hbase.of_le (by omega) + have h4 : Fits w 4 := hbase.of_le (by omega) + have h9 : Fits w 9 := hbase.of_le (by omega) + have h10 : Fits w 10 := hbase.of_le (by omega) + have hpow (j : ℕ) (hj : j ≤ L) : Fits w (10 ^ j) := by + have hle := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hj + exact hbase.of_le (by omega) + let I (y : Data registers) : Prop := + y.mem = x.mem ∧ y.regs inputDepth = L ∧ y.regs span = 10 ^ y.regs index ∧ + y.regs index ≤ L ∧ y.regs one = 1 ∧ y.regs ten = 10 + have hstep (y : Data registers) (hy : I y) (hc : (Cond.lt index inputDepth).Holds y) : + ∃ z, ExecWithin w powerStep y z 2 ∧ I z ∧ + L - z.regs index < L - y.regs index := by + obtain ⟨hm, hd, hp, hj, ho, ht⟩ := hy + have hlt : y.regs index < L := by simpa only [Cond.holds_lt, hd] using hc + have hmul : Fits w (y.regs span * 10) := by + rw [hp, ← pow_succ] + exact hpow _ (by omega) + have hinc : Fits w (y.regs index + 1) := hbase.of_le (by omega) + let z := (y.set span (y.regs span * 10)).set index (y.regs index + 1) + have he : ExecWithin w powerStep y z 2 := by + ram_eval [powerStep, z, ho, ht, hmul, hinc, span, index, ten, one, Fin.mk.injEq] + refine ⟨z, he, ?_, ?_⟩ + · refine ⟨hm, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [z, inputDepth, span, index, Function.update_apply] using hd + · simp +decide [z, span, index, hp, pow_succ] + · simpa [z] using Nat.succ_le_of_lt hlt + · simpa +decide [z, one, span, index, Function.update_apply] using ho + · simpa +decide [z, ten, span, index, Function.update_apply] using ht + · simp only [z, Data.set_regs, Function.update_self] + omega + let x0 := ((((((((x.set nil 0).set one 1).set two 2).set ten 10).set nine 9).set + frameWidth 4).set span 1).set index 0) + have hsetup : ExecWithin w powerSetup x x0 8 := by + ram_eval [powerSetup, x0, h0, h1, h2, h4, h9, h10] + have hI : I x0 := by + simp +decide [I, x0, hL, inputDepth, nil, one, two, ten, nine, frameWidth, span, index] + obtain ⟨y, hy, hIy, hstop⟩ := ExecWithin.loop_invariant I (fun y => L - y.regs index) + hstep hI + have he : ExecWithin w powers x y (4 * L + 10) := by + exact (hsetup.seq hy).mono (by simp [x0]; omega) + obtain ⟨hm, hd, hp, hj, ho, ht⟩ := hIy + have hidx : y.regs index = L := by + simp only [Cond.holds_lt, hd, not_lt] at hstop + omega + refine ⟨y, he, hm, hd, hp.trans (congrArg (fun n => 10 ^ n) hidx), hidx, + ?_, ho, ?_, ht, ?_, ?_, fun i hi => he.regs_eq (powers_preserves i hi)⟩ + · simpa +decide [x0, nil, one, two, ten, nine, frameWidth, span, index] using + hy.regs_eq (by decide : + (Code.loop (.lt index inputDepth) powerStep).Preserves nil) + · simpa +decide [x0, nil, one, two, ten, nine, frameWidth, span, index] using + hy.regs_eq (by decide : + (Code.loop (.lt index inputDepth) powerStep).Preserves two) + · simpa +decide [x0, nil, one, two, ten, nine, frameWidth, span, index] using + hy.regs_eq (by decide : + (Code.loop (.lt index inputDepth) powerStep).Preserves nine) + · simpa +decide [x0, nil, one, two, ten, nine, frameWidth, span, index] using + hy.regs_eq (by decide : + (Code.loop (.lt index inputDepth) powerStep).Preserves frameWidth) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Program.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Program.lean new file mode 100644 index 0000000000..37537e76f7 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Program.lean @@ -0,0 +1,370 @@ +/- +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.Pruned.Codes +public import Complexity.Computability.WordRAM.Signed.Arithmetic + +/-! +# Fixed native code for pruned tensor queries + +The controller uses an explicit depth-first stack. Query and value directories have one row +per depth. A shared timestamp directory deduplicates child suffixes without clearing a dense +array at every node. Encoded products and sums use the existing native signed kernels. + +## Main definitions + +* `PrunedQuery.kernel`: evaluator with explicit caller-supplied array and scratch addresses. +* `PrunedQuery.program`: initialized-input wrapper computing all addresses from the depth and + query-count headers. + +Client execution interfaces are `exec_kernel` in `Pruned.Kernel`, `runs` and `runs_bilinear` +in `Pruned.Flat`, and `runs_wide` in `Pruned.Width`. Other stage `exec_*` lemmas and controller +invariants under `Pruned/` are internal implementation APIs. They are public to compose proofs +across the stage modules, rather than intended as independent caller contracts. + +## References + +The query-set recursion is from [AlmanWilliams2026, Section 2.4.2], selectively adapted from +Anthropic's formal-math revision `16a5ce41304fcdaea4e0be7c132e1c361dc26da5`, +[pinned source](https://github.com/anthropics/formal-math/blob/16a5ce41304fcdaea4e0be7c132e1c361dc26da5/3sum-apsp/ThreeSumApsp/Programs/Sec2/Theorem5/Pruned/Recursion.lean). The explicit iterative stack and +timestamp implementation are new native code; the source machine is not imported. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed + +/-- The program has a fixed register bank, including nine signed-arithmetic scratch registers. -/ +abbrev registers : ℕ := 96 + +/-- Original tensor depth, loaded from the ordinary input header. -/ +abbrev inputDepth : Fin registers := 9 +/-- Computed encoding-array length and per-depth directory capacity, `10^L`. -/ +abbrev span : Fin registers := 10 +/-- Pointer to the left canonical signed encoding array. -/ +abbrev arrayA : Fin registers := 11 +/-- Pointer to the right canonical signed encoding array. -/ +abbrev arrayB : Fin registers := 12 +/-- Pointer to the original unsigned query-code list, including repeats. -/ +abbrev queries : Fin registers := 13 +/-- Number of supplied queries, before native deduplication. -/ +abbrev queryCount : Fin registers := 14 +/-- Pointer to the answer list, in the original query order. -/ +abbrev output : Fin registers := 15 +/-- Caller-supplied start of the explicit stack and directory workspace. -/ +abbrev scratch : Fin registers := 16 +/-- Current timestamp generation for native query deduplication. -/ +abbrev epoch : Fin registers := 17 +/-- Pointer to the first four-word stack frame. -/ +abbrev frames : Fin registers := 18 +/-- Pointer to the first per-depth distinct-query row. -/ +abbrev lists : Fin registers := 19 +/-- Pointer to the first per-depth signed-value row. -/ +abbrev values : Fin registers := 20 +/-- Pointer to the shared query-membership timestamp directory. -/ +abbrev stamps : Fin registers := 21 +/-- Current stack depth, with zero denoting the root. -/ +abbrev level : Fin registers := 22 +/-- Controller phase: zero for entry, one for dispatch, two for returned-child accumulation. -/ +abbrev mode : Fin registers := 23 +/-- Traversal flag; returning from the root sets this to zero. -/ +abbrev active : Fin registers := 24 +/-- Raw unsigned constant one for counters and offsets. -/ +abbrev one : Fin registers := 25 +/-- Raw unsigned radix ten for the fixed term and query dictionaries. -/ +abbrev ten : Fin registers := 26 +/-- Inner-output wildcard digit, in the existing term numbering. -/ +abbrev nine : Fin registers := 27 +/-- Raw unsigned width of a stack frame: four words. -/ +abbrev frameWidth : Fin registers := 28 +/-- Address of the currently loaded stack frame. -/ +abbrev frame : Fin registers := 29 +/-- Number of distinct requests stored in the current frame. -/ +abbrev count : Fin registers := 30 +/-- Processed least-significant leaf digits of the current node. -/ +abbrev leafPrefix : Fin registers := 31 +/-- Place value of the next leaf digit, `10^level`. -/ +abbrev stride : Fin registers := 32 +/-- Next term number to dispatch or accumulate. -/ +abbrev term : Fin registers := 33 +/-- Loop counter for powers, scans, clearing, accumulation and reporting. -/ +abbrev index : Fin registers := 34 +/-- Distinct child count computed by the actual timestamp scan. -/ +abbrev childCount : Fin registers := 35 +/-- Current numerical output-string code loaded from a query list. -/ +abbrev query : Fin registers := 36 +/-- First output digit computed by remainder modulo ten. -/ +abbrev digit : Fin registers := 37 +/-- Remaining output digits computed by division by ten. -/ +abbrev suffix : Fin registers := 38 +/-- Scratch address used by native indexed loads and stores. -/ +abbrev address : Fin registers := 39 +/-- Scratch word for arithmetic, branch fallbacks and loaded values. -/ +abbrev temporary : Fin registers := 40 +/-- Scratch word for row-size and address products. -/ +abbrev temporary₂ : Fin registers := 41 +/-- Timestamp loaded from the membership directory. -/ +abbrev marker : Fin registers := 43 +/-- Address of the current depth's request row. -/ +abbrev listBase : Fin registers := 44 +/-- Address of the current depth's accumulator row. -/ +abbrev valueBase : Fin registers := 45 +/-- Address of the next depth's computed request row. -/ +abbrev childList : Fin registers := 46 +/-- Address of the next depth's returned-value row. -/ +abbrev childValues : Fin registers := 47 +/-- Computed child stack depth. -/ +abbrev nextLevel : Fin registers := 48 +/-- Computed address of the frame about to be pushed. -/ +abbrev childFrame : Fin registers := 49 +/-- Computed leaf prefix of the child about to be pushed. -/ +abbrev childPrefix : Fin registers := 50 +/-- Computed leaf place value for the child about to be pushed. -/ +abbrev childStride : Fin registers := 51 +/-- Raw unsigned constant zero. -/ +abbrev nil : Fin registers := 52 +/-- Raw unsigned constant two, initialized with the fixed scalar constants. -/ +abbrev two : Fin registers := 53 + +/-- Constant initialization, before the depth-controlled power loop. -/ +def powerSetup : Code .standard registers := ram[ + nil ← #0; one ← #1; two ← #2; ten ← #10; nine ← #9; frameWidth ← #4; + span ← #1; index ← #0 ] + +/-- One charged power multiplication and counter increment. -/ +def powerStep : Code .standard registers := ram[ + span ← span * ten; index ← index + one ] + +/-- Initialize constants and compute the actual encoding-array length `10^L`. -/ +def powers : Code .standard registers := ram[ + ⟪powerSetup⟫; while index < inputDepth do [ ⟪powerStep⟫ ] ] + +/-- Separate stack frames, query lists, value directories and membership timestamps. -/ +def locate : Code .standard registers := ram[ + frames ← scratch; + temporary ← inputDepth + one; temporary₂ ← temporary * frameWidth; + lists ← frames + temporary₂; + temporary₂ ← temporary * span; values ← lists + temporary₂; + stamps ← values + temporary₂ ] + +/-- Append one previously unseen suffix and mark it with the current epoch. -/ +def emit : Code .standard registers := ram[ + address ← stamps + suffix; marker ← *address; + if marker = epoch then [ temporary ← temporary ] else [ + address ← childList + childCount; *address ← suffix; + address ← stamps + suffix; *address ← epoch; + childCount ← childCount + one ] ] + +/-- The two fixed suffix kernels either divide a selected child query or copy a root query. -/ +def putSuffix (child : Bool) : Code .standard registers := + .seq (if child then ram[ suffix ← query / ten ] else ram[ suffix ← query ]) emit + +/-- A selected child suffix is computed by an actual unsigned division. -/ +def selectSuffix : Code .standard registers := putSuffix true + +/-- Load the next supplied query and compute its actual first digit. -/ +def scanLoad : Code .standard registers := ram[ + address ← listBase + index; query ← *address; digit ← query % ten ] + +/-- Root queries are copied directly; child queries are filtered by the native digit tests. -/ +def scanDispatch (child : Bool) : Code .standard registers := + if child then ram[ + if digit = nine then [ ⟪selectSuffix⟫ ] else [ + if digit = term then [ ⟪selectSuffix⟫ ] else [ temporary ← temporary ] ] ] + else putSuffix false + +/-- One deduplicating scan step, including input indexing, dispatch and counter increment. -/ +def scanStep (child : Bool) : Code .standard registers := ram[ + ⟪scanLoad⟫; ⟪scanDispatch child⟫; index ← index + one ] + +/-- Initialize one query scan with a genuinely fresh native epoch. -/ +def scanSetup : Code .standard registers := ram[ + epoch ← epoch + one; childCount ← #0; index ← #0 ] + +/-- Scan and deduplicate an explicit source list. -/ +def scan (child : Bool) : Code .standard registers := ram[ + ⟪scanSetup⟫; while index < count do [ ⟪scanStep child⟫ ] ] + +/-- Point the deduplicating root scan at the actual supplied query list. -/ +def rootScanSetup : Code .standard registers := ram[ + listBase ← queries; count ← queryCount; childList ← lists ] + +/-- Store the four actual root-frame words and enter its first controller phase. -/ +def rootFinish : Code .standard registers := ram[ + address ← frames; *address ← childCount; + address ← address + one; *address ← nil; + address ← address + one; *address ← one; + address ← address + one; *address ← nil; + level ← #0; mode ← #0; active ← #1 ] + +/-- Deduplicate supplied queries into the root list; the original list remains read-only. -/ +def rootList : Code .standard registers := ram[ + ⟪rootScanSetup⟫; ⟪scan false⟫; ⟪rootFinish⟫ ] + +/-- Load the current frame and compute its depth-specific list/value addresses. -/ +def loadFrame : Code .standard registers := ram[ + frame ← level * frameWidth; frame ← frames + frame; + address ← frame; count ← *address; + address ← address + one; leafPrefix ← *address; + address ← address + one; stride ← *address; + address ← address + one; term ← *address; + temporary ← level * span; listBase ← lists + temporary; + valueBase ← values + temporary; + nextLevel ← level + one; temporary ← nextLevel * span; + childList ← lists + temporary; childValues ← values + temporary ] + +/-- Return to the parent's accumulation phase, or finish the root. -/ +def pop : Code .standard registers := ram[ + if level = nil then [ active ← #0 ] else [ level ← level - one; mode ← #2 ] ] + +/-- Load both actual encoded words at the current leaf prefix. -/ +def leafLoad : Code .standard registers := ram[ + address ← arrayA + leafPrefix; left ← *address; + address ← arrayB + leafPrefix; right ← *address ] + +/-- One requested leaf product, using the existing native signed multiplication kernel. -/ +def leafProduct : Code .standard registers := ram[ + ⟪leafLoad⟫; ⟪multiply⟫; *valueBase ← result ] + +/-- Evaluate a requested leaf from the actual two encoded words. -/ +def leaf : Code .standard registers := ram[ + if nil < count then [ ⟪leafProduct⟫ ] else [ temporary ← temporary ]; + ⟪pop⟫ ] + +/-- One actual indexed store for an explicitly listed requested value. -/ +def clearValueStep : Code .standard registers := ram[ + address ← listBase + index; query ← *address; + address ← valueBase + query; *address ← nil; index ← index + one ] + +/-- Clear exactly the requested parent values, not the full dense directory. -/ +def clearRequested : Code .standard registers := ram[ + index ← #0; while index < count do [ ⟪clearValueStep⟫ ] ] + +/-- Store the actual next-term counter and enter the frame's dispatch phase. -/ +def storeTerm : Code .standard registers := ram[ + address ← frame + frameWidth; address ← address - one; + *address ← term; mode ← #1 ] + +/-- Initialize the stored next-term counter after sparse accumulator clearing. -/ +def clearValuesFinish : Code .standard registers := ram[ term ← #0; ⟪storeTerm⟫ ] + +/-- Initialize requested values and the frame's next-term counter. -/ +def clearValues : Code .standard registers := ram[ + ⟪clearRequested⟫; ⟪clearValuesFinish⟫ ] + +/-- Entry phase: multiply at a leaf; otherwise initialize the requested accumulators. -/ +def enter : Code .standard registers := ram[ + if level = inputDepth then [ ⟪leaf⟫ ] else [ ⟪clearValues⟫ ] ] + +/-- Form and deduplicate a child request set using actual tests, loads and stores. -/ +def buildChild : Code .standard registers := scan true + +/-- Advance the parent's term after an empty child or after accumulating its returned values. -/ +def advance : Code .standard registers := ram[ + term ← term + one; ⟪storeTerm⟫ ] + +/-- Push a nonempty child, with the exact least-significant-first leaf leafPrefix. -/ +def push : Code .standard registers := ram[ + childFrame ← nextLevel * frameWidth; childFrame ← frames + childFrame; + temporary₂ ← term * stride; childPrefix ← leafPrefix + temporary₂; + childStride ← stride * ten; + address ← childFrame; *address ← childCount; + address ← address + one; *address ← childPrefix; + address ← address + one; *address ← childStride; + address ← address + one; *address ← nil; + level ← nextLevel; mode ← #0 ] + +/-- Dispatch the next of the ten actual terms, recursing only on nonempty computed sets. -/ +def dispatch : Code .standard registers := ram[ + if term < ten then [ + ⟪buildChild⟫; + if nil < childCount then [ ⟪push⟫ ] else [ ⟪advance⟫ ] ] else [ ⟪pop⟫ ] ] + +/-- Load the two encoded operands at the actual parent query and child suffix addresses. -/ +def addValueLoad : Code .standard registers := ram[ + suffix ← query / ten; + address ← valueBase + query; left ← *address; + address ← childValues + suffix; right ← *address ] + +/-- Store the signed sum back at the computed parent query address. -/ +def addValueStore : Code .standard registers := ram[ + address ← valueBase + query; *address ← result ] + +/-- Add one returned child value to its parent's actual signed accumulator. -/ +def addValue : Code .standard registers := ram[ + ⟪addValueLoad⟫; ⟪add⟫; ⟪addValueStore⟫ ] + +/-- Select the returned child by the same two real digit comparisons as child construction. -/ +def accumulateDispatch : Code .standard registers := ram[ + if digit = nine then [ ⟪addValue⟫ ] else [ + if digit = term then [ ⟪addValue⟫ ] else [ temporary ← temporary ] ] ] + +/-- Load, dispatch and advance one actual parent query. -/ +def accumulateStep : Code .standard registers := ram[ + ⟪scanLoad⟫; ⟪accumulateDispatch⟫; index ← index + one ] + +/-- Consume a returned child's values at the explicitly listed contributing parent queries. -/ +def accumulateLoop : Code .standard registers := ram[ + index ← #0; while index < count do [ ⟪accumulateStep⟫ ] ] + +/-- Traverse the parent requests once, then advance the stored term counter. -/ +def accumulate : Code .standard registers := ram[ ⟪accumulateLoop⟫; ⟪advance⟫ ] + +/-- One explicit-stack controller step. Every branch and frame address is native work. -/ +def step : Code .standard registers := ram[ + ⟪loadFrame⟫; + if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ] + +/-- One actual lookup and indexed output store, including the reporting counter. -/ +def reportStep : Code .standard registers := ram[ + address ← queries + index; query ← *address; + address ← values + query; temporary ← *address; + address ← output + index; *address ← temporary; index ← index + one ] + +/-- Report results in the original input query order, including repeated queries. -/ +def report : Code .standard registers := ram[ + index ← #0; while index < queryCount do [ ⟪reportStep⟫ ] ] + +/-- The common stack evaluator after powers and caller parameters have been initialized. -/ +def controller : Code .standard registers := ram[ + while nil < active do [ ⟪step⟫ ] ] + +/-- Evaluate the computed root, then report the original query list. -/ +def work : Code .standard registers := ram[ + ⟪locate⟫; ⟪rootList⟫; ⟪controller⟫; ⟪report⟫ ] + +/-- Generic native evaluator with explicit pointer arguments and a fresh timestamp epoch. -/ +def kernel : Code .standard registers := ram[ ⟪powers⟫; ⟪work⟫ ] + +/-- Load the two ordinary scalar input headers. -/ +def header : Code .standard registers := ram[ + address ← #1; inputDepth ← *address; + address ← #2; queryCount ← *address ] + +/-- Compute all initial array/output/scratch pointers from the actual array and query lengths. -/ +def parameters : Code .standard registers := ram[ + arrayA ← #3; arrayB ← arrayA + span; queries ← arrayB + span; + output ← queries + queryCount; scratch ← output + queryCount; epoch ← #0 ] + +/-- One finite program reading the header and encoded arrays from ordinary initialized memory. -/ +def program : Code .standard registers := ram[ + ⟪header⟫; ⟪powers⟫; ⟪parameters⟫; ⟪work⟫ ] + +/-- The same program is used at every word width; it never reads the physical word size. -/ +theorem not_readsWidth : ¬program.ReadsWidth := by decide + +set_option maxRecDepth 4096 in +-- Flattening the fixed nested controller also expands its two signed arithmetic kernels. +/-- A fixed bound on both control code and all arithmetic blocks, including the final halt. -/ +theorem program_length_le : (program.program output).length ≤ 4096 := by decide + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Progress.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Progress.lean new file mode 100644 index 0000000000..55f0c3ff88 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Progress.lean @@ -0,0 +1,152 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Ready +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.NodeFrame + +/-! +# Stored partial sums between native child calls + +## Main definitions + +* `PrunedQuery.NodeProgress`: the actual dispatch cursor, frame, directory and partial values. + +## Main results + +* `PrunedQuery.exec_ready_enter`: native entry establishes the zero-term invariant. + +This is an invariant of the existing finite controller. It contains ordinary register and memory +equalities, not assumed executions or resource bounds. The timestamp directory is shared by all +nodes, so the parent retains its list but does not retain a stale membership-marker assertion. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Progress after `k` of the ten actual child dispatches. The initial state is kept only to +express the concrete spatial frame and preservation of its occupied request list. -/ +structure NodeProgress (L f a0 b0 n d p E : ℕ) (S : Finset (OutStr (n + 1))) + (a b : Leaf (n + 1) → ℤ) (x : Data registers) (k : ℕ) (y : Data registers) : Prop where + /-- The constants and workspace pointers are unchanged. -/ + regs : KernelRegs L f a0 b0 y + /-- The cursor is at this internal node. -/ + level_eq : y.regs level = d + /-- The next controller step dispatches the stored term. -/ + mode_eq : y.regs mode = 1 + /-- A dispatching node is active. -/ + active_eq : y.regs active = 1 + /-- The first three frame words survive all child returns. -/ + frame : (Block.mk (f + 4 * d) 4).Realizes y.mem ![S.card, p, 10 ^ d, k] + /-- The parent's requests remain in the actual original row. -/ + requests : ∀ i < S.card, + y.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = + x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) + /-- Every requested accumulator holds the completed prefix of terms. -/ + values : ∀ q ∈ S, + y.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + (queryIndex q).val) = + encode (partialValue a b S q k) + /-- Native scans only increase the generation. -/ + epoch_le : E ≤ y.regs epoch + /-- Each completed term pays for its scan and its possible descendant calls. -/ + epoch_upper : y.regs epoch ≤ E + k * (1 + epochAllowance n) + /-- All directory cells admit a further fresh generation. -/ + timestamps : ∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ y.regs epoch + /-- Only the explicit descendant workspace may have changed. -/ + footprint : Modifies (nodeWrites L f d) x.mem y.mem + +/-- The native scan's quotient image is the existing mathematical child set, even though +the shared timestamp directory no longer marks the parent's membership. -/ +theorem NodeProgress.scan_eq {L f a0 b0 n d p E k : ℕ} {S : Finset (OutStr (n + 1))} + {a b : Leaf (n + 1) → ℤ} {x y : Data registers} + (hx : NodeReady L f a0 b0 (n + 1) d p E S x) + (hy : NodeProgress L f a0 b0 n d p E S a b x k y) (t : Fin 10) : + scanSet true t.val (fun i => y.mem (f + 4 * (L + 1) + d * 10 ^ L + i)) S.card = + (childSet S (Encoding.termDigits t)).image (fun q => (queryIndex q).val) := by + rw [scanSet_child] + have he : (Finset.range S.card).image + (fun i => y.mem (f + 4 * (L + 1) + d * 10 ^ L + i)) = + (Finset.range S.card).image + (fun i => x.mem (f + 4 * (L + 1) + d * 10 ^ L + i)) := + Finset.image_congr (fun i hi => hy.requests i (Finset.mem_range.mp hi)) + rw [he, hx.image_read, childCodes_eq_image] + +/-- Before a dispatch, the original scalar budget covers the fresh scan and every possible +descendant generation for that term. -/ +theorem NodeProgress.epoch_child_le {L f a0 b0 n d p E k : ℕ} + {S : Finset (OutStr (n + 1))} {a b : Leaf (n + 1) → ℤ} {x y : Data registers} + (hy : NodeProgress L f a0 b0 n d p E S a b x k y) (hk : k < 10) : + y.regs epoch + 1 + epochAllowance n ≤ E + epochAllowance (n + 1) := by + have hp := mul_epochAllowance_le (n := n) (by omega : k + 1 ≤ 10) + nlinarith only [hy.epoch_upper, hp] + +/-- Entry performs the actual frame loads, clear loop and term store. The same returned state +has zero partial sums and the original request list, including when that list is empty. -/ +theorem exec_ready_enter {w L f a0 b0 n d p E : ℕ} (S : Finset (OutStr (n + 1))) + (a b : Leaf (n + 1) → ℤ) (x : Data registers) + (hx : NodeReady L f a0 b0 (n + 1) d p E S x) + (hfit : Fits w (nodeBudget L f a0 b0 E (n + 1))) : + ∃ y, ExecWithin w step x y (7 * S.card + 28) ∧ + NodeProgress L f a0 b0 n d p E S a b x 0 y := by + have hd : d < L := by have := hx.depth_add; omega + have hpow : (10 : ℕ) ^ (n + 1) ≤ 10 ^ L := + Nat.pow_le_pow_right (by decide) (by have := hx.depth_add; omega) + let input := fun i => x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) + have hcodes : ∀ i < S.card, input i < 10 ^ L := + fun i hi => (hx.read_lt hi).trans_le hpow + obtain ⟨y, hy, hregs, hlevel, hmode, hE, hactive, hmem, hread, hframe⟩ := + exec_node_enter (N := S.card) x hx.regs hx.level_eq hd hx.mode_eq (hx.frame 0) hx.card_le + input (fun _ _ => rfl) hcodes (hfit.of_le (by unfold nodeBudget; omega)) + have hrow (j : ℕ) (hj : j < 4) : + ¬(Block.mk (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) + (10 ^ L)).Owns (f + 4 * d + j) := by + dsimp only [Block.Owns] + omega + have hstamp (q : ℕ) : + ¬(Block.mk (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L) + (10 ^ L)).Owns (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) := by + dsimp only [Block.Owns] + have hm := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 1 ≤ L + 1) + rintro ⟨_, hlt⟩ + nlinarith only [hm, hlt] + refine ⟨y, hy, ⟨hregs, hlevel, hmode, hactive.trans hx.active_eq, ?_, hread, ?_, + ?_, ?_, ?_, hframe⟩⟩ + · intro j + change y.mem (f + 4 * d + j.val) = ![S.card, p, 10 ^ d, 0] j + rw [hmem] + fin_cases j + · change Function.update (_ : ℕ → ℕ) (f + 4 * d + 3) 0 (f + 4 * d + 0) = S.card + rw [Function.update_of_ne (by omega), zeroQueryPrefix_outside hcodes (hrow 0 (by decide))] + exact hx.frame 0 + · change Function.update (_ : ℕ → ℕ) (f + 4 * d + 3) 0 (f + 4 * d + 1) = p + rw [Function.update_of_ne (by omega), zeroQueryPrefix_outside hcodes (hrow 1 (by decide))] + exact hx.frame 1 + · change Function.update (_ : ℕ → ℕ) (f + 4 * d + 3) 0 (f + 4 * d + 2) = 10 ^ d + rw [Function.update_of_ne (by omega), zeroQueryPrefix_outside hcodes (hrow 2 (by decide))] + exact hx.frame 2 + · exact Function.update_self _ _ _ + · intro q hq + obtain ⟨i, hi, hqi⟩ := hx.exists_read hq + have hval : f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + + (queryIndex q).val ≠ f + 4 * d + 3 := by omega + rw [hmem, Function.update_of_ne hval, partialValue_zero, show encode 0 = 0 from rfl] + rw [← hqi] + exact zeroQueryPrefix_at hi + · rw [hE, hx.epoch_eq] + · simp only [Nat.zero_mul, Nat.add_zero, hE, hx.epoch_eq, le_refl] + · intro q hq + rw [hmem, Function.update_of_ne (by omega), zeroQueryPrefix_outside hcodes (hstamp q), + hE, hx.epoch_eq] + exact hx.requests.stamp_le q hq + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressChild.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressChild.lean new file mode 100644 index 0000000000..f9504b89b3 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressChild.lean @@ -0,0 +1,108 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Progress + +/-! +# Child entry established by the native dispatch + +## Main results + +* `PrunedQuery.exec_progress_child`: an actual nonempty scan and push establishes the next + induction assertion, with its computed set and prefix. + +The finite nonemptiness test is a proof case split. The machine uses the count it computes by +scanning the parent's actual list; that count, its contents and the fresh directory are proved +at the same returned state as the execution. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- A pushed child's strided input cells are the existing `sliceAt`, rather than a supplied +child array. Both preceding native phases preserve the original source block below `f`. -/ +theorem NodeProgress.child_source {L f a0 b0 n d p E base : ℕ} + {S : Finset (OutStr (n + 1))} {a b c : Leaf (n + 1) → ℤ} + {x y u : Data registers} (hx : NodeReady L f a0 b0 (n + 1) d p E S x) (k : Fin 10) + (hy : NodeProgress L f a0 b0 n d p E S a b x k.val y) + (hpush : Modifies (childWrites L f d) y.mem u.mem) (hbase : base + 10 ^ L ≤ f) + (hc : ∀ v, x.mem (base + p + 10 ^ d * (Encoding.leafIndex v).val) = + Signed.encode (c v)) (v : Leaf n) : + u.mem (base + (p + k.val * 10 ^ d) + 10 ^ (d + 1) * (Encoding.leafIndex v).val) = + Signed.encode (sliceAt c (Encoding.termDigits k) v) := by + have hd : d < L := by have := hx.depth_add; omega + let t := Encoding.termDigits k + have haddr : base + (p + k.val * 10 ^ d) + 10 ^ (d + 1) * (Encoding.leafIndex v).val = + base + p + 10 ^ d * (Encoding.leafIndex (Fin.cons t v)).val := by + rw [leafIndex_cons, show Encoding.termDigits.symm t = k from + Encoding.termDigits.symm_apply_apply k, pow_succ] + ring + rw [haddr] + have hbefore := hx.source_before hbase (Fin.cons t v) + have hnot := notMem_nodeWrites_before hd.le hbefore + rw [hpush _ (fun h => hnot (childWrites_subset_nodeWrites hd h)), hy.footprint _ hnot] + exact hc (Fin.cons t v) + +/-- The computed child state satisfies the concrete node-entry assertion and has a budget +derived from the original parent parameters. No child execution is assumed here. -/ +theorem exec_progress_child {w L f a0 b0 n d p E : ℕ} + (S : Finset (OutStr (n + 1))) (a b : Leaf (n + 1) → ℤ) (x y : Data registers) + (hx : NodeReady L f a0 b0 (n + 1) d p E S x) (k : Fin 10) + (hy : NodeProgress L f a0 b0 n d p E S a b x k.val y) + (hne : (childSet S (Encoding.termDigits k)).Nonempty) + (hfit : Fits w (nodeBudget L f a0 b0 E (n + 1))) : + ∃ z, ExecWithin w step y z (20 * S.card + 42) ∧ + NodeReady L f a0 b0 n (d + 1) (p + k.val * 10 ^ d) (y.regs epoch + 1) + (childSet S (Encoding.termDigits k)) z ∧ + Modifies (childWrites L f d) y.mem z.mem ∧ + Fits w (nodeBudget L f a0 b0 (y.regs epoch + 1) n) := by + have hd : d < L := by have := hx.depth_add; omega + have hpow := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) + (by have := hx.depth_add; omega : n + 1 ≤ L) + let input := fun i => y.mem (f + 4 * (L + 1) + d * 10 ^ L + i) + have hcodes : ∀ i < S.card, input i < 10 ^ L := by + intro i hi + exact (hy.requests i hi ▸ hx.read_lt hi).trans_le hpow + have hscan := hy.scan_eq hx k + have hnonempty : (scanSet true k.val input S.card).Nonempty := by + rw [hscan] + exact hne.image _ + have hE := hy.epoch_child_le k.isLt + obtain ⟨z, hz, hregs, hlevel, hmode, hactive, hepoch, hrequests, hcount, hpref, + hstride, hterm, hframe⟩ := exec_node_child (N := S.card) (p := p) (k := k.val) y + hy.regs hy.level_eq hd hy.mode_eq (hy.frame 0) hx.card_le (hy.frame 1) hx.prefix_lt + (hy.frame 2) (hy.frame 3) k.isLt input (fun _ _ => rfl) hcodes hy.timestamps + hnonempty (hfit.of_le (by unfold nodeBudget; omega)) + have hset : ScanMemory z.mem (f + 4 * (L + 1) + (d + 1) * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) (y.regs epoch + 1) (10 ^ L) + ((childSet S (Encoding.termDigits k)).image (fun q => (queryIndex q).val)) := by + simpa only [input, hscan] using hrequests + refine ⟨z, hz, ⟨hregs, ?_, ?_, hlevel, hmode, hactive.trans hy.active_eq, hepoch, + ?_, hset⟩, hframe, hfit.of_le (nodeBudget_child_le hE)⟩ + · have := hx.depth_add + omega + · have hp := hx.prefix_lt + have hk := k.isLt + rw [pow_succ] + nlinarith only [hp, hk] + · intro j + change z.mem (f + 4 * (d + 1) + j.val) = + ![(childSet S (Encoding.termDigits k)).card, p + k.val * 10 ^ d, 10 ^ (d + 1), 0] j + fin_cases j + · change z.mem (f + 4 * (d + 1)) = (childSet S (Encoding.termDigits k)).card + simpa only [input, hscan, card_image_queryIndex] using hcount + · exact hpref + · exact hstride + · exact hterm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressEmpty.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressEmpty.lean new file mode 100644 index 0000000000..e779242cbd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressEmpty.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Progress + +/-! +# Skipping a computed empty child + +## Main results + +* `PrunedQuery.exec_progress_empty`: native scanning and its computed zero count advance the + partial-sum invariant without a recursive call or a value store. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +private theorem notMem_emptyChildWrites_values {L f d q : ℕ} (hd : d < L) + (hq : q < 10 ^ L) : + f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + q ∉ emptyChildWrites L f d := by + simp only [emptyChildWrites, Finset.mem_union, Block.mem_footprint, Block.Owns] + have hm := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 1 ≤ L + 1) + have hnext : d * 10 ^ L + q < (d + 1) * 10 ^ L := by nlinarith only [hq] + have hbound := hnext.trans_le hm + have hsep : (d + 1) * 10 ^ L + 10 ^ L ≤ (L + 1) * 10 ^ L := by + have := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 2 ≤ L + 1) + nlinarith only [this] + rw [show 2 * (L + 1) * 10 ^ L = (L + 1) * 10 ^ L + (L + 1) * 10 ^ L by ring] + omega + +/-- The proof selects the empty mathematical case, and the actual native scan proves that +the machine takes its zero-count branch. Every directory update and loop test is charged. -/ +theorem exec_progress_empty {w L f a0 b0 n d p E : ℕ} + (S : Finset (OutStr (n + 1))) (a b : Leaf (n + 1) → ℤ) (x y : Data registers) + (hx : NodeReady L f a0 b0 (n + 1) d p E S x) (k : Fin 10) + (hy : NodeProgress L f a0 b0 n d p E S a b x k.val y) + (hempty : childSet S (Encoding.termDigits k) = ∅) + (hfit : Fits w (nodeBudget L f a0 b0 E (n + 1))) : + ∃ z, ExecWithin w step y z (20 * S.card + 33) ∧ + NodeProgress L f a0 b0 n d p E S a b x (k.val + 1) z := by + have hd : d < L := by have := hx.depth_add; omega + have hpow := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) + (by have := hx.depth_add; omega : n + 1 ≤ L) + let input := fun i => y.mem (f + 4 * (L + 1) + d * 10 ^ L + i) + have hcodes : ∀ i < S.card, input i < 10 ^ L := by + intro i hi + exact (hy.requests i hi ▸ hx.read_lt hi).trans_le hpow + have hscan : scanSet true k.val input S.card = ∅ := by + rw [hy.scan_eq hx k, hempty, Finset.image_empty] + have hE := hy.epoch_child_le k.isLt + obtain ⟨z, hz, hregs, hlevel, hmode, hactive, hepoch, htable, hfirst, hterm, hframe⟩ := + exec_node_emptyChild (N := S.card) (k := k.val) y hy.regs hy.level_eq hd hy.mode_eq + (hy.frame 0) hx.card_le (hy.frame 3) k.isLt input (fun _ _ => rfl) hcodes + hy.timestamps hscan (hfit.of_le (by unfold nodeBudget; omega)) + have hmod : Modifies (nodeWrites L f d) y.mem z.mem := + fun c hc => hframe c (fun h => hc (emptyChildWrites_subset_nodeWrites hd h)) + refine ⟨z, hz, ⟨hregs, hlevel, hmode, hactive.trans hy.active_eq, ?_, ?_, ?_, + ?_, ?_, ?_, ?_⟩⟩ + · intro j + change z.mem (f + 4 * d + j.val) = ![S.card, p, 10 ^ d, k.val + 1] j + fin_cases j + · exact (hfirst 0 (by decide)).trans (hy.frame 0) + · exact (hfirst 1 (by decide)).trans (hy.frame 1) + · exact (hfirst 2 (by decide)).trans (hy.frame 2) + · exact hterm + · intro i hi + exact (hmod _ (notMem_nodeWrites_list hd.le (hi.trans_le hx.card_le))).trans + (hy.requests i hi) + · intro q hq + have hqbound := (queryIndex q).isLt.trans_le hpow + rw [hframe _ (notMem_emptyChildWrites_values hd hqbound), hy.values q hq, + partialValue_succ] + have hc : Pruned.childResult a b S (Encoding.termDigits k) (Fin.tail q) = 0 := by + simp [Pruned.childResult, hempty] + simp only [hc, ite_self, add_zero] + · rw [hepoch] + exact hy.epoch_le.trans (Nat.le_succ _) + · rw [hepoch] + have he := hy.epoch_upper + nlinarith only [he] + · intro q hq + rw [hepoch] + exact htable.stamp_le q hq + · simpa only [Finset.union_self] using hy.footprint.trans hmod + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressFinish.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressFinish.lean new file mode 100644 index 0000000000..c137ad0a7b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressFinish.lean @@ -0,0 +1,56 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Progress +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Finish + +/-! +# Returning from a completed native internal node + +## Main results + +* `PrunedQuery.exec_progress_finish`: the native exhausted-term branch returns the existing + pruned values and all frame, cursor and timestamp conclusions used by the parent. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- All ten stored partial sums give the existing `Pruned.result`. The native final dispatch +and pop change registers only, stopping at the root and resuming accumulation at a parent. -/ +theorem exec_progress_finish {w L f a0 b0 n d p E : ℕ} + (S : Finset (OutStr (n + 1))) (a b : Leaf (n + 1) → ℤ) (x y : Data registers) + (hx : NodeReady L f a0 b0 (n + 1) d p E S x) + (hy : NodeProgress L f a0 b0 n d p E S a b x 10 y) + (hfit : Fits w (nodeBudget L f a0 b0 E (n + 1))) : + ∃ z, ExecWithin w step y z 26 ∧ NodeResult L f a0 b0 (n + 1) d E S a b x z := by + have hd : d ≤ L := by have := hx.depth_add; omega + obtain ⟨z, hz, hregs, hmem, hlevel, hmode, hactive, hepoch⟩ := + exec_node_finish y hy.regs hy.level_eq hd hy.mode_eq (hy.frame 3) + (hfit.of_le (by unfold nodeBudget; omega)) + refine ⟨z, hz, ⟨hregs, hlevel, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩⟩ + · intro hpos + simpa only [ite_eq_right (by omega : d ≠ 0)] using hmode + · simpa only [hy.active_eq] using hactive + · rw [hepoch] + exact hy.epoch_le + · rw [hepoch] + exact hy.epoch_upper.trans (Nat.add_le_add_left (epochAllowance_succ n) E) + · intro q hq + rw [hmem, hepoch] + exact hy.timestamps q hq + · intro q hq + rw [hmem, hy.values q hq, partialValue_ten a b S q hq] + · simpa only [hmem] using hy.footprint + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressReturn.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressReturn.lean new file mode 100644 index 0000000000..4a113cd1dd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ProgressReturn.lean @@ -0,0 +1,180 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Progress +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Return + +/-! +# Accumulation after a proved native child return + +## Main results + +* `PrunedQuery.exec_progress_return`: actual indexed signed accumulation establishes the next + partial sum, using the child's ordinary value and frame postconditions. + +Execution of the child is composed separately by the depth induction. This lemma proves the +parent's subsequent native instructions; no child execution or cost certificate is input here. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +private theorem notMem_childWrites_frame {L f d j : ℕ} (hd : d < L) (hj : j < 4) : + f + 4 * d + j ∉ childWrites L f d := by + simp only [childWrites, Finset.mem_union, Block.mem_footprint, Block.Owns] + omega + +private theorem notMem_childWrites_values {L f d q : ℕ} (hd : d < L) + (hq : q < 10 ^ L) : + f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + q ∉ childWrites L f d := by + simp only [childWrites, Finset.mem_union, Block.mem_footprint, Block.Owns] + have hm := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 2 ≤ L + 1) + have hnext : d * 10 ^ L + q < (d + 1) * 10 ^ L := by nlinarith only [hq] + have hbound := Nat.mul_le_mul_right (10 ^ L) (by omega : d + 1 ≤ L + 1) + rw [show 2 * (L + 1) * 10 ^ L = (L + 1) * 10 ^ L + (L + 1) * 10 ^ L by ring] + have hv := hnext.trans_le hbound + have hsep : (d + 1) * 10 ^ L + 10 ^ L ≤ (L + 1) * 10 ^ L := by + nlinarith only [hm] + omega + +/-- Ordinary child postconditions suffice to prove the actual parent accumulation step. +All signed fits follow from the common original-input magnitude envelope. -/ +theorem exec_progress_return {w L f a0 b0 n d p E A B : ℕ} + (S : Finset (OutStr (n + 1))) (a b : Leaf (n + 1) → ℤ) + (x y u z : Data registers) (hx : NodeReady L f a0 b0 (n + 1) d p E S x) (k : Fin 10) + (hy : NodeProgress L f a0 b0 n d p E S a b x k.val y) + (hne : (childSet S (Encoding.termDigits k)).Nonempty) + (hpush : Modifies (childWrites L f d) y.mem u.mem) + (hz : NodeResult L f a0 b0 n (d + 1) (y.regs epoch + 1) + (childSet S (Encoding.termDigits k)) + (sliceAt a (Encoding.termDigits k)) (sliceAt b (Encoding.termDigits k)) u z) + (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hw : 0 < w) (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (nodeBudget L f a0 b0 E (n + 1))) : + ∃ v, ExecWithin (w + 2) step z v (31 * S.card + 29) ∧ + NodeProgress L f a0 b0 n d p E S a b x (k.val + 1) v := by + have hd : d < L := by have := hx.depth_add; omega + have hdn : d + 1 ≤ L := by omega + have hn : n + 1 ≤ L := by have := hx.depth_add; omega + have hpow := Nat.pow_le_pow_right (by decide : 1 ≤ (10 : ℕ)) hn + have henv := magnitudeBound_fits hw hmag + have hsub := childWrites_subset_nodeWrites (f := f) hd + have hsub' := nodeWrites_child_subset (f := f) hd + have hmid : Modifies (nodeWrites L f d) y.mem z.mem := by + intro c hc + exact (hz.frame c (fun h => hc (hsub' h))).trans (hpush c (fun h => hc (hsub h))) + have hparent (j : ℕ) (hj : j < 4) : z.mem (f + 4 * d + j) = y.mem (f + 4 * d + j) := + (hz.frame _ (notMem_nodeWrites_ancestor_frame hdn (by omega) hj)).trans + (hpush _ (notMem_childWrites_frame hd hj)) + have hvalues (q : ℕ) (hq : q < 10 ^ L) : + z.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + q) = + y.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + q) := + (hz.frame _ (notMem_nodeWrites_ancestor_values hdn (by omega) hq)).trans + (hpush _ (notMem_childWrites_values hd hq)) + let input := fun i => x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) + have hinput : ∀ i < S.card, + z.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i := by + intro i hi + exact (hmid _ (notMem_nodeWrites_list hd.le (hi.trans_le hx.card_le))).trans + (hy.requests i hi) + have hquery : ∀ i < S.card, ∃ q ∈ S, (queryIndex q).val = input i := + fun i hi => hx.exists_of_read hi + have hcodes : ∀ i < S.card, input i < 10 ^ L := + fun i hi => (hx.read_lt hi).trans_le hpow + let V := fun c => if h : c < 10 ^ (n + 1) then + partialValue a b S (queryAt ⟨c, h⟩) k.val else 0 + let C := fun c => if h : c < 10 ^ n then + Pruned.childResult a b S (Encoding.termDigits k) (queryAt ⟨c, h⟩) else 0 + have hV (q : OutStr (n + 1)) : V (queryIndex q).val = partialValue a b S q k.val := by + dsimp only [V] + rw [dite_eq_left (queryIndex q).isLt] + change partialValue a b S (queryAt (queryIndex q)) k.val = _ + rw [queryAt_queryIndex] + have hC (q : OutStr n) : C (queryIndex q).val = + Pruned.childResult a b S (Encoding.termDigits k) q := by + dsimp only [C] + rw [dite_eq_left (queryIndex q).isLt] + change Pruned.childResult a b S (Encoding.termDigits k) (queryAt (queryIndex q)) = _ + rw [queryAt_queryIndex] + have hsel (q : OutStr (n + 1)) : selected true k.val (queryIndex q).val ↔ + (Encoding.termDigits k).Contributes (q 0) := by + simp only [selected, ↓reduceIte, queryIndex_mod, contributes_iff_digit] + have hlevel : z.regs level = d := by simpa only [ite_eq_right (by omega : d + 1 ≠ 0), + Nat.add_sub_cancel_right] using hz.level_eq + have hactive : z.regs active = 1 := by simpa only [ite_eq_right (by omega : d + 1 ≠ 0)] + using hz.active_eq + have hN : z.mem (f + 4 * d) = S.card := (hparent 0 (by decide)).trans (hy.frame 0) + have hterm : z.mem (f + 4 * d + 3) = k.val := (hparent 3 (by decide)).trans (hy.frame 3) + have hparentValues : ∀ i < S.card, + z.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + input i) = + encode (V (input i)) := by + intro i hi + obtain ⟨q, hq, he⟩ := hquery i hi + rw [← he, hvalues _ ((queryIndex q).isLt.trans_le hpow), hy.values q hq, hV] + have hchildValues : ∀ i < S.card, selected true k.val (input i) → + z.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + (d + 1) * 10 ^ L + input i / 10) = + encode (C (input i / 10)) := by + intro i hi hselected + obtain ⟨q, hq, he⟩ := hquery i hi + have hcontrib := (hsel q).mp (he ▸ hselected) + have htail : Fin.tail q ∈ childSet S (Encoding.termDigits k) := + mem_childSet.mpr ⟨q, hq, hcontrib, rfl⟩ + rw [← he, queryIndex_div, hC] + simpa only [Pruned.childResult, hne, ↓reduceIte] using hz.values (Fin.tail q) htail + obtain ⟨v, hv, hregs, hlevel', hmode, hactive', hepoch, hresult, hfirst, hterm', + hstamp, hframe⟩ := exec_node_return (N := S.card) (k := k.val) z hz.regs hlevel hd + (hz.mode_eq (by omega)) hN hx.card_le hterm k.isLt input V C hinput hcodes hx.injOn_read + hparentValues hchildValues + (by + intro i hi + obtain ⟨q, _, he⟩ := hquery i hi + rw [← he, hV] + exact fits_partialValue hn henv a b ha hb S q k.val) + (by + intro i hi + obtain ⟨q, _, he⟩ := hquery i hi + rw [← he, queryIndex_div, hC] + exact fits_childResult (by omega) henv a b ha hb S _ _) + (hfit.of_le (by unfold nodeBudget; omega)) + refine ⟨v, hv, ⟨hregs, hlevel', hmode, hactive'.trans hactive, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩⟩ + · intro j + change v.mem (f + 4 * d + j.val) = ![S.card, p, 10 ^ d, k.val + 1] j + fin_cases j + · exact (hfirst 0 (by decide)).trans hN + · exact ((hfirst 1 (by decide)).trans (hparent 1 (by decide))).trans (hy.frame 1) + · exact ((hfirst 2 (by decide)).trans (hparent 2 (by decide))).trans (hy.frame 2) + · exact hterm' + · intro i hi + exact ((hframe _ (notMem_nodeWrites_list hd.le (hi.trans_le hx.card_le))).trans + (hmid _ (notMem_nodeWrites_list hd.le (hi.trans_le hx.card_le)))).trans (hy.requests i hi) + · intro q hq + obtain ⟨i, hi, he⟩ := hx.exists_read hq + have hr := hresult i hi + change input i = (queryIndex q).val at he + rw [he, hV, queryIndex_div, hC] at hr + rw [hr, partialValue_succ] + simp only [hsel] + · rw [hepoch] + exact hy.epoch_le.trans ((Nat.le_succ _).trans hz.epoch_le) + · rw [hepoch] + have hzu := hz.epoch_upper + have hyu := hy.epoch_upper + nlinarith only [hzu, hyu] + · intro q hq + rw [hstamp q hq, hepoch] + exact hz.timestamps q hq + · have hmod : Modifies (nodeWrites L f d) x.mem z.mem := by + simpa only [Finset.union_self] using hy.footprint.trans hmid + simpa only [Finset.union_self] using hmod.trans hframe + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Push.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Push.lean new file mode 100644 index 0000000000..252435cd0b --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Push.lean @@ -0,0 +1,96 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Control +public import Complexity.Computability.WordRAM.Heap + +/-! +# Computed child-frame writes + +## Main results + +* `PrunedQuery.exec_push`: compute the next stack address, prefix and stride, store all four + child-frame words, and enter that frame in fifteen native instructions. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers changed while constructing and entering one child frame. -/ +def PushLocal (i : Fin registers) : Prop := + i = childFrame ∨ i = temporary₂ ∨ i = childPrefix ∨ i = childStride ∨ i = address ∨ + i = level ∨ i = mode + +instance (i : Fin registers) : Decidable (PushLocal i) := + inferInstanceAs (Decidable + (i = childFrame ∨ i = temporary₂ ∨ i = childPrefix ∨ i = childStride ∨ i = address ∨ + i = level ∨ i = mode)) + +private theorem push_preserves (i : Fin registers) : ¬PushLocal i → push.Preserves i := by + decide +kernel +revert + +/-- The child list's actual count is written to the computed frame, followed by the new leaf +prefix, stride and zero term counter. All scalar fit premises follow from the displayed budget. -/ +theorem exec_push {w : ℕ} (x : Data registers) (h0 : x.regs nil = 0) + (h1 : x.regs one = 1) (h4 : x.regs frameWidth = 4) (h10 : x.regs ten = 10) + (hw : Fits w (x.regs frames + x.regs nextLevel * 4 + x.regs leafPrefix + + x.regs term * x.regs stride + x.regs stride * 10 + 8)) : + ∃ y, ExecWithin w push x y 15 ∧ y.regs level = x.regs nextLevel ∧ y.regs mode = 0 ∧ + y.regs childFrame = x.regs frames + x.regs nextLevel * 4 ∧ + y.regs childPrefix = x.regs leafPrefix + x.regs term * x.regs stride ∧ + y.regs childStride = x.regs stride * 10 ∧ + y.mem = Function.update (Function.update (Function.update (Function.update x.mem + (x.regs frames + x.regs nextLevel * 4) (x.regs childCount)) + (x.regs frames + x.regs nextLevel * 4 + 1) + (x.regs leafPrefix + x.regs term * x.regs stride)) + (x.regs frames + x.regs nextLevel * 4 + 2) (x.regs stride * 10)) + (x.regs frames + x.regs nextLevel * 4 + 3) 0 ∧ + Modifies (Block.mk (x.regs frames + x.regs nextLevel * 4) 4).footprint x.mem y.mem ∧ + (∀ i, ¬PushLocal i → y.regs i = x.regs i) := by + let f := x.regs frames + x.regs nextLevel * 4 + let p := x.regs leafPrefix + x.regs term * x.regs stride + let s := x.regs stride * 10 + let y := (((((((((((((((x.set childFrame (x.regs nextLevel * 4)).set childFrame f).set + temporary₂ (x.regs term * x.regs stride)).set childPrefix p).set childStride s).set + address f).write f (x.regs childCount)).set address (f + 1)).write (f + 1) p).set + address (f + 2)).write (f + 2) s).set address (f + 3)).write (f + 3) 0).set + level (x.regs nextLevel)).set mode 0) + have hprod : Fits w (x.regs nextLevel * 4) := hw.of_le (by omega) + have hf (k : ℕ) (hk : k ≤ 3) : Fits w (x.regs frames + x.regs nextLevel * 4 + k) := + hw.of_le (by omega) + have hf0 : Fits w (x.regs frames + x.regs nextLevel * 4) := hw.of_le (by omega) + have ht : Fits w (x.regs term * x.regs stride) := hw.of_le (by omega) + have hp : Fits w (x.regs leafPrefix + x.regs term * x.regs stride) := hw.of_le (by omega) + have hs : Fits w (x.regs stride * 10) := hw.of_le (by omega) + have hexact : Exec w push x y 15 := by + ram_eval [push, y, f, p, s, h0, h1, h4, h10, hprod, hf0, + hf 1 (by decide), hf 2 (by decide), hf 3 (by decide), ht, hp, hs, + childFrame, temporary₂, childPrefix, childStride, address, level, mode, frames, + nextLevel, leafPrefix, term, stride, ten, one, frameWidth, childCount, nil, + Fin.mk.injEq, Nat.add_zero] + have he := hexact.within + have hmod : Modifies (Block.mk f 4).footprint x.mem y.mem := by + intro c hc + have hnot : ¬(f ≤ c ∧ c < f + 4) := by + simpa only [Block.mem_footprint, Block.Owns, Block.base, Block.length] using hc + have h0' : c ≠ f := by omega + have h1' : c ≠ f + 1 := by omega + have h2' : c ≠ f + 2 := by omega + have h3' : c ≠ f + 3 := by omega + simp only [y, Data.set_mem, Data.write_mem, Function.update_of_ne h3', + Function.update_of_ne h2', Function.update_of_ne h1', Function.update_of_ne h0'] + refine ⟨y, he, ?_, by simp [y], ?_, ?_, ?_, rfl, hmod, + fun i hi => he.regs_eq (push_preserves i hi)⟩ + all_goals simp +decide [y, f, p, s, childFrame, temporary₂, childPrefix, childStride, + address, level, mode] + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Ready.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Ready.lean new file mode 100644 index 0000000000..a476936c01 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Ready.lean @@ -0,0 +1,179 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Bounds +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Node + +/-! +# Entry and return assertions for the native node induction + +## Main definitions + +* `PrunedQuery.NodeReady`: the actual stored frame and timestamped query representation. +* `PrunedQuery.NodeResult`: register, value and spatial conclusions after returning. + +## Main results + +* `PrunedQuery.exec_ready_zero`: the terminal induction case for arbitrary supplied arrays. + +Neither assertion contains execution, time or fit fields. Width and magnitude assumptions are +ordinary numerical input bounds, and execution is proved in the existing native semantics. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- Entry to one node, as established by the native root scan or a native child push. -/ +structure NodeReady (L f a0 b0 n d p E : ℕ) (S : Finset (OutStr n)) + (x : Data registers) : Prop where + /-- The computed workspace pointers and constant registers. -/ + regs : KernelRegs L f a0 b0 x + /-- The two tuple lengths agree with the original depth. -/ + depth_add : d + n = L + /-- The leaf prefix is within its already processed digit space. -/ + prefix_lt : p < 10 ^ d + /-- The actual cursor points to this frame. -/ + level_eq : x.regs level = d + /-- This node has not begun its entry phase. -/ + mode_eq : x.regs mode = 0 + /-- The traversal is still active. -/ + active_eq : x.regs active = 1 + /-- Current actual timestamp generation. -/ + epoch_eq : x.regs epoch = E + /-- Four stored words: count, prefix, stride and first term. -/ + frame : (Block.mk (f + 4 * d) 4).Realizes x.mem ![S.card, p, 10 ^ d, 0] + /-- The actual list and directory represent exactly the requested numerical codes. -/ + requests : ScanMemory x.mem (f + 4 * (L + 1) + d * 10 ^ L) + (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L) E (10 ^ L) + (S.image fun q => (queryIndex q).val) + +/-- The spatial and mathematical result of one node. This is a postcondition on the actual +data, with no execution or time claim embedded in it. -/ +structure NodeResult (L f a0 b0 n d E : ℕ) (S : Finset (OutStr n)) + (a b : Leaf n → ℤ) (x y : Data registers) : Prop where + /-- Constant parameters remain unchanged. -/ + regs : KernelRegs L f a0 b0 y + /-- Return to the parent, keeping zero at the root. -/ + level_eq : y.regs level = if d = 0 then 0 else d - 1 + /-- A parent resumes accumulation after a non-root node. -/ + mode_eq : 0 < d → y.regs mode = 2 + /-- Only returning from the root stops traversal. -/ + active_eq : y.regs active = if d = 0 then 0 else 1 + /-- Timestamp generations do not go backwards. -/ + epoch_le : E ≤ y.regs epoch + /-- All fresh generations are covered by the remaining-depth allowance. -/ + epoch_upper : y.regs epoch ≤ E + epochAllowance n + /-- The directory is ready for the next fresh-generation scan. -/ + timestamps : ∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) ≤ y.regs epoch + /-- Every requested cell holds the existing pruned result, in its original code position. -/ + values : ∀ q ∈ S, + y.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + (queryIndex q).val) = + encode (Pruned.result n a b S q) + /-- Caller memory, including ancestor rows, is preserved outside the concrete node frame. -/ + frame : Modifies (nodeWrites L f d) x.mem y.mem + +/-- A ready frame's stored count cannot exceed its actual list row capacity. -/ +theorem NodeReady.card_le {L f a0 b0 n d p E : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) : S.card ≤ 10 ^ L := by + simpa only [card_image_queryIndex] using h.requests.card_le + +/-- The occupied words of the actual row encode exactly the requested output strings. -/ +theorem NodeReady.image_read {L f a0 b0 n d p E : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) : + (Finset.range S.card).image (fun i => + x.mem (f + 4 * (L + 1) + d * 10 ^ L + i)) = + S.image (fun q => (queryIndex q).val) := by + simpa only [card_image_queryIndex] using h.requests.image_read + +/-- Every occupied source-list word comes from one of the requested mathematical outputs. -/ +theorem NodeReady.exists_of_read {L f a0 b0 n d p E i : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) (hi : i < S.card) : + ∃ q ∈ S, (queryIndex q).val = x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) := by + apply Finset.mem_image.mp + rw [← h.image_read] + exact Finset.mem_image.mpr ⟨i, Finset.mem_range.mpr hi, rfl⟩ + +/-- Actual query words fit the remaining query space, not only the original workspace row. -/ +theorem NodeReady.read_lt {L f a0 b0 n d p E i : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) (hi : i < S.card) : + x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) < 10 ^ n := by + obtain ⟨q, _, hq⟩ := h.exists_of_read hi + exact hq ▸ (queryIndex q).isLt + +/-- Every requested output occurs at a concrete occupied position of the native list. -/ +theorem NodeReady.exists_read {L f a0 b0 n d p E : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) {q : OutStr n} (hq : q ∈ S) : + ∃ i < S.card, x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = (queryIndex q).val := by + have hm : (queryIndex q).val ∈ S.image (fun v => (queryIndex v).val) := + Finset.mem_image.mpr ⟨q, hq, rfl⟩ + rw [← h.image_read] at hm + obtain ⟨i, hi, he⟩ := Finset.mem_image.mp hm + exact ⟨i, Finset.mem_range.mp hi, he⟩ + +/-- Distinct occupied positions cannot name the same parent query. -/ +theorem NodeReady.injOn_read {L f a0 b0 n d p E : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) : + Set.InjOn (fun i => x.mem (f + 4 * (L + 1) + d * 10 ^ L + i)) {i | i < S.card} := by + simpa only [card_image_queryIndex] using h.requests.injOn_read + +/-- Each strided input address remains inside the original input block below the workspace. -/ +theorem NodeReady.source_before {L f a0 b0 n d p E base : ℕ} {S : Finset (OutStr n)} + {x : Data registers} (h : NodeReady L f a0 b0 n d p E S x) + (hbase : base + 10 ^ L ≤ f) (v : Leaf n) : + base + p + 10 ^ d * (Encoding.leafIndex v).val < f := by + have hs : (10 : ℕ) ^ d * 10 ^ n = 10 ^ L := by rw [← pow_add, h.depth_add] + have hv := leafAddress_lt h.prefix_lt hs v + omega + +/-- The terminal node proof obtains its signed fits from the common original magnitude +envelope. The same returned memory satisfies the result and the charged native continuation. -/ +theorem exec_ready_zero {w L f a0 b0 d p E A B : ℕ} (S : Finset (OutStr 0)) + (a b : Leaf 0 → ℤ) (x : Data registers) (hx : NodeReady L f a0 b0 0 d p E S x) + (hleft : ∀ v, x.mem (a0 + p + 10 ^ d * (Encoding.leafIndex v).val) = encode (a v)) + (hright : ∀ v, x.mem (b0 + p + 10 ^ d * (Encoding.leafIndex v).val) = encode (b v)) + (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hw : 0 < w) (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (nodeBudget L f a0 b0 E 0)) : + ∃ y, NodeResult L f a0 b0 0 d E S a b x y ∧ + ∀ z U, ExecWithin (w + 2) controller y z U → + ExecWithin (w + 2) controller x z (traceWeight S + U) := by + have hd : d = L := by have := hx.depth_add; omega + subst d + have henv := magnitudeBound_fits hw hmag + have hi : (Encoding.leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by decide + have hcount : x.mem (f + 4 * L) = S.card := hx.frame 0 + have hpref : x.mem (f + 4 * L + 1) = p := hx.frame 1 + have hA : x.mem (a0 + p) = encode (a Fin.elim0) := by + simpa only [hi, Nat.mul_zero, Nat.add_zero] using hleft Fin.elim0 + have hB : x.mem (b0 + p) = encode (b Fin.elim0) := by + simpa only [hi, Nat.mul_zero, Nat.add_zero] using hright Fin.elim0 + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + obtain ⟨y, hregs, hyl, hym, hya, hye, hmarks, hvalue, hframe, _, hrun⟩ := + exec_node_zero (fits_left henv (ha Fin.elim0)) (fits_right henv (hb Fin.elim0)) + (fits_product henv (ha Fin.elim0) (hb Fin.elim0)) S x hx.regs hx.level_eq + hx.mode_eq hx.active_eq hcount hpref hA hB hx.epoch_eq hx.requests.stamp_le + (hfit.of_le (by + unfold nodeBudget + rw [epochAllowance_zero] + nlinarith only [hpow, hL, hx.prefix_lt])) + refine ⟨y, ⟨hregs, hyl, ?_, hya, ?_, ?_, ?_, hvalue, hframe⟩, hrun⟩ + · intro hLpos + simpa only [ite_eq_right (by omega : L ≠ 0)] using hym + · omega + · simp only [hye, epochAllowance_zero, Nat.add_zero, le_refl] + · intro q hq + simpa only [hye] using hmarks q hq + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Recursive.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Recursive.lean new file mode 100644 index 0000000000..c5989f0f32 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Recursive.lean @@ -0,0 +1,125 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.ProgressChild +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressEmpty +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressFinish +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ProgressReturn + +/-! +# All-depth execution of the native pruned controller + +## Main results + +* `PrunedQuery.exec_ready`: the fixed controller computes the existing pruned recursion, with + actual stored child lists, signed values, frames and a bound charged to its mathematical trace. + +The induction composes only `Code.ExecWithin` for the existing standard unsigned machine. Its +continuation form stops the inductive node at the parent return cursor; at the root the proved +zero active flag supplies the ordinary native loop-exit execution. +The stage execution lemmas and node invariants here are internal implementation APIs, +exported for proof composition across modules. Callers use `Pruned.Kernel` or `Pruned.Flat`. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- An arbitrary-depth ready node returns the unchanged finite pruned result. The only width +premises concern original pointers, depths, epochs and input magnitudes; all intermediate fits +are derived. Every child is generated and executed by the same fixed native controller. -/ +theorem exec_ready {w L f a0 b0 A B : ℕ} (n : ℕ) {d p E : ℕ} + (S : Finset (OutStr n)) (a b : Leaf n → ℤ) (x : Data registers) + (hx : NodeReady L f a0 b0 n d p E S x) + (hleft : ∀ v, x.mem (a0 + p + 10 ^ d * (Encoding.leafIndex v).val) = encode (a v)) + (hright : ∀ v, x.mem (b0 + p + 10 ^ d * (Encoding.leafIndex v).val) = encode (b v)) + (hbeforeA : a0 + 10 ^ L ≤ f) (hbeforeB : b0 + 10 ^ L ≤ f) + (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hw : 0 < w) (hmag : magnitudeBound L A B < 2 ^ (w - 1)) + (hfit : Fits (w + 2) (nodeBudget L f a0 b0 E n)) : + ∃ y, NodeResult L f a0 b0 n d E S a b x y ∧ + ∀ z U, ExecWithin (w + 2) controller y z U → + ExecWithin (w + 2) controller x z (traceWeight S + U) := by + induction n generalizing d p E x with + | zero => exact exec_ready_zero S a b x hx hleft hright ha hb hw hmag hfit + | succ n ih => + obtain ⟨y, henter, hy⟩ := exec_ready_enter S a b x hx hfit + have hterms : ∀ k ≤ 10, ∃ u, NodeProgress L f a0 b0 n d p E S a b x k u ∧ + ∀ z U, ExecWithin (w + 2) controller u z U → + ExecWithin (w + 2) controller y z + (k * (51 * S.card + 75) + childWeightPrefix S k + U) := by + intro k + induction k with + | zero => + intro _ + refine ⟨y, hy, ?_⟩ + intro z U hz + simpa only [Nat.zero_mul, childWeightPrefix_zero, Nat.zero_add] using hz + | succ k ihk => + intro hk + obtain ⟨u, hu, hprevious⟩ := ihk (by omega) + let t : Fin 10 := ⟨k, by omega⟩ + by_cases hne : (childSet S (Encoding.termDigits t)).Nonempty + · obtain ⟨v, hpush, hv, hpushFrame, hvfit⟩ := exec_progress_child S a b x u hx t hu hne hfit + obtain ⟨z, hz, hchild⟩ := ih (childSet S (Encoding.termDigits t)) + (sliceAt a (Encoding.termDigits t)) (sliceAt b (Encoding.termDigits t)) v hv + (hu.child_source hx t hpushFrame hbeforeA hleft) + (hu.child_source hx t hpushFrame hbeforeB hright) + (fun v => ha (Fin.cons (Encoding.termDigits t) v)) + (fun v => hb (Fin.cons (Encoding.termDigits t) v)) hvfit + obtain ⟨v', hreturn, hv'⟩ := exec_progress_return S a b x u v z hx t hu hne + hpushFrame hz ha hb hw hmag hfit + refine ⟨v', hv', ?_⟩ + intro endState U hend + have hza : z.regs active = 1 := by + simpa only [ite_eq_right (by omega : d + 1 ≠ 0)] using hz.active_eq + have hreturnLoop := ExecWithin.loop_iter + (by simp only [Cond.holds_lt, hz.regs.nil_eq, hza]; decide) hreturn hend + have hchildLoop := hchild endState _ hreturnLoop + have hpushLoop := ExecWithin.loop_iter + (by simp only [Cond.holds_lt, hu.regs.nil_eq, hu.active_eq]; decide) hpush hchildLoop + have he := hprevious endState _ hpushLoop + apply he.mono + have hweight := childWeightPrefix_succ S t + simp only [hne, ↓reduceIte] at hweight + change childWeightPrefix S (k + 1) = childWeightPrefix S k + + traceWeight (childSet S (Encoding.termDigits t)) at hweight + simp only [Nat.add_mul, Nat.one_mul] + omega + · have hempty := Finset.not_nonempty_iff_eq_empty.mp hne + obtain ⟨v, hskip, hv⟩ := exec_progress_empty S a b x u hx t hu hempty hfit + refine ⟨v, hv, ?_⟩ + intro endState U hend + have hskipLoop := ExecWithin.loop_iter + (by simp only [Cond.holds_lt, hu.regs.nil_eq, hu.active_eq]; decide) hskip hend + have he := hprevious endState _ hskipLoop + apply he.mono + have hweight := childWeightPrefix_succ S t + simp only [hne, ↓reduceIte, Nat.add_zero] at hweight + change childWeightPrefix S (k + 1) = childWeightPrefix S k at hweight + simp only [Nat.add_mul, Nat.one_mul] + omega + obtain ⟨u, hu, hchildren⟩ := hterms 10 (by omega) + obtain ⟨z, hfinish, hz⟩ := exec_progress_finish S a b x u hx hu hfit + refine ⟨z, hz, ?_⟩ + intro endState U hend + have hfinishLoop := ExecWithin.loop_iter + (by simp only [Cond.holds_lt, hu.regs.nil_eq, hu.active_eq]; decide) hfinish hend + have hchildrenLoop := hchildren endState _ hfinishLoop + have he := ExecWithin.loop_iter + (by simp only [Cond.holds_lt, hx.regs.nil_eq, hx.active_eq]; decide) henter hchildrenLoop + apply he.mono + rw [traceWeight_succ_prefix] + have hc := node_local_cost_le S.card + omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Report.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Report.lean new file mode 100644 index 0000000000..20563c2d6c --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Report.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.MatrixMultiplication.Schonhage.Pruned.Memory +public import Complexity.Computability.WordRAM.Heap + +/-! +# Returning queried values in the actual input order + +## Main results + +* `PrunedQuery.exec_report`: the native reporting loop preserves duplicate requests and writes + their corresponding values in order, charging every load, address, store and loop test. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers used while reporting the explicitly supplied query list. -/ +def ReportLocal (i : Fin registers) : Prop := + i = index ∨ i = address ∨ i = query ∨ i = temporary + +instance (i : Fin registers) : Decidable (ReportLocal i) := + inferInstanceAs (Decidable (i = index ∨ i = address ∨ i = query ∨ i = temporary)) + +private theorem reportStep_preserves (i : Fin registers) : + ¬ReportLocal i → reportStep.Preserves i := by decide +kernel +revert + +private theorem report_preserves (i : Fin registers) : + ¬ReportLocal i → report.Preserves i := by decide +kernel +revert + +/-- The finite native loop reads the original requests, including duplicates. Its only writes +are the `N` output cells; the value directory and original requests remain unchanged. -/ +theorem exec_report {w N capacity : ℕ} (x : Data registers) (codes : ℕ → ℕ) + (hN : x.regs queryCount = N) + (hinput : ∀ i < N, x.mem (x.regs queries + i) = codes i) + (hcodes : ∀ i < N, codes i < capacity) + (hsrc : x.regs queries + N ≤ x.regs output ∨ + x.regs output + N ≤ x.regs queries) + (hvals : x.regs values + capacity ≤ x.regs output ∨ + x.regs output + N ≤ x.regs values) + (h1 : x.regs one = 1) + (hw : Fits w (x.regs queries + N + x.regs values + capacity + + x.regs output + N + N + 12)) : + ∃ y, ExecWithin w report x y (9 * N + 3) ∧ y.regs index = N ∧ + (∀ i < N, y.mem (x.regs output + i) = x.mem (x.regs values + codes i)) ∧ + Modifies (Block.mk (x.regs output) N).footprint x.mem y.mem ∧ + (∀ i, ¬ReportLocal i → y.regs i = x.regs i) := by + let u := x.set index 0 + have hsetup : ExecWithin w (ram[ index ← #0 ] : Code .standard registers) x u 1 := by + ram_eval [u] + let I (y : Data registers) : Prop := + y.regs index ≤ N ∧ + (∀ i < y.regs index, y.mem (x.regs output + i) = x.mem (x.regs values + codes i)) ∧ + (∀ i < N, y.mem (x.regs queries + i) = codes i) ∧ + (∀ q < capacity, y.mem (x.regs values + q) = x.mem (x.regs values + q)) ∧ + Modifies (Block.mk (x.regs output) N).footprint x.mem y.mem ∧ + (∀ i, ¬ReportLocal i → y.regs i = x.regs i) + have hinit : I u := by + refine ⟨by simp [u], ?_, hinput, fun _ _ => rfl, Modifies.refl _ _, ?_⟩ + · simp only [u, Data.set_regs, Function.update_self, not_lt_zero, false_implies, + implies_true] + · intro i hi + exact Function.update_of_ne (by intro he; exact hi (Or.inl he)) _ _ + have hstep (y : Data registers) (hy : I y) + (hc : (Cond.lt index queryCount).Holds y) : + ∃ z, ExecWithin w reportStep y z 7 ∧ I z ∧ N - z.regs index < N - y.regs index := by + obtain ⟨hle, hdone, hread, hvalue, hframe, hfrozen⟩ := hy + have hq := hfrozen queries (by decide) + have hv := hfrozen values (by decide) + have ho := hfrozen output (by decide) + have hn := (hfrozen queryCount (by decide)).trans hN + have h1' := (hfrozen one (by decide)).trans h1 + have hk : y.regs index < N := by simpa only [Cond.holds_lt, hn] using hc + let k := y.regs index + let value := x.mem (x.regs values + codes k) + let z := (((((((y.set address (y.regs queries + k)).set query (codes k)).set + address (y.regs values + codes k)).set temporary value).set + address (y.regs output + k)).write (y.regs output + k) value).set index (k + 1)) + have hread' : y.mem (y.regs queries + k) = codes k := by rw [hq]; exact hread k hk + have hval' : y.mem (y.regs values + codes k) = value := by + rw [hv] + exact hvalue (codes k) (hcodes k hk) + have hqa : Fits w (y.regs queries + k) := hw.of_le (by rw [hq]; dsimp [k]; omega) + have hva : Fits w (y.regs values + codes k) := + hw.of_le (by rw [hv]; have := hcodes k hk; omega) + have hoa : Fits w (y.regs output + k) := hw.of_le (by rw [ho]; dsimp [k]; omega) + have hki : Fits w (k + 1) := hw.of_le (by dsimp [k]; omega) + have hz : ExecWithin w reportStep y z 7 := by + ram_eval [reportStep, z, k, value, hread', hval', h1', hqa, hva, hoa, hki, + index, address, query, temporary, queries, values, output, one, Fin.mk.injEq] + have hmem : z.mem = Function.update y.mem (x.regs output + k) value := by + simp only [z, Data.set_mem, Data.write_mem, ho] + have hidx : z.regs index = k + 1 := by simp [z] + have hawaySrc (i : ℕ) (hi : i < N) : x.regs queries + i ≠ x.regs output + k := by + rcases hsrc with h | h <;> dsimp [k] <;> omega + have hawayVal (q : ℕ) (hq' : q < capacity) : + x.regs values + q ≠ x.regs output + k := by + rcases hvals with h | h <;> dsimp [k] <;> omega + refine ⟨z, hz, ?_, by dsimp [k] at hidx; omega⟩ + refine ⟨by dsimp [k] at hidx; omega, ?_, ?_, ?_, ?_, ?_⟩ + · intro i hi + rw [hidx] at hi + by_cases he : i = k + · subst i + simp only [hmem, Function.update_self, value] + · rw [hmem, Function.update_of_ne (by omega : x.regs output + i ≠ x.regs output + k)] + exact hdone i (by dsimp [k] at hi; omega) + · intro i hi + rw [hmem, Function.update_of_ne (hawaySrc i hi)] + exact hread i hi + · intro q hq' + rw [hmem, Function.update_of_ne (hawayVal q hq')] + exact hvalue q hq' + · intro a ha + have hnot : a ≠ x.regs output + k := by + intro he + apply ha + rw [Block.mem_footprint] + change x.regs output ≤ a ∧ a < x.regs output + N + dsimp [k] at he + omega + rw [hmem, Function.update_of_ne hnot] + exact hframe a ha + · exact fun i hi => (hz.regs_eq (reportStep_preserves i hi)).trans (hfrozen i hi) + obtain ⟨y, hy, hIy, hstop⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs index) + hstep hinit + obtain ⟨hle, hdone, _, _, hframe, hfrozen⟩ := hIy + have hn := (hfrozen queryCount (by decide)).trans hN + have hidx : y.regs index = N := by + simp only [Cond.holds_lt, hn, not_lt] at hstop + omega + have hloop : ExecWithin w (.loop (.lt index queryCount) reportStep) u y (9 * N + 2) := by + simpa only [u, Data.set_regs, Function.update_self, Nat.sub_zero, Nat.mul_comm] using hy + have he : ExecWithin w report x y (9 * N + 3) := (hsetup.seq hloop).mono (by omega) + exact ⟨y, he, hidx, by simpa only [hidx] using hdone, hframe, + fun i hi => he.regs_eq (report_preserves i hi)⟩ + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Return.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Return.lean new file mode 100644 index 0000000000..9b35198c47 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Return.lean @@ -0,0 +1,196 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Node +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Accumulate + +/-! +# The actual controller phase after a child returns + +## Main results + +* `PrunedQuery.exec_node_return`: load the parent frame, add the returned child only at + contributing requests, and advance the stored term counter. + +The value-directory hypotheses describe ordinary memory established by the child proof. +They do not assume a child execution or its cost. All native accumulation and controller +instructions are proved and charged here. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed Finset + +/-- The complete parent-return phase, including its real frame loads, two mode comparisons, +signed indexed-add loop and stored counter increment. Unselected child cells are not read. -/ +theorem exec_node_return {w L f a0 b0 d N k : ℕ} (x : Data registers) + (hregs : KernelRegs L f a0 b0 x) (hd : x.regs level = d) (hdL : d < L) + (hm : x.regs mode = 2) (hN : x.mem (f + 4 * d) = N) (hcap : N ≤ 10 ^ L) + (hterm : x.mem (f + 4 * d + 3) = k) (hk : k < 10) + (input : ℕ → ℕ) (V B : ℕ → ℤ) + (hinput : ∀ i < N, x.mem (f + 4 * (L + 1) + d * 10 ^ L + i) = input i) + (hcodes : ∀ i < N, input i < 10 ^ L) (hinj : Set.InjOn input {i | i < N}) + (hparent : ∀ i < N, + x.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + input i) = + encode (V (input i))) + (hchild : ∀ i < N, selected true k (input i) → + x.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + (d + 1) * 10 ^ L + input i / 10) = + encode (B (input i / 10))) + (hV : ∀ i < N, GenericRAM.Signed.Fits w (V (input i))) + (hB : ∀ i < N, GenericRAM.Signed.Fits w (B (input i / 10))) + (hw : Fits (w + 2) (8 * f + 64 * (L + 1) * 10 ^ L + 128)) : + ∃ y, ExecWithin (w + 2) step x y (31 * N + 29) ∧ KernelRegs L f a0 b0 y ∧ + y.regs level = d ∧ y.regs mode = 1 ∧ y.regs active = x.regs active ∧ + y.regs epoch = x.regs epoch ∧ + (∀ i < N, + y.mem (f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + input i) = + encode (V (input i) + if selected true k (input i) then B (input i / 10) else 0)) ∧ + (∀ j < 3, y.mem (f + 4 * d + j) = x.mem (f + 4 * d + j)) ∧ + y.mem (f + 4 * d + 3) = k + 1 ∧ + (∀ q < 10 ^ L, + y.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q) = + x.mem (f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q)) ∧ + Modifies (nodeWrites L f d) x.mem y.mem := by + have hpow : 1 ≤ (10 : ℕ) ^ L := Nat.one_le_pow L _ (by decide) + have hL : L + 1 ≤ (L + 1) * 10 ^ L := by nlinarith only [hpow] + have hmul : (d + 1) * 10 ^ L ≤ (L + 1) * 10 ^ L := + Nat.mul_le_mul_right _ (by omega) + obtain ⟨u, hu, humem, huf, huc, _, _, hut, hul, huv, _, _, huch, hureg⟩ := + exec_loadFrame x hregs.one_eq hregs.frameWidth_eq (hw.of_le (by + rw [hregs.frames_eq, hregs.lists_eq, hregs.values_eq, hregs.span_eq, hd] + nlinarith only [hpow, hL, hmul])) + have fixed (i : Fin registers) (hi : ¬LoadFrameLocal i) : u.regs i = x.regs i := + hureg i hi + have huf' : u.regs frame = f + 4 * d := by simpa only [hregs.frames_eq, hd] using huf + have hul' : u.regs listBase = f + 4 * (L + 1) + d * 10 ^ L := by + simpa only [hregs.lists_eq, hregs.span_eq, hd] using hul + let vb := f + 4 * (L + 1) + (L + 1) * 10 ^ L + d * 10 ^ L + have huv' : u.regs valueBase = vb := by + simpa only [hregs.values_eq, hregs.span_eq, hd] using huv + have huch' : u.regs childValues = f + 4 * (L + 1) + (L + 1) * 10 ^ L + + (d + 1) * 10 ^ L := by + simpa only [hregs.values_eq, hregs.span_eq, hd] using huch + have huc' : u.regs count = N := by simpa only [hregs.frames_eq, hd, hN] using huc + have hut' : u.regs term = k := by simpa only [hregs.frames_eq, hd, hterm] using hut + obtain ⟨v, hv, _, hvmem, hvFrame, _, hvreg⟩ := exec_accumulateLoop u input V B huc' + (by simpa only [hul', humem] using hinput) hcodes hinj + (by simpa only [huv', humem] using hparent) + (by simpa only [huch', humem, hut'] using hchild) hV hB + (Or.inl (by rw [hul', huv']; dsimp [vb]; nlinarith only [hcap, hpow])) + (Or.inl (by rw [huv', huch']; dsimp [vb]; nlinarith only [])) + ((fixed one (by decide)).trans hregs.one_eq) + ((fixed nine (by decide)).trans hregs.nine_eq) + ((fixed ten (by decide)).trans hregs.ten_eq) + (hw.of_le (by + rw [hul', huv', huch'] + dsimp [vb] + nlinarith only [hcap, hpow, hmul, hL])) + have hvf : v.regs frame = f + 4 * d := (hvreg frame (by decide)).trans huf' + have hvt : v.regs term = k := (hvreg term (by decide)).trans hut' + obtain ⟨y, hy, _, hym, hymem, _⟩ := exec_advance v + ((hvreg one (by decide)).trans ((fixed one (by decide)).trans hregs.one_eq)) + ((hvreg frameWidth (by decide)).trans + ((fixed frameWidth (by decide)).trans hregs.frameWidth_eq)) + (hw.of_le (by rw [hvt]; omega)) (hw.of_le (by rw [hvf]; nlinarith only [hL, hdL])) + have hacc : ExecWithin (w + 2) accumulate u y (31 * N + 8) := (hv.seq hy).mono (by omega) + have hum : u.regs mode = 2 := (fixed mode (by decide)).trans hm + have hu0 := (fixed nil (by decide)).trans hregs.nil_eq + have hu1 := (fixed one (by decide)).trans hregs.one_eq + have hbranch : ExecWithin (w + 2) + (ram[ if mode = nil then [ ⟪enter⟫ ] else [ + if mode = one then [ ⟪dispatch⟫ ] else [ ⟪accumulate⟫ ] ] ]) u y (31 * N + 12) := + (hacc.branch_neg (by simp only [Cond.holds_eq, hum, hu1]; decide)).branch_neg + (by simp only [Cond.holds_eq, hum, hu0]; decide) + have he : ExecWithin (w + 2) step x y (31 * N + 29) := (hu.seq hbranch).mono (by omega) + let written := ((range N).filter fun i => selected true k (input i)).image + fun i => vb + input i + have hvmem' : v.mem = written.piecewise + (fun a => encode (V (a - vb) + B ((a - vb) / 10))) x.mem := by + rw [huv', hut', humem] at hvmem + exact hvmem + have hyframe : Modifies (Block.mk (f + 4 * d) 4).footprint v.mem y.mem := by + rw [hymem, hvf] + exact Modifies.write_of_owns (by constructor <;> omega) _ + have hvframe : Modifies (Block.mk vb (10 ^ L)).footprint x.mem v.mem := by + simpa only [huv', humem] using hvFrame + have frameBeforeValue : f + 4 * d + 4 ≤ vb := by dsimp [vb]; omega + refine ⟨y, he, hregs.of_step he, ?_, hym, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩ + · exact (hy.regs_eq (by decide : advance.Preserves level)).trans + ((hvreg level (by decide)).trans ((fixed level (by decide)).trans hd)) + · exact (hy.regs_eq (by decide : advance.Preserves active)).trans + ((hvreg active (by decide)).trans (fixed active (by decide))) + · exact (hy.regs_eq (by decide : advance.Preserves epoch)).trans + ((hvreg epoch (by decide)).trans (fixed epoch (by decide))) + · intro i hi + change y.mem (vb + input i) = _ + have haway : ¬(Block.mk (f + 4 * d) 4).Owns (vb + input i) := by + change ¬(_ ≤ _ ∧ _ < f + 4 * d + 4) + omega + rw [hyframe _ (by simpa only [Block.mem_footprint] using haway), hvmem'] + by_cases hsel : selected true k (input i) + · have hmem : vb + input i ∈ written := mem_image.mpr + ⟨i, mem_filter.mpr ⟨mem_range.mpr hi, hsel⟩, rfl⟩ + simp only [piecewise, hmem, hsel, ↓reduceIte, Nat.add_sub_cancel_left] + · have hmem : vb + input i ∉ written := by + rintro h + obtain ⟨j, hj, hji⟩ := mem_image.mp h + have heq : input j = input i := Nat.add_left_cancel hji + exact hsel (heq ▸ (mem_filter.mp hj).2) + simpa only [piecewise, hmem, hsel, ↓reduceIte, add_zero] using hparent i hi + · intro j hj + rw [hymem, hvf, Function.update_of_ne (by omega : f + 4 * d + j ≠ f + 4 * d + 3)] + apply hvframe + rw [Block.mem_footprint] + change ¬(vb ≤ f + 4 * d + j ∧ _) + omega + · simp only [hymem, hvf, hvt, Function.update_self] + · intro q _ + have hafter : vb + 10 ^ L ≤ + f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q := by + dsimp only [vb] + nlinarith only [hmul] + have hf : f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q ∉ + (Block.mk (f + 4 * d) 4).footprint := by + rw [Block.mem_footprint] + dsimp only [Block.Owns] + omega + have hvf : f + 4 * (L + 1) + 2 * (L + 1) * 10 ^ L + q ∉ + (Block.mk vb (10 ^ L)).footprint := by + rw [Block.mem_footprint] + dsimp only [Block.Owns] + omega + exact (hyframe _ hf).trans (hvframe _ hvf) + · intro c hc + have hnotFrame : c ∉ (Block.mk (f + 4 * d) 4).footprint := by + intro hf + apply hc + apply mem_union_left + apply mem_union_left + apply mem_union_left + rw [Block.mem_footprint] at hf ⊢ + change f + 4 * d ≤ c ∧ c < f + 4 * d + 4 * (L + 1 - d) + change f + 4 * d ≤ c ∧ c < f + 4 * d + 4 at hf + have hrem : 1 ≤ L + 1 - d := by omega + omega + have hnotValue : c ∉ (Block.mk vb (10 ^ L)).footprint := by + intro hc' + apply hc + apply mem_union_left + apply mem_union_right + rw [Block.mem_footprint] at hc' ⊢ + change vb ≤ c ∧ c < vb + (L + 1 - d) * 10 ^ L + change vb ≤ c ∧ c < vb + 10 ^ L at hc' + have hrem : 1 ≤ L + 1 - d := by omega + have hlen := Nat.mul_le_mul_right (10 ^ L) hrem + exact ⟨hc'.1, by omega⟩ + exact (hyframe c hnotFrame).trans (hvframe c hnotValue) + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Root.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Root.lean new file mode 100644 index 0000000000..e1852d3acd --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Root.lean @@ -0,0 +1,192 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Scan +public import Complexity.Computability.WordRAM.Heap + +/-! +# Initialization of the actual root stack frame + +## Main results + +* `PrunedQuery.exec_rootFinish`: write the computed root count and enter its first phase. + +The count comes from the preceding native deduplication. The frame words are ordinary stores, +not an externally supplied traversal certificate. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers assigned after the actual root query list has been computed. -/ +def RootFinishLocal (i : Fin registers) : Prop := + i = address ∨ i = level ∨ i = mode ∨ i = active + +instance (i : Fin registers) : Decidable (RootFinishLocal i) := + inferInstanceAs (Decidable (i = address ∨ i = level ∨ i = mode ∨ i = active)) + +private theorem rootFinish_preserves (i : Fin registers) : + ¬RootFinishLocal i → rootFinish.Preserves i := by decide +kernel +revert + +/-- Eleven native instructions write the root's computed count, zero prefix, unit stride and +zero term, then set the controller to its entry phase. -/ +theorem exec_rootFinish {w : ℕ} (x : Data registers) (h0 : x.regs nil = 0) + (h1 : x.regs one = 1) (hw : Fits w (x.regs frames + 3)) : + ∃ y, ExecWithin w rootFinish x y 11 ∧ y.regs level = 0 ∧ y.regs mode = 0 ∧ + y.regs active = 1 ∧ + y.mem = Function.update (Function.update (Function.update (Function.update x.mem + (x.regs frames) (x.regs childCount)) (x.regs frames + 1) 0) + (x.regs frames + 2) 1) (x.regs frames + 3) 0 ∧ + Modifies (Block.mk (x.regs frames) 4).footprint x.mem y.mem ∧ + (∀ i, ¬RootFinishLocal i → y.regs i = x.regs i) := by + let f := x.regs frames + let y := (((((((((((x.set address f).write f (x.regs childCount)).set address (f + 1)).write + (f + 1) 0).set address (f + 2)).write (f + 2) 1).set address (f + 3)).write + (f + 3) 0).set level 0).set mode 0).set active 1) + have hconst : Fits w 1 := hw.of_le (by omega) + have hf1 : Fits w (x.regs frames + 1) := hw.of_le (by omega) + have hf2 : Fits w (x.regs frames + 2) := hw.of_le (by omega) + have he : ExecWithin w rootFinish x y 11 := by + ram_eval [rootFinish, y, f, h0, h1, hw, hconst, hf1, hf2, address, level, mode, active, + one, nil, childCount, frames, Fin.mk.injEq] + refine ⟨y, he, ?_, ?_, by simp [y], rfl, ?_, + fun i hi => he.regs_eq (rootFinish_preserves i hi)⟩ + · simp +decide [y, active, mode, level] + · simp +decide [y, active, mode] + · intro a ha + have hnot : ¬(f ≤ a ∧ a < f + 4) := by + simpa only [Block.mem_footprint, Block.Owns, f, Block.base, Block.length] using ha + have h0' : a ≠ f := by omega + have h1' : a ≠ f + 1 := by omega + have h2' : a ≠ f + 2 := by omega + have h3' : a ≠ f + 3 := by omega + simp only [y, Data.set_mem, Data.write_mem, Function.update_of_ne h3', + Function.update_of_ne h2', Function.update_of_ne h1', Function.update_of_ne h0'] + +/-- Registers written by root-list construction and its first frame initialization. -/ +def RootLocal (i : Fin registers) : Prop := + ScanLocal i ∨ RootFinishLocal i ∨ i = listBase ∨ i = count ∨ i = childList + +instance (i : Fin registers) : Decidable (RootLocal i) := + inferInstanceAs (Decidable + (ScanLocal i ∨ RootFinishLocal i ∨ i = listBase ∨ i = count ∨ i = childList)) + +private theorem rootList_preserves (i : Fin registers) : + ¬RootLocal i → rootList.Preserves i := by decide +kernel +revert + +/-- Construct the actual distinct root list and frame. Repeated input queries are read and +removed by the native timestamp scan, rather than supplied as a mathematical set. The frame, +list and timestamp intervals are the only possible memory writes. -/ +theorem exec_rootList {w N capacity : ℕ} (x : Data registers) (input : ℕ → ℕ) + (hN : x.regs queryCount = N) + (hinput : ∀ i < N, x.mem (x.regs queries + i) = input i) + (hcodes : ∀ i < N, input i < capacity) + (hsep : x.regs lists + capacity ≤ x.regs stamps) + (hsrcOut : x.regs queries + N ≤ x.regs lists) + (hframeList : x.regs frames + 4 ≤ x.regs lists) + (hsrcFrame : x.regs queries + N ≤ x.regs frames) + (hmarks : ∀ q < capacity, x.mem (x.regs stamps + q) ≤ x.regs epoch) + (h0 : x.regs nil = 0) (h1 : x.regs one = 1) + (h10 : x.regs ten = 10) (h9 : x.regs nine = 9) + (hw : Fits w (x.regs queries + N + x.regs lists + capacity + + x.regs stamps + capacity + N + capacity + x.regs epoch + x.regs frames + 16)) : + ∃ y, ExecWithin w rootList x y (20 * N + 19) ∧ + y.regs level = 0 ∧ y.regs mode = 0 ∧ y.regs active = 1 ∧ + y.regs epoch = x.regs epoch + 1 ∧ + ScanMemory y.mem (x.regs lists) (x.regs stamps) (x.regs epoch + 1) capacity + ((Finset.range N).image input) ∧ + y.mem (x.regs frames) = ((Finset.range N).image input).card ∧ + y.mem (x.regs frames + 1) = 0 ∧ y.mem (x.regs frames + 2) = 1 ∧ + y.mem (x.regs frames + 3) = 0 ∧ + (∀ i < N, y.mem (x.regs queries + i) = input i) ∧ + (∀ a, ¬(Block.mk (x.regs lists) capacity).Owns a → + ¬(Block.mk (x.regs stamps) capacity).Owns a → + ¬(Block.mk (x.regs frames) 4).Owns a → y.mem a = x.mem a) ∧ + (∀ i, ¬RootLocal i → y.regs i = x.regs i) := by + let u := ((x.set listBase (x.regs queries)).set count N).set childList (x.regs lists) + have hu : ExecWithin w rootScanSetup x u 3 := by + ram_eval [rootScanSetup, u, hN, listBase, count, childList, queryCount, queries, lists, + Fin.mk.injEq] + obtain ⟨v, hv, _, hve, hvc, htable, _, hscanFrame, hvreg⟩ := exec_scan (N := N) + (capacity := capacity) false u input + (by simp +decide [u, count, childList]) + (by simpa +decide [u, listBase, count, childList] using hinput) + (by simpa only [shortened, Bool.false_eq_true, ↓reduceIte] using hcodes) + (Or.inl (by simpa +decide [u, childList, stamps, count, listBase] using hsep)) + (Or.inl (by simpa +decide [u, childList, listBase, count] using hsrcOut)) + (Or.inl (by simpa +decide [u, stamps, listBase, count, childList] using + hsrcOut.trans (Nat.le_trans (Nat.le_add_right _ _) hsep))) + (by simpa +decide [u, stamps, epoch, childList, count, listBase] using hmarks) + (by simpa +decide [u, one, childList, count, listBase] using h1) + (by simpa +decide [u, ten, childList, count, listBase] using h10) + (by simpa +decide [u, nine, childList, count, listBase] using h9) + (hw.of_le (by simp +decide [u, listBase, count, childList, stamps, epoch]; omega)) + have fixed (i : Fin registers) (hi : ¬ScanLocal i) + (hb : i ≠ listBase) (hc : i ≠ count) (ho : i ≠ childList) : + v.regs i = x.regs i := by + rw [hvreg i hi] + simp only [u, Data.set_regs, Function.update_of_ne ho, Function.update_of_ne hc, + Function.update_of_ne hb] + have hvf := fixed frames (by decide) (by decide) (by decide) (by decide) + have hvs := fixed stamps (by decide) (by decide) (by decide) (by decide) + obtain ⟨y, hy, hyl, hym, hya, hmem, hfinishFrame, _⟩ := exec_rootFinish v + ((fixed nil (by decide) (by decide) (by decide) (by decide)).trans h0) + ((fixed one (by decide) (by decide) (by decide) (by decide)).trans h1) + (hw.of_le (by rw [hvf]; omega)) + have he : ExecWithin w rootList x y (20 * N + 19) := by + exact (hu.seq (hv.seq hy)).mono (by omega) + have hyoutside (a : ℕ) (ha : ¬(Block.mk (x.regs frames) 4).Owns a) : + y.mem a = v.mem a := hfinishFrame a (by simpa only [Block.mem_footprint, hvf] using ha) + have hawayList (i : ℕ) : ¬(Block.mk (x.regs frames) 4).Owns (x.regs lists + i) := by + change ¬(x.regs frames ≤ _ ∧ _ < x.regs frames + 4) + omega + have hawayStamp (i : ℕ) : ¬(Block.mk (x.regs frames) 4).Owns (x.regs stamps + i) := by + change ¬(x.regs frames ≤ _ ∧ _ < x.regs frames + 4) + omega + have htable' : ScanMemory v.mem (x.regs lists) (x.regs stamps) (x.regs epoch + 1) + capacity ((Finset.range N).image input) := by + simpa +decide [u, childList, stamps, epoch, listBase, count, term, scanSet_root] using htable + refine ⟨y, he, hyl, hym, hya, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, + fun i hi => he.regs_eq (rootList_preserves i hi)⟩ + · simpa +decide [hy.regs_eq (by decide : rootFinish.Preserves epoch), u, epoch, + childList, count, listBase] using hve + · exact htable'.congr (fun i _ => hyoutside _ (hawayList i)) + (fun q _ => hyoutside _ (hawayStamp q)) + · rw [hmem, hvf] + simpa +decide [Function.update_apply, u, childList, count, listBase, term, scanSet_root] + using hvc + · rw [hmem, hvf] + simp + · rw [hmem, hvf] + simp + · rw [hmem, hvf] + simp + · intro i hi + rw [hyoutside _ (by change ¬(x.regs frames ≤ _ ∧ _ < x.regs frames + 4); omega)] + rw [hscanFrame] + · exact hinput i hi + · change ¬(u.regs childList ≤ _ ∧ _ < u.regs childList + capacity) + simp +decide only [u, Data.set_regs, childList, count, listBase, + Function.update_apply, ↓reduceIte] + omega + · change ¬(u.regs stamps ≤ _ ∧ _ < u.regs stamps + capacity) + have hus : u.regs stamps = x.regs stamps := by + simp +decide [u, stamps, childList, count, listBase] + rw [hus] + omega + · intro a hao has haf + rw [hyoutside a haf, hscanFrame] + · rfl + · simpa +decide [u, childList, count, listBase] using hao + · simpa +decide [u, stamps, childList, count, listBase] using has + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Scan.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Scan.lean new file mode 100644 index 0000000000..238c264122 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Scan.lean @@ -0,0 +1,166 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanInsert + +/-! +# Complete charged native query scans + +## Main results + +* `PrunedQuery.exec_scan`: the fixed native loop reads, filters and deduplicates its source + list in at most `20 N + 5` instructions, without clearing a dense directory. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers changed by the whole scan, including its fresh-epoch initialization. -/ +def ScanLocal (i : Fin registers) : Prop := ScanStepLocal i ∨ i = epoch + +instance (i : Fin registers) : Decidable (ScanLocal i) := + inferInstanceAs (Decidable (ScanStepLocal i ∨ i = epoch)) + +private theorem scan_preserves (child : Bool) (i : Fin registers) : + ¬ScanLocal i → (scan child).Preserves i := by decide +kernel +revert + +/-- The actual native scan constructs a distinct query list and its membership timestamps. +All width premises concern ordinary addresses, lengths and the incoming epoch. Source/output/ +directory separation is checked on concrete intervals. No child list is supplied in advance. -/ +theorem exec_scan {w N capacity : ℕ} (child : Bool) (x : Data registers) (input : ℕ → ℕ) + (hN : x.regs count = N) + (hinput : ∀ i < N, x.mem (x.regs listBase + i) = input i) + (hcodes : ∀ i < N, shortened child (input i) < capacity) + (hsep : x.regs childList + capacity ≤ x.regs stamps ∨ + x.regs stamps + capacity ≤ x.regs childList) + (hsrcOut : x.regs listBase + N ≤ x.regs childList ∨ + x.regs childList + capacity ≤ x.regs listBase) + (hsrcStamp : x.regs listBase + N ≤ x.regs stamps ∨ + x.regs stamps + capacity ≤ x.regs listBase) + (hmarks : ∀ q < capacity, x.mem (x.regs stamps + q) ≤ x.regs epoch) + (h1 : x.regs one = 1) (h10 : x.regs ten = 10) (h9 : x.regs nine = 9) + (hwidth : Fits w (x.regs listBase + N + x.regs childList + capacity + + x.regs stamps + capacity + N + capacity + x.regs epoch + 12)) : + ∃ y, ExecWithin w (scan child) x y (20 * N + 5) ∧ y.regs index = N ∧ + y.regs epoch = x.regs epoch + 1 ∧ + y.regs childCount = (scanSet child (x.regs term) input N).card ∧ + ScanMemory y.mem (x.regs childList) (x.regs stamps) (x.regs epoch + 1) capacity + (scanSet child (x.regs term) input N) ∧ + (∀ i < N, y.mem (x.regs listBase + i) = input i) ∧ + (∀ a, ¬(Block.mk (x.regs childList) capacity).Owns a → + ¬(Block.mk (x.regs stamps) capacity).Owns a → y.mem a = x.mem a) ∧ + (∀ i, ¬ScanLocal i → y.regs i = x.regs i) := by + let E := x.regs epoch + 1 + let x0 := ((x.set epoch E).set childCount 0).set index 0 + have h0 : Fits w 0 := fits_zero w + have hE : Fits w E := hwidth.of_le (by dsimp [E]; omega) + have hsetup : ExecWithin w scanSetup x x0 3 := by + ram_eval [scanSetup, x0, E, h1, hE, h0] + let I (y : Data registers) : Prop := + y.regs index ≤ N ∧ + y.regs childCount = (scanSet child (x.regs term) input (y.regs index)).card ∧ + ScanMemory y.mem (x.regs childList) (x.regs stamps) E capacity + (scanSet child (x.regs term) input (y.regs index)) ∧ + (∀ i < N, y.mem (x.regs listBase + i) = input i) ∧ + (∀ a, ¬(Block.mk (x.regs childList) capacity).Owns a → + ¬(Block.mk (x.regs stamps) capacity).Owns a → y.mem a = x.mem a) ∧ + (∀ i, ¬ScanStepLocal i → y.regs i = x0.regs i) + have hinit : I x0 := by + refine ⟨by simp [x0], by simp +decide [x0, childCount, index], ?_, hinput, ?_, + fun _ _ => rfl⟩ + · simpa only [x0, Data.set_mem, Data.set_regs, Function.update_self, scanSet_zero] using + (ScanMemory.empty (out := x.regs childList) (epoch := E) + (fun q hq => Nat.lt_succ_of_le (hmarks q hq))) + · exact fun _ _ _ => rfl + have hstep (y : Data registers) (hy : I y) (hc : (Cond.lt index count).Holds y) : + ∃ z, ExecWithin w (scanStep child) y z 18 ∧ I z ∧ + N - z.regs index < N - y.regs index := by + obtain ⟨hle, hcount, htable, hread, hframe, hfrozen⟩ := hy + have hfixed (i : Fin registers) (hi : ¬ScanStepLocal i) (he : i ≠ epoch) : + y.regs i = x.regs i := by + rw [hfrozen i hi] + simp only [x0, Data.set_regs] + have hic : i ≠ childCount := by intro h; subst h; exact hi (by decide) + have hii : i ≠ index := by intro h; subst h; exact hi (by decide) + simp only [Function.update_of_ne hii, Function.update_of_ne hic, + Function.update_of_ne he] + have hye : y.regs epoch = E := by + simpa +decide [x0, epoch, childCount, index] using hfrozen epoch (by decide) + have hyb := hfixed listBase (by decide) (by decide) + have hyo := hfixed childList (by decide) (by decide) + have hys := hfixed stamps (by decide) (by decide) + have hyt := hfixed term (by decide) (by decide) + have hyn := (hfixed count (by decide) (by decide)).trans hN + have hy1 := (hfixed one (by decide) (by decide)).trans h1 + have hy10 := (hfixed ten (by decide) (by decide)).trans h10 + have hy9 := (hfixed nine (by decide) (by decide)).trans h9 + have hk : y.regs index < N := by simpa only [Cond.holds_lt, hyn] using hc + have hq := hcodes (y.regs index) hk + have hcap := htable.card_le + have hw10 : Fits w 10 := hwidth.of_le (by omega) + have hwread : Fits w (y.regs listBase + y.regs index) := + hwidth.of_le (by rw [hyb]; omega) + have hwstamp : Fits w (y.regs stamps + shortened child (input (y.regs index))) := + hwidth.of_le (by rw [hys]; omega) + have hwout : Fits w (y.regs childList + y.regs childCount) := + hwidth.of_le (by rw [hyo, hcount]; omega) + have hwcount : Fits w (y.regs childCount + 1) := + hwidth.of_le (by rw [hcount]; omega) + have hwindex : Fits w (y.regs index + 1) := hwidth.of_le (by omega) + obtain ⟨z, hz, hidx, hcz, htz, hmf, hrf⟩ := exec_scanInsert child y + (scanSet child (x.regs term) input (y.regs index)) (input (y.regs index)) + (by rw [hyb]; exact hread _ hk) hcount + (by simpa only [hyo, hys, hye] using htable) hq + (by simpa only [hyo, hys] using hsep) hy1 hy10 hy9 + hw10 hwread hwstamp hwout hwcount hwindex + have hsource (i : ℕ) (hi : i < N) : z.mem (x.regs listBase + i) = input i := by + rw [hmf] + · exact hread i hi + · rw [hyo] + change ¬(x.regs childList ≤ x.regs listBase + i ∧ + x.regs listBase + i < x.regs childList + capacity) + rcases hsrcOut with h | h <;> omega + · rw [hys] + change ¬(x.regs stamps ≤ x.regs listBase + i ∧ + x.regs listBase + i < x.regs stamps + capacity) + rcases hsrcStamp with h | h <;> omega + refine ⟨z, hz, ?_, by omega⟩ + refine ⟨by omega, ?_, ?_, hsource, ?_, ?_⟩ + · simpa only [hyt, hidx, ← scanSet_succ] using hcz + · simpa only [hyo, hys, hye, hyt, hidx, ← scanSet_succ] using htz + · intro a hao has + rw [hmf a (by simpa only [hyo] using hao) (by simpa only [hys] using has)] + exact hframe a hao has + · exact fun i hi => (hrf i hi).trans (hfrozen i hi) + obtain ⟨y, hy, hIy, hstop⟩ := ExecWithin.loop_invariant I (fun y => N - y.regs index) + hstep hinit + obtain ⟨hle, hcount, htable, hread, hframe, hfrozen⟩ := hIy + have hyn : y.regs count = N := by + have := hfrozen count (by decide) + simpa +decide [x0, count, epoch, childCount, index] using this.trans (by + simpa +decide [x0, count, epoch, childCount, index] using hN) + have hindex : y.regs index = N := by + simp only [Cond.holds_lt, hyn, not_lt] at hstop + omega + have he : ExecWithin w (scan child) x y (20 * N + 5) := by + have hloop : ExecWithin w (Code.loop (.lt index count) (scanStep child)) x0 y + (20 * N + 2) := by + simpa only [x0, Data.set_regs, Function.update_self, Nat.sub_zero, + Nat.mul_comm] using hy + exact (hsetup.seq hloop).mono (by omega) + refine ⟨y, he, hindex, ?_, ?_, ?_, hread, hframe, + fun i hi => he.regs_eq (scan_preserves child i hi)⟩ + · simpa +decide [x0, E, epoch, childCount, index] using hfrozen epoch (by decide) + · simpa only [hindex] using hcount + · simpa only [hindex] using htable + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanInsert.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanInsert.lean new file mode 100644 index 0000000000..4a78483c7d --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanInsert.lean @@ -0,0 +1,90 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanMemory +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanStep + +/-! +# Query-set meaning of an actual native scan step + +## Main results + +* `PrunedQuery.exec_scanInsert`: the native filter and timestamp comparisons compute exactly + a finite-set insertion, preserving the list/directory invariant and the outside memory frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- One charged native step computes its set insertion from the loaded source word and the +actual timestamp. `S` describes the current memory, rather than supplying a decision oracle. -/ +theorem exec_scanInsert {w capacity : ℕ} (child : Bool) (x : Data registers) (S : Finset ℕ) + (q : ℕ) (hq : x.mem (x.regs listBase + x.regs index) = q) + (hcount : x.regs childCount = S.card) + (htable : ScanMemory x.mem (x.regs childList) (x.regs stamps) (x.regs epoch) capacity S) + (hcode : shortened child q < capacity) + (hsep : x.regs childList + capacity ≤ x.regs stamps ∨ + x.regs stamps + capacity ≤ x.regs childList) + (h1 : x.regs one = 1) (h10 : x.regs ten = 10) (h9 : x.regs nine = 9) + (h10fit : Fits w 10) (hread : Fits w (x.regs listBase + x.regs index)) + (hs : Fits w (x.regs stamps + shortened child q)) + (hl : Fits w (x.regs childList + x.regs childCount)) + (hn : Fits w (x.regs childCount + 1)) (hi : Fits w (x.regs index + 1)) : + ∃ y, ExecWithin w (scanStep child) x y 18 ∧ y.regs index = x.regs index + 1 ∧ + y.regs childCount = (if selected child (x.regs term) q then + insert (shortened child q) S else S).card ∧ + ScanMemory y.mem (x.regs childList) (x.regs stamps) (x.regs epoch) capacity + (if selected child (x.regs term) q then insert (shortened child q) S else S) ∧ + (∀ a, ¬(Block.mk (x.regs childList) capacity).Owns a → + ¬(Block.mk (x.regs stamps) capacity).Owns a → y.mem a = x.mem a) ∧ + (∀ i, ¬ScanStepLocal i → y.regs i = x.regs i) := by + obtain ⟨y, he, hindex, hc, hm, hf⟩ := exec_scanStep child x q hq h1 h10 h9 + h10fit hread hs hl hn hi + have hseen := htable.marked (shortened child q) hcode + refine ⟨y, he, hindex, ?_, ?_, ?_, hf⟩ + · by_cases hsel : selected child (x.regs term) q + · by_cases hmem : shortened child q ∈ S + · simpa [hsel, hmem, hseen.mpr hmem, hcount] using hc + · have hmark : x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch := + fun h => hmem (hseen.mp h) + simpa [hsel, hmem, hmark, hcount] using hc + · simpa [hsel, hcount] using hc + · by_cases hsel : selected child (x.regs term) q + · by_cases hmem : shortened child q ∈ S + · simpa [hm, hsel, hmem, hseen.mpr hmem] using htable + · have hmark : x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch := + fun h => hmem (hseen.mp h) + simpa [hm, hsel, hmark, hcount] using htable.insert_new hcode hmem hsep + · simpa [hm, hsel] using htable + · intro a hout hstamp + rw [hm] + split_ifs with hnew + · have hmem : shortened child q ∉ S := fun h => hnew.2 (hseen.mpr h) + have hcapa := htable.card_lt_of_notMem hcode hmem + have hao : a ≠ x.regs childList + x.regs childCount := by + intro ha + apply hout + rw [ha, hcount] + change x.regs childList ≤ x.regs childList + S.card ∧ + x.regs childList + S.card < x.regs childList + capacity + exact ⟨by omega, by omega⟩ + have has : a ≠ x.regs stamps + shortened child q := by + intro ha + apply hstamp + rw [ha] + change x.regs stamps ≤ x.regs stamps + shortened child q ∧ + x.regs stamps + shortened child q < x.regs stamps + capacity + exact ⟨by omega, by omega⟩ + simp only [Function.update_of_ne has, Function.update_of_ne hao] + · rfl + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanMemory.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanMemory.lean new file mode 100644 index 0000000000..25d5184b17 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanMemory.lean @@ -0,0 +1,164 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.ScanSet + +/-! +# Memory invariant of a timestamped query list + +## Main definitions + +* `PrunedQuery.ScanMemory`: a distinct finite list and its timestamp membership directory. + +## Main results + +* `PrunedQuery.ScanMemory.insert_new`: the two actual stores preserve the invariant on insertion. + +The invariant describes memory contents only. Execution and instruction charges are proved +separately for the native scan; no execution certificate is included here. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- The occupied output prefix enumerates `S` once, and the timestamp table identifies exactly +its members. Older timestamps may remain elsewhere in the directory. -/ +structure ScanMemory (mem : ℕ → ℕ) (out stamps epoch capacity : ℕ) (S : Finset ℕ) : Prop where + /-- Each listed value is a valid directory index. -/ + bounded : ∀ q ∈ S, q < capacity + /-- Every timestamp is at most the current epoch. -/ + stamp_le : ∀ q < capacity, mem (stamps + q) ≤ epoch + /-- The current epoch is exactly the membership marker. -/ + marked : ∀ q < capacity, mem (stamps + q) = epoch ↔ q ∈ S + /-- The actual occupied prefix is an injective enumeration of the set. -/ + listed : ∃ v : Fin S.card → ℕ, Function.Injective v ∧ + Set.range v = (S : Set ℕ) ∧ (Block.mk out S.card).Realizes mem v + +/-- A realized distinct list cannot exceed its concrete directory capacity. -/ +theorem ScanMemory.card_le {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} {S : Finset ℕ} + (h : ScanMemory mem out stamps epoch capacity S) : S.card ≤ capacity := by + have hs : S ⊆ Finset.range capacity := fun q hq => Finset.mem_range.mpr (h.bounded q hq) + simpa using Finset.card_le_card hs + +/-- Agreement on the occupied list and timestamp directory preserves the scan invariant. +Unoccupied list cells have no specified meaning and need not agree. -/ +theorem ScanMemory.congr {mem mem' : ℕ → ℕ} {out stamps epoch capacity : ℕ} {S : Finset ℕ} + (h : ScanMemory mem out stamps epoch capacity S) + (hlist : ∀ i < S.card, mem' (out + i) = mem (out + i)) + (hstamp : ∀ q < capacity, mem' (stamps + q) = mem (stamps + q)) : + ScanMemory mem' out stamps epoch capacity S := by + refine ⟨h.bounded, ?_, ?_, ?_⟩ + · intro q hq + rw [hstamp q hq] + exact h.stamp_le q hq + · intro q hq + rw [hstamp q hq] + exact h.marked q hq + · obtain ⟨v, hv, hrange, hreal⟩ := h.listed + exact ⟨v, hv, hrange, fun i => (hlist i i.isLt).trans (hreal i)⟩ + +/-- Reading the occupied native list gives exactly its represented finite set. -/ +theorem ScanMemory.image_read {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} {S : Finset ℕ} + (h : ScanMemory mem out stamps epoch capacity S) : + (Finset.range S.card).image (fun i => mem (out + i)) = S := by + obtain ⟨v, _, hrange, hreal⟩ := h.listed + ext q + constructor + · rintro hq + obtain ⟨i, hi, hq⟩ := Finset.mem_image.mp hq + have hi' : i < S.card := Finset.mem_range.mp hi + have hread : mem (out + i) = v ⟨i, hi'⟩ := hreal ⟨i, hi'⟩ + have hv : v ⟨i, hi'⟩ ∈ S := by + rw [← Finset.mem_coe, ← hrange] + exact ⟨⟨i, hi'⟩, rfl⟩ + exact (hread.symm.trans hq) ▸ hv + · intro hq + have hv : q ∈ Set.range v := by rw [hrange]; exact hq + obtain ⟨i, rfl⟩ := hv + exact Finset.mem_image.mpr ⟨i.val, Finset.mem_range.mpr i.isLt, hreal i⟩ + +/-- Occupied native list positions store different codes. -/ +theorem ScanMemory.injOn_read {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} {S : Finset ℕ} + (h : ScanMemory mem out stamps epoch capacity S) : + Set.InjOn (fun i => mem (out + i)) {i | i < S.card} := by + obtain ⟨v, hv, _, hreal⟩ := h.listed + intro i hi j hj he + have hreadi : mem (out + i) = v ⟨i, hi⟩ := hreal ⟨i, hi⟩ + have hreadj : mem (out + j) = v ⟨j, hj⟩ := hreal ⟨j, hj⟩ + exact congrArg Fin.val (hv (hreadi.symm.trans (he.trans hreadj))) + +/-- An unseen valid code leaves a free output cell for its insertion. -/ +theorem ScanMemory.card_lt_of_notMem {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} + {S : Finset ℕ} (h : ScanMemory mem out stamps epoch capacity S) {q : ℕ} + (hq : q < capacity) (hnew : q ∉ S) : S.card < capacity := by + have hs : insert q S ⊆ Finset.range capacity := by + intro a ha + rcases Finset.mem_insert.mp ha with rfl | ha + · exact Finset.mem_range.mpr hq + · exact Finset.mem_range.mpr (h.bounded a ha) + have hc := Finset.card_le_card hs + simp only [Finset.card_insert_of_notMem hnew, Finset.card_range] at hc + omega + +/-- A fresh epoch establishes the empty directory without clearing its cells. -/ +theorem ScanMemory.empty {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} + (h : ∀ q < capacity, mem (stamps + q) < epoch) : + ScanMemory mem out stamps epoch capacity ∅ := by + refine ⟨by simp, fun q hq => (h q hq).le, ?_, ?_⟩ + · intro q hq + simp only [Finset.notMem_empty, iff_false] + exact Nat.ne_of_lt (h q hq) + · refine ⟨Fin.elim0, (fun i => i.elim0), ?_, (fun i => i.elim0)⟩ + ext q + simp + +/-- Append one previously unseen code, then mark its directory entry. Disjointness of the +two concrete blocks ensures that neither store corrupts the other representation. -/ +theorem ScanMemory.insert_new {mem : ℕ → ℕ} {out stamps epoch capacity : ℕ} {S : Finset ℕ} + (h : ScanMemory mem out stamps epoch capacity S) {q : ℕ} (hq : q < capacity) + (hnew : q ∉ S) (hsep : out + capacity ≤ stamps ∨ stamps + capacity ≤ out) : + ScanMemory + (Function.update (Function.update mem (out + S.card) q) (stamps + q) epoch) + out stamps epoch capacity (insert q S) := by + have hcard := h.card_lt_of_notMem hq hnew + have haway (a : ℕ) (ha : a < capacity) : stamps + a ≠ out + S.card := by + rcases hsep with hs | hs <;> omega + refine ⟨?_, ?_, ?_, ?_⟩ + · intro a ha + rcases Finset.mem_insert.mp ha with rfl | ha + · exact hq + · exact h.bounded a ha + · intro a ha + by_cases he : a = q + · subst a + simp + · simpa only [Function.update_of_ne (by omega : stamps + a ≠ stamps + q), + Function.update_of_ne (haway a ha)] using h.stamp_le a ha + · intro a ha + by_cases he : a = q + · subst a + simp + · simpa only [Function.update_of_ne (by omega : stamps + a ≠ stamps + q), + Function.update_of_ne (haway a ha), Finset.mem_insert, he, false_or] using h.marked a ha + · obtain ⟨v, hv, hrange, hreal⟩ := h.listed + have hnot : q ∉ Set.range v := by simpa only [hrange, Finset.mem_coe] using hnew + have hlist := hreal.snoc q + have hown : ¬(Block.mk out (S.card + 1)).Owns (stamps + q) := by + change ¬(out ≤ stamps + q ∧ stamps + q < out + (S.card + 1)) + rcases hsep with hs | hs <;> omega + have hlist' := hlist.update_of_not_owns hown epoch + rw [Finset.card_insert_of_notMem hnew] + refine ⟨Fin.snoc v q, Fin.snoc_injective_iff.mpr ⟨hv, hnot⟩, ?_, hlist'⟩ + rw [Fin.range_snoc, hrange] + simp + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanSet.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanSet.lean new file mode 100644 index 0000000000..88c30f215f --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanSet.lean @@ -0,0 +1,92 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Codes +public import Complexity.Computability.WordRAM.ArrayAppend + +/-! +# Finite-set invariant of the native deduplicating scan + +## Main definitions + +* `PrunedQuery.scanSet`: the selected, shortened codes among a processed input prefix. + +## Main results + +* `PrunedQuery.scanSet_succ`: the exact insertion made by one scan step. +* `PrunedQuery.scanSet_child`: agreement of the child scan with `childCodes`. + +These describe the native loop's mathematical invariant; they do not assume its execution. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Finset + +/-- Root scans retain every query; child scans retain exactly contributing first digits. -/ +def selected (child : Bool) (term code : ℕ) : Prop := + if child then code % 10 = 9 ∨ code % 10 = term else True + +instance (child : Bool) (term code : ℕ) : Decidable (selected child term code) := + inferInstanceAs (Decidable (if child then code % 10 = 9 ∨ code % 10 = term else True)) + +/-- Child requests discard one digit; root requests keep the supplied complete code. -/ +def shortened (child : Bool) (code : ℕ) : ℕ := if child then code / 10 else code + +/-- The deduplicated codes selected from the first `k` actual input entries. -/ +def scanSet (child : Bool) (term : ℕ) (input : ℕ → ℕ) (k : ℕ) : Finset ℕ := + ((range k).filter fun i => selected child term (input i)).image + fun i => shortened child (input i) + +/-- No input positions have been visited initially. -/ +@[simp] +theorem scanSet_zero (child : Bool) (term : ℕ) (input : ℕ → ℕ) : + scanSet child term input 0 = ∅ := by simp [scanSet] + +/-- A scan step inserts one selected suffix, leaving any previous occurrence deduplicated. -/ +theorem scanSet_succ (child : Bool) (term : ℕ) (input : ℕ → ℕ) (k : ℕ) : + scanSet child term input (k + 1) = + if selected child term (input k) then + insert (shortened child (input k)) (scanSet child term input k) + else scanSet child term input k := by + by_cases h : selected child term (input k) <;> simp [scanSet, range_add_one, filter_insert, h] + +/-- At most one new distinct code is emitted for each processed input word. -/ +theorem card_scanSet_le (child : Bool) (term : ℕ) (input : ℕ → ℕ) (k : ℕ) : + (scanSet child term input k).card ≤ k := + (card_image_le.trans (card_filter_le _ _)).trans_eq (card_range k) + +/-- Root deduplication computes exactly the image of the input prefix. -/ +theorem scanSet_root (term : ℕ) (input : ℕ → ℕ) (k : ℕ) : + scanSet false term input k = (range k).image input := by + simp [scanSet, selected, shortened] + +/-- A child scan uses the same selected quotient image as the finite pruning specification. -/ +theorem scanSet_child (term : ℕ) (input : ℕ → ℕ) (k : ℕ) : + scanSet true term input k = childCodes ((range k).image input) term := by + ext q + simp only [scanSet, selected, ↓reduceIte, shortened, mem_image, + mem_filter, childCodes] + constructor + · rintro ⟨i, ⟨hi, hm⟩, he⟩ + exact ⟨input i, ⟨⟨i, hi, rfl⟩, hm⟩, he⟩ + · rintro ⟨v, ⟨⟨i, hi, rfl⟩, hm⟩, he⟩ + exact ⟨i, ⟨hi, hm⟩, he⟩ + +/-- Every stored code lies in the concrete destination capacity when each selected source does. -/ +theorem scanSet_bounded {child : Bool} {term k capacity : ℕ} {input : ℕ → ℕ} + (h : ∀ i < k, selected child term (input i) → shortened child (input i) < capacity) : + ∀ q ∈ scanSet child term input k, q < capacity := by + intro q hq + obtain ⟨i, hi, rfl⟩ := mem_image.mp hq + exact h i (mem_range.mp (mem_filter.mp hi).1) (mem_filter.mp hi).2 + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanStep.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanStep.lean new file mode 100644 index 0000000000..dc933a6d91 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/ScanStep.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.MatrixMultiplication.Schonhage.Pruned.Emit +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.ScanSet + +/-! +# Charged native suffix selection + +## Main results + +* `PrunedQuery.exec_suffix`: one real quotient computation and timestamp insertion, + within ten native instructions, with its complete memory and register frame. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM + +/-- Registers changed by shortening a query and attempting its insertion. -/ +def SuffixLocal (i : Fin registers) : Prop := EmitLocal i ∨ i = suffix + +instance (i : Fin registers) : Decidable (SuffixLocal i) := + inferInstanceAs (Decidable (EmitLocal i ∨ i = suffix)) + +private theorem suffix_preserves (child : Bool) (i : Fin registers) : + ¬SuffixLocal i → (putSuffix child).Preserves i := by decide +kernel +revert + +/-- The selected suffix is divided from the actual loaded query, then deduplicated by its +actual memory timestamp. No query-set computation is supplied as a hypothesis. -/ +theorem exec_suffix {w : ℕ} (child : Bool) (x : Data registers) (q : ℕ) + (hq : x.regs query = q) (h1 : x.regs one = 1) (h10 : x.regs ten = 10) + (hs : Fits w (x.regs stamps + shortened child q)) + (hl : Fits w (x.regs childList + x.regs childCount)) + (hn : Fits w (x.regs childCount + 1)) : + ∃ y, ExecWithin w (putSuffix child) x y 10 ∧ y.regs suffix = shortened child q ∧ + y.regs childCount = + (if x.mem (x.regs stamps + shortened child q) = x.regs epoch then + x.regs childCount else x.regs childCount + 1) ∧ + y.mem = + (if x.mem (x.regs stamps + shortened child q) = x.regs epoch then x.mem else + Function.update (Function.update x.mem + (x.regs childList + x.regs childCount) (shortened child q)) + (x.regs stamps + shortened child q) (x.regs epoch)) ∧ + (∀ i, ¬SuffixLocal i → y.regs i = x.regs i) := by + let z := x.set suffix (shortened child q) + have hqfit : Fits w (shortened child q) := hs.of_le (by omega) + have hpre : ExecWithin w + (if child then ram[ suffix ← query / ten ] else ram[ suffix ← query ]) x z 1 := by + cases child with + | false => + change ExecWithin w (ram[ suffix ← query ]) x z 1 + ram_eval [z, shortened, hq] + | true => + have hv : Fits w (q / 10) := hqfit + change ExecWithin w (ram[ suffix ← query / ten ]) x (x.set suffix (q / 10)) 1 + ram_eval [hq, h10, hv] + have hz1 : z.regs one = 1 := by simpa [z, one, suffix] using h1 + have hzs : Fits w (z.regs stamps + z.regs suffix) := by + simpa [z, stamps, suffix] using hs + have hzl : Fits w (z.regs childList + z.regs childCount) := by + simpa [z, childList, childCount, suffix] using hl + have hzn : Fits w (z.regs childCount + 1) := by + simpa [z, childCount, suffix] using hn + obtain ⟨y, hy, hc, hm, hf⟩ := exec_emit z hz1 hzs hzl hzn + have he : ExecWithin w (putSuffix child) x y 10 := hpre.seq hy + refine ⟨y, he, ?_, ?_, ?_, fun i hi => he.regs_eq (suffix_preserves child i hi)⟩ + · simpa [z] using hf suffix (by decide) + · simpa [z, stamps, suffix, childCount, epoch] using hc + · simpa [z, stamps, suffix, childList, childCount, epoch] using hm + +private theorem dispatch_preserves (child : Bool) (i : Fin registers) : + ¬SuffixLocal i → (scanDispatch child).Preserves i := by decide +kernel +revert + +/-- Execute the actual query filter and unique insertion. The numerical selection is proved +from the two real digit comparisons; it is not a supplied child-set certificate. -/ +theorem exec_scanDispatch {w : ℕ} (child : Bool) (x : Data registers) (q : ℕ) + (hq : x.regs query = q) (hdigit : x.regs digit = q % 10) + (h1 : x.regs one = 1) (h10 : x.regs ten = 10) (h9 : x.regs nine = 9) + (hs : Fits w (x.regs stamps + shortened child q)) + (hl : Fits w (x.regs childList + x.regs childCount)) + (hn : Fits w (x.regs childCount + 1)) : + ∃ y, ExecWithin w (scanDispatch child) x y 14 ∧ + y.regs childCount = + (if selected child (x.regs term) q ∧ + x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch then + x.regs childCount + 1 else x.regs childCount) ∧ + y.mem = + (if selected child (x.regs term) q ∧ + x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch then + Function.update (Function.update x.mem + (x.regs childList + x.regs childCount) (shortened child q)) + (x.regs stamps + shortened child q) (x.regs epoch) else x.mem) ∧ + (∀ i, ¬SuffixLocal i → y.regs i = x.regs i) := by + by_cases hsel : selected child (x.regs term) q + · obtain ⟨y, hy, _, hc, hm, _⟩ := exec_suffix child x q hq h1 h10 hs hl hn + have he : ExecWithin w (scanDispatch child) x y 14 := by + cases child with + | false => exact hy.mono (by decide) + | true => + have hm : q % 10 = 9 ∨ q % 10 = x.regs term := hsel + by_cases hfirst : q % 10 = 9 + · exact (ExecWithin.branch_pos (by + simpa only [Cond.holds_eq, hdigit, h9] using hfirst) hy).mono (by decide) + · have hsecond := hm.resolve_left hfirst + exact (ExecWithin.branch_neg (by + simpa only [Cond.holds_eq, hdigit, h9] using hfirst) + (ExecWithin.branch_pos (by simpa only [Cond.holds_eq, hdigit] using hsecond) hy)).mono + (by decide) + refine ⟨y, he, ?_, ?_, fun i hi => he.regs_eq (dispatch_preserves child i hi)⟩ + · by_cases hseen : x.mem (x.regs stamps + shortened child q) = x.regs epoch <;> + simpa [hsel, hseen] using hc + · by_cases hseen : x.mem (x.regs stamps + shortened child q) = x.regs epoch <;> + simpa [hsel, hseen] using hm + · have hchild : child = true := by + cases child <;> simp_all [selected] + subst hchild + have hfirst : q % 10 ≠ 9 := fun h => hsel (Or.inl h) + have hsecond : q % 10 ≠ x.regs term := fun h => hsel (Or.inr h) + let y := x.set temporary (x.regs temporary) + have he : ExecWithin w (scanDispatch true) x y 5 := + ExecWithin.branch_neg (by simpa only [Cond.holds_eq, hdigit, h9] using hfirst) + (ExecWithin.branch_neg (by simpa only [Cond.holds_eq, hdigit] using hsecond) + ExecWithin.copy) + refine ⟨y, he.mono (by decide), ?_, ?_, + fun i hi => he.regs_eq (dispatch_preserves true i hi)⟩ + · simp [hsel, y, childCount, temporary] + · simp [hsel, y] + +/-- All scan-step scratch registers; caller pointers, counts and the current epoch are kept. -/ +def ScanStepLocal (i : Fin registers) : Prop := + SuffixLocal i ∨ i = query ∨ i = digit ∨ i = index + +instance (i : Fin registers) : Decidable (ScanStepLocal i) := + inferInstanceAs (Decidable (SuffixLocal i ∨ i = query ∨ i = digit ∨ i = index)) + +private theorem scanStep_preserves (child : Bool) (i : Fin registers) : + ¬ScanStepLocal i → (scanStep child).Preserves i := by decide +kernel +revert + +/-- One complete native scan step includes source indexing, the actual load and digit +computation, conditional deduplicated insertion, and the loop-counter increment. -/ +theorem exec_scanStep {w : ℕ} (child : Bool) (x : Data registers) (q : ℕ) + (hq : x.mem (x.regs listBase + x.regs index) = q) + (h1 : x.regs one = 1) (h10 : x.regs ten = 10) (h9 : x.regs nine = 9) + (h10fit : Fits w 10) (hread : Fits w (x.regs listBase + x.regs index)) + (hs : Fits w (x.regs stamps + shortened child q)) + (hl : Fits w (x.regs childList + x.regs childCount)) + (hn : Fits w (x.regs childCount + 1)) (hi : Fits w (x.regs index + 1)) : + ∃ y, ExecWithin w (scanStep child) x y 18 ∧ y.regs index = x.regs index + 1 ∧ + y.regs childCount = + (if selected child (x.regs term) q ∧ + x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch then + x.regs childCount + 1 else x.regs childCount) ∧ + y.mem = + (if selected child (x.regs term) q ∧ + x.mem (x.regs stamps + shortened child q) ≠ x.regs epoch then + Function.update (Function.update x.mem + (x.regs childList + x.regs childCount) (shortened child q)) + (x.regs stamps + shortened child q) (x.regs epoch) else x.mem) ∧ + (∀ i, ¬ScanStepLocal i → y.regs i = x.regs i) := by + let z := ((x.set address (x.regs listBase + x.regs index)).set query q).set digit (q % 10) + have hmod : Fits w (q % 10) := h10fit.of_le (Nat.le_of_lt (Nat.mod_lt _ (by decide))) + have hload : ExecWithin w scanLoad x z 3 := by + ram_eval [scanLoad, z, hq, h10, hread, hmod, address, listBase, index, query, digit, ten, + Fin.mk.injEq] + have hreg (i : Fin registers) (ha : i ≠ address) (hq' : i ≠ query) (hd : i ≠ digit) : + z.regs i = x.regs i := by simp [z, ha, hq', hd] + obtain ⟨u, hu, hc, hm, hf⟩ := exec_scanDispatch child z q + (by simp [z, query, digit]) (by simp [z]) + ((hreg one (by decide) (by decide) (by decide)).trans h1) + ((hreg ten (by decide) (by decide) (by decide)).trans h10) + ((hreg nine (by decide) (by decide) (by decide)).trans h9) + (by simpa [z, stamps, address, query, digit] using hs) + (by simpa [z, childList, childCount, address, query, digit] using hl) + (by simpa [z, childCount, address, query, digit] using hn) + have hui : u.regs index = x.regs index := + (hf index (by decide)).trans (hreg index (by decide) (by decide) (by decide)) + have huo : u.regs one = 1 := + (hf one (by decide)).trans ((hreg one (by decide) (by decide) (by decide)).trans h1) + let y := u.set index (x.regs index + 1) + have hinc : ExecWithin w (ram[ index ← index + one ]) u y 1 := by + ram_eval [y, hui, huo, hi] + have he : ExecWithin w (scanStep child) x y 18 := hload.seq (hu.seq hinc) + refine ⟨y, he, by simp [y], ?_, ?_, + fun i hi => he.regs_eq (scanStep_preserves child i hi)⟩ + · simpa [y, z, index, childCount, term, address, query, digit, stamps, epoch] using hc + · simpa [y, z, childList, childCount, term, address, query, digit, stamps, epoch] using hm + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Values.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Values.lean new file mode 100644 index 0000000000..8a3f7d1a54 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Values.lean @@ -0,0 +1,160 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Pruned.Codes +public import Complexity.Algorithms.MatrixMultiplication.Schonhage.Magnitude + +/-! +# Values and flat addresses of the native recursion + +## Main definitions + +* `PrunedQuery.partialValue`: the requested accumulator after a prefix of the ten terms. + +## Main results + +* `PrunedQuery.slice_strided`: flat strided leaf arrays agree with the existing `sliceAt`. +* `PrunedQuery.abs_partialValue_le`: every intermediate accumulator has an explicit bound. + +These are identities and magnitude bounds for the controller invariant. They do not assume +that a machine executes the recursion. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Finset + +/-- The existing leaf indexing has the same least-significant-first decomposition as queries. -/ +theorem leafIndex_cons {n : ℕ} (lam : Term) (v : Leaf n) : + (Encoding.leafIndex (Fin.cons lam v)).val = + (Encoding.termDigits.symm lam).val + 10 * (Encoding.leafIndex v).val := by + simp only [Encoding.leafIndex, finFunctionFinEquiv_apply, Fin.sum_univ_succ, + Fin.cons_zero, Fin.cons_succ, Fin.val_zero, pow_zero, mul_one, Fin.val_succ, + pow_succ, mul_sum] + congr 1 + apply sum_congr rfl + intro i _ + ring + +/-- The native prefix/stride update selects exactly the existing mathematical child slice. -/ +theorem slice_strided {n : ℕ} (a : ℕ → ℤ) (p s : ℕ) (lam : Term) : + sliceAt (fun v : Leaf (n + 1) => a (p + s * (Encoding.leafIndex v).val)) lam = + fun v : Leaf n => a (p + (Encoding.termDigits.symm lam).val * s + + (s * 10) * (Encoding.leafIndex v).val) := by + funext v + change a (p + s * (Encoding.leafIndex (Fin.cons lam v)).val) = _ + rw [leafIndex_cons] + congr 1 + ring + +/-- A frame's prefix and stride put every remaining leaf inside the original encoding array. -/ +theorem leafAddress_lt {n p s size : ℕ} (hp : p < s) (hs : s * 10 ^ n = size) + (v : Leaf n) : p + s * (Encoding.leafIndex v).val < size := by + have hi := (Encoding.leafIndex v).isLt + nlinarith + +/-- The mathematical value of a requested native accumulator after terms numbered below `k`. +Unrequested directory cells are deliberately unspecified by the native interface. -/ +def partialValue {n : ℕ} (a b : Leaf (n + 1) → ℤ) (S : Finset (OutStr (n + 1))) + (w : OutStr (n + 1)) (k : ℕ) : ℤ := + ∑ i : Fin 10 with i.val < k ∧ (Encoding.termDigits i).Contributes (w 0), + Pruned.childResult a b S (Encoding.termDigits i) (Fin.tail w) + +/-- Before the first child, the native accumulator is zero. -/ +@[simp] +theorem partialValue_zero {n : ℕ} (a b : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) : partialValue a b S w 0 = 0 := by + simp [partialValue] + +/-- Processing the next term adds exactly its contributing child, or leaves the value unchanged. -/ +theorem partialValue_succ {n : ℕ} (a b : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) (k : Fin 10) : + partialValue a b S w (k.val + 1) = partialValue a b S w k.val + + if (Encoding.termDigits k).Contributes (w 0) then + Pruned.childResult a b S (Encoding.termDigits k) (Fin.tail w) else 0 := by + have hfilter : + (univ.filter fun i : Fin 10 => i.val < k.val + 1 ∧ + (Encoding.termDigits i).Contributes (w 0)) = + if (Encoding.termDigits k).Contributes (w 0) then + insert k (univ.filter fun i : Fin 10 => i.val < k.val ∧ + (Encoding.termDigits i).Contributes (w 0)) else + univ.filter fun i : Fin 10 => i.val < k.val ∧ + (Encoding.termDigits i).Contributes (w 0) := by + ext i + by_cases hc : (Encoding.termDigits k).Contributes (w 0) + · simp only [mem_filter, mem_univ, true_and, hc, ↓reduceIte, mem_insert] + constructor + · rintro ⟨hi, hci⟩ + by_cases he : i = k + · exact Or.inl he + · exact Or.inr ⟨by have hne := Fin.val_ne_of_ne he; omega, hci⟩ + · rintro (rfl | ⟨hi, hci⟩) + · exact ⟨by omega, hc⟩ + · exact ⟨by omega, hci⟩ + · simp only [mem_filter, mem_univ, true_and, hc, ↓reduceIte] + constructor + · rintro ⟨hi, hci⟩ + have hne : i ≠ k := fun he => hc (he ▸ hci) + exact ⟨by have hv := Fin.val_ne_of_ne hne; omega, hci⟩ + · exact fun ⟨hi, hci⟩ => ⟨by omega, hci⟩ + unfold partialValue + rw [hfilter] + split_ifs + · rw [sum_insert (by simp)] + omega + · simp + +/-- After all ten terms, a requested accumulator is the existing pruned result. -/ +theorem partialValue_ten {n : ℕ} (a b : Leaf (n + 1) → ℤ) + (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) (hw : w ∈ S) : + partialValue a b S w 10 = Pruned.result (n + 1) a b S w := by + rw [Pruned.result_succ_apply] + simp only [hw, ↓reduceIte] + unfold partialValue + apply sum_equiv Encoding.termDigits + · intro i + simp only [mem_filter, mem_univ, true_and, i.isLt] + · intro _ _ + rfl + +/-- Every child's mathematical result has the bound for its remaining depth, including a +skipped empty child whose value is zero. -/ +theorem abs_childResult_le {n : ℕ} (a b : Leaf (n + 1) → ℤ) (A B : ℤ) + (hA : ∀ v, |a v| ≤ A) (hB : ∀ v, |b v| ≤ B) + (S : Finset (OutStr (n + 1))) (lam : Term) (w : OutStr n) : + |Pruned.childResult a b S lam w| ≤ 10 ^ n * (A * B) := by + have hA0 : 0 ≤ A := (abs_nonneg _).trans (hA fun _ => .P0) + have hB0 : 0 ≤ B := (abs_nonneg _).trans (hB fun _ => .P0) + unfold Pruned.childResult + split_ifs + · exact Pruned.abs_result_le _ _ A B (fun v => hA (Fin.cons lam v)) + (fun v => hB (Fin.cons lam v)) _ _ + · simp only [Pi.zero_apply, abs_zero] + positivity + +/-- The bound covers all partial sums, not only the final recursively specified output. -/ +theorem abs_partialValue_le {n : ℕ} (a b : Leaf (n + 1) → ℤ) (A B : ℤ) + (hA : ∀ v, |a v| ≤ A) (hB : ∀ v, |b v| ≤ B) + (S : Finset (OutStr (n + 1))) (w : OutStr (n + 1)) (k : ℕ) : + |partialValue a b S w k| ≤ 10 ^ (n + 1) * (A * B) := by + have hA0 : 0 ≤ A := (abs_nonneg _).trans (hA fun _ => .P0) + have hB0 : 0 ≤ B := (abs_nonneg _).trans (hB fun _ => .P0) + let T := (univ : Finset (Fin 10)).filter fun i => + i.val < k ∧ (Encoding.termDigits i).Contributes (w 0) + have hcard : T.card ≤ 10 := (card_le_univ _).trans_eq (by simp) + calc + |partialValue a b S w k| ≤ (T.card : ℤ) * (10 ^ n * (A * B)) := + abs_sum_le_card_mul T (fun i _ => abs_childResult_le a b A B hA hB S _ _) + _ ≤ 10 * (10 ^ n * (A * B)) := by gcongr; exact_mod_cast hcard + _ = 10 ^ (n + 1) * (A * B) := by ring + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Weight.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Weight.lean new file mode 100644 index 0000000000..b655337334 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Weight.lean @@ -0,0 +1,143 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.MatrixMultiplication.Schonhage.PrunedCounts +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Codes + +/-! +# A finite trace weight for the native controller's potential + +## Main definitions + +* `PrunedQuery.traceWeight`: a weight on the already defined finite mathematical call trace. + +## Main results + +* `PrunedQuery.traceWeight_le`: a bound from contributing leaves and recursion depth. + +This weight is a mathematical potential, not an assumed execution-time certificate. A separate +native execution proof must show that actual instructions decrease it sufficiently. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Finset + +/-- Weighting each call by its request count plus one pays for scans and fixed controller work. -/ +def traceWeight {n : ℕ} (S : Finset (OutStr n)) : ℕ := + 4096 * (Pruned.totalSize (fun _ => 0) (fun _ => 0) S + + (Pruned.calls n (fun _ => 0) (fun _ => 0) S).card) + +/-- A timestamp budget for a node with `n` levels remaining. A leaf scans no children; +an internal node scans ten children, each of which may use its own recursive budget. -/ +def epochAllowance (n : ℕ) : ℕ := 2 * (10 ^ n - 1) + +/-- A leaf needs no new child-set timestamp. -/ +@[simp] +theorem epochAllowance_zero : epochAllowance 0 = 0 := rfl + +/-- Ten fresh child scans and all ten recursive budgets fit in the parent's allowance. -/ +theorem epochAllowance_succ (n : ℕ) : + 10 * (1 + epochAllowance n) ≤ epochAllowance (n + 1) := by + have hp : 1 ≤ (10 : ℕ) ^ n := Nat.one_le_pow n _ (by decide) + unfold epochAllowance + rw [pow_succ] + omega + +/-- Every partial traversal of the ten children fits the same concrete timestamp allowance. -/ +theorem mul_epochAllowance_le {n k : ℕ} (hk : k ≤ 10) : + k * (1 + epochAllowance n) ≤ epochAllowance (n + 1) := + (Nat.mul_le_mul_right _ hk).trans (epochAllowance_succ n) + +/-- The timestamp budget is bounded by twice the original leaf-array size. -/ +theorem epochAllowance_le (n : ℕ) : epochAllowance n ≤ 2 * 10 ^ n := by + unfold epochAllowance + omega + +/-- The weight of the zero-depth root, including the empty-query boundary. -/ +theorem traceWeight_zero (S : Finset (OutStr 0)) : traceWeight S = 4096 * (S.card + 1) := by + simp [traceWeight, Pruned.totalSize, Pruned.calls, Pruned.run] + +/-- The existing leaf/passed-set theorem bounds this potential without a runtime assumption. -/ +theorem traceWeight_le {n : ℕ} (S : Finset (OutStr n)) : + traceWeight S ≤ 4096 * (2 * (n + 1) * (leaves S).card + 1) := by + have hc := Pruned.card_calls_le_totalSize_add_one (fun _ => 0) (fun _ => 0) S + have hs := Pruned.totalSize_le (fun _ => 0) (fun _ => 0) S + unfold traceWeight + nlinarith + +/-- One root's scan budget and the weights of exactly its nonempty children. -/ +theorem traceWeight_succ {n : ℕ} (S : Finset (OutStr (n + 1))) : + traceWeight S = 4096 * (S.card + 1) + ∑ lam, + if (childSet S lam).Nonempty then traceWeight (childSet S lam) else 0 := by + unfold traceWeight + rw [Pruned.totalSize_succ, Pruned.card_calls_succ] + calc + _ = 4096 * (S.card + 1) + + 4096 * ((∑ lam, if (childSet S lam).Nonempty then + Pruned.totalSize (sliceAt (fun _ => 0) lam) (sliceAt (fun _ => 0) lam) + (childSet S lam) else 0) + + ∑ lam, if (childSet S lam).Nonempty then + (Pruned.calls n (sliceAt (fun _ => 0) lam) (sliceAt (fun _ => 0) lam) + (childSet S lam)).card else 0) := by ring + _ = 4096 * (S.card + 1) + ∑ lam, + 4096 * ((if (childSet S lam).Nonempty then + Pruned.totalSize (sliceAt (fun _ => 0) lam) (sliceAt (fun _ => 0) lam) + (childSet S lam) else 0) + + if (childSet S lam).Nonempty then + (Pruned.calls n (sliceAt (fun _ => 0) lam) (sliceAt (fun _ => 0) lam) + (childSet S lam)).card else 0) := by + rw [← sum_add_distrib, mul_sum] + _ = _ := by + apply congrArg (Nat.add (4096 * (S.card + 1))) + apply sum_congr rfl + intro lam _ + split_ifs + · rw [Pruned.totalSize_irrel _ _ (fun _ => 0) (fun _ => 0), + Pruned.calls_irrel _ _ (fun _ => 0) (fun _ => 0)] + · simp + +/-- Potential contributed by the child subtrees with numerical labels below `k`. This is a +finite sum of mathematical trace weights, not an assumed execution-time bound. -/ +def childWeightPrefix {n : ℕ} (S : Finset (OutStr (n + 1))) (k : ℕ) : ℕ := + ∑ i ∈ range k, if hi : i < 10 then + if (childSet S (Encoding.termDigits ⟨i, hi⟩)).Nonempty then + traceWeight (childSet S (Encoding.termDigits ⟨i, hi⟩)) else 0 + else 0 + +/-- No child potential has been spent before the first term. -/ +@[simp] +theorem childWeightPrefix_zero {n : ℕ} (S : Finset (OutStr (n + 1))) : + childWeightPrefix S 0 = 0 := by simp [childWeightPrefix] + +/-- Advancing the native term counter adds exactly the next nonempty child's potential. -/ +theorem childWeightPrefix_succ {n : ℕ} (S : Finset (OutStr (n + 1))) (k : Fin 10) : + childWeightPrefix S (k.val + 1) = childWeightPrefix S k.val + + if (childSet S (Encoding.termDigits k)).Nonempty then + traceWeight (childSet S (Encoding.termDigits k)) else 0 := by + simp only [childWeightPrefix, sum_range_succ, dite_eq_left k.isLt] + +/-- After all ten terms, the child-prefix potential is exactly the recursive part of the +existing mathematical trace weight, in the program's unchanged numerical term order. -/ +theorem traceWeight_succ_prefix {n : ℕ} (S : Finset (OutStr (n + 1))) : + traceWeight S = 4096 * (S.card + 1) + childWeightPrefix S 10 := by + rw [traceWeight_succ] + congr 1 + +/-- The complete local controller work fits within the root summand of `traceWeight`. +The terms account for entry, ten scan/push/accumulate pairs, return, and all loop controls. +Empty children use less work than the displayed pair. Child recursive work is separate. -/ +theorem node_local_cost_le (N : ℕ) : + (7 * N + 30) + 10 * ((20 * N + 44) + (31 * N + 31)) + 28 ≤ + 4096 * (N + 1) := by omega + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery diff --git a/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Width.lean b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Width.lean new file mode 100644 index 0000000000..1a957b3409 --- /dev/null +++ b/Complexity/Algorithms/WordRAM/MatrixMultiplication/Schonhage/Pruned/Width.lean @@ -0,0 +1,84 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Flat +public import Mathlib.Data.Nat.Log + +/-! +# Sufficient physical width from original pruned-query inputs + +## Main definitions + +* `PrunedQuery.admissionWidth`: three bits beyond the binary ceiling logarithm of the combined + original magnitude and address budgets. + +## Main results + +* `PrunedQuery.runs_wide`: at every admitted physical width, the same finite program starts + from actual initialized input, returns every requested result and halts within the charged bound. + +Input fit is a conclusion, including both scalar headers and the machine's length word. This +component's threshold is logarithmic in its explicit budgets. Relating the encoding depth, query +count and magnitudes to a whole 3SUM input is separate composition work under #1394. +-/ + +@[expose] public section + +namespace Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage + +/-- A positive threshold for the physical target width, computed from original parameters. +The code does not inspect this number or the machine width. -/ +def admissionWidth (L Q A B : ℕ) : ℕ := + Nat.clog 2 (magnitudeBound L A B + addressBound L Q + 1) + 3 + +/-- The physical threshold is nonzero even for empty inputs and zero operands. -/ +theorem admissionWidth_pos (L Q A B : ℕ) : 0 < admissionWidth L Q A B := by + unfold admissionWidth + omega + +/-- Mathlib's ceiling logarithm derives both actual address-word fit and the signed source +envelope, retaining the two extra bits charged by the existing signed arithmetic kernels. -/ +theorem admission_bounds {W L Q A B : ℕ} (hW : admissionWidth L Q A B ≤ W) : + 0 < W - 2 ∧ magnitudeBound L A B < 2 ^ ((W - 2) - 1) ∧ Fits W (addressBound L Q) := by + have hlog := Nat.le_pow_clog (by decide : 1 < (2 : ℕ)) + (magnitudeBound L A B + addressBound L Q + 1) + have hs : Nat.clog 2 (magnitudeBound L A B + addressBound L Q + 1) ≤ (W - 2) - 1 := by + unfold admissionWidth at hW + omega + have hpower := Nat.pow_le_pow_right (by decide : 1 ≤ (2 : ℕ)) hs + refine ⟨by unfold admissionWidth at hW; omega, by omega, ?_⟩ + change addressBound L Q < 2 ^ W + have hpower' := Nat.pow_le_pow_right (by decide : 1 ≤ (2 : ℕ)) (by omega : + Nat.clog 2 (magnitudeBound L A B + addressBound L Q + 1) ≤ W) + omega + +/-- A complete initialized run at every sufficiently wide physical width, with guaranteed input +fit, a shared pre-halt memory witness, charged halt, all output values and outside preservation. -/ +theorem runs_wide {W L A B : ℕ} (a b : Fin (10 ^ L) → ℤ) + (qs : List (Fin (10 ^ L))) (ha : ∀ v, |a v| ≤ (A : ℤ)) (hb : ∀ v, |b v| ≤ (B : ℤ)) + (hW : admissionWidth L qs.length A B ≤ W) : + InputFits W (input L a b qs) ∧ + ∃ y n, n ≤ timeBound qs ∧ + Runs W (program.program output) (State.init (input L a b qs)) ⟨program.size, y⟩ n ∧ + Executes W (program.program output) (State.init (input L a b qs)) + (outputBase L qs.length) (n + 1) ∧ y.Bounded W ∧ + (∀ i : Fin qs.length, y.mem (outputBase L qs.length + i.val) = encode + (Pruned.result L (fun v => a (Encoding.leafIndex v)) + (fun v => b (Encoding.leafIndex v)) (requested qs) (queryAt (qs.get i)))) ∧ + (∀ c, ¬(outputBase L qs.length ≤ c ∧ c < memoryEnd L qs.length) → + y.mem c = (State.init (input L a b qs) : State registers).data.mem c) := by + obtain ⟨hw, hm, ha'⟩ := admission_bounds hW + have htwo : W - 2 + 2 = W := by unfold admissionWidth at hW; omega + have h := runs hw a b qs ha hb hm (by simpa only [htwo] using ha') + simpa only [htwo] using h + +end Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery 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/ArrayAppend.lean b/Complexity/Computability/WordRAM/ArrayAppend.lean new file mode 100644 index 0000000000..a2b04816bf --- /dev/null +++ b/Complexity/Computability/WordRAM/ArrayAppend.lean @@ -0,0 +1,40 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Allocation +public import Mathlib.Data.Fin.Tuple.Basic + +/-! +# Appending one word to a realized memory block + +## Main results + +* `Block.Realizes.snoc`: the actual one-cell memory update realizes `Fin.snoc` of the prior + finite array. This is a memory identity; a caller must separately charge its store instruction. + +This narrow topic module keeps the `Fin.snoc` dependency and append-specific proof out of +`Allocation`. Clients of core allocation then avoid an interface change and rebuild when the +append API changes; consumers import this module alongside `Block.Realizes.update` as needed. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.Block + +/-- Appending a value at the first cell after an array realizes the extended finite function. -/ +theorem Realizes.snoc {base n : ℕ} {mem : ℕ → ℕ} {v : Fin n → ℕ} + (h : (Block.mk base n).Realizes mem v) (a : ℕ) : + (Block.mk base (n + 1)).Realizes (Function.update mem (base + n) a) (Fin.snoc v a) := by + intro i + refine Fin.lastCases ?_ (fun j => ?_) i + · simp [read] + · have hne : base + j.val ≠ base + n := by have := j.isLt; omega + simpa only [read, Fin.val_castSucc, Function.update_of_ne hne, Fin.snoc_castSucc] using h j + +end Cslib.WordRAM.Block 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/InputArray.lean b/Complexity/Computability/WordRAM/InputArray.lean new file mode 100644 index 0000000000..2977deef48 --- /dev/null +++ b/Complexity/Computability/WordRAM/InputArray.lean @@ -0,0 +1,43 @@ +/- +Copyright (c) 2026 Erik Demaine. 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.Allocation +import Mathlib.Data.List.GetD + +/-! +# Array blocks in ordinary initialized inputs + +## Main results + +* `WordRAM.Data.init_realizes_append`: a list segment appears at its actual initialized address, + including the machine's length header. + +This is a representation lemma for the existing `Data.init`, with no alternate input convention. +It lives in a narrow input-array module to keep the list `getD` dependency and initialization +bridge out of `Allocation`. This avoids changing the allocation interface and rebuilding its +unrelated importers when the input representation API changes. +-/ + +@[expose] public section + +namespace Cslib.WordRAM + +/-- The payload between an arbitrary prefix and suffix occupies its corresponding actual +input block. The extra address word is the standard unsigned input-length header. -/ +theorem Data.init_realizes_append {r : ℕ} (pre payload post : List ℕ) : + (Block.mk (pre.length + 1) payload.length).Realizes + (Data.init (pre ++ (payload ++ post)) : Data r).mem payload.get := by + intro i + change (Data.init (pre ++ (payload ++ post)) : Data r).mem + (pre.length + 1 + i.val) = payload.get i + rw [show pre.length + 1 + i.val = (pre.length + i.val) + 1 by omega, + Data.init_mem_succ, List.getD_append_right _ _ _ _ (by omega), + Nat.add_sub_cancel_left, List.getD_append _ _ _ _ i.isLt, List.getD_eq_get] + +end Cslib.WordRAM diff --git a/Complexity/Computability/WordRAM/Power.lean b/Complexity/Computability/WordRAM/Power.lean new file mode 100644 index 0000000000..a49be46a73 --- /dev/null +++ b/Complexity/Computability/WordRAM/Power.lean @@ -0,0 +1,93 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Computability.WordRAM.Notation + +/-! +# Native natural powers + +## Main definitions + +* `Power.program`: repeated native multiplication, with base and exponent in registers. + +## Main results + +* `Power.exec`: `b^n` within `4n+5` instructions, with input-derived intermediate fits. +-/ + +@[expose] public section + +namespace Cslib.WordRAM.Power + +/-- Result accumulator, initially one. -/ +abbrev value : Fin 5 := 0 +/-- Number of completed multiplications. -/ +abbrev counter : Fin 5 := 1 +/-- Input base. -/ +abbrev base : Fin 5 := 2 +/-- Input exponent. -/ +abbrev exponent : Fin 5 := 3 +/-- Constant one, initialized by the routine. -/ +abbrev one : Fin 5 := 4 + +/-- One multiplication and one counter increment. -/ +def body : Code .standard 5 := ram[ value ← value * base; counter ← counter + one ] + +/-- A fixed program for natural exponentiation by repeated multiplication. -/ +def program : Code .standard 5 := + .seq (ram[ value ← #1; counter ← #0; one ← #1 ]) (.loop (.lt counter exponent) body) + +/-- The routine is uniform in its arguments and preserves memory and both input registers. +Every multiplication fit follows from the final power and exponent bound. -/ +theorem exec {w : ℕ} (b n : ℕ) (hb : 1 ≤ b) (x : Data 5) + (hx : x.regs base = b) (hn : x.regs exponent = n) (hfit : Fits w (b ^ n + n + 2)) : + ∃ y, ExecWithin w program x y (4 * n + 5) ∧ y.regs value = b ^ n ∧ + y.regs counter = n ∧ y.regs one = 1 ∧ y.mem = x.mem ∧ + y.regs base = b ∧ y.regs exponent = n := by + have h0 : Fits w 0 := hfit.of_le (Nat.zero_le _) + have h1 : Fits w 1 := hfit.of_le (by omega) + let start := ((x.set value 1).set counter 0).set one 1 + have hs : ExecWithin (B := .standard) w + (ram[ value ← #1; counter ← #0; one ← #1 ]) x start 3 := by + ram_eval [start, h0, h1, value, counter, one, Fin.mk.injEq] + let I (y : Data 5) : Prop := + y.regs base = b ∧ y.regs exponent = n ∧ y.regs one = 1 ∧ y.regs counter ≤ n ∧ + y.regs value = b ^ y.regs counter ∧ y.mem = x.mem + have hi : I start := by simp [I, start, hx, hn, base, exponent, one, counter, value] + have hbody (y : Data 5) (hy : I y) (hc : (Cond.lt counter exponent).Holds y) : + ∃ z, ExecWithin w body y z 2 ∧ I z ∧ n - z.regs counter < n - y.regs counter := by + obtain ⟨hbv, hnv, ho, hc_le, hv, hm⟩ := hy + have hc_lt : y.regs counter < n := by simpa only [Cond.holds_lt, hnv] using hc + have hpn : b ^ (y.regs counter + 1) ≤ b ^ n := Nat.pow_le_pow_right hb (by omega) + have hp : Fits w (b ^ (y.regs counter + 1)) := hfit.of_le (hpn.trans (by omega)) + have hj : Fits w (y.regs counter + 1) := hfit.of_le (by omega) + let z := (y.set value (b ^ (y.regs counter + 1))).set counter (y.regs counter + 1) + have he : ExecWithin w body y z 2 := by + ram_eval [body, z, hbv, ho, hv, ← pow_succ, hp, hj, + value, counter, base, one, Fin.mk.injEq] + refine ⟨z, he, ?_, ?_⟩ + · simpa +decide [I, z, base, exponent, one, counter, value, Function.update_apply] using + (show y.regs base = b ∧ y.regs exponent = n ∧ y.regs one = 1 ∧ + y.regs counter + 1 ≤ n ∧ y.mem = x.mem from ⟨hbv, hnv, ho, by omega, hm⟩) + · simp only [z, Data.set_regs, Function.update_self] + omega + obtain ⟨y, he, hy, hc⟩ := ExecWithin.loop_invariant I + (fun y => n - y.regs counter) hbody hi + have hj : y.regs counter = n := by + simp only [Cond.holds_lt, hy.2.1] at hc + have := hy.2.2.2.1 + omega + refine ⟨y, ?_, ?_, hj, hy.2.2.1, hy.2.2.2.2.2, hy.1, hy.2.1⟩ + · apply (hs.seq he).mono + have hs0 : start.regs counter = 0 := by simp [start, counter, one] + rw [hs0, Nat.sub_zero] + omega + · simpa only [hj] using hy.2.2.2.2.1 + +end Cslib.WordRAM.Power diff --git a/Complexity/Foundations/Data/Finset/OrderedFilter.lean b/Complexity/Foundations/Data/Finset/OrderedFilter.lean new file mode 100644 index 0000000000..81ed3ec1f9 --- /dev/null +++ b/Complexity/Foundations/Data/Finset/OrderedFilter.lean @@ -0,0 +1,42 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Data.Finset.Sort +public import Mathlib.Data.List.OfFn + +/-! +# Ordered finite-set enumeration and filtering + +## Main results + +* `Finset.sort_eq_filter_finRange`: a level set in increasing order is the filtered level list. +* `Finset.ofFn_orderEmbOfFin`: values at its ordered positions form its sorted mapped list. +-/ + +@[expose] public section + +namespace Finset + +/-- Increasing level-set enumeration is ordinary filtering of all levels. -/ +theorem sort_eq_filter_finRange {L : ℕ} (s : Finset (Fin L)) : + s.sort = (List.finRange L).filter fun i => decide (i ∈ s) := by + apply s.sortedLT_sort.eq_of_mem_iff + ((List.sortedLT_finRange L).pairwise.filter _ |>.sortedLT) + intro a + simp + +/-- Applying a function to the ordered finite-set positions agrees with its sorted list. -/ +theorem ofFn_orderEmbOfFin {α β : Type*} [LinearOrder α] (s : Finset α) + {k : ℕ} (h : s.card = k) (f : α → β) : + List.ofFn (fun i => f (s.orderEmbOfFin h i)) = s.sort.map f := by + rw [← s.listMap_orderEmbOfFin_finRange h, List.map_map, List.ofFn_eq_map] + rfl + +end Finset diff --git a/Complexity/Foundations/Data/List/RowMajor.lean b/Complexity/Foundations/Data/List/RowMajor.lean new file mode 100644 index 0000000000..c03ab40239 --- /dev/null +++ b/Complexity/Foundations/Data/List/RowMajor.lean @@ -0,0 +1,64 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Init +public import Mathlib.Data.List.OfFn +public import Mathlib.Data.List.FinRange +public import Mathlib.Logic.Equiv.Fin.Basic +import Mathlib.Data.List.Basic +import Mathlib.Data.List.GetD + +/-! +# Row-major finite arrays as lists + +## Main results + +* `List.flatMap_ofFn_prod`: nested row-major enumeration agrees with the existing product + equivalence on finite indices. +* `List.getD_flatMap_ofFn`: read a cell at its ordinary flat row-major index. +-/ + +@[expose] public section + +namespace List + +/-- Flattening all rows enumerates the standard finite product equivalence. -/ +theorem flatMap_ofFn_prod {α : Type*} {n d : ℕ} (f : Fin n → Fin d → α) : + (finRange n).flatMap (fun i => ofFn (f i)) = + ofFn (fun p : Fin (n * d) => f (finProdFinEquiv.symm p).1 + (finProdFinEquiv.symm p).2) := by + rw [ofFn_mul] + simp only [ofFn_eq_map, ← flatMap_def] + apply flatMap_congr + intro i _ + apply map_congr_left + intro j _ + have heq : (⟨i.val * d + j.val, by + calc + i.val * d + j.val < i.val * d + d := Nat.add_lt_add_left j.isLt _ + _ = (i.val + 1) * d := by rw [Nat.add_mul, Nat.one_mul] + _ ≤ n * d := Nat.mul_le_mul_right d i.isLt⟩ : Fin (n * d)) = finProdFinEquiv (i, j) := by + apply Fin.ext + simp [finProdFinEquiv, Nat.mul_comm, Nat.add_comm] + simp only [heq, Equiv.symm_apply_apply] + +/-- Ordinary flat indexing reads the corresponding original row and column. -/ +theorem getD_flatMap_ofFn {α : Type*} {n d : ℕ} (f : Fin n → Fin d → α) + (default : α) (i : Fin n) (j : Fin d) : + ((finRange n).flatMap (fun i => ofFn (f i))).getD (i.val * d + j.val) default = + f i j := by + rw [flatMap_ofFn_prod] + have heq : i.val * d + j.val = (finProdFinEquiv (i, j)).val := by + simp [finProdFinEquiv, Nat.mul_comm, Nat.add_comm] + rw [heq, getD_eq_getElem _ _ (by + rw [length_ofFn] + exact (finProdFinEquiv (i, j)).isLt), List.getElem_ofFn] + exact congrArg (fun p => f p.1 p.2) (finProdFinEquiv.symm_apply_apply (i, j)) + +end List diff --git a/Complexity/Foundations/Data/Nat/OfDigits.lean b/Complexity/Foundations/Data/Nat/OfDigits.lean new file mode 100644 index 0000000000..9fd7ecce81 --- /dev/null +++ b/Complexity/Foundations/Data/Nat/OfDigits.lean @@ -0,0 +1,32 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +module +-- upstream: candidate + +public import Complexity.Foundations.Data.Nat.Radix + +/-! +# Finite-function digit numbering + +## Main results + +* `Nat.ofDigits_finFunctionFinEquiv`: agreement with Mathlib's finite-function numbering. + +The weighted-digit identity is the existing `Nat.ofDigits_ofFn_eq_sum` from fixed-length radix +arithmetic; this module supplies only its bridge to Mathlib's finite-function enumeration. +-/ + +@[expose] public section + +namespace Nat + +/-- `finFunctionFinEquiv` uses exactly `ofDigits`'s least-significant-first convention. -/ +theorem ofDigits_finFunctionFinEquiv {b L : ℕ} (f : Fin L → Fin b) : + ofDigits b (List.ofFn fun i => (f i).val) = (finFunctionFinEquiv f).val := + ofDigits_ofFn_eq_sum b _ + +end Nat diff --git a/ComplexityTests/SchonhageBandPreparation.lean b/ComplexityTests/SchonhageBandPreparation.lean new file mode 100644 index 0000000000..bd2d3af56b --- /dev/null +++ b/ComplexityTests/SchonhageBandPreparation.lean @@ -0,0 +1,167 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandThin +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.BandCatalogue +import Complexity.Algorithms.MatrixMultiplication.Schonhage.MaskTable + +-- These regressions evaluate actual native code; general execution proofs are checked separately. +set_option linter.hashCommand false + +namespace ComplexityTests.SchonhageBandPreparation + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.MatrixMultiplication.Schonhage +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage +open BandPreparation + +private def leafAt {L : ℕ} (i : Fin (10 ^ L)) : Leaf L := + fun ℓ => Encoding.termAt (finFunctionFinEquiv.symm i ℓ) + +private def expected {L m N : ℕ} (hm : m ≤ L) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) : List ℤ := + ((List.range (numBands L m N)).flatMap fun β => + List.ofFn fun i => encodingL (bandArrayL (MaskTable.layout hm) X β) (leafAt i)) ++ + ((List.range (numBands L m N)).flatMap fun β => + List.ofFn fun i => encodingR (bandArrayR (MaskTable.layout hm) Y β) (leafAt i)) + +private def observe (w L m N : ℕ) + (X : Matrix (Fin N) (Fin (D m)) ℤ) (Y : Matrix (Fin (D m)) (Fin N) ℤ) : + Option ((List ℤ × Bool) × List ℕ) := + let initial := ((State.init (input L m N X Y) : State 96).data.write + (memoryEnd L m N) 91) + (program.eval w 1024 initial).map fun (y, _) => + (((List.range (2 * numBands L m N * 10 ^ L)).map fun i => + decode (y.mem (catalogueBase L m N + i)), + decide ((∀ a < inputEnd m N, y.mem a = initial.mem a) ∧ + y.mem (memoryEnd L m N) = 91)), + (List.range (2 ^ L)).map fun i => y.mem (inverseBase L m N + i)) + +-- Distinct tiles share the existing row/column band blocks, with no per-tile duplication. +#guard tileInputBases 1 0 4 (0, 1) == (57, 87) +#guard tileInputBases 1 0 4 (1, 1) == (67, 87) + +-- A zero-dimensional matrix computes its tables and has no bands. Its input is restored. +#guard observe 8 0 0 0 (fun _ _ => 0) (fun _ _ => 0) == some (([], true), [1]) + +-- A genuine signed zero-depth band on each side calls both actual encoder programs. +#guard observe 8 0 0 1 (fun _ _ => -3) (fun _ _ => 2) == some (([-3, 2], true), [1]) + +-- All leaves of four distinct shared bands agree with the unchanged mathematical arrays. +#guard (observe 12 1 1 2 (fun i k => (i.val : ℤ) - k.val) + (fun k j => (k.val : ℤ) - 2 * j.val)).map (fun z => z.1) == + some (expected (N := 2) (by decide : 1 ≤ 1) (fun i k => (i.val : ℤ) - k.val) + (fun k j => (k.val : ℤ) - 2 * j.val), true) + +-- Two row and column bands with padding: the last band has only one real row/column. +#guard (observe 12 1 0 4 (fun i _ => (i.val : ℤ) - 2) + (fun _ j => 1 - (j.val : ℤ))).map (fun z => z.1) == + some (expected (N := 4) (by decide : 0 ≤ 1) (fun i _ => (i.val : ℤ) - 2) + (fun _ j => 1 - (j.val : ℤ)), true) + +-- The second size-one mask is unselected when K0² = 1; the first is mask one. +#guard (observe 12 2 1 1 (fun _ _ => -1) (fun _ _ => 1)).map Prod.snd == + some [0, 1, 0, 0] + +-- Address-width boundary for a padded, two-band call. The nonzero sentinel also survives. +#guard observe 6 1 0 4 (fun _ _ => 0) (fun _ _ => 0) == none +#guard (observe 7 1 0 4 (fun _ _ => 0) (fun _ _ => 0)).map (fun z => z.1.2) == some true + +/-- The original numerical width premise has a checked strict boundary. -/ +example : ¬Fits 13 (wordBudget 1 0 4) ∧ Fits 14 (wordBudget 1 0 4) := by decide +kernel + +/-- A negative zero-depth initialized client. The actual final memory belongs to the +charged prefix run, which is followed by its charged halt. -/ +example : ∃ y : Data 96, ∃ n, n ≤ timeBound 0 0 1 ∧ + Runs 11 (program.program catalogue) + (State.init (input 0 0 1 (fun _ _ => -3) (fun _ _ => 2))) ⟨program.size, y⟩ n ∧ + Executes 11 (program.program catalogue) + (State.init (input 0 0 1 (fun _ _ => -3) (fun _ _ => 2))) + (catalogueBase 0 0 1) (n + 1) ∧ + y.mem (catalogueAddress 0 0 1 false 0 0) = 7 ∧ + y.mem (catalogueAddress 0 0 1 true 0 0) = 4 := by + obtain ⟨_, y, n, hn, hr, he, _, hl, hright, _⟩ := runs (w := 9) (N := 1) (by decide) + (by decide : 0 ≤ 0) (fun _ _ => -3) (fun _ _ => 2) 3 (by decide) + (by intro i k; decide) (by intro k j; decide) (by decide) (by decide +kernel) + refine ⟨y, n, hn, hr, he, ?_, ?_⟩ + · have h := hl 0 (by decide +kernel) (Fin.elim0 : Leaf 0) + have hi : (Encoding.leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by + have := (Encoding.leafIndex (Fin.elim0 : Leaf 0)).isLt + norm_num at this ⊢ + have hv : encodingL (bandArrayL (N := 1) (MaskTable.layout (by decide : 0 ≤ 0)) + (fun _ _ => -3) 0) Fin.elim0 = -3 := by decide +kernel + simpa only [hi, hv, show encode (-3) = 7 from rfl] using h + · have h := hright 0 (by decide +kernel) (Fin.elim0 : Leaf 0) + have hi : (Encoding.leafIndex (Fin.elim0 : Leaf 0)).val = 0 := by + have := (Encoding.leafIndex (Fin.elim0 : Leaf 0)).isLt + norm_num at this ⊢ + have hv : encodingR (bandArrayR (N := 1) (MaskTable.layout (by decide : 0 ≤ 0)) + (fun _ _ => 2) 0) Fin.elim0 = 2 := by decide +kernel + simpa only [hi, hv, show encode 2 = 4 from rfl] using h + +/-- A positive-depth, signed padded multi-band client of the general kernel-checked run. +The prefix is tied to its actual final memory, including restored input and outside frame. -/ +example : ∃ y : Data 96, ∃ n, n ≤ timeBound 1 0 4 ∧ + Runs 14 (program.program catalogue) + (State.init (input 1 0 4 (fun i _ => (i.val : ℤ) - 2) + (fun _ j => 1 - (j.val : ℤ)))) ⟨program.size, y⟩ n ∧ + Executes 14 (program.program catalogue) + (State.init (input 1 0 4 (fun i _ => (i.val : ℤ) - 2) + (fun _ j => 1 - (j.val : ℤ)))) (catalogueBase 1 0 4) (n + 1) ∧ + y.mem (catalogueAddress 1 0 4 false 0 + (Encoding.leafIndex (fun _ : Fin 1 => Term.P 0 0)).val) = encode (-2) ∧ + (∀ b, ¬(inputEnd 0 4 ≤ b ∧ b < memoryEnd 1 0 4) → + y.mem b = (State.init (input 1 0 4 (fun i _ => (i.val : ℤ) - 2) + (fun _ j => 1 - (j.val : ℤ))) : State 96).data.mem b) := by + obtain ⟨_, y, n, hn, hr, he, _, hl, _, _, _, hf⟩ := runs (w := 12) (N := 4) (by decide) + (by decide : 0 ≤ 1) (fun i _ => (i.val : ℤ) - 2) (fun _ j => 1 - (j.val : ℤ)) + 2 (by decide) (by decide +kernel) (by decide +kernel) (by decide) (by decide +kernel) + refine ⟨y, n, hn, hr, he, ?_, hf⟩ + have hv : encodingL (bandArrayL (N := 4) (MaskTable.layout (by decide : 0 ≤ 1)) + (fun i _ => (i.val : ℤ) - 2) 0) (fun _ : Fin 1 => Term.P 0 0) = -2 := by decide +kernel + simpa only [hv] using hl 0 (by decide +kernel) (fun _ : Fin 1 => Term.P 0 0) + +private def framedEmpty : Data 96 := + ((State.init (input 0 0 0 (fun _ _ => 0) (fun _ _ => 0)) : State 96).data.write + 0 77).write (memoryEnd 0 0 0) 91 + +/-- General-call framing allows an unrelated native header zero and a nonzero adjacent +query/scratch sentinel. The full original prefix, including header zero, is restored. -/ +example : ∃ y : Data 96, ∃ n, n ≤ timeBound 0 0 0 ∧ + Runs 10 (program.program catalogue) ⟨0, framedEmpty⟩ ⟨program.size, y⟩ n ∧ + y.mem 0 = 77 ∧ y.mem (memoryEnd 0 0 0) = 91 := by + obtain ⟨y, ⟨n, hn, he⟩, _, _, _, _, hf⟩ := exec (w := 8) (L := 0) (m := 0) + (N := 0) (by decide) (by decide) framedEmpty (by decide +kernel) + (by decide +kernel) (by decide +kernel) (fun _ _ _ => 0) + (by intro side r hr; omega) 0 (by decide) (by intro side r hr; omega) + (by decide) (by decide +kernel) + refine ⟨y, n, hn, ?_, ?_, ?_⟩ + · simpa only [Nat.zero_add] using he.runs (pc := 0) + (p := program.program catalogue) (Program.Contains.self _).append_left + · rw [hf 0 (by decide +kernel)] + decide +kernel + · rw [hf (memoryEnd 0 0 0) (by omega)] + rfl + +/-- Symbolic positive-depth thin parameters and nonzero signed matrices simultaneously +satisfy all width premises and the genuine charged native saving, without enumerating them. -/ +example (m : ℕ) : let M := m + 1 + ∃ w n, 0 < w ∧ 0 < M ∧ Fits (w + 2) (wordBudget (19 * M) M (D M ^ 18)) ∧ + (7 ^ (19 * M) : ℕ) * (1 : ℤ) < (2 : ℤ) ^ (w - 1) ∧ + Executes (w + 2) (program.program catalogue) + (State.init (input (19 * M) M (D M ^ 18) (fun _ _ => 1) (fun _ _ => -1))) + (catalogueBase (19 * M) M (D M ^ 18)) n ∧ + (n : ℝ) ≤ 81920 * ((19 * M + 1 : ℕ) : ℝ) * (D M ^ 18 : ℕ) ^ (2 : ℕ) / + (D M : ℝ) ^ (1 / 18 : ℝ) := by + dsimp only + obtain ⟨w, hw, hmag, hfit⟩ := width_exists (19 * (m + 1)) (m + 1) + (D (m + 1) ^ 18) 1 (by decide) + obtain ⟨n, he, ht⟩ := runs_thin (m := m + 1) (N := D (m + 1) ^ 18) hw (Nat.le_refl _) + (fun _ _ => 1) (fun _ _ => -1) 1 (by decide) + (by intro i k; decide) (by intro k j; decide) hmag hfit + exact ⟨w, n, hw, Nat.succ_pos _, hfit, hmag, he, ht⟩ + +end ComplexityTests.SchonhageBandPreparation 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/SchonhagePruned.lean b/ComplexityTests/SchonhagePruned.lean new file mode 100644 index 0000000000..ead284f2e0 --- /dev/null +++ b/ComplexityTests/SchonhagePruned.lean @@ -0,0 +1,158 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Memory +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Root +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Report +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.EmptyChild +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Finish +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Return +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Ready + +-- Full controller regressions evaluate the actual native interpreter at elaboration time. +set_option linter.hashCommand false + +/-! Actual native controller regressions, including shared and repeated query suffixes. -/ + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +namespace ComplexityTests.SchonhagePruned + +private def leafData : Data registers where + regs i := match i.val with + | 10 => 1 | 11 => 3 | 12 => 4 | 18 => 8 | 19 => 12 | 20 => 13 | 21 => 14 + | 24 => 1 | 25 => 1 | 26 => 10 | 27 => 9 | 28 => 4 | _ => 0 + mem a := if a = 3 then encode (-3) else if a = 4 then encode 2 else + if a = 8 then 1 else if a = 10 then 1 else if a = 13 then 41 else 0 + +private theorem leafData_parameters : KernelRegs 0 8 3 4 leafData := by + constructor <;> rfl + +-- The general entry assertion is satisfied by concrete memory. Its timestamp directory has +-- one cell and its occupied list names the unique zero-depth query exactly once. +private theorem leafData_ready : NodeReady 0 8 3 4 0 0 0 0 {Fin.elim0} leafData := by + refine ⟨leafData_parameters, rfl, by decide, rfl, rfl, rfl, rfl, ?_, ?_⟩ + · intro i + fin_cases i <;> rfl + · change ScanMemory leafData.mem 12 14 0 1 {0} + refine ⟨?_, ?_, ?_, ?_⟩ + · intro q hq + have he := Finset.mem_singleton.mp hq + exact he ▸ (by decide : 0 < 1) + · intro q hq + have he : q = 0 := by omega + subst q + decide + · intro q hq + have he : q = 0 := by omega + subst q + decide + · refine ⟨fun _ => 0, ?_, ?_, ?_⟩ + · change Function.Injective (fun _ : Fin 1 => (0 : ℕ)) + intro i j _ + exact Subsingleton.elim i j + · ext q + constructor + · rintro ⟨i, rfl⟩ + exact Finset.mem_singleton_self _ + · intro hq + exact ⟨⟨0, by simp⟩, (Finset.mem_singleton.mp hq).symm⟩ + · intro i + change leafData.mem (12 + i.val) = 0 + have hi : i.val < 1 := i.isLt + have he : i.val = 0 := by omega + rw [he] + rfl + +-- The numerical magnitude/address hypotheses are derived from original input bounds, and the +-- value witness belongs to the same actual native execution (including the loop exit). +example : ∃ y, ExecWithin 9 controller leafData y 8194 ∧ y.mem 13 = encode (-6) := by + let S : Finset (Cslib.Algorithms.MatrixMultiplication.Schonhage.OutStr 0) := {Fin.elim0} + obtain ⟨y, hy, hrun⟩ := exec_ready_zero (w := 7) (A := 3) (B := 2) S + (fun _ => -3) (fun _ => 2) leafData leafData_ready + (by intro v; rfl) (by intro v; rfl) (by intro v; decide) (by intro v; decide) + (by decide) (by decide) (by decide) + have hexit : ExecWithin 9 controller y y 2 := ExecWithin.loop_exit + (by simp only [Cond.holds_lt, hy.regs.nil_eq, hy.active_eq]; decide) + refine ⟨y, ?_, ?_⟩ + · simpa only [traceWeight_zero, S, Finset.card_singleton] using hrun y 2 hexit + · have hv := hy.values Fin.elim0 (Finset.mem_singleton_self _) + simpa [Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result, + Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.run, Finset.piecewise, S] using hv + +-- A concrete negative-product node discharges the general invariant, signed and address +-- premises. Its returned memory is attached to the same charged native loop execution. +example : ∃ y, ExecWithin 7 controller leafData y 8194 ∧ y.mem 13 = encode (-6) ∧ + y.mem 100 = 0 := by + let S : Finset (Cslib.Algorithms.MatrixMultiplication.Schonhage.OutStr 0) := {Fin.elim0} + obtain ⟨y, hregs, _, _, hactive, _, _, hvalue, hframe, _, hrun⟩ := + exec_node_zero (w := 5) (p := 0) (E := 0) (a := -3) (b := 2) + (by decide) (by decide) (by decide) S leafData leafData_parameters + rfl rfl rfl (by simp [leafData, S]) rfl rfl rfl rfl + (by + intro q hq + have he : q = 0 := by omega + subst q + decide) (by decide) + have hexit : ExecWithin 7 controller y y 2 := + ExecWithin.loop_exit (by simp only [Cond.holds_lt, hregs.nil_eq, hactive]; decide) + refine ⟨y, ?_, ?_, ?_⟩ + · simpa only [traceWeight_zero, S, Finset.card_singleton] using hrun y 2 hexit + · have hv := hvalue Fin.elim0 (Finset.mem_singleton_self _) + simpa [Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result, + Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.run, + Finset.piecewise, S] using hv + · exact (hframe 100 (by decide)).trans rfl + +-- An empty leaf performs its real controller return without overwriting the old value cell. +example : ∃ y, ExecWithin 7 controller (leafData.write 8 0) y 47 ∧ y.mem 13 = 41 := by + let x := leafData.write 8 0 + obtain ⟨y, hy, hmem, _, _, hactive, hreg⟩ := exec_leafStep (w := 5) (p := 0) + (a := -3) (b := 2) (by decide) (by decide) (by decide) x + rfl rfl rfl rfl rfl rfl rfl rfl (by decide) + have hy0 : y.regs nil = 0 := (hreg nil (by decide)).trans rfl + have he : ExecWithin 7 controller x y 47 := ExecWithin.loop_once + (by decide) hy (by simp only [Cond.holds_lt, hy0, hactive]; decide) + refine ⟨y, he, ?_⟩ + rw [hmem] + rfl + +-- Naming the reporting body and controller loop changes no native control-code tree. +example : report = ram[ + index ← #0; while index < queryCount do [ + address ← queries + index; query ← *address; + address ← values + query; temporary ← *address; + address ← output + index; *address ← temporary; index ← index + one ] ] := rfl + +example : controller = ram[ while nil < active do [ ⟪step⟫ ] ] := rfl + +-- Query codes use least-significant-first digits and retain the encoder's wildcard digit nine. +example : queryAt (L := 2) (90 : Fin 100) = ![.z 0 0, .z0] := by decide +kernel +example : (requested (L := 2) ([90, 9, 90] : List (Fin 100))).card = 2 := by decide +kernel + +private def observe (w L fuel : ℕ) (a b : Fin (10 ^ L) → ℤ) (qs : List (Fin (10 ^ L))) := + (program.eval w fuel (State.init (input L a b qs) : State registers).data).map fun (y, time) => + ((List.range qs.length).map fun i => decode (y.mem (outputBase L qs.length + i)), time) + +private def answers (w L fuel : ℕ) (a b : Fin (10 ^ L) → ℤ) (qs : List (Fin (10 ^ L))) := + (observe w L fuel a b qs).map Prod.fst + +-- One leaf is multiplied once and reported twice, including its negative sign. +#guard answers 16 0 64 (fun _ => -3) (fun _ => 2) [0, 0] == some [-6, -6] +#guard answers 16 0 64 (fun _ => -3) (fun _ => 2) [] == some [] + +-- Digit nine is the inner wildcard; the outer query uses exactly its private leaf. +#guard answers 20 1 128 (fun i => i.val + 1) (fun _ => 1) [9, 0, 9] == some [55, 1, 55] +#guard answers 20 1 128 (fun i => -(i.val + 1 : ℤ)) (fun _ => 1) [9, 0, 9] == + some [-55, -1, -55] +#guard answers 20 1 128 (fun _ => 5) (fun _ => 0) [] == some [] + +-- The depth-two mixed-query and outside-sentinel cases now have kernel-checked actual native +-- run witnesses in SchonhagePrunedInput and SchonhagePrunedKernel, respectively. The shallow +-- interpreter checks above remain independent executable sanity checks of the code. + +end ComplexityTests.SchonhagePruned diff --git a/ComplexityTests/SchonhagePrunedInput.lean b/ComplexityTests/SchonhagePrunedInput.lean new file mode 100644 index 0000000000..1992f13814 --- /dev/null +++ b/ComplexityTests/SchonhagePrunedInput.lean @@ -0,0 +1,128 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Width + +/-! Kernel-checked initialized native runs with actual output witnesses and charged halt. -/ + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +namespace ComplexityTests.SchonhagePrunedInput + +-- A negative leaf is computed once and reported at both duplicate positions. The length +-- header, original operands and first outside cell belong to the same actual run witness. +example : ∃ y n, n ≤ timeBound (L := 0) ([0, 0] : List (Fin 1)) ∧ + Runs 10 (program.program output) (State.init (input 0 (fun _ => -3) (fun _ => 2) [0, 0])) + ⟨program.size, y⟩ n ∧ + Executes 10 (program.program output) (State.init (input 0 (fun _ => -3) (fun _ => 2) [0, 0])) + 7 (n + 1) ∧ y.mem 7 = encode (-6) ∧ y.mem 8 = encode (-6) ∧ + y.mem 0 = 6 ∧ y.mem 3 = encode (-3) ∧ y.mem (memoryEnd 0 2) = 0 := by + obtain ⟨_, y, n, hn, hr, he, _, hv, hf⟩ := runs (w := 8) (L := 0) (A := 3) (B := 2) + (by decide) (fun _ => -3) (fun _ => 2) ([0, 0] : List (Fin 1)) + (by intro i; decide) (by intro i; decide) (by decide) (by decide) + refine ⟨y, n, hn, hr, he, ?_, ?_, ?_, ?_, ?_⟩ + · simpa +decide [Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result, + Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.run, Finset.piecewise, + requested, outputBase] using hv 0 + · simpa +decide [Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result, + Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.run, Finset.piecewise, + requested, outputBase] using hv 1 + · exact (hf 0 (by decide)).trans rfl + · exact (hf 3 (by decide)).trans rfl + · rw [hf _ (by decide)] + rfl + +-- The conservative address admission is explicit and has a checked failing boundary. +example : Fits 10 (addressBound 0 2) ∧ ¬Fits 9 (addressBound 0 2) := by decide +kernel + +-- The bilinear specialization has an admitted signed client: the supplied leaf arrays are +-- proved to be the actual encodingL/R of the original zero-depth arrays. Its same returned +-- memory witness is reached from State.init and holds -3 * 2 = -6 after the charged run. +example : InputFits 10 (input 0 (fun _ => -3) (fun _ => 2) [0]) ∧ + ∃ y n, n ≤ timeBound (L := 0) ([0] : List (Fin 1)) ∧ + Runs 10 (program.program output) (State.init (input 0 (fun _ => -3) (fun _ => 2) [0])) + ⟨program.size, y⟩ n ∧ + Executes 10 (program.program output) + (State.init (input 0 (fun _ => -3) (fun _ => 2) [0])) 6 (n + 1) ∧ + y.mem 6 = encode (-6) := by + obtain ⟨hi, y, n, hn, hr, he, _, hv, _⟩ := + runs_bilinear (w := 8) (L := 0) (A := 3) (B := 2) (by decide) + (fun _ => -3) (fun _ => 2) ([0] : List (Fin 1)) (fun _ => -3) (fun _ => 2) + (by + intro v + simp [Cslib.Algorithms.MatrixMultiplication.Schonhage.encodingL, + Cslib.Algorithms.MatrixMultiplication.Schonhage.encodeLeft]) + (by + intro v + simp [Cslib.Algorithms.MatrixMultiplication.Schonhage.encodingR, + Cslib.Algorithms.MatrixMultiplication.Schonhage.encodeRight]) + (by intro i; decide) (by intro i; decide) (by decide) (by decide) + refine ⟨hi, y, n, hn, hr, he, ?_⟩ + simpa +decide [outputBase, Cslib.Algorithms.MatrixMultiplication.Schonhage.bilinearProduct, + Cslib.Algorithms.MatrixMultiplication.Schonhage.Leaf.Contributes, + Cslib.Algorithms.MatrixMultiplication.Schonhage.encodeLeft, + Cslib.Algorithms.MatrixMultiplication.Schonhage.encodeRight] using hv 0 + +-- The input-derived physical threshold is positive and guarantees actual runs at every larger +-- width. It is a conservative sufficient threshold, rather than a claim of necessity. +example : admissionWidth 0 2 3 2 = 13 := by decide +kernel + +example (W : ℕ) (hW : 13 ≤ W) : + InputFits W (input 0 (fun _ => -3) (fun _ => 2) [0, 0]) ∧ + ∃ n, Executes W (program.program output) + (State.init (input 0 (fun _ => -3) (fun _ => 2) [0, 0])) 7 (n + 1) := by + have hadmit : admissionWidth 0 2 3 2 ≤ W := by + change 13 ≤ W + exact hW + obtain ⟨hi, _, n, _, _, he, _, _, _⟩ := runs_wide (W := W) (L := 0) (A := 3) (B := 2) + (fun _ => -3) (fun _ => 2) ([0, 0] : List (Fin 1)) + (by intro i; decide) (by intro i; decide) hadmit + exact ⟨hi, n, he⟩ + +-- An empty query list still executes setup, root deduplication and the real controller exit. +example : ∃ y n, n ≤ timeBound (L := 0) ([] : List (Fin 1)) ∧ + Runs 10 (program.program output) (State.init (input 0 (fun _ => -3) (fun _ => 2) [])) + ⟨program.size, y⟩ n ∧ + Executes 10 (program.program output) (State.init (input 0 (fun _ => -3) (fun _ => 2) [])) + 5 (n + 1) ∧ y.mem 3 = encode (-3) := by + obtain ⟨_, y, n, hn, hr, he, _, _, hf⟩ := runs (w := 8) (L := 0) (A := 3) (B := 2) + (by decide) (fun _ => -3) (fun _ => 2) ([] : List (Fin 1)) + (by intro i; decide) (by intro i; decide) (by decide) (by decide) + exact ⟨y, n, hn, hr, he, (hf 3 (by decide)).trans rfl⟩ + +private def left2 (i : Fin 100) : ℤ := i.val + 1 +private def queries2 : List (Fin 100) := [99, 90, 9, 0, 99] + +-- Both digit orders, shared wildcard leaves and duplicates are observed in a depth-two actual +-- flat run. Computing the small finite specification uses the kernel, not native_decide. +example : ∃ y n, n ≤ timeBound (L := 2) queries2 ∧ + Runs 17 (program.program output) (State.init (input 2 left2 (fun _ => 1) queries2)) + ⟨program.size, y⟩ n ∧ + Executes 17 (program.program output) (State.init (input 2 left2 (fun _ => 1) queries2)) + (outputBase 2 5) (n + 1) ∧ + ∀ i : Fin 5, y.mem (outputBase 2 5 + i.val) = encode (![5050, 460, 55, 1, 5050] i) := by + obtain ⟨_, y, n, hn, hr, he, _, hv, _⟩ := runs (w := 15) (L := 2) (A := 100) (B := 1) + (by decide) left2 (fun _ => 1) queries2 + (by + intro i + have := i.isLt + dsimp only [left2] + rw [abs_of_nonneg (by omega)] + omega) + (by intro i; decide) (by decide) (by decide) + refine ⟨y, n, hn, hr, he, ?_⟩ + intro i + have hvalue : Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result 2 + (fun v => left2 + (Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding.leafIndex v)) + (fun _ => 1) (requested (L := 2) queries2) (queryAt (queries2.get i)) = + ![5050, 460, 55, 1, 5050] i := by + fin_cases i <;> decide +kernel + rw [← hvalue] + exact hv i + +end ComplexityTests.SchonhagePrunedInput diff --git a/ComplexityTests/SchonhagePrunedKernel.lean b/ComplexityTests/SchonhagePrunedKernel.lean new file mode 100644 index 0000000000..c54ab08545 --- /dev/null +++ b/ComplexityTests/SchonhagePrunedKernel.lean @@ -0,0 +1,70 @@ +/- +Copyright (c) 2026 Erik Demaine. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Erik Demaine +-/ + +import Complexity.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Pruned.Kernel + +/-! A callable signed native instance with a nonzero epoch and preserved outside memory. -/ + +open Cslib.WordRAM Cslib.WordRAM.Signed +open Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.PrunedQuery + +namespace ComplexityTests.SchonhagePrunedKernel + +private def caller : Data registers where + regs i := match i.val with + | 9 => 1 | 11 => 3 | 12 => 13 | 13 => 23 | 14 => 1 | 15 => 24 | 16 => 25 | 17 => 7 + | _ => 0 + mem c := if c = 1000 then 91 else if 3 ≤ c ∧ c < 13 then encode (-2) else + if 13 ≤ c ∧ c < 23 then encode 3 else if c = 23 then 9 else 0 + +private def wildcard : Fin 1 → Cslib.Algorithms.MatrixMultiplication.Schonhage.OutStr 1 := + fun _ => ![.z0] + +-- The kernel itself computes powers, layout, all ten contributing children and their stored +-- requests. The returned epoch/directory are ready for the next caller, without a dense clear. +example : ∃ y, ExecWithin 13 kernel caller y + (4 + 29 + traceWeight (Finset.univ.image wildcard) + 41) ∧ + y.Bounded 13 ∧ y.mem 24 = encode (-60) ∧ y.mem 3 = encode (-2) ∧ y.mem 1000 = 91 ∧ + 8 ≤ y.regs epoch ∧ y.regs epoch ≤ 26 ∧ ∀ q < 10, y.mem (73 + q) ≤ y.regs epoch := by + have hbounded : caller.Bounded 13 := by + constructor + · intro i + fin_cases i <;> decide + · intro c + dsimp only [caller] + split_ifs <;> decide + obtain ⟨y, hy, hv, hf, _, hel, heu, htable⟩ := exec_kernel (w := 11) (L := 1) + (f := 25) (a0 := 3) (b0 := 13) (q0 := 23) (o0 := 24) (Q := 1) (E := 7) + (A := 2) (B := 3) (fun _ => -2) (fun _ => 3) + wildcard caller rfl rfl rfl rfl rfl rfl rfl rfl + (by intro i; fin_cases i; decide +kernel) + (by + intro v + have h := (Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding.leafIndex v).isLt + dsimp only [caller] + split_ifs <;> first | rfl | omega) + (by + intro v + have h := (Cslib.Algorithms.WordRAM.MatrixMultiplication.Schonhage.Encoding.leafIndex v).isLt + dsimp only [caller] + split_ifs <;> first | rfl | omega) + (by decide) (by decide) (by decide) (by decide) + (by + intro q hq + dsimp only [caller] + split_ifs <;> first | decide | omega) + (by intro v; decide) (by intro v; decide) (by decide) (by decide) (by decide) + refine ⟨y, hy, hy.bounded hbounded, ?_, ?_, ?_, hel, heu, htable⟩ + · have hr := hv (0 : Fin 1) + have hm : Cslib.Algorithms.MatrixMultiplication.Schonhage.Pruned.result 1 + (fun _ => -2) (fun _ => 3) + (Finset.univ.image wildcard) ![.z0] = -60 := by + decide +kernel + exact hr.trans (congrArg encode hm) + · exact (hf 3 (by decide) (by decide)).trans rfl + · exact (hf 1000 (by decide) (by decide)).trans rfl + +end ComplexityTests.SchonhagePrunedKernel diff --git a/NOTICE b/NOTICE index dcdc12ee98..17fb0a9d8c 100644 --- a/NOTICE +++ b/NOTICE @@ -27,3 +27,15 @@ 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. + +The explicit-stack pruned-query evaluator uses original native code against Complexitylib's +standard WordRAM APIs. Its finite pruning specification reuses the credited pinned source +above; no source machine or instruction-cost contract supplies its native execution proofs. + +The native shared-band preparation selectively adapts the band-array and all-band +organization from that same pinned source. Anthropic, PBC credit is retained in +BandProgram.lean; its native mask scans, dense inverse table and execution proofs are new. diff --git a/ROADMAP.md b/ROADMAP.md index 5fc6cb5a6f..330da57d4c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -434,6 +434,23 @@ 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). +- **Native pruned-query evaluator** ([#1526](https://github.com/cslib-community/complexitylib/issues/1526)): + fixed 96-register code computes distinct root/child queries, stack traversal and signed + accumulators. Initialized flat runs preserve query order and duplicates, input and outside + frame. Setup, sparse clearing, control/index work and halt fit + `8192*(L+Q+1+(L+1)*|leaves(requested queries)|)` instructions, with widths derived from + original magnitude/address bounds. A callable pointer/epoch kernel permits directory reuse. + Query grouping, band/tile composition, sparse-product and Exact Triangle solving, and full + input/time/width/asymptotic composition remain under #1394. + +- **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. +- **Native shared-band preparation** ([#1523](https://github.com/cslib-community/complexitylib/issues/1523)): computes deterministic subset tables and a valid + layout, prepares padded signed matrix bands, invokes both shared encoders for every band, and + restores the original initialized input. Fixed 96-register code has a charged bound, including + halt, of `4096*(L+1)*(1+N*4^m+2^L+2*numBands*10^L)+1`; at `L=19m`, `N≥(4^m)^18`, it is at most + `81920*(L+1)*N²/(4^m)^(1/18)`. - **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 +459,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 diff --git a/UPSTREAM.md b/UPSTREAM.md index b009e0bce4..edd6d767bd 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -154,6 +154,37 @@ 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). +* **Native shared-band preparation.** The native shared-band preparation component + ([#1523](https://github.com/cslib-community/complexitylib/issues/1523)) computes a deterministic + `Layout` and both subset tables by native mask/popcount scans, computes the square-root block + count by a charged loop, and decodes each band-array index into the original row/column and + thin-dimension coordinates. Real matrix bounds are checked before row-major multiplication. + Each shared row/column band calls the unchanged encoder once; tiles refer to its catalogue + block in the existing leaf order. The fixed 96-register standard code starts from actual + `State.init` scalar/matrix input, charges snapshot and restoration of the complete original + prefix, and preserves arbitrary outside memory. All counter/address fits follow from an + explicit original-parameter word budget; signed fits follow from the original entry bound. + Its complete instruction bound, including halt, is + `81920*(L+1)*N²/(4^m)^(1/18)` when `L=19m` and `N≥(4^m)^18`. Native power, ordered-filter, + finite-digit and row-major list lemmas live in small topic modules; the existing mathematical + layout, matrix and encoding interfaces are reused. No source machine or call/time oracle + is introduced. * **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), @@ -230,6 +261,29 @@ Do not add a ledger row for an ordinary candidate or repeat its source marker he [#1394](https://github.com/cslib-community/complexitylib/issues/1394), including the improved Section 4 route needed for the published exponent 1.9992. Anthropic's copyright and attribution are retained; the native proofs and representation choices are local. +* **Native pruned-query evaluator.** The explicit-stack pruned-query evaluator ([#1526](https://github.com/cslib-community/complexitylib/issues/1526)) + is original native code, reusing the finite `Pruned.result`, `childSet` and trace/leaf + quantities from `Sec2/Lemma10.lean`. `PrunedQuery.queryIndex` directly applies the existing + encoder `leafIndex` to `privateLeaf`; its executable output dictionary retains digit nine + for the inner wildcard. Fixed 96-register code computes child lists by actual digit tests, + division and timestamp deduplication, scans requested cells only, and charges the full stack, + signed kernels and reporting. Its complete initialized runs and callable pointer/epoch kernel + have explicit source-magnitude, address and outside-frame contracts. The actual bound including + halt is `8192*(L+Q+1+(L+1)*|leaves(requested queries)|)`. The source Light implementation's + semantics and instruction constants are not reused. Query grouping, sparse-product/Exact + Triangle solving and full input/time/width composition, including the improved Section 4 route + to exponent 1.9992, remain in #1394. The independent band producer is #1523; its catalogue + retains the same leaf ordering and canonical signed words. Raw 0/1 matrix producers must + actually double entries before copying into signed input arrays, with that work charged. + Stage `exec_*` lemmas are internal implementation APIs exported across proof modules; + callers use the flat initialized runs, width theorem or pointer/epoch kernel. The narrow + `WordRAM.ArrayAppend` and `WordRAM.InputArray` topic modules keep append/input dependencies + and interface changes away from the widely imported `Allocation` core. + Post-integration [#1553](https://github.com/cslib-community/complexitylib/issues/1553) includes + replacing pruned `powers` with sibling #1536's generic `Power.program`, preserving the + `4L+10` budget via five retained constant initializations plus its `4L+5` run. Its exact + register map, affected execution/width/cost clients and sibling integration order are + recorded there. This documentation/test repair does not change either sibling's program. * **Typed RAM upstream core.** [CSLib #1158](https://github.com/leanprover/cslib/pull/1158) at `beea60a15eddc91d93e92fa5af9fb965c351d0d5` proposes `Cslib/Computability/Machines/RAM/Basic.lean`, in namespace `Cslib.RAM.Typed`.